Merge remote-tracking branch 'origin/rebrand/r2-binary-names' into work/v0.8.41-codewhale-ready
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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");
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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")
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user