Merge remote-tracking branch 'origin/rebrand/r2-binary-names' into work/v0.8.41-codewhale-ready

This commit is contained in:
Hunter Bown
2026-05-23 13:18:49 -05:00
13 changed files with 369 additions and 209 deletions
+22 -20
View File
@@ -24,46 +24,48 @@ jobs:
fail-fast: false
matrix:
include:
# --- codewhale (cli dispatcher, canonical) ---
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
binary: deepseek
artifact_name: deepseek-linux-x64
binary: codewhale
artifact_name: codewhale-linux-x64
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
binary: deepseek
artifact_name: deepseek-linux-arm64
binary: codewhale
artifact_name: codewhale-linux-arm64
- os: macos-latest
target: x86_64-apple-darwin
binary: deepseek
artifact_name: deepseek-macos-x64
binary: codewhale
artifact_name: codewhale-macos-x64
- os: macos-latest
target: aarch64-apple-darwin
binary: deepseek
artifact_name: deepseek-macos-arm64
binary: codewhale
artifact_name: codewhale-macos-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
binary: deepseek.exe
artifact_name: deepseek-windows-x64.exe
binary: codewhale.exe
artifact_name: codewhale-windows-x64.exe
# --- codewhale-tui (TUI runtime, canonical) ---
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
binary: deepseek-tui
artifact_name: deepseek-tui-linux-x64
binary: codewhale-tui
artifact_name: codewhale-tui-linux-x64
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
binary: deepseek-tui
artifact_name: deepseek-tui-linux-arm64
binary: codewhale-tui
artifact_name: codewhale-tui-linux-arm64
- os: macos-latest
target: x86_64-apple-darwin
binary: deepseek-tui
artifact_name: deepseek-tui-macos-x64
binary: codewhale-tui
artifact_name: codewhale-tui-macos-x64
- os: macos-latest
target: aarch64-apple-darwin
binary: deepseek-tui
artifact_name: deepseek-tui-macos-arm64
binary: codewhale-tui
artifact_name: codewhale-tui-macos-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
binary: deepseek-tui.exe
artifact_name: deepseek-tui-windows-x64.exe
binary: codewhale-tui.exe
artifact_name: codewhale-tui-windows-x64.exe
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
+80 -20
View File
@@ -47,11 +47,11 @@ jobs:
- name: Workspace tests
run: cargo test --workspace --all-features --locked
- name: TUI snapshot parity
run: cargo test -p deepseek-tui-core --test snapshot --locked
run: cargo test -p codewhale-tui-core --test snapshot --locked
- name: Protocol schema parity
run: cargo test -p deepseek-protocol --test parity_protocol --locked
run: cargo test -p codewhale-protocol --test parity_protocol --locked
- name: State persistence parity
run: cargo test -p deepseek-state --test parity_state --locked
run: cargo test -p codewhale-state --test parity_state --locked
- name: Lockfile drift guard
run: git diff --exit-code -- Cargo.lock
@@ -100,7 +100,49 @@ jobs:
strategy:
matrix:
include:
# --- deepseek (cli) ---
# --- 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
@@ -121,7 +163,7 @@ jobs:
target: x86_64-pc-windows-msvc
binary: deepseek.exe
artifact_name: deepseek-windows-x64.exe
# --- deepseek-tui (TUI) ---
# --- deepseek-tui (legacy TUI shim; removed in v0.9.0) ---
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
binary: deepseek-tui
@@ -253,20 +295,27 @@ jobs:
- uses: actions/download-artifact@v4
with:
path: artifacts
pattern: deepseek*
# 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
manifest="artifacts/checksums/deepseek-artifacts-sha256.txt"
# 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:
@@ -274,12 +323,19 @@ jobs:
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 deepseek-tui
npm install -g codewhale
```
The wrapper downloads both binaries from this Release and places them in the same directory.
@@ -293,15 +349,15 @@ jobs:
ghcr.io/hmbown/deepseek-tui:${{ needs.resolve.outputs.tag }}
```
The image ships the `deepseek` dispatcher and `deepseek-tui` runtime. The `latest` tag is also updated on release.
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 deepseek-tui-cli deepseek-tui --locked
cargo install codewhale-cli codewhale-tui --locked
```
Both crates are required — `deepseek-tui-cli` produces the `deepseek` dispatcher and `deepseek-tui` produces the interactive runtime that the dispatcher delegates to. Installing only one binary will fail at runtime with a `MISSING_COMPANION_BINARY` error.
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
@@ -309,26 +365,30 @@ jobs:
| Platform | Dispatcher | TUI runtime |
|---|---|---|
| Linux x64 | `deepseek-linux-x64` | `deepseek-tui-linux-x64` |
| Linux ARM64 | `deepseek-linux-arm64` | `deepseek-tui-linux-arm64` |
| macOS x64 | `deepseek-macos-x64` | `deepseek-tui-macos-x64` |
| macOS ARM | `deepseek-macos-arm64` | `deepseek-tui-macos-arm64` |
| Windows x64 | `deepseek-windows-x64.exe` | `deepseek-tui-windows-x64.exe` |
| Linux x64 | `codewhale-linux-x64` | `codewhale-tui-linux-x64` |
| Linux ARM64 | `codewhale-linux-arm64` | `codewhale-tui-linux-arm64` |
| macOS x64 | `codewhale-macos-x64` | `codewhale-tui-macos-x64` |
| macOS ARM | `codewhale-macos-arm64` | `codewhale-tui-macos-arm64` |
| Windows x64 | `codewhale-windows-x64.exe` | `codewhale-tui-windows-x64.exe` |
Then `chmod +x` both (Unix) and run `./deepseek`.
Then `chmod +x` both (Unix) and run `./codewhale`.
Legacy `deepseek-*` and `deepseek-tui-*` assets are also attached 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 `deepseek-artifacts-sha256.txt` from this Release and verify:
Download `codewhale-artifacts-sha256.txt` from this Release and verify:
```bash
# Linux
sha256sum -c deepseek-artifacts-sha256.txt
sha256sum -c codewhale-artifacts-sha256.txt
# macOS
shasum -a 256 -c deepseek-artifacts-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.
## Changelog
See [CHANGELOG.md](https://github.com/Hmbown/DeepSeek-TUI/blob/main/CHANGELOG.md) for the full notes for this release.
+7 -1
View File
@@ -7,9 +7,15 @@ repository.workspace = true
description = "Codex-style CLI facade for DeepSeek workspace architecture"
[[bin]]
name = "deepseek"
name = "codewhale"
path = "src/main.rs"
# Legacy alias — forwards to `codewhale` and prints a deprecation notice.
# Will be removed in v0.9.0.
[[bin]]
name = "deepseek"
path = "src/bin/deepseek_legacy_shim.rs"
[dependencies]
anyhow.workspace = true
clap.workspace = true
@@ -0,0 +1,32 @@
//! Legacy `deepseek` alias.
//!
//! Forwards argv to the `codewhale` dispatcher and prints a one-line
//! deprecation notice to stderr on each invocation. This binary exists
//! for one release cycle to give existing installs a smooth path to the
//! new name; it will be removed in v0.9.0. See `docs/REBRAND.md` for the
//! full migration story.
use std::env;
use std::process::Command;
fn main() {
eprintln!(
"warning: `deepseek` is deprecated; run `codewhale` instead. \
This alias will be removed in v0.9.0."
);
let args: Vec<String> = env::args_os()
.skip(1)
.map(|a| a.to_string_lossy().into_owned())
.collect();
let status = match Command::new("codewhale").args(&args).status() {
Ok(s) => s,
Err(e) => {
eprintln!(
"error: failed to spawn `codewhale`: {e}. Is it on PATH? \
Install with `cargo install codewhale-cli` or via npm/Homebrew."
);
std::process::exit(127);
}
};
std::process::exit(status.code().unwrap_or(1));
}
+49 -49
View File
@@ -56,10 +56,10 @@ impl From<ProviderArg> for ProviderKind {
#[derive(Debug, Parser)]
#[command(
name = "deepseek",
name = "codewhale",
version = env!("DEEPSEEK_BUILD_VERSION"),
bin_name = "deepseek",
override_usage = "deepseek [OPTIONS] [PROMPT]\n deepseek [OPTIONS] <COMMAND> [ARGS]"
bin_name = "codewhale",
override_usage = "codewhale [OPTIONS] [PROMPT]\n codewhale [OPTIONS] <COMMAND> [ARGS]"
)]
struct Cli {
#[arg(long)]
@@ -175,26 +175,26 @@ Common forwarded flags:
/// Generate shell completions.
#[command(after_help = r#"Examples:
Bash (current shell only):
source <(deepseek completion bash)
source <(codewhale completion bash)
Bash (persistent, Linux/bash-completion):
mkdir -p ~/.local/share/bash-completion/completions
deepseek completion bash > ~/.local/share/bash-completion/completions/deepseek
codewhale completion bash > ~/.local/share/bash-completion/completions/codewhale
# Requires bash-completion to be installed and loaded by your shell.
Zsh:
mkdir -p ~/.zfunc
deepseek completion zsh > ~/.zfunc/_deepseek
codewhale completion zsh > ~/.zfunc/_codewhale
# Add to ~/.zshrc if needed:
# fpath=(~/.zfunc $fpath)
# autoload -Uz compinit && compinit
Fish:
mkdir -p ~/.config/fish/completions
deepseek completion fish > ~/.config/fish/completions/deepseek.fish
codewhale completion fish > ~/.config/fish/completions/codewhale.fish
PowerShell (current shell only):
deepseek completion powershell | Out-String | Invoke-Expression
codewhale completion powershell | Out-String | Invoke-Expression
The command prints the completion script to stdout; redirect it to a path your shell loads automatically."#)]
Completion {
@@ -203,7 +203,7 @@ The command prints the completion script to stdout; redirect it to a path your s
},
/// Print a usage rollup from the audit log and session store.
Metrics(MetricsArgs),
/// Check for and apply updates to the `deepseek` binary.
/// Check for and apply updates to the `codewhale` binary.
Update,
}
@@ -521,7 +521,7 @@ fn run() -> Result<()> {
Some(Commands::AppServer(args)) => run_app_server_command(args),
Some(Commands::Completion { shell }) => {
let mut cmd = Cli::command();
generate(shell, &mut cmd, "deepseek", &mut io::stdout());
generate(shell, &mut cmd, "codewhale", &mut io::stdout());
Ok(())
}
Some(Commands::Metrics(args)) => run_metrics_command(args),
@@ -1356,7 +1356,7 @@ fn run_dispatcher_resume_picker(
println!();
println!("Windows note: enter a session id or prefix from the list above.");
println!("You can also run `deepseek resume --last` to skip this prompt.");
println!("You can also run `codewhale resume --last` to skip this prompt.");
print!("Session id/prefix (Enter to cancel): ");
io::stdout().flush()?;
@@ -1423,7 +1423,7 @@ fn build_tui_command(
| ProviderKind::Ollama
) {
bail!(
"The interactive TUI supports DeepSeek, NVIDIA NIM, OpenAI-compatible, AtlasCloud, Wanjie Ark, OpenRouter, Novita, Fireworks, SGLang, vLLM, and Ollama providers. Remove --provider {} or use `deepseek model ...` for provider registry inspection.",
"The interactive TUI supports DeepSeek, NVIDIA NIM, OpenAI-compatible, AtlasCloud, Wanjie Ark, OpenRouter, Novita, Fireworks, SGLang, vLLM, and Ollama providers. Remove --provider {} or use `codewhale model ...` for provider registry inspection.",
resolved_runtime.provider.as_str()
);
}
@@ -1502,7 +1502,7 @@ fn build_tui_command(
fn exit_with_tui_status(status: std::process::ExitStatus) -> Result<()> {
match status.code() {
Some(code) => std::process::exit(code),
None => bail!("deepseek-tui terminated by signal"),
None => bail!("codewhale-tui terminated by signal"),
}
}
@@ -1514,7 +1514,7 @@ fn delegate_simple_tui(args: Vec<String>) -> Result<()> {
.map_err(|err| anyhow!("{}", tui_spawn_error(&tui, &err)))?;
match status.code() {
Some(code) => std::process::exit(code),
None => bail!("deepseek-tui terminated by signal"),
None => bail!("codewhale-tui terminated by signal"),
}
}
@@ -1522,23 +1522,23 @@ fn tui_spawn_error(tui: &Path, err: &io::Error) -> String {
format!(
"failed to spawn companion TUI binary at {}: {err}\n\
\n\
The `deepseek` dispatcher found a `deepseek-tui` file, but the OS refused \
The `codewhale` dispatcher found a `codewhale-tui` file, but the OS refused \
to execute it. Common fixes:\n\
- Reinstall with `npm install -g deepseek-tui`, or run `deepseek update`.\n\
- On Windows, run `where deepseek` and `where deepseek-tui`; both should \
- Reinstall with `npm install -g codewhale`, or run `codewhale update`.\n\
- On Windows, run `where codewhale` and `where codewhale-tui`; both should \
come from the same install directory.\n\
- If you downloaded release assets manually, keep both `deepseek` and \
`deepseek-tui` binaries together and make sure the TUI binary is executable.\n\
- Set DEEPSEEK_TUI_BIN to the absolute path of a working `deepseek-tui` \
- If you downloaded release assets manually, keep both `codewhale` and \
`codewhale-tui` binaries together and make sure the TUI binary is executable.\n\
- Set DEEPSEEK_TUI_BIN to the absolute path of a working `codewhale-tui` \
binary.",
tui.display()
)
}
/// Resolve the sibling `deepseek-tui` executable next to the running
/// Resolve the sibling `codewhale-tui` executable next to the running
/// dispatcher. Honours platform executable suffix (`.exe` on Windows) so
/// the npm-distributed Windows package — which ships
/// `bin/downloads/deepseek-tui.exe` — is found by `Path::exists` (#247).
/// `bin/downloads/codewhale-tui.exe` — is found by `Path::exists` (#247).
///
/// `DEEPSEEK_TUI_BIN` is consulted first as an explicit override for
/// custom installs and CI test layouts. On Windows we additionally try
@@ -1562,39 +1562,39 @@ fn locate_sibling_tui_binary() -> Result<PathBuf> {
}
// Build a stable error path so the user sees the platform-correct
// expected name, not "deepseek-tui" on Windows.
let expected = current.with_file_name(format!("deepseek-tui{}", std::env::consts::EXE_SUFFIX));
// expected name, not "codewhale-tui" on Windows.
let expected = current.with_file_name(format!("codewhale-tui{}", std::env::consts::EXE_SUFFIX));
bail!(
"Companion `deepseek-tui` binary not found at {}.\n\
"Companion `codewhale-tui` binary not found at {}.\n\
\n\
The `deepseek` dispatcher delegates interactive sessions to a sibling \
`deepseek-tui` binary. To fix this, install one of:\n\
• npm: npm install -g deepseek-tui (downloads both binaries)\n\
• cargo: cargo install deepseek-tui-cli deepseek-tui --locked\n\
• GitHub Releases: download BOTH `deepseek-<platform>` AND \
`deepseek-tui-<platform>` from https://github.com/Hmbown/DeepSeek-TUI/releases/latest \
The `codewhale` dispatcher delegates interactive sessions to a sibling \
`codewhale-tui` binary. To fix this, install one of:\n\
• npm: npm install -g codewhale (downloads both binaries)\n\
• cargo: cargo install codewhale-cli codewhale-tui --locked\n\
• GitHub Releases: download BOTH `codewhale-<platform>` AND \
`codewhale-tui-<platform>` from https://github.com/Hmbown/DeepSeek-TUI/releases/latest \
and place them in the same directory.\n\
\n\
Or set DEEPSEEK_TUI_BIN to the absolute path of an existing `deepseek-tui` binary.",
Or set DEEPSEEK_TUI_BIN to the absolute path of an existing `codewhale-tui` binary.",
expected.display()
);
}
/// Return the first existing sibling-binary path under any of the names
/// `deepseek-tui` might use on this platform. Pure function to keep
/// `codewhale-tui` might use on this platform. Pure function to keep
/// `locate_sibling_tui_binary` testable.
fn sibling_tui_candidate(dispatcher: &Path) -> Option<PathBuf> {
// Primary: platform-correct name. EXE_SUFFIX is "" on Unix and ".exe"
// on Windows.
let primary =
dispatcher.with_file_name(format!("deepseek-tui{}", std::env::consts::EXE_SUFFIX));
dispatcher.with_file_name(format!("codewhale-tui{}", std::env::consts::EXE_SUFFIX));
if primary.is_file() {
return Some(primary);
}
// Windows fallback: a user who manually renamed `.exe` away (per the
// workaround in #247) still launches successfully under the new code.
if cfg!(windows) {
let suffixless = dispatcher.with_file_name("deepseek-tui");
let suffixless = dispatcher.with_file_name("codewhale-tui");
if suffixless.is_file() {
return Some(suffixless);
}
@@ -2712,11 +2712,11 @@ mod tests {
vec![
"<SHELL>",
"bash",
"source <(deepseek completion bash)",
"~/.local/share/bash-completion/completions/deepseek",
"source <(codewhale completion bash)",
"~/.local/share/bash-completion/completions/codewhale",
"fpath=(~/.zfunc $fpath)",
"deepseek completion fish > ~/.config/fish/completions/deepseek.fish",
"deepseek completion powershell | Out-String | Invoke-Expression",
"codewhale completion fish > ~/.config/fish/completions/codewhale.fish",
"codewhale completion powershell | Out-String | Invoke-Expression",
],
),
("metrics", vec!["--json", "--since"]),
@@ -2735,8 +2735,8 @@ mod tests {
}
/// Regression for issue #247: on Windows the dispatcher must find the
/// sibling `deepseek-tui.exe`, not bail out looking for an
/// extension-less `deepseek-tui`. The candidate resolver also accepts
/// sibling `codewhale-tui.exe`, not bail out looking for an
/// extension-less `codewhale-tui`. The candidate resolver also accepts
/// the suffix-less name on Windows so users who manually renamed the
/// file as a workaround keep working after the upgrade.
#[test]
@@ -2744,7 +2744,7 @@ mod tests {
let dir = tempfile::TempDir::new().expect("tempdir");
let dispatcher = dir
.path()
.join("deepseek")
.join("codewhale")
.with_extension(std::env::consts::EXE_EXTENSION);
// Touch the dispatcher so its parent dir is the lookup root.
std::fs::write(&dispatcher, b"").unwrap();
@@ -2753,7 +2753,7 @@ mod tests {
assert!(sibling_tui_candidate(&dispatcher).is_none());
let target =
dispatcher.with_file_name(format!("deepseek-tui{}", std::env::consts::EXE_SUFFIX));
dispatcher.with_file_name(format!("codewhale-tui{}", std::env::consts::EXE_SUFFIX));
std::fs::write(&target, b"").unwrap();
let found = sibling_tui_candidate(&dispatcher).expect("must locate sibling");
@@ -2763,11 +2763,11 @@ mod tests {
#[test]
fn dispatcher_spawn_error_names_path_and_recovery_checks() {
let err = io::Error::new(io::ErrorKind::PermissionDenied, "access is denied");
let message = tui_spawn_error(Path::new("C:/tools/deepseek-tui.exe"), &err);
let message = tui_spawn_error(Path::new("C:/tools/codewhale-tui.exe"), &err);
assert!(message.contains("C:/tools/deepseek-tui.exe"));
assert!(message.contains("C:/tools/codewhale-tui.exe"));
assert!(message.contains("access is denied"));
assert!(message.contains("where deepseek"));
assert!(message.contains("where codewhale"));
assert!(message.contains("DEEPSEEK_TUI_BIN"));
}
@@ -2779,15 +2779,15 @@ mod tests {
#[test]
fn sibling_tui_candidate_windows_falls_back_to_suffixless() {
let dir = tempfile::TempDir::new().expect("tempdir");
let dispatcher = dir.path().join("deepseek.exe");
let dispatcher = dir.path().join("codewhale.exe");
std::fs::write(&dispatcher, b"").unwrap();
// Only the suffixless name exists — emulates the manual rename.
let suffixless = dispatcher.with_file_name("deepseek-tui");
let suffixless = dispatcher.with_file_name("codewhale-tui");
std::fs::write(&suffixless, b"").unwrap();
let found = sibling_tui_candidate(&dispatcher)
.expect("Windows fallback must locate suffixless deepseek-tui");
.expect("Windows fallback must locate suffixless codewhale-tui");
assert_eq!(found, suffixless);
}
+108 -94
View File
@@ -1,4 +1,4 @@
//! Self-update for the `deepseek` binary.
//! Self-update for the `codewhale` binary.
//!
//! The `update` subcommand fetches the latest release from
//! `github.com/Hmbown/DeepSeek-TUI/releases/latest`, downloads the
@@ -11,14 +11,14 @@ use std::path::{Path, PathBuf};
use anyhow::{Context, Result, bail};
use std::io::Write;
const CHECKSUM_MANIFEST_ASSET: &str = "deepseek-artifacts-sha256.txt";
const CHECKSUM_MANIFEST_ASSET: &str = "codewhale-artifacts-sha256.txt";
const LATEST_RELEASE_URL: &str = "https://api.github.com/repos/Hmbown/DeepSeek-TUI/releases/latest";
const CNB_REPO_URL: &str = "https://cnb.cool/deepseek-tui.com/DeepSeek-TUI";
const RELEASE_BASE_URL_ENV: &str = "DEEPSEEK_TUI_RELEASE_BASE_URL";
const LEGACY_RELEASE_BASE_URL_ENV: &str = "DEEPSEEK_RELEASE_BASE_URL";
const UPDATE_VERSION_ENV: &str = "DEEPSEEK_TUI_VERSION";
const LEGACY_UPDATE_VERSION_ENV: &str = "DEEPSEEK_VERSION";
const UPDATE_USER_AGENT: &str = "deepseek-tui-updater";
const UPDATE_USER_AGENT: &str = "codewhale-updater";
/// Run the self-update workflow.
pub fn run_update() -> Result<()> {
@@ -134,19 +134,19 @@ pub(crate) fn binary_prefix_for_exe(current_exe: &Path) -> &'static str {
let exe_name = current_exe
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("deepseek");
if exe_name.contains("deepseek-tui") {
"deepseek-tui"
.unwrap_or("codewhale");
if exe_name.contains("codewhale-tui") {
"codewhale-tui"
} else {
"deepseek"
"codewhale"
}
}
fn sibling_prefix_for(prefix: &str) -> &'static str {
if prefix == "deepseek-tui" {
"deepseek"
if prefix == "codewhale-tui" {
"codewhale"
} else {
"deepseek-tui"
"codewhale-tui"
}
}
@@ -337,7 +337,7 @@ fn release_from_mirror_base_url(
browser_download_url: mirror_asset_url(base_url, CHECKSUM_MANIFEST_ASSET),
}];
for prefix in ["deepseek", "deepseek-tui"] {
for prefix in ["codewhale", "codewhale-tui"] {
let name = release_asset_name_for_prefix(prefix, os, rust_arch);
assets.push(Asset {
browser_download_url: mirror_asset_url(base_url, &name),
@@ -357,10 +357,10 @@ fn update_network_fallback_hint() -> String {
"GitHub release downloads may be blocked or slow on this network.\n\
For mainland China, use one of these fallback paths:\n\
1. Source build from the CNB mirror, installing both shipped binaries:\n\
cargo install --git {CNB_REPO_URL} --tag vX.Y.Z deepseek-tui-cli --locked --force\n\
cargo install --git {CNB_REPO_URL} --tag vX.Y.Z deepseek-tui --locked --force\n\
cargo install --git {CNB_REPO_URL} --tag vX.Y.Z codewhale-cli --locked --force\n\
cargo install --git {CNB_REPO_URL} --tag vX.Y.Z codewhale-tui --locked --force\n\
2. Use a binary asset mirror:\n\
{RELEASE_BASE_URL_ENV}=https://<mirror>/<release-assets>/ {UPDATE_VERSION_ENV}=X.Y.Z deepseek update\n\
{RELEASE_BASE_URL_ENV}=https://<mirror>/<release-assets>/ {UPDATE_VERSION_ENV}=X.Y.Z codewhale update\n\
The mirror directory must contain {CHECKSUM_MANIFEST_ASSET} and the platform binaries."
)
}
@@ -428,7 +428,7 @@ fn replace_binary(target: &Path, new_bytes: &[u8]) -> Result<()> {
.unwrap_or_else(|| Path::new("."));
let mut tmp = tempfile::Builder::new()
.prefix(".deepseek-update-")
.prefix(".codewhale-update-")
.tempfile_in(parent)
.with_context(|| format!("failed to create temp file in {}", parent.display()))?;
tmp.write_all(new_bytes)
@@ -532,46 +532,57 @@ mod tests {
/// Verify binary prefix detection for dispatcher vs TUI binary.
#[test]
fn test_binary_prefix_detection() {
// TUI binary should use deepseek-tui prefix
// TUI binary should use codewhale-tui prefix
assert_eq!(
binary_prefix_for_exe(Path::new("deepseek-tui")),
"deepseek-tui"
binary_prefix_for_exe(Path::new("codewhale-tui")),
"codewhale-tui"
);
assert_eq!(
binary_prefix_for_exe(Path::new("deepseek-tui.exe")),
"deepseek-tui"
binary_prefix_for_exe(Path::new("codewhale-tui.exe")),
"codewhale-tui"
);
assert_eq!(
binary_prefix_for_exe(Path::new("/usr/local/bin/deepseek-tui")),
"deepseek-tui"
binary_prefix_for_exe(Path::new("/usr/local/bin/codewhale-tui")),
"codewhale-tui"
);
// Dispatcher binary should use deepseek prefix
assert_eq!(binary_prefix_for_exe(Path::new("deepseek")), "deepseek");
assert_eq!(binary_prefix_for_exe(Path::new("deepseek.exe")), "deepseek");
// Dispatcher binary should use codewhale prefix
assert_eq!(binary_prefix_for_exe(Path::new("codewhale")), "codewhale");
assert_eq!(
binary_prefix_for_exe(Path::new("/usr/local/bin/deepseek")),
"deepseek"
binary_prefix_for_exe(Path::new("codewhale.exe")),
"codewhale"
);
assert_eq!(
binary_prefix_for_exe(Path::new("/usr/local/bin/codewhale")),
"codewhale"
);
// Fallback for unknown names
assert_eq!(binary_prefix_for_exe(Path::new("other-binary")), "deepseek");
assert_eq!(
binary_prefix_for_exe(Path::new("other-binary")),
"codewhale"
);
}
#[test]
fn test_release_asset_stem_for_supported_platforms() {
let cases = [
("deepseek", "macos", "aarch64", "deepseek-macos-arm64"),
("deepseek", "macos", "x86_64", "deepseek-macos-x64"),
("deepseek", "linux", "x86_64", "deepseek-linux-x64"),
("deepseek", "windows", "x86_64", "deepseek-windows-x64"),
("codewhale", "macos", "aarch64", "codewhale-macos-arm64"),
("codewhale", "macos", "x86_64", "codewhale-macos-x64"),
("codewhale", "linux", "x86_64", "codewhale-linux-x64"),
("codewhale", "windows", "x86_64", "codewhale-windows-x64"),
(
"deepseek-tui",
"codewhale-tui",
"macos",
"aarch64",
"deepseek-tui-macos-arm64",
"codewhale-tui-macos-arm64",
),
(
"codewhale-tui",
"linux",
"x86_64",
"codewhale-tui-linux-x64",
),
("deepseek-tui", "linux", "x86_64", "deepseek-tui-linux-x64"),
];
for (exe, os, arch, expected) in cases {
@@ -584,10 +595,10 @@ mod tests {
let dir = tempfile::TempDir::new().unwrap();
let dispatcher = dir
.path()
.join(format!("deepseek{}", std::env::consts::EXE_SUFFIX));
.join(format!("codewhale{}", std::env::consts::EXE_SUFFIX));
let tui = dir
.path()
.join(format!("deepseek-tui{}", std::env::consts::EXE_SUFFIX));
.join(format!("codewhale-tui{}", std::env::consts::EXE_SUFFIX));
std::fs::write(&dispatcher, b"dispatcher").unwrap();
std::fs::write(&tui, b"tui").unwrap();
@@ -598,8 +609,8 @@ mod tests {
.collect::<Vec<_>>();
assert_eq!(paths, vec![dispatcher.as_path(), tui.as_path()]);
assert!(targets[0].asset_stem.starts_with("deepseek-"));
assert!(targets[1].asset_stem.starts_with("deepseek-tui-"));
assert!(targets[0].asset_stem.starts_with("codewhale-"));
assert!(targets[1].asset_stem.starts_with("codewhale-tui-"));
}
#[test]
@@ -607,37 +618,37 @@ mod tests {
let dir = tempfile::TempDir::new().unwrap();
let dispatcher = dir
.path()
.join(format!("deepseek{}", std::env::consts::EXE_SUFFIX));
.join(format!("codewhale{}", std::env::consts::EXE_SUFFIX));
std::fs::write(&dispatcher, b"dispatcher").unwrap();
let targets = update_targets_for_exe(&dispatcher);
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].path, dispatcher);
assert!(targets[0].asset_stem.starts_with("deepseek-"));
assert!(targets[0].asset_stem.starts_with("codewhale-"));
}
#[test]
fn test_asset_matching_accepts_binary_assets_and_rejects_checksums() {
assert!(asset_matches_platform(
"deepseek-macos-arm64",
"deepseek-macos-arm64"
"codewhale-macos-arm64",
"codewhale-macos-arm64"
));
assert!(asset_matches_platform(
"deepseek-macos-arm64.tar.gz",
"deepseek-macos-arm64"
"codewhale-macos-arm64.tar.gz",
"codewhale-macos-arm64"
));
assert!(asset_matches_platform(
"deepseek-tui-windows-x64.exe",
"deepseek-tui-windows-x64"
"codewhale-tui-windows-x64.exe",
"codewhale-tui-windows-x64"
));
assert!(!asset_matches_platform(
"deepseek-tui-windows-x64.exe.sha256",
"deepseek-tui-windows-x64"
"codewhale-tui-windows-x64.exe.sha256",
"codewhale-tui-windows-x64"
));
assert!(!asset_matches_platform(
"deepseek-macos-aarch64.tar.gz",
"deepseek-macos-arm64"
"codewhale-macos-aarch64.tar.gz",
"codewhale-macos-arm64"
));
}
@@ -663,18 +674,18 @@ mod tests {
#[test]
fn parse_checksum_manifest_accepts_sha256sum_format() {
let manifest = "\
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 deepseek-macos-arm64
E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *deepseek-windows-x64.exe
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 codewhale-macos-arm64
E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *codewhale-windows-x64.exe
";
let checksums = parse_checksum_manifest(manifest).expect("valid manifest");
assert_eq!(
checksums.get("deepseek-macos-arm64").map(String::as_str),
checksums.get("codewhale-macos-arm64").map(String::as_str),
Some("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824")
);
assert_eq!(
checksums
.get("deepseek-windows-x64.exe")
.get("codewhale-windows-x64.exe")
.map(String::as_str),
Some("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
);
@@ -682,7 +693,7 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *deepseek-wind
#[test]
fn parse_checksum_manifest_rejects_malformed_lines() {
let err = parse_checksum_manifest("not-a-hash deepseek-macos-arm64")
let err = parse_checksum_manifest("not-a-hash codewhale-macos-arm64")
.expect_err("invalid manifest line should fail");
assert!(
err.to_string().contains("invalid SHA256 manifest line"),
@@ -694,11 +705,11 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *deepseek-wind
fn expected_sha256_from_manifest_requires_matching_asset() {
let manifest =
"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 other-asset\n";
let err = expected_sha256_from_manifest(manifest, "deepseek-macos-arm64")
let err = expected_sha256_from_manifest(manifest, "codewhale-macos-arm64")
.expect_err("missing asset should fail");
assert!(
err.to_string()
.contains("checksum manifest is missing deepseek-macos-arm64"),
.contains("checksum manifest is missing codewhale-macos-arm64"),
"unexpected error: {err:#}"
);
}
@@ -706,7 +717,7 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *deepseek-wind
#[test]
fn test_replace_binary_creates_and_replaces() {
let dir = tempfile::TempDir::new().unwrap();
let target = dir.path().join("deepseek-test");
let target = dir.path().join("codewhale-test");
// Write initial content
std::fs::write(&target, b"old binary").unwrap();
@@ -718,30 +729,30 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *deepseek-wind
#[test]
fn test_replace_binary_creates_new_file() {
let dir = tempfile::TempDir::new().unwrap();
let target = dir.path().join("deepseek-new-test");
let target = dir.path().join("codewhale-new-test");
replace_binary(&target, b"fresh binary").unwrap();
let content = std::fs::read_to_string(&target).unwrap();
assert_eq!(content, "fresh binary");
}
/// Mocked GitHub release payload covering both the dispatcher (`deepseek`)
/// and the legacy TUI (`deepseek-tui`) binaries across our published
/// Mocked GitHub release payload covering both the dispatcher (`codewhale`)
/// and the legacy TUI (`codewhale-tui`) binaries across our published
/// platform/arch matrix, plus a checksum sibling that must never be picked
/// as the primary binary.
fn mocked_release() -> Release {
let json = r#"{
"tag_name": "v0.8.8",
"assets": [
{ "name": "deepseek-linux-x64", "browser_download_url": "https://example.invalid/deepseek-linux-x64" },
{ "name": "deepseek-macos-x64", "browser_download_url": "https://example.invalid/deepseek-macos-x64" },
{ "name": "deepseek-macos-arm64", "browser_download_url": "https://example.invalid/deepseek-macos-arm64" },
{ "name": "deepseek-windows-x64.exe", "browser_download_url": "https://example.invalid/deepseek-windows-x64.exe" },
{ "name": "deepseek-windows-x64.exe.sha256", "browser_download_url": "https://example.invalid/deepseek-windows-x64.exe.sha256" },
{ "name": "deepseek-tui-linux-x64", "browser_download_url": "https://example.invalid/deepseek-tui-linux-x64" },
{ "name": "deepseek-tui-macos-x64", "browser_download_url": "https://example.invalid/deepseek-tui-macos-x64" },
{ "name": "deepseek-tui-macos-arm64", "browser_download_url": "https://example.invalid/deepseek-tui-macos-arm64" },
{ "name": "deepseek-tui-windows-x64.exe","browser_download_url": "https://example.invalid/deepseek-tui-windows-x64.exe" }
{ "name": "codewhale-linux-x64", "browser_download_url": "https://example.invalid/codewhale-linux-x64" },
{ "name": "codewhale-macos-x64", "browser_download_url": "https://example.invalid/codewhale-macos-x64" },
{ "name": "codewhale-macos-arm64", "browser_download_url": "https://example.invalid/codewhale-macos-arm64" },
{ "name": "codewhale-windows-x64.exe", "browser_download_url": "https://example.invalid/codewhale-windows-x64.exe" },
{ "name": "codewhale-windows-x64.exe.sha256", "browser_download_url": "https://example.invalid/codewhale-windows-x64.exe.sha256" },
{ "name": "codewhale-tui-linux-x64", "browser_download_url": "https://example.invalid/codewhale-tui-linux-x64" },
{ "name": "codewhale-tui-macos-x64", "browser_download_url": "https://example.invalid/codewhale-tui-macos-x64" },
{ "name": "codewhale-tui-macos-arm64", "browser_download_url": "https://example.invalid/codewhale-tui-macos-arm64" },
{ "name": "codewhale-tui-windows-x64.exe","browser_download_url": "https://example.invalid/codewhale-tui-windows-x64.exe" }
]
}"#;
serde_json::from_str(json).expect("mock release JSON")
@@ -751,14 +762,14 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *deepseek-wind
fn mocked_release_selects_dispatcher_asset_for_supported_platforms() {
let release = mocked_release();
let cases = [
("macos", "aarch64", "deepseek-macos-arm64"),
("macos", "x86_64", "deepseek-macos-x64"),
("linux", "x86_64", "deepseek-linux-x64"),
("windows", "x86_64", "deepseek-windows-x64.exe"),
("macos", "aarch64", "codewhale-macos-arm64"),
("macos", "x86_64", "codewhale-macos-x64"),
("linux", "x86_64", "codewhale-linux-x64"),
("windows", "x86_64", "codewhale-windows-x64.exe"),
];
for (os, arch, expected) in cases {
let stem = release_asset_stem_for(Path::new("/usr/local/bin/deepseek"), os, arch);
let stem = release_asset_stem_for(Path::new("/usr/local/bin/codewhale"), os, arch);
let asset = select_platform_asset(&release, &stem)
.unwrap_or_else(|| panic!("no asset for {os}/{arch} (stem {stem})"));
assert_eq!(asset.name, expected, "{os}/{arch}");
@@ -768,10 +779,13 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *deepseek-wind
#[test]
fn mocked_release_selects_tui_asset_when_tui_binary_invokes_update() {
let release = mocked_release();
let stem =
release_asset_stem_for(Path::new("/usr/local/bin/deepseek-tui"), "macos", "aarch64");
let stem = release_asset_stem_for(
Path::new("/usr/local/bin/codewhale-tui"),
"macos",
"aarch64",
);
let asset = select_platform_asset(&release, &stem).expect("TUI platform asset");
assert_eq!(asset.name, "deepseek-tui-macos-arm64");
assert_eq!(asset.name, "codewhale-tui-macos-arm64");
}
#[test]
@@ -787,19 +801,19 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *deepseek-wind
assert_eq!(release.assets[0].name, CHECKSUM_MANIFEST_ASSET);
assert_eq!(
release.assets[0].browser_download_url,
"https://mirror.example/releases/v0.8.36/deepseek-artifacts-sha256.txt"
"https://mirror.example/releases/v0.8.36/codewhale-artifacts-sha256.txt"
);
let dispatcher =
select_platform_asset(&release, "deepseek-linux-x64").expect("dispatcher asset");
select_platform_asset(&release, "codewhale-linux-x64").expect("dispatcher asset");
assert_eq!(
dispatcher.browser_download_url,
"https://mirror.example/releases/v0.8.36/deepseek-linux-x64"
"https://mirror.example/releases/v0.8.36/codewhale-linux-x64"
);
let tui = select_platform_asset(&release, "deepseek-tui-linux-x64").expect("tui asset");
let tui = select_platform_asset(&release, "codewhale-tui-linux-x64").expect("tui asset");
assert_eq!(
tui.browser_download_url,
"https://mirror.example/releases/v0.8.36/deepseek-tui-linux-x64"
"https://mirror.example/releases/v0.8.36/codewhale-tui-linux-x64"
);
}
@@ -814,12 +828,12 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *deepseek-wind
assert_eq!(release.tag_name, "v0.8.36");
assert!(
select_platform_asset(&release, "deepseek-windows-x64")
.is_some_and(|asset| asset.name == "deepseek-windows-x64.exe")
select_platform_asset(&release, "codewhale-windows-x64")
.is_some_and(|asset| asset.name == "codewhale-windows-x64.exe")
);
assert!(
select_platform_asset(&release, "deepseek-tui-windows-x64")
.is_some_and(|asset| asset.name == "deepseek-tui-windows-x64.exe")
select_platform_asset(&release, "codewhale-tui-windows-x64")
.is_some_and(|asset| asset.name == "codewhale-tui-windows-x64.exe")
);
}
@@ -830,8 +844,8 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *deepseek-wind
assert!(hint.contains(CNB_REPO_URL), "{hint}");
assert!(hint.contains(RELEASE_BASE_URL_ENV), "{hint}");
assert!(hint.contains(UPDATE_VERSION_ENV), "{hint}");
assert!(hint.contains("deepseek-tui-cli"), "{hint}");
assert!(hint.contains("deepseek-tui --locked"), "{hint}");
assert!(hint.contains("codewhale-cli"), "{hint}");
assert!(hint.contains("codewhale-tui --locked"), "{hint}");
}
fn serve_http_once(
@@ -868,8 +882,8 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *deepseek-wind
let body = br#"{
"tag_name": "v9.9.9",
"assets": [
{ "name": "deepseek-linux-x64", "browser_download_url": "http://example.invalid/deepseek-linux-x64" },
{ "name": "deepseek-artifacts-sha256.txt", "browser_download_url": "http://example.invalid/deepseek-artifacts-sha256.txt" }
{ "name": "codewhale-linux-x64", "browser_download_url": "http://example.invalid/codewhale-linux-x64" },
{ "name": "codewhale-artifacts-sha256.txt", "browser_download_url": "http://example.invalid/codewhale-artifacts-sha256.txt" }
]
}"#;
let (url, request_rx, handle) = serve_http_once("200 OK", "application/json", body);
@@ -886,7 +900,7 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *deepseek-wind
"got {request:?}"
);
assert!(
request_lower.contains("user-agent: deepseek-tui-updater"),
request_lower.contains("user-agent: codewhale-updater"),
"got {request:?}"
);
handle.join().expect("test server thread");
@@ -917,7 +931,7 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *deepseek-wind
let request_lower = request.to_ascii_lowercase();
assert!(request.starts_with("GET /release "), "got {request:?}");
assert!(
request_lower.contains("user-agent: deepseek-tui-updater"),
request_lower.contains("user-agent: codewhale-updater"),
"got {request:?}"
);
handle.join().expect("test server thread");
+8 -2
View File
@@ -5,7 +5,7 @@ edition.workspace = true
license.workspace = true
repository.workspace = true
description = "Terminal UI for DeepSeek"
default-run = "deepseek-tui"
default-run = "codewhale-tui"
[features]
default = ["tui", "json", "toml"]
@@ -15,9 +15,15 @@ json = ["schemaui/json"]
toml = ["schemaui/toml"]
[[bin]]
name = "deepseek-tui"
name = "codewhale-tui"
path = "src/main.rs"
# Legacy alias — forwards to `codewhale-tui` and prints a deprecation
# notice. Will be removed in v0.9.0.
[[bin]]
name = "deepseek-tui"
path = "src/bin/deepseek_tui_legacy_shim.rs"
[dependencies]
anyhow = "1.0.100"
arboard = "3.4"
@@ -0,0 +1,32 @@
//! Legacy `deepseek-tui` alias.
//!
//! Forwards argv to the `codewhale-tui` runtime and prints a one-line
//! deprecation notice to stderr on each invocation. This binary exists
//! for one release cycle to give existing installs a smooth path to the
//! new name; it will be removed in v0.9.0. See `docs/REBRAND.md` for the
//! full migration story.
use std::env;
use std::process::Command;
fn main() {
eprintln!(
"warning: `deepseek-tui` is deprecated; run `codewhale-tui` (or `codewhale`) instead. \
This alias will be removed in v0.9.0."
);
let args: Vec<String> = env::args_os()
.skip(1)
.map(|a| a.to_string_lossy().into_owned())
.collect();
let status = match Command::new("codewhale-tui").args(&args).status() {
Ok(s) => s,
Err(e) => {
eprintln!(
"error: failed to spawn `codewhale-tui`: {e}. Is it on PATH? \
Install with `cargo install codewhale-tui` or via npm/Homebrew."
);
std::process::exit(127);
}
};
std::process::exit(status.code().unwrap_or(1));
}
+2 -2
View File
@@ -38,7 +38,7 @@ fn boot_minimal_without_retry() -> anyhow::Result<(qa_harness::harness::SealedWo
fn spawn_minimal(
ws: qa_harness::harness::SealedWorkspace,
) -> anyhow::Result<(qa_harness::harness::SealedWorkspace, Harness)> {
let h = Harness::builder(Harness::cargo_bin("deepseek-tui"))
let h = Harness::builder(Harness::cargo_bin("codewhale-tui"))
.cwd(ws.workspace())
.seal_home(ws.home())
// Provide a stub key so the onboarding screen is bypassed and the TUI
@@ -165,7 +165,7 @@ fn skills_menu_shows_local_and_global_skills() -> anyhow::Result<()> {
"Workspace beta skill",
)?;
let mut h = Harness::builder(Harness::cargo_bin("deepseek-tui"))
let mut h = Harness::builder(Harness::cargo_bin("codewhale-tui"))
.cwd(ws.workspace())
.seal_home(ws.home())
.env("DEEPSEEK_API_KEY", "ci-test-key-not-real")
@@ -46,7 +46,7 @@ spin up a PTY just to assert a function returns the right value.
3. Spawn:
```rust
let mut h = Harness::builder(Harness::cargo_bin("deepseek-tui"))
let mut h = Harness::builder(Harness::cargo_bin("codewhale-tui"))
.cwd(ws.workspace())
.seal_home(ws.home())
.env("DEEPSEEK_API_KEY", "ci-test-key")
@@ -221,6 +221,12 @@ impl Harness {
if let Some(path) = std::env::var_os(&key) {
return PathBuf::from(path);
}
if name == "codewhale-tui"
&& let Some(path) = option_env!("CARGO_BIN_EXE_codewhale-tui")
{
return PathBuf::from(path);
}
// Legacy fallback for callers still referencing the old bin name.
if name == "deepseek-tui"
&& let Some(path) = option_env!("CARGO_BIN_EXE_deepseek-tui")
{
+7 -5
View File
@@ -6,8 +6,10 @@
# 1. No `crates/*/Cargo.toml` carries a literal `version = "x.y.z"`; every
# crate must inherit `version.workspace = true`.
# 2. `npm/deepseek-tui/package.json` `version` matches the workspace
# `version` in the root `Cargo.toml`.
# 3. Internal `deepseek-*` path dependency pins match the workspace version.
# `version` in the root `Cargo.toml`. (The npm wrapper directory is
# renamed to `npm/codewhale/` in a follow-up phase; this script will
# be updated then.)
# 3. Internal `codewhale-*` path dependency pins match the workspace version.
# 4. The TUI crate's packaged changelog copy matches root `CHANGELOG.md`.
# 5. The current release has a dated Keep a Changelog entry and compare link.
# 6. README contributor additions are mentioned in the current release entry.
@@ -38,11 +40,11 @@ fi
# 3) Internal path dependency pins.
internal_dep_drift="$(
grep -nE 'deepseek-[a-z-]+[[:space:]]*=[[:space:]]*\{[^}]*version[[:space:]]*=[[:space:]]*"' crates/*/Cargo.toml \
grep -nE 'codewhale-[a-z-]+[[:space:]]*=[[:space:]]*\{[^}]*version[[:space:]]*=[[:space:]]*"' crates/*/Cargo.toml \
| grep -v "version[[:space:]]*=[[:space:]]*\"${workspace_version}\"" || true
)"
if [[ -n "${internal_dep_drift}" ]]; then
echo "::error::Internal deepseek-* path dependency versions must match workspace version ${workspace_version}:" >&2
echo "::error::Internal codewhale-* path dependency versions must match workspace version ${workspace_version}:" >&2
echo "${internal_dep_drift}" >&2
fail=1
fi
@@ -125,7 +127,7 @@ fi
# 8) Cargo.lock in sync.
if ! cargo metadata --locked --format-version 1 --no-deps >/dev/null 2>&1; then
echo "::error::Cargo.lock is out of sync with the manifests. Run 'cargo update -p deepseek-tui' or 'cargo build' and commit the result." >&2
echo "::error::Cargo.lock is out of sync with the manifests. Run 'cargo update -p codewhale-tui' or 'cargo build' and commit the result." >&2
fail=1
fi
+15 -15
View File
@@ -1,19 +1,19 @@
#!/usr/bin/env bash
# Crates published for each DeepSeek TUI release, in dependency order.
# Crates published for each codewhale release, in dependency order.
release_crates=(
deepseek-secrets
deepseek-config
deepseek-protocol
deepseek-state
deepseek-agent
deepseek-execpolicy
deepseek-hooks
deepseek-mcp
deepseek-tools
deepseek-core
deepseek-app-server
deepseek-tui-core
deepseek-tui-cli
deepseek-tui
codewhale-secrets
codewhale-config
codewhale-protocol
codewhale-state
codewhale-agent
codewhale-execpolicy
codewhale-hooks
codewhale-mcp
codewhale-tools
codewhale-core
codewhale-app-server
codewhale-tui-core
codewhale-cli
codewhale-tui
)