diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6fdcc7..06a7f5d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1f876a4 --- /dev/null +++ b/.github/workflows/release.yml @@ -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 diff --git a/AGENTS.md b/AGENTS.md index 89f4a00..ecbc139 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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//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 diff --git a/Cargo.lock b/Cargo.lock index 8cdada3..be74904 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -415,6 +415,7 @@ dependencies = [ "futures-util", "ignore", "include_dir", + "libc", "nanoid", "pulldown-cmark", "ratatui", diff --git a/Cargo.toml b/Cargo.toml index 9ac1c5b..13f3409 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 6edafcd..459af36 100644 --- a/README.md +++ b/README.md @@ -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 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 diff --git a/cassady-desktop/README.md b/cassady-desktop/README.md new file mode 100644 index 0000000..06a1ef9 --- /dev/null +++ b/cassady-desktop/README.md @@ -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. diff --git a/cassady-desktop/index.html b/cassady-desktop/index.html index 9872259..fa68a08 100644 --- a/cassady-desktop/index.html +++ b/cassady-desktop/index.html @@ -5,9 +5,21 @@ Cassady + -
+
loading Cassady…
diff --git a/cassady-desktop/src-tauri/build.rs b/cassady-desktop/src-tauri/build.rs index d860e1e..a7369e9 100644 --- a/cassady-desktop/src-tauri/build.rs +++ b/cassady-desktop/src-tauri/build.rs @@ -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() } diff --git a/cassady-desktop/src-tauri/gen/schemas/linux-schema.json b/cassady-desktop/src-tauri/gen/schemas/linux-schema.json new file mode 100644 index 0000000..3286645 --- /dev/null +++ b/cassady-desktop/src-tauri/gen/schemas/linux-schema.json @@ -0,0 +1,2292 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`", + "type": "string", + "const": "core:app:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide", + "markdownDescription": "Enables the app_hide command without any pre-configured scope." + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show", + "markdownDescription": "Enables the app_show command without any pre-configured scope." + }, + { + "description": "Enables the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-bundle-type", + "markdownDescription": "Enables the bundle_type command without any pre-configured scope." + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon", + "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." + }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers", + "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier", + "markdownDescription": "Enables the identifier command without any pre-configured scope." + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name", + "markdownDescription": "Enables the name command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store", + "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." + }, + { + "description": "Enables the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-listener", + "markdownDescription": "Enables the remove_listener command without any pre-configured scope." + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme", + "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-dock-visibility", + "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Enables the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-supports-multiple-windows", + "markdownDescription": "Enables the supports_multiple_windows command without any pre-configured scope." + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version", + "markdownDescription": "Enables the tauri_version command without any pre-configured scope." + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version", + "markdownDescription": "Enables the version command without any pre-configured scope." + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide", + "markdownDescription": "Denies the app_hide command without any pre-configured scope." + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show", + "markdownDescription": "Denies the app_show command without any pre-configured scope." + }, + { + "description": "Denies the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-bundle-type", + "markdownDescription": "Denies the bundle_type command without any pre-configured scope." + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon", + "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." + }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers", + "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier", + "markdownDescription": "Denies the identifier command without any pre-configured scope." + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name", + "markdownDescription": "Denies the name command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store", + "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." + }, + { + "description": "Denies the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-listener", + "markdownDescription": "Denies the remove_listener command without any pre-configured scope." + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme", + "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-dock-visibility", + "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Denies the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-supports-multiple-windows", + "markdownDescription": "Denies the supports_multiple_windows command without any pre-configured scope." + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version", + "markdownDescription": "Denies the tauri_version command without any pre-configured scope." + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version", + "markdownDescription": "Denies the version command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", + "type": "string", + "const": "core:event:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit", + "markdownDescription": "Enables the emit command without any pre-configured scope." + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to", + "markdownDescription": "Enables the emit_to command without any pre-configured scope." + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen", + "markdownDescription": "Enables the listen command without any pre-configured scope." + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten", + "markdownDescription": "Enables the unlisten command without any pre-configured scope." + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit", + "markdownDescription": "Denies the emit command without any pre-configured scope." + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to", + "markdownDescription": "Denies the emit_to command without any pre-configured scope." + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen", + "markdownDescription": "Denies the listen command without any pre-configured scope." + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten", + "markdownDescription": "Denies the unlisten command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", + "type": "string", + "const": "core:image:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes", + "markdownDescription": "Enables the from_bytes command without any pre-configured scope." + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path", + "markdownDescription": "Enables the from_path command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba", + "markdownDescription": "Enables the rgba command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes", + "markdownDescription": "Denies the from_bytes command without any pre-configured scope." + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path", + "markdownDescription": "Denies the from_path command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba", + "markdownDescription": "Denies the rgba command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", + "type": "string", + "const": "core:menu:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append", + "markdownDescription": "Enables the append command without any pre-configured scope." + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default", + "markdownDescription": "Enables the create_default command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert", + "markdownDescription": "Enables the insert command without any pre-configured scope." + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked", + "markdownDescription": "Enables the is_checked command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items", + "markdownDescription": "Enables the items command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup", + "markdownDescription": "Enables the popup command without any pre-configured scope." + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend", + "markdownDescription": "Enables the prepend command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at", + "markdownDescription": "Enables the remove_at command without any pre-configured scope." + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator", + "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu", + "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp", + "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu", + "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp", + "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked", + "markdownDescription": "Enables the set_checked command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text", + "markdownDescription": "Enables the set_text command without any pre-configured scope." + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text", + "markdownDescription": "Enables the text command without any pre-configured scope." + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append", + "markdownDescription": "Denies the append command without any pre-configured scope." + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default", + "markdownDescription": "Denies the create_default command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert", + "markdownDescription": "Denies the insert command without any pre-configured scope." + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked", + "markdownDescription": "Denies the is_checked command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items", + "markdownDescription": "Denies the items command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup", + "markdownDescription": "Denies the popup command without any pre-configured scope." + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend", + "markdownDescription": "Denies the prepend command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at", + "markdownDescription": "Denies the remove_at command without any pre-configured scope." + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator", + "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu", + "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp", + "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu", + "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp", + "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked", + "markdownDescription": "Denies the set_checked command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text", + "markdownDescription": "Denies the set_text command without any pre-configured scope." + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text", + "markdownDescription": "Denies the text command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", + "type": "string", + "const": "core:path:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename", + "markdownDescription": "Enables the basename command without any pre-configured scope." + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname", + "markdownDescription": "Enables the dirname command without any pre-configured scope." + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname", + "markdownDescription": "Enables the extname command without any pre-configured scope." + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute", + "markdownDescription": "Enables the is_absolute command without any pre-configured scope." + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join", + "markdownDescription": "Enables the join command without any pre-configured scope." + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize", + "markdownDescription": "Enables the normalize command without any pre-configured scope." + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve", + "markdownDescription": "Enables the resolve command without any pre-configured scope." + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory", + "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename", + "markdownDescription": "Denies the basename command without any pre-configured scope." + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname", + "markdownDescription": "Denies the dirname command without any pre-configured scope." + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname", + "markdownDescription": "Denies the extname command without any pre-configured scope." + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute", + "markdownDescription": "Denies the is_absolute command without any pre-configured scope." + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join", + "markdownDescription": "Denies the join command without any pre-configured scope." + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize", + "markdownDescription": "Denies the normalize command without any pre-configured scope." + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve", + "markdownDescription": "Denies the resolve command without any pre-configured scope." + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory", + "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", + "type": "string", + "const": "core:resources:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`", + "type": "string", + "const": "core:tray:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id", + "markdownDescription": "Enables the get_by_id command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id", + "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template", + "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-with-as-template", + "markdownDescription": "Enables the set_icon_with_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu", + "markdownDescription": "Enables the set_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click", + "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path", + "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip", + "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible", + "markdownDescription": "Enables the set_visible command without any pre-configured scope." + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id", + "markdownDescription": "Denies the get_by_id command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id", + "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template", + "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-with-as-template", + "markdownDescription": "Denies the set_icon_with_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu", + "markdownDescription": "Denies the set_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click", + "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path", + "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip", + "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible", + "markdownDescription": "Denies the set_visible command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", + "type": "string", + "const": "core:webview:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data", + "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview", + "markdownDescription": "Enables the create_webview command without any pre-configured scope." + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window", + "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews", + "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools", + "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print", + "markdownDescription": "Enables the print command without any pre-configured scope." + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent", + "markdownDescription": "Enables the reparent command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-auto-resize", + "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-background-color", + "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus", + "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position", + "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size", + "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom", + "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close", + "markdownDescription": "Enables the webview_close command without any pre-configured scope." + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide", + "markdownDescription": "Enables the webview_hide command without any pre-configured scope." + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position", + "markdownDescription": "Enables the webview_position command without any pre-configured scope." + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show", + "markdownDescription": "Enables the webview_show command without any pre-configured scope." + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size", + "markdownDescription": "Enables the webview_size command without any pre-configured scope." + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data", + "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview", + "markdownDescription": "Denies the create_webview command without any pre-configured scope." + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window", + "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews", + "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools", + "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print", + "markdownDescription": "Denies the print command without any pre-configured scope." + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent", + "markdownDescription": "Denies the reparent command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-auto-resize", + "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-background-color", + "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus", + "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position", + "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size", + "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom", + "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close", + "markdownDescription": "Denies the webview_close command without any pre-configured scope." + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide", + "markdownDescription": "Denies the webview_hide command without any pre-configured scope." + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position", + "markdownDescription": "Denies the webview_position command without any pre-configured scope." + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show", + "markdownDescription": "Denies the webview_show command without any pre-configured scope." + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size", + "markdownDescription": "Denies the webview_size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`", + "type": "string", + "const": "core:window:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-activity-name", + "markdownDescription": "Enables the activity_name command without any pre-configured scope." + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors", + "markdownDescription": "Enables the available_monitors command without any pre-configured scope." + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center", + "markdownDescription": "Enables the center command without any pre-configured scope." + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor", + "markdownDescription": "Enables the current_monitor command without any pre-configured scope." + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position", + "markdownDescription": "Enables the cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy", + "markdownDescription": "Enables the destroy command without any pre-configured scope." + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows", + "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide", + "markdownDescription": "Enables the hide command without any pre-configured scope." + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position", + "markdownDescription": "Enables the inner_position command without any pre-configured scope." + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size", + "markdownDescription": "Enables the inner_size command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize", + "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top", + "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable", + "markdownDescription": "Enables the is_closable command without any pre-configured scope." + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated", + "markdownDescription": "Enables the is_decorated command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused", + "markdownDescription": "Enables the is_focused command without any pre-configured scope." + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen", + "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable", + "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized", + "markdownDescription": "Enables the is_maximized command without any pre-configured scope." + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable", + "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized", + "markdownDescription": "Enables the is_minimized command without any pre-configured scope." + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable", + "markdownDescription": "Enables the is_resizable command without any pre-configured scope." + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible", + "markdownDescription": "Enables the is_visible command without any pre-configured scope." + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize", + "markdownDescription": "Enables the maximize command without any pre-configured scope." + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize", + "markdownDescription": "Enables the minimize command without any pre-configured scope." + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point", + "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position", + "markdownDescription": "Enables the outer_position command without any pre-configured scope." + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size", + "markdownDescription": "Enables the outer_size command without any pre-configured scope." + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor", + "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention", + "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor", + "markdownDescription": "Enables the scale_factor command without any pre-configured scope." + }, + { + "description": "Enables the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scene-identifier", + "markdownDescription": "Enables the scene_identifier command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom", + "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top", + "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-background-color", + "markdownDescription": "Enables the set_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-count", + "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-label", + "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable", + "markdownDescription": "Enables the set_closable command without any pre-configured scope." + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected", + "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab", + "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon", + "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position", + "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible", + "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations", + "markdownDescription": "Enables the set_decorations command without any pre-configured scope." + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects", + "markdownDescription": "Enables the set_effects command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus", + "markdownDescription": "Enables the set_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focusable", + "markdownDescription": "Enables the set_focusable command without any pre-configured scope." + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen", + "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events", + "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size", + "markdownDescription": "Enables the set_max_size command without any pre-configured scope." + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable", + "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size", + "markdownDescription": "Enables the set_min_size command without any pre-configured scope." + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable", + "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-overlay-icon", + "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position", + "markdownDescription": "Enables the set_position command without any pre-configured scope." + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar", + "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable", + "markdownDescription": "Enables the set_resizable command without any pre-configured scope." + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow", + "markdownDescription": "Enables the set_shadow command without any pre-configured scope." + }, + { + "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-simple-fullscreen", + "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size", + "markdownDescription": "Enables the set_size command without any pre-configured scope." + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints", + "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar", + "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme", + "markdownDescription": "Enables the set_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style", + "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces", + "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging", + "markdownDescription": "Enables the start_dragging command without any pre-configured scope." + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging", + "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme", + "markdownDescription": "Enables the theme command without any pre-configured scope." + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title", + "markdownDescription": "Enables the title command without any pre-configured scope." + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize", + "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize", + "markdownDescription": "Enables the unmaximize command without any pre-configured scope." + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize", + "markdownDescription": "Enables the unminimize command without any pre-configured scope." + }, + { + "description": "Denies the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-activity-name", + "markdownDescription": "Denies the activity_name command without any pre-configured scope." + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors", + "markdownDescription": "Denies the available_monitors command without any pre-configured scope." + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center", + "markdownDescription": "Denies the center command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor", + "markdownDescription": "Denies the current_monitor command without any pre-configured scope." + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position", + "markdownDescription": "Denies the cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy", + "markdownDescription": "Denies the destroy command without any pre-configured scope." + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows", + "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide", + "markdownDescription": "Denies the hide command without any pre-configured scope." + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position", + "markdownDescription": "Denies the inner_position command without any pre-configured scope." + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size", + "markdownDescription": "Denies the inner_size command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize", + "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top", + "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable", + "markdownDescription": "Denies the is_closable command without any pre-configured scope." + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated", + "markdownDescription": "Denies the is_decorated command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused", + "markdownDescription": "Denies the is_focused command without any pre-configured scope." + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen", + "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable", + "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized", + "markdownDescription": "Denies the is_maximized command without any pre-configured scope." + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable", + "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized", + "markdownDescription": "Denies the is_minimized command without any pre-configured scope." + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable", + "markdownDescription": "Denies the is_resizable command without any pre-configured scope." + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible", + "markdownDescription": "Denies the is_visible command without any pre-configured scope." + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize", + "markdownDescription": "Denies the maximize command without any pre-configured scope." + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize", + "markdownDescription": "Denies the minimize command without any pre-configured scope." + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point", + "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position", + "markdownDescription": "Denies the outer_position command without any pre-configured scope." + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size", + "markdownDescription": "Denies the outer_size command without any pre-configured scope." + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor", + "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention", + "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor", + "markdownDescription": "Denies the scale_factor command without any pre-configured scope." + }, + { + "description": "Denies the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scene-identifier", + "markdownDescription": "Denies the scene_identifier command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize", + "markdownDescription": "Denies the unminimize command without any pre-configured scope." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + } + } +} \ No newline at end of file diff --git a/cassady-desktop/src-tauri/tauri.conf.json b/cassady-desktop/src-tauri/tauri.conf.json index 64df4c7..0f370ca 100644 --- a/cassady-desktop/src-tauri/tauri.conf.json +++ b/cassady-desktop/src-tauri/tauri.conf.json @@ -29,7 +29,7 @@ "withGlobalTauri": false }, "bundle": { - "active": true, + "active": false, "targets": "all", "icon": [ "icons/32x32.png", diff --git a/cassady-desktop/src/App.tsx b/cassady-desktop/src/App.tsx index 7c8ed15..125af85 100644 --- a/cassady-desktop/src/App.tsx +++ b/cassady-desktop/src/App.tsx @@ -18,7 +18,7 @@ export default function App() { if (!cwd) { return ( -
+
loading…
); diff --git a/cassady-desktop/src/components/ChatShell.tsx b/cassady-desktop/src/components/ChatShell.tsx index 1bff540..afc8edd 100644 --- a/cassady-desktop/src/components/ChatShell.tsx +++ b/cassady-desktop/src/components/ChatShell.tsx @@ -139,7 +139,7 @@ export function ChatShell({ cwd }: { cwd: string }) { }, [configError, chat, turn.blocks, turn.pendingApproval, cwd, handleNew]); return ( -
+
void handleNew()} diff --git a/cassady-desktop/vite.config.ts b/cassady-desktop/vite.config.ts index a49cc3d..47f99e7 100644 --- a/cassady-desktop/vite.config.ts +++ b/cassady-desktop/vite.config.ts @@ -6,6 +6,7 @@ import path from "path"; const host = process.env.TAURI_DEV_HOST; export default defineConfig({ + base: "./", plugins: [react(), tailwindcss()], resolve: { alias: { diff --git a/docs/commands.md b/docs/commands.md index f74404d..e5d33e6 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -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: diff --git a/docs/embedding.md b/docs/embedding.md index cfc9a3c..b8fcd9d 100644 --- a/docs/embedding.md +++ b/docs/embedding.md @@ -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 diff --git a/docs/platforms.md b/docs/platforms.md index 6f42c47..883fc94 100644 --- a/docs/platforms.md +++ b/docs/platforms.md @@ -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`. diff --git a/docs/releases/v0.4.0.md b/docs/releases/v0.4.0.md new file mode 100644 index 0000000..0f43e0c --- /dev/null +++ b/docs/releases/v0.4.0.md @@ -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 +``` diff --git a/npm/scripts/lib/release-config.mjs b/npm/scripts/lib/release-config.mjs index 51cb693..b081a2f 100644 --- a/npm/scripts/lib/release-config.mjs +++ b/npm/scripts/lib/release-config.mjs @@ -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', diff --git a/npm/scripts/prepare-release.mjs b/npm/scripts/prepare-release.mjs index 764581e..6906efa 100755 --- a/npm/scripts/prepare-release.mjs +++ b/npm/scripts/prepare-release.mjs @@ -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'; diff --git a/src/app.rs b/src/app.rs index c739d9b..6c457ff 100644 --- a/src/app.rs +++ b/src/app.rs @@ -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()); diff --git a/src/cli.rs b/src/cli.rs index 49d8a8b..afb9369 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -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. diff --git a/src/desktop.rs b/src/desktop.rs new file mode 100644 index 0000000..e6f889a --- /dev/null +++ b/src/desktop.rs @@ -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 { + 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 { + 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 { + 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 { + 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()); + } +} diff --git a/src/lib.rs b/src/lib.rs index 7a75ba3..cb61e24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/update.rs b/src/update.rs index 7b32d1e..982acd9 100644 --- a/src/update.rs +++ b/src/update.rs @@ -92,6 +92,7 @@ struct InstallPlan { struct StagedBinaries { cass: PathBuf, cassady: PathBuf, + desktop: Option, } 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 { .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> { @@ -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();