Files
codewhale/.github/workflows/release.yml
T
Hunter Bown 1763261503 v0.8.46: release archives, sandbox depth, quick fixes, web install, docs
* docs: v0.8.46 CHANGELOG — platform archives, palette, sub-agents, sandbox, web install, search fixes

Closes #2188

* feat(v0.8.46): quick fixes — palette, model picker Esc, sub-agent sidebar, shell chip, model name casing, CVE bump (#2212)

* fix: bump qs to >=6.15.2 for CVE-2026-8723

Add qs override in feishu-bridge package.json to force transitive
dependency resolution to >=6.15.2, addressing CVE-2026-8723.

Refs: #2198

* fix: Esc in model picker applies last-highlighted choice

Previously Esc reverted to the initial model when the user hadn't
moved the selection. Now Esc always applies the currently highlighted
model and thinking-effort tier, making Esc consistent with Enter.

Also updates the picker footer hint from 'Esc cancel' to 'Esc apply'.

Refs: #2196

* feat: show ' shell running' chip in TUI footer

Adds a footer_shell_chip function that displays a ' shell running'
status chip in the footer's right cluster whenever a foreground shell
command is active via exec_shell. The chip is always visible regardless
of user-configured status items.

Refs: #2194

* feat: auto-collapse finished sub-agents in sidebar

When a sub-agent completes (status = 'done'), its detail lines
(id, steps, duration, progress) are now hidden in the sidebar agents
panel. Only the summary label line is shown, keeping the sidebar
compact. Running agents still show full detail.

Refs: #2195

* feat: refresh Whale dark palette for better contrast

Improve contrast and layer separation in the Whale dark theme:
- Deepen base background for more depth (10,17,32)
- Lighten panel (22,34,56) for clearer distinction from bg
- Lighten elevated surface (36,52,78) for better elevation
- Lighten selection (48,68,100) for clearer selected state
- Boost text hint (138,150,174) and dim (118,130,156) readability
- Brighter border (52,88,145) for better edge definition
- Update tool surface colors for consistency

Refs: #2197

* fix: preserve model name casing in normalize_model_name_for_provider

When the user enters a model name like 'DeepSeek-V4-Flash', the
normalizer was lowercasing it to 'deepseek-v4-flash' via the
canonical_official_deepseek_model_id function. Now the normalizer
preserves the caller's casing when the input already matches a known
model id case-insensitively. Compact aliases like 'deepseek-v4pro'
are still rewritten to 'deepseek-v4-pro'.

Refs: #2109

* feat(web): install download tile with arch detection, SHA256, China mirrors + companion binary fix (#2213)

* fix(web): download both codewhale and codewhale-tui binaries in install snippets

The SNIPPETS map only fetched one binary per platform, causing the
dispatcher to fail with MISSING_COMPANION_BINARY. Every arch now
downloads both codewhale AND codewhale-tui side-by-side.

- macOS/Linux: added second curl + combined chmod/xattr/mv for tui
- Windows: added second Invoke-WebRequest for codewhale-tui.exe
- VERIFY: PowerShell now hashes both binaries; Unix --ignore-missing
  covers all present binaries in a single sha256sum pass

* feat(web): add install download tile with arch detection, SHA256, and China mirrors (#2192)

* feat(sandbox/linux): process hardening — PR_SET_DUMPABLE, NO_NEW_PRIVS, RLIMIT_CORE (#2214)

* feat(sandbox/linux): add process hardening module — PR_SET_DUMPABLE, NO_NEW_PRIVS, RLIMIT_CORE (#2183)

* feat(sandbox/linux): seccomp filter + bwrap passthrough

- seccomp: BPF filter whitelisting safe syscalls, denying ptrace/mount/kexec
  and other dangerous syscalls. Uses raw BPF instructions via libc prctl to
  avoid external dependencies (#2182).
- bwrap: optional bubblewrap passthrough when /usr/bin/bwrap is present
  and [sandbox] prefer_bwrap=true in config. Creates read-only rootfs with
  write access limited to the working directory (#2184).
- landlock detect_denial extended to recognize seccomp SIGSYS/"Bad system
  call" patterns alongside existing Landlock EACCES/EPERM detection.
- SandboxManager gains prefer_bwrap field; set_prefer_bwrap on ShellManager.
- EngineConfig gains prefer_bwrap field, wired through main/ui/runtime_threads.
- Diagnostics now reports bwrap_available and cgroup_version.
- config.example.toml documents the prefer_bwrap key.

Pre-existing clippy fixes picked up in the same build:
- collapsible_if in ui.rs version-check
- cmp_owned in goal.rs test
- consecutive str::replace in normalize_auth_mode

Closes #2182, closes #2184

* docs: add cross-links to issue and PR templates in CONTRIBUTING.md (#2215)

- Link .github/ISSUE_TEMPLATE/bug_report.md and feature_request.md from
  the Reporting Issues section
- Link .github/PULL_REQUEST_TEMPLATE.md from the Pull Request Guidelines
  section

* feat(release): bundle platform archives with install scripts (#2216)

- Add bundle job to release workflow that creates per-platform archives
  (tar.gz for Linux/macOS, .zip for Windows) containing both codewhale
  and codewhale-tui binaries plus install scripts
- Create install.bat (Windows) — copies binaries to %USERPROFILE%\bin
- Create install.sh (Unix) — copies binaries to ~/.local/bin
- Windows gets a portable .zip variant without install script
- Release notes updated to promote archives as primary download method
- Individual binaries retained for npm wrapper and scripting

Closes #2193

* fix(web_search): fall back to DuckDuckGo when Bing returns zero results (#2130)

When the configured search provider is Bing and the query returns zero
results (common for technical/compound queries), fall through to the
DuckDuckGo path instead of reporting empty. A provenance message is
surfaced: "Bing returned no results; used DuckDuckGo fallback".

Also adds Security and Code of Conduct cross-links to CONTRIBUTING.md
per the sub-agent renovation (#2203).

* docs: SANDBOX.md threat model + RFCs for persistence and MCP + SandboxExecutor trait

- docs/SANDBOX.md: complete threat model describing each platform's sandbox
  (Seatbelt, Landlock, seccomp, process hardening, bwrap, Windows v1).
  Covers defense-in-depth layering, config keys, denial detection, limitations.
- docs/rfcs/2189-persistence-sqlite.md: RFC for SQLite migration (drafted by sub-agent)
- docs/rfcs/2190-mcp-modularization.md: RFC for MCP crate split into
  protocol/client/server with OAuth support
- crates/tui/src/sandbox/policy.rs: SandboxExecutor trait definition and
  SafetyLevel→SandboxPolicyBehavior mapping function with tests

Closes #2180, closes #2186, closes #2189, closes #2190

* feat: sandbox parity tests + remove sub-agent 100-turn cap

- Add sandbox parity tests covering platform detection, denial patterns,
  bwrap preference, and policy consistency across modes (#2187)
- Remove arbitrary 100-turn sub-agent cap: DEFAULT_MAX_STEPS changed
  from 100 to u32::MAX. Sub-agents now run until they produce a final
  text response, are cancelled by the parent, or hit a configured
  explicit budget (#2034)

Closes #2187, closes #2034
2026-05-26 09:52:22 -05:00

564 lines
22 KiB
YAML

name: Release
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
version:
description: 'Package/release version to publish to npm, without the leading v'
required: true
type: string
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings
jobs:
parity:
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- name: Install Linux system dependencies
if: runner.os == 'Linux'
run: |
for i in 1 2 3 4 5; do
sudo apt-get update && break
echo "apt-get update failed (attempt $i); retrying in 15s"
sleep 15
done
sudo apt-get install -y libdbus-1-dev pkg-config
- uses: Swatinem/rust-cache@v2
with:
cache-bin: false
- name: Format check
run: cargo fmt --all -- --check
- name: Compile check
run: cargo check --workspace --all-targets --locked
- name: Clippy
run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
- name: Workspace tests
run: cargo test --workspace --all-features --locked
- name: TUI snapshot parity
run: cargo test -p codewhale-tui-core --test snapshot --locked
- name: Protocol schema parity
run: cargo test -p codewhale-protocol --test parity_protocol --locked
- name: State persistence parity
run: cargo test -p codewhale-state --test parity_state --locked
- name: Lockfile drift guard
run: git diff --exit-code -- Cargo.lock
resolve:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.release.outputs.tag }}
source_ref: ${{ steps.release.outputs.source_ref }}
sha: ${{ steps.release.outputs.sha }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Resolve release source
id: release
shell: bash
run: |
if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then
tag="v${{ inputs.version }}"
git fetch --force origin "refs/tags/${tag}:refs/tags/${tag}"
sha="$(git rev-list -n 1 "${tag}")"
source_ref="${tag}"
else
tag="${GITHUB_REF_NAME}"
sha="${GITHUB_SHA}"
source_ref="${GITHUB_REF_NAME}"
fi
if [ -z "${sha}" ]; then
echo "Unable to resolve release source for ${tag}" >&2
exit 1
fi
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
echo "source_ref=${source_ref}" >> "$GITHUB_OUTPUT"
echo "sha=${sha}" >> "$GITHUB_OUTPUT"
build:
needs: [parity, resolve]
# `parity` is gated to tag-push events. On manual `workflow_dispatch`,
# parity is skipped, so let `build` proceed when parity either succeeded
# or was skipped — but never when it actually failed or the run was
# cancelled. Operators using dispatch are expected to have already run
# the same gates locally / via ci.yml on `main`.
if: ${{ !cancelled() && (needs.parity.result == 'success' || needs.parity.result == 'skipped') }}
strategy:
matrix:
include:
# --- codewhale (cli dispatcher, canonical) ---
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
binary: codewhale
artifact_name: codewhale-linux-x64
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
binary: codewhale
artifact_name: codewhale-linux-arm64
- os: macos-latest
target: x86_64-apple-darwin
binary: codewhale
artifact_name: codewhale-macos-x64
- os: macos-latest
target: aarch64-apple-darwin
binary: codewhale
artifact_name: codewhale-macos-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
binary: codewhale.exe
artifact_name: codewhale-windows-x64.exe
# --- codewhale-tui (TUI runtime, canonical) ---
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
binary: codewhale-tui
artifact_name: codewhale-tui-linux-x64
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
binary: codewhale-tui
artifact_name: codewhale-tui-linux-arm64
- os: macos-latest
target: x86_64-apple-darwin
binary: codewhale-tui
artifact_name: codewhale-tui-macos-x64
- os: macos-latest
target: aarch64-apple-darwin
binary: codewhale-tui
artifact_name: codewhale-tui-macos-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
binary: codewhale-tui.exe
artifact_name: codewhale-tui-windows-x64.exe
# --- deepseek (legacy dispatcher shim; removed in v0.9.0) ---
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
binary: deepseek
artifact_name: deepseek-linux-x64
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
binary: deepseek
artifact_name: deepseek-linux-arm64
- os: macos-latest
target: x86_64-apple-darwin
binary: deepseek
artifact_name: deepseek-macos-x64
- os: macos-latest
target: aarch64-apple-darwin
binary: deepseek
artifact_name: deepseek-macos-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
binary: deepseek.exe
artifact_name: deepseek-windows-x64.exe
# --- deepseek-tui (legacy TUI shim; removed in v0.9.0) ---
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
binary: deepseek-tui
artifact_name: deepseek-tui-linux-x64
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
binary: deepseek-tui
artifact_name: deepseek-tui-linux-arm64
- os: macos-latest
target: x86_64-apple-darwin
binary: deepseek-tui
artifact_name: deepseek-tui-macos-x64
- os: macos-latest
target: aarch64-apple-darwin
binary: deepseek-tui
artifact_name: deepseek-tui-macos-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
binary: deepseek-tui.exe
artifact_name: deepseek-tui-windows-x64.exe
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.resolve.outputs.source_ref }}
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
with:
cache-bin: false
- name: Install Linux system dependencies
if: runner.os == 'Linux'
run: |
for i in 1 2 3 4 5; do
sudo apt-get update && break
echo "apt-get update failed (attempt $i); retrying in 15s"
sleep 15
done
sudo apt-get install -y libdbus-1-dev pkg-config
- name: Build
shell: bash
env:
DEEPSEEK_BUILD_SHA: ${{ needs.resolve.outputs.sha }}
run: cargo build --release --locked --target ${{ matrix.target }}
- name: Rename binary
shell: bash
run: |
BIN_PATH="target/${{ matrix.target }}/release/${{ matrix.binary }}"
if [ ! -f "${BIN_PATH}" ]; then
echo "Binary not at ${BIN_PATH}; searching target/ for ${{ matrix.binary }}:"
find target -name "${{ matrix.binary }}" -type f
exit 1
fi
cp "${BIN_PATH}" "${{ matrix.artifact_name }}"
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}
path: ${{ matrix.artifact_name }}
bundle:
needs: [build, resolve]
if: ${{ !cancelled() && needs.build.result == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.resolve.outputs.source_ref }}
- uses: actions/download-artifact@v4
with:
path: artifacts
pattern: 'codewhale*'
- name: Create platform archives
shell: bash
run: |
set -euo pipefail
mkdir -p bundles/checksums
MANIFEST="bundles/checksums/codewhale-bundles-sha256.txt"
: > "$MANIFEST"
bundle() {
local platform="$1" # linux-x64, linux-arm64, macos-x64, macos-arm64, windows-x64
local cli_src="$2" # artifact name for codewhale binary
local tui_src="$3" # artifact name for codewhale-tui binary
local ext="$4" # tar.gz or zip
local variant="$5" # '' (standard) or 'portable' (Windows only, no install script)
shift 5
local dir="bundles/codewhale-${platform}${variant:+-}${variant}"
mkdir -p "$dir"
# Copy binaries, stripping platform suffixes
local cli_dst="codewhale"
local tui_dst="codewhale-tui"
if [[ "$platform" == windows-* ]]; then
cli_dst="codewhale.exe"
tui_dst="codewhale-tui.exe"
fi
cp "artifacts/${cli_src}/${cli_src}" "$dir/${cli_dst}"
cp "artifacts/${tui_src}/${tui_src}" "$dir/${tui_dst}"
# Add install script (standard variant only)
if [[ "$variant" != "portable" ]]; then
if [[ "$platform" == windows-* ]]; then
cp scripts/release/install.bat "$dir/"
# Convert line endings to CRLF for Windows
sed -i 's/$/\r/' "$dir/install.bat" 2>/dev/null || true
else
cp scripts/release/install.sh "$dir/"
chmod +x "$dir/install.sh"
fi
fi
if [[ "$ext" == "zip" ]]; then
(cd bundles && zip -r "codewhale-${platform}${variant:+-}${variant}.zip" "codewhale-${platform}${variant:+-}${variant}/")
else
tar -czf "bundles/codewhale-${platform}${variant:+-}${variant}.tar.gz" -C bundles "codewhale-${platform}${variant:+-}${variant}/"
fi
local archive="codewhale-${platform}${variant:+-}${variant}.${ext}"
sha256sum "bundles/${archive}" | awk '{printf "%s %s\n", $1, $2}' >> "$MANIFEST"
echo " Created bundles/${archive}"
}
# Platform: linux-x64
bundle linux-x64 \
codewhale-linux-x64 codewhale-tui-linux-x64 tar.gz ""
# Platform: linux-arm64
bundle linux-arm64 \
codewhale-linux-arm64 codewhale-tui-linux-arm64 tar.gz ""
# Platform: macos-x64
bundle macos-x64 \
codewhale-macos-x64 codewhale-tui-macos-x64 tar.gz ""
# Platform: macos-arm64
bundle macos-arm64 \
codewhale-macos-arm64 codewhale-tui-macos-arm64 tar.gz ""
# Platform: windows-x64 (standard + portable)
bundle windows-x64 \
codewhale-windows-x64.exe codewhale-tui-windows-x64.exe zip ""
bundle windows-x64 \
codewhale-windows-x64.exe codewhale-tui-windows-x64.exe zip "portable"
echo ""
echo "=== Archive checksums ==="
cat "$MANIFEST"
- name: Upload bundle artifacts
uses: actions/upload-artifact@v4
with:
name: codewhale-bundles
path: bundles/*
if-no-files-found: error
docker:
needs: [build, resolve]
if: ${{ !cancelled() && needs.build.result == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout release source
uses: actions/checkout@v4
with:
ref: ${{ needs.resolve.outputs.source_ref }}
path: source
- name: Checkout release infrastructure
uses: actions/checkout@v4
with:
path: infra
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Normalize image name
id: image
shell: bash
run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ steps.image.outputs.name }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern=v{{major}}
type=ref,event=tag
type=semver,pattern={{version}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=semver,pattern=v{{major}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=raw,value=${{ inputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=raw,value=v${{ inputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=raw,value=latest
- name: Build and push
uses: docker/build-push-action@v6
env:
# The build record is useful in CI, but it is uploaded as a
# `.dockerbuild` artifact. The release job intentionally downloads
# all binary artifacts, so suppress the extra record artifact there.
DOCKER_BUILD_RECORD_UPLOAD: false
DOCKER_BUILD_SUMMARY: false
with:
context: source
file: infra/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
build-args: |
DEEPSEEK_BUILD_SHA=${{ needs.resolve.outputs.sha }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
release:
needs: [build, bundle, docker, resolve]
if: ${{ !cancelled() && needs.build.result == 'success' && needs.bundle.result == 'success' && needs.docker.result == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts
# Match both the canonical `codewhale*` artifacts and the legacy
# `deepseek*` shim artifacts that ship for the transition release.
pattern: '*'
- name: List artifacts
run: find artifacts -type f
- name: Generate checksum manifest
shell: bash
run: |
mkdir -p artifacts/checksums
# Canonical manifest used by codewhale's `codewhale update` flow.
manifest="artifacts/checksums/codewhale-artifacts-sha256.txt"
: > "${manifest}"
while IFS= read -r -d '' file; do
hash="$(sha256sum "${file}" | awk '{print $1}')"
base="$(basename "${file}")"
printf '%s %s\n' "${hash}" "${base}" >> "${manifest}"
done < <(find artifacts -type f ! -path 'artifacts/checksums/*' -print0 | sort -z)
# Legacy alias manifest so v0.8.40 `deepseek update` clients can
# still find a manifest by their hardcoded name. Same content; will
# be removed once the legacy shim binaries are retired in v0.9.0.
cp "${manifest}" "artifacts/checksums/deepseek-artifacts-sha256.txt"
cat "${manifest}"
- uses: softprops/action-gh-release@v1
with:
tag_name: ${{ needs.resolve.outputs.tag }}
files: artifacts/*/*
prerelease: false
body: |
> This release renames the project to **CodeWhale**. The legacy
> `deepseek` and `deepseek-tui` binaries continue to ship as
> deprecation shims for one release cycle; they print a one-line
> warning and forward to `codewhale` / `codewhale-tui`. They will
> be removed in v0.9.0. See `docs/REBRAND.md` for the full
> migration story.
## Install
### Recommended — npm (one command, both binaries)
```bash
npm install -g codewhale
```
The wrapper downloads both binaries from this Release and places them in the same directory.
### Docker / GHCR
```bash
docker run --rm -it \
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
-v ~/.deepseek:/home/codewhale/.deepseek \
ghcr.io/hmbown/codewhale:${{ needs.resolve.outputs.tag }}
```
The image ships the `codewhale` dispatcher and `codewhale-tui` runtime (plus the legacy `deepseek` / `deepseek-tui` shims during the transition). The `latest` tag is also updated on release.
### Cargo (Linux / macOS)
```bash
cargo install codewhale-cli codewhale-tui --locked
```
Both crates are required — `codewhale-cli` produces the `codewhale` dispatcher and `codewhale-tui` produces the interactive runtime that the dispatcher delegates to. Installing only one binary will fail at runtime with a `MISSING_COMPANION_BINARY` error.
### Manual download — platform archives (recommended)
Each archive below contains **both** the `codewhale` dispatcher and `codewhale-tui` runtime, plus an install script:
| Platform | Archive | Install script |
|---|---|---|
| Linux x64 | `codewhale-linux-x64.tar.gz` | `install.sh` |
| Linux ARM64 | `codewhale-linux-arm64.tar.gz` | `install.sh` |
| macOS x64 | `codewhale-macos-x64.tar.gz` | `install.sh` |
| macOS ARM | `codewhale-macos-arm64.tar.gz` | `install.sh` |
| Windows x64 | `codewhale-windows-x64.zip` | `install.bat` |
| Windows x64 (portable) | `codewhale-windows-x64-portable.zip` | — |
**Unix (Linux / macOS):**
```bash
tar xzf codewhale-<platform>.tar.gz
cd codewhale-<platform>
./install.sh
```
**Windows:**
- Extract `codewhale-windows-x64.zip`
- Run `install.bat` (copies to `%USERPROFILE%\bin`)
- Add `%USERPROFILE%\bin` to your PATH
The **portable** Windows archive skips the install script — extract and run from any directory.
Individual binaries are also attached below for scripting and the npm wrapper. Legacy `deepseek-*` and `deepseek-tui-*` assets ship for one release cycle so that existing `deepseek update` invocations on v0.8.40 keep working; they install the deprecation shims, which forward to the canonical binaries.
### Verify (recommended)
Download the checksum manifests from this Release and verify:
```bash
# Linux — archive bundles
sha256sum -c codewhale-bundles-sha256.txt
# Linux — individual binaries
sha256sum -c codewhale-artifacts-sha256.txt
# macOS
shasum -a 256 -c codewhale-bundles-sha256.txt
shasum -a 256 -c codewhale-artifacts-sha256.txt
```
The legacy `deepseek-artifacts-sha256.txt` is also attached for backward compatibility and contains the same hashes as the canonical manifest.
## Changelog
See [CHANGELOG.md](https://github.com/Hmbown/CodeWhale/blob/main/CHANGELOG.md) for the full notes for this release.
homebrew:
needs: [release, resolve]
if: ${{ !cancelled() && needs.release.result == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Check Homebrew tap token
id: homebrew-token
env:
TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
run: |
if [ -z "${TOKEN:-}" ]; then
echo "No Homebrew tap token configured; skipping tap update."
echo "available=false" >> "${GITHUB_OUTPUT}"
else
echo "available=true" >> "${GITHUB_OUTPUT}"
fi
# Checkout main (not the tag) so the release-infra script is always
# available, even for tags created before this workflow was added.
- uses: actions/checkout@v4
if: steps.homebrew-token.outputs.available == 'true'
with:
ref: main
- name: Download checksum manifest
if: steps.homebrew-token.outputs.available == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release download ${{ needs.resolve.outputs.tag }} \
--repo ${{ github.repository }} \
--pattern 'deepseek-artifacts-sha256.txt' \
--dir /tmp
- name: Update Homebrew tap
if: steps.homebrew-token.outputs.available == 'true'
env:
TAG: ${{ needs.resolve.outputs.tag }}
MANIFEST: /tmp/deepseek-artifacts-sha256.txt
TAP_REPO: Hmbown/homebrew-deepseek-tui
TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
run: bash .github/scripts/update-homebrew-tap.sh
# npm publish is intentionally not automated. The npm account requires 2FA OTP
# on every publish, and a granular automation token that bypasses 2FA has not
# been provisioned. Release the npm wrapper manually from a developer machine
# after the GitHub Release has been created — see CLAUDE.md "Releases" for the
# exact commands.