Sweep brand mentions of `DeepSeek TUI` / `deepseek-tui` / bare
`deepseek` (the dispatcher binary) across all user-facing docs to
the new `codewhale` brand. The DeepSeek **provider** integration is
left untouched throughout: env vars (`DEEPSEEK_*`), model IDs
(`deepseek-v4-pro`, `deepseek-v4-flash`, `deepseek-chat`,
`deepseek-reasoner`), the `api.deepseek.com` host, the
`~/.deepseek/` config dir, and the `--provider deepseek` argument
value all keep the legacy spelling.
Anti-scope items deliberately left as the legacy `deepseek-tui`:
- Homebrew tap and formula (`Hmbown/homebrew-deepseek-tui`,
`brew install deepseek-tui`, `scoop install deepseek-tui`). The
tap rename ships separately.
- Docker image (`ghcr.io/hmbown/deepseek-tui`). Image-tag rename
ships separately.
- CNB mirror namespace (`cnb.cool/deepseek-tui.com/DeepSeek-TUI`).
Third-party hosted path.
- Security contact email (`security@deepseek-tui.com`).
- GitHub repo URL (`Hmbown/DeepSeek-TUI`).
New artifact:
- `docs/REBRAND.md` documents what changed, what didn't, the
deprecation window, and migration commands for npm / Cargo /
Homebrew / manual installs.
CHANGELOG entries:
- Root `CHANGELOG.md` and `crates/tui/CHANGELOG.md` both gain a
new `[Unreleased]` section describing the rename and the one-
release deprecation window. Historical entries are untouched.
Issue templates:
- `.github/ISSUE_TEMPLATE/bug_report.md` and `feature_request.md`
refer to "codewhale" / `codewhale --version` instead of the old
brand name in their environment fields.
The rebrand sweep was driven by a perl script with bulk patterns
(`deepseek-tui` -> `codewhale-tui`, `DeepSeek TUI` -> `codewhale`,
bare `deepseek` -> `codewhale` with provider/model/host/env-var/
config-path negative lookbehind/lookahead) followed by targeted
reverts for the anti-scope items above. Output was visually
reviewed file-by-file before committing.
Verified:
- `cargo check --workspace --all-targets --locked` — pass.
- `cargo test --workspace --all-features --locked` — pass (no
test source touched here; suite stayed green to confirm no
doc-from-string assertions broke).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rename the npm wrapper directory and package from `deepseek-tui` to
`codewhale`. Move under `npm/codewhale/`:
- `package.json` renamed (name, bin, internal field) — keeps a
`deepseekBinaryVersion` fallback so old metadata still works.
- Bin entry points renamed to `bin/codewhale.js` and
`bin/codewhale-tui.js`; they spawn the corresponding canonical
binaries via the wrapper.
- `scripts/artifacts.js` switches to the canonical asset-name matrix
(`codewhale-*`, `codewhale-tui-*`) and `codewhale-artifacts-sha256.txt`.
- `scripts/run.js` exports `runCodewhale` and `runCodewhaleTui`; the
legacy `runDeepseek` exports are gone since nothing else inside the
package depended on them.
- `scripts/install.js`, `verify-release-assets.js`, `preflight-glibc.js`
update brand-mention strings + User-Agent headers. Env vars
(`DEEPSEEK_TUI_*`, `DEEPSEEK_*`) are explicitly anti-scope and are
left in place.
- Tests retargeted at the canonical asset names; all 19 still pass.
- README rewritten with the new install command and a deprecation
note about the old package.
Create a one-release deprecation shim at `npm/deepseek-tui/`:
- `package.json` with no `bin`, just a postinstall script that
prints a clear message telling the user to install `codewhale`
instead.
- `README.md` with the same migration note.
- Will be removed in v0.9.0 (or whenever Hunter retires the shims).
Release-side scripts in `scripts/release/` follow the rename:
- `prepare-local-release-assets.js` now requires `npm/codewhale/...`
and copies the canonical `codewhale*` binaries.
- `npm-wrapper-smoke.js` smokes the renamed package.
- `check-versions.sh` reads `npm/codewhale/package.json` for the
primary check and additionally pins the legacy shim package to
the same version.
- `check-published.sh` queries `codewhale@<version>` (with
`codewhaleBinaryVersion` lookup that falls back to the legacy
`deepseekBinaryVersion` field).
- `.github/workflows/auto-tag.yml` watches both `npm/codewhale/` and
`npm/deepseek-tui/` package.json for auto-tag triggers.
Verified:
- `npm test` inside `npm/codewhale/` passes 19/19.
- `npm install --dry-run --ignore-scripts` succeeds for both
`npm/codewhale/` and `npm/deepseek-tui/`.
- `scripts/release/check-versions.sh` reports OK.
- Rust gates re-run: `cargo check`, `cargo fmt --check`,
`cargo clippy -- -D warnings`, all clean.
No `npm publish` is run from this change — Hunter publishes manually
when the rebrand is ready to ship.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The qa_pty integration tests booted the canonical TUI via
`Harness::cargo_bin("deepseek-tui")`. After the previous commit
renamed the canonical bin to `codewhale-tui` and made `deepseek-tui`
a tiny deprecation shim that forwards to it via PATH, those tests
launched the shim and hung waiting for a TUI frame because the
sealed PATH in the test sandbox has no `codewhale-tui`.
Point the harness at `cargo_bin("codewhale-tui")` directly. Also
add a `CARGO_BIN_EXE_codewhale-tui` compile-time fallback in the
harness so older Cargo versions still work, alongside the existing
legacy `deepseek-tui` fallback.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rename the canonical binaries:
- `deepseek` → `codewhale` (CLI dispatcher)
- `deepseek-tui` → `codewhale-tui` (TUI runtime)
Both legacy names continue to ship as tiny deprecation shims that print
a one-line warning to stderr and forward argv to the new binary. The
shims are produced by two new `[[bin]]` entries in `crates/cli/Cargo.toml`
and `crates/tui/Cargo.toml` pointing at small source files under
`src/bin/`. They will be removed in v0.9.0.
Touchpoints:
- Cargo bin entries + new shim source files.
- clap `name`/`bin_name`/usage strings flip to `codewhale`.
- Dispatcher's sibling-binary discovery looks for `codewhale-tui` and
reports `codewhale` in its error/help prose. `DEEPSEEK_TUI_BIN` env
var stays — env vars are explicitly anti-scope.
- `update.rs` now downloads `codewhale-*` assets and verifies them
against `codewhale-artifacts-sha256.txt`. Legacy `deepseek-*` assets
and `deepseek-artifacts-sha256.txt` are still produced by the release
matrix so v0.8.40's `deepseek update` keeps working through one
transition release.
- `ci.yml`, `nightly.yml`, `release.yml` updated to build/upload the new
canonical binaries; `release.yml`'s matrix doubles to also ship the
legacy shim binaries so v0.8.40 update clients land on the shim.
- `scripts/release/crates.sh` and `check-versions.sh` updated for the
renamed crate names from R1.
Local gates green: `cargo check --workspace --all-targets --locked`,
`cargo fmt --all -- --check`, `cargo clippy --workspace --all-targets
--all-features --locked -- -D warnings`, `cargo test --workspace
--all-features --locked` (3226+ pass, 0 fail), and `cargo build
--release` produces all four binaries:
- target/release/codewhale (canonical dispatcher)
- target/release/codewhale-tui (canonical TUI)
- target/release/deepseek (legacy shim, forwards to codewhale)
- target/release/deepseek-tui (legacy shim, forwards to codewhale-tui)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rename the 14 workspace member crates from `deepseek-*` (and
`deepseek-tui-*`) to `codewhale-*`. Internal-only — binary names
(`deepseek` and `deepseek-tui`) are intentionally untouched in this
phase; they move in the next phase along with the deprecation shims.
Affects:
- 14 `[package] name = "..."` declarations.
- All inter-crate `[dependencies]` entries that referenced the old
package names.
- All `use deepseek_*::...` statements rewritten to `use codewhale_*`.
- Cargo.lock regenerated.
CI workflows and release scripts that pass `-p deepseek-*` still
reference the old names; those move with the binary rename phase so
that pair lands together.
Local gates green: `cargo check --workspace --all-targets --locked`,
`cargo fmt --all -- --check`, `cargo clippy --workspace --all-targets
--all-features --locked -- -D warnings`, `cargo test --workspace
--all-features --locked` (3226+ pass, 0 fail).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per gemini-code-assist review on #1749: End on a newline character
should stay at the end of the current line (idempotent), not skip
to the next line. Removes the non-standard skip-past-newline logic
and updates the associated tests.
Plain Home and End now navigate within the current line instead of
jumping to the absolute start/end of the entire input. Ctrl+A and
Ctrl+E remain as absolute start/end shortcuts.
- Add move_cursor_line_start() / move_cursor_line_end() to App
- Wire Home -> move_cursor_line_start(), End -> move_cursor_line_end()
- On single-line input the new methods behave identically to the
absolute versions (no behaviour change)
- End on a newline character skips to the end of the next line
- 14 tests covering multiline, singleline, and edge cases
Address gemini-code-assist review on PR #1744 (two MEDIUM):
- cmd detection used program.eq_ignore_ascii_case("cmd"), which fails
for a full path (C:\Windows\System32\cmd.exe) or a .exe suffix, so the
raw_arg quoting fix would not apply. Use Path::file_stem() instead
(fully-qualified std::path::Path -> no unused import off-Windows).
- Strengthen the Windows block of issue_1691_quoted_commit_message_round_trips
to assert argv content equals spec.args, not just arg count, so the
raw_arg payload (quotes preserved, no extra escaping) is actually
verified. sandbox/mod.rs already asserts content -- left untouched.
Windows paths are cfg-gated (compile-checked on macOS, executed on
Windows CI). macOS build + clippy clean.
Refs #1691.
git commit -m "feat: complete sub-pages" failed on Windows with
'pathspec sub-pages" did not match' because the quoted -m message was
split on spaces. Root cause is not a tokenizer bug: CommandSpec::shell()
builds 'cmd /C "chcp 65001 >/dev/null & <command>"' and std::process::Command
applies MSVCRT escaping (" -> \"); cmd.exe does not use MSVCRT parsing,
so the quoting is destroyed and git receives feat:/complete/sub-pages"
as separate pathspecs. The Unix sh -c path was already correct.
Add a cfg-split push_shell_args() replacing cmd.args() at the 3 std
spawn sites in shell.rs. On Windows, for the cmd /C <payload> shape
only, pass /C and payload via CommandExt::raw_arg so the string reaches
cmd.exe verbatim (as a terminal does); other programs keep normal
escaping. Non-Windows is a faithful pass-through (byte-for-byte
unchanged). portable_pty path intentionally untouched (out of scope).
The Unix path is provably unchanged (tested); the Windows raw_arg
runtime correctness is only verifiable on a Windows runner -- flagged in
the PR for Windows CI verification per the #1736 Windows policy.
Refs #1691, #1736.
Address gemini-code-assist review on PR #1742 (MEDIUM): the status was
emitted at the assistant-persist site, but the same turn can still
CONTINUE for pending steers or sub-agent completions -- the user would
see a spurious 'turn ended without output' notice immediately before the
turn resumed.
Capture thinking_only_no_sendable at the persist site (no emission
there) and decide at the end of the tool_uses.is_empty() path, just
before the terminal break -- reachable only when there were no pending
steers, no sub-agent completions, and we were not holding for running
children. Extend should_emit_thinking_only_status with steers_pending
and holding_for_subagents (false if either), recomputed live at the
decision point as defense-in-depth. Unit test updated with the two new
no-emit cases.
Refs #1727.
When a model streams a turn with only a reasoning block (empty content,
no tool_calls -- e.g. gpt-oss via ollama's harmony->OpenAI shim mapping
to reasoning_content), has_sendable_assistant_content is false: the
if-only persist branch was skipped, NO event was emitted, and the turn
fell through to break. The UI spinner hung with no reply and no error.
Add an else-if at the same persist site that, only on a clean end
(tool_uses empty, turn_error.is_none(), not cancelled), warns and emits
an Event::status notice telling the user the turn ended and they can
retry. No assistant message is persisted (prior behavior preserved); no
retry/re-prompt/placeholder policy is added (out of scope). Guard
ordering extracted into a pure should_emit_thinking_only_status() helper
with a unit test, matching the existing should_hold_turn_for_subagents
style. Maintainer audit #1736 confirms #1727 is not shipped in v0.8.39
and release-blocking.
Refs #1727, #1736.
Address gemini-code-assist review on PR #1743:
- HIGH: should_replay_reasoning_content_for_provider was made model-aware
in the previous commit, but handle_chat_completion_stream still computed
is_reasoning_model = requires_reasoning_content(model) &&
provider_accepts_reasoning_content(provider). On the openai provider +
a DeepSeek model that was false during SSE parsing, so reasoning tokens
were stored as content (not reasoning_content) and the next request
still 400'd -- the fix was incomplete. Extract is_reasoning_model_for_stream()
and route the stream call site through it; add an equivalence test
locking it to the replay predicate so the two paths can't drift.
- MEDIUM: rename generic_openai_provider_drops_deepseek_reasoning_content
-> generic_openai_provider_drops_reasoning_content_for_non_deepseek_models
(now uses gpt-4o; old name was misleading).
Non-DeepSeek models on any provider are unaffected (#1542 not regressed).
Refs #1739, #1694, #1542.
should_replay_reasoning_content_for_provider() returned false whenever
provider_accepts_reasoning_content(provider) was false (true for
ApiProvider::Openai) without checking the model. This single gate feeds
both build_for_provider (include_reasoning) and
sanitize_thinking_mode_messages, so a DeepSeek reasoning model on the
generic openai provider (DeepSeek-compatible endpoint) had all
reasoning_content stripped -> the DeepSeek thinking-mode API 400s
('reasoning_content in the thinking mode must be passed back'). This is
the over-aggressive half of ac01b225 (fix#1542).
Gate the early return on the model too:
!provider_accepts_reasoning_content(provider) && !requires_reasoning_content(model).
Known DeepSeek reasoning models replay regardless of provider; genuine
non-DeepSeek models on openai still strip (effort=off still wins). #1542
not regressed (provider_accepts_reasoning_content untouched).
Two pre-existing client.rs tests asserted the buggy case (deepseek-v4-pro
on Openai -> dropped); retargeted to gpt-4o to preserve their #1542
intent without encoding the bug. New positive/negative coverage in
chat.rs.
Refs #1739, #1694, #1542, #1736.
Address gemini-code-assist review on PR #1740 (MEDIUM, clarity): the
non-DeepSeek else-branch match listed ApiProvider::Deepseek /
DeepseekCN arms that are logically unreachable (handled by the if
branch above). Collapse to a single
'Deepseek | DeepseekCN => unreachable!(...)' arm so the intent is
explicit for future maintainers. No behavior change.
Refs #1714.
The CLI --model handoff only exports DEEPSEEK_MODEL, which apply_env_overrides
funneled into the DeepSeek-only root default_text_model slot. default_model()
then treated it as a normalizable weak default and fell back to a
DeepSeek/provider default for an unrecognized custom model on a non-DeepSeek
provider, so e.g. --provider openai --model MiniMax-M2.7 silently sent a
DeepSeek model to the endpoint.
Route DEEPSEEK_MODEL into the active provider's model slot for non-DeepSeek
providers (mirroring the existing OPENAI_MODEL branch), and return an explicit
non-DeepSeek-alias model verbatim from default_model(). DeepSeek/DeepSeekCN
behavior is unchanged. Adds a regression test next to
openai_provider_accepts_custom_model_and_base_url.
Refs #1714, #1739, #1736.
Only DEEPSEEK_LOG_LEVEL should gate verbose CLI output. RUST_LOG controls
the tracing subscriber independently (file logging). On Windows stderr is
not redirected to the log file, so coupling the two causes tracing log
messages to leak into the TUI alt-screen, corrupting the display.
Closes#1774
On Linux, `arboard::Clipboard::new()` opens a blocking connect() to the
X11 Unix socket. When no X server is running (headless, WSL2 without
WSLg), the call hangs indefinitely. Because raw mode and the alternate
screen are already active at that point, Ctrl+C no longer generates
SIGINT and the event loop hasn't started yet — leaving the user with a
blank screen and no way to exit.
Move clipboard initialization from `ClipboardHandler::new()` (called
synchronously during App construction) to a lazy `ensure_clipboard()`
that runs on first read/write with a 500 ms timeout. If the X11
connection doesn't respond in time, the handler stays in fallback mode
and `write_text` falls through to the existing OSC 52 / pbcopy /
PowerShell paths.
The v0.8.38 upgrade dramatically changed two user-visible behaviors that
were not intended as regressions:
- The /model picker was reworked (#1201/#1632) to make a blocking network
fetch on open and replace the curated tier list with the raw provider
catalog. Revert model_picker.rs and the OpenModelPicker handler to the
v0.8.37 instant curated picker. The /models command still lists the live
catalog.
- #1617 rekeyed the approval cache to an exact full-argument fingerprint,
which also dropped the v0.8.37 arity-aware command-family grouping for
"approve for session". Reintroduce build_approval_grouping_key (the lossy
v0.8.37 logic) for approvals while keeping the exact key for denials, so
denying one call no longer over-blocks later differing calls.
https://claude.ai/code/session_01NDuRxM56o17SE7SDLcTFYT