feat(npm): publish as codewhale; keep deepseek-tui as deprecation shim

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>
This commit is contained in:
Hunter Bown
2026-05-23 11:11:50 -05:00
parent 8da9fb7d52
commit 23daefbe24
24 changed files with 327 additions and 219 deletions
+1
View File
@@ -18,6 +18,7 @@ on:
branches: [main]
paths:
- 'Cargo.toml'
- 'npm/codewhale/package.json'
- 'npm/deepseek-tui/package.json'
workflow_dispatch:
+94
View File
@@ -0,0 +1,94 @@
# codewhale
Install and run the `codewhale` and `codewhale-tui` binaries from GitHub release artifacts.
> Previously published as `deepseek-tui`. See `docs/REBRAND.md` in the upstream
> repository for the migration notes; the legacy `deepseek-tui` npm package
> still exists as a deprecation shim for one release cycle.
## Install
```bash
npm install -g codewhale
# or
pnpm add -g codewhale
```
For project-local usage:
```bash
npm install codewhale
npx codewhale --help
```
`postinstall` tries to download platform binaries into `bin/downloads/` and
exposes `codewhale` and `codewhale-tui` commands. If GitHub release assets are
temporarily unreachable, install continues and the wrapper retries the download
on first run.
## First run
```bash
codewhale login --api-key "YOUR_DEEPSEEK_API_KEY"
codewhale doctor
codewhale
```
The `codewhale` facade and `codewhale-tui` binary share `~/.deepseek/config.toml`
for DeepSeek auth and default model settings. Common TUI commands are available
directly through the facade, including `codewhale doctor`, `codewhale models`,
`codewhale sessions`, and `codewhale resume --last`.
The app talks to DeepSeek's documented OpenAI-compatible Chat Completions API.
Set `DEEPSEEK_BASE_URL` only if you need the China endpoint or DeepSeek beta
features such as strict tool mode, chat prefix completion, or FIM completion.
NVIDIA NIM-hosted DeepSeek V4 Pro is also supported:
```bash
codewhale auth set --provider nvidia-nim --api-key "YOUR_NVIDIA_API_KEY"
codewhale --provider nvidia-nim
```
For a single process, set `DEEPSEEK_PROVIDER=nvidia-nim` and `NVIDIA_API_KEY`
or `NVIDIA_NIM_API_KEY` (with `DEEPSEEK_API_KEY` as a compatibility fallback).
The NIM default model is `deepseek-ai/deepseek-v4-pro` and the default base URL
is `https://integrate.api.nvidia.com/v1`. With `--provider nvidia-nim`,
`--model deepseek-v4-flash` maps to `deepseek-ai/deepseek-v4-flash`.
## Supported platforms
Prebuilt binaries for the GitHub release are downloaded automatically:
- Linux x64
- Linux arm64 (v0.8.8+)
- macOS x64 / arm64
- Windows x64
Other platform/architecture combinations (musl, riscv64, FreeBSD, …) aren't
shipped as prebuilts. Unsupported platforms, checksum failures, and glibc
compatibility problems still fail with a clear error pointing you at
`cargo install codewhale-cli codewhale-tui --locked` and the full
[docs/INSTALL.md](https://github.com/Hmbown/DeepSeek-TUI/blob/main/docs/INSTALL.md)
build-from-source guide.
## Configuration
- Default binary version comes from `codewhaleBinaryVersion` in `package.json`
(with `deepseekBinaryVersion` as a backward-compat fallback).
- Set `DEEPSEEK_TUI_VERSION` or `DEEPSEEK_VERSION` to override the release version.
- Set `DEEPSEEK_TUI_GITHUB_REPO` or `DEEPSEEK_GITHUB_REPO` to override the source repo (defaults to `Hmbown/DeepSeek-TUI`).
- Set `DEEPSEEK_TUI_RELEASE_BASE_URL` to use an internal or mirrored
release-asset directory when GitHub Releases is unavailable. The directory
must contain `codewhale-artifacts-sha256.txt` and the platform binaries.
- Set `DEEPSEEK_TUI_FORCE_DOWNLOAD=1` to force download even when the cached binary is already present.
- Set `DEEPSEEK_TUI_DISABLE_INSTALL=1` to skip install-time download.
- Set `DEEPSEEK_TUI_OPTIONAL_INSTALL=1` to make install-time retryable download
failures warn and exit `0` instead of failing `npm install`.
## Release integrity
- `npm publish` runs a release-asset check to ensure all required binary assets
exist for the target GitHub release before publishing.
- Install-time downloads are verified against the release checksum manifest before
the wrapper marks them executable.
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env node
const { runCodewhaleTui } = require("../scripts/run");
runCodewhaleTui().catch((error) => {
console.error("Failed to start codewhale-tui:", error.message);
process.exit(1);
});
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env node
const { runCodewhale } = require("../scripts/run");
runCodewhale().catch((error) => {
console.error("Failed to start codewhale:", error.message);
process.exit(1);
});
+51
View File
@@ -0,0 +1,51 @@
{
"name": "codewhale",
"version": "0.8.40",
"codewhaleBinaryVersion": "0.8.40",
"description": "Install and run the codewhale CLI dispatcher and codewhale-tui terminal UI from GitHub release artifacts.",
"author": "Hmbown",
"license": "MIT",
"homepage": "https://github.com/Hmbown/DeepSeek-TUI",
"repository": {
"type": "git",
"url": "git+https://github.com/Hmbown/DeepSeek-TUI.git"
},
"bugs": {
"url": "https://github.com/Hmbown/DeepSeek-TUI/issues"
},
"keywords": [
"codewhale",
"deepseek",
"cli",
"tui",
"rust",
"binary",
"terminal"
],
"type": "commonjs",
"bin": {
"codewhale": "bin/codewhale.js",
"codewhale-tui": "bin/codewhale-tui.js"
},
"scripts": {
"release:check": "node scripts/verify-release-assets.js",
"postinstall": "node scripts/install.js --optional",
"prepublishOnly": "node scripts/verify-release-assets.js",
"prepack": "node scripts/install.js",
"test": "node --test test/*.test.js"
},
"engines": {
"node": ">=18"
},
"publishConfig": {
"access": "public"
},
"preferGlobal": true,
"files": [
"bin/*.js",
"scripts/*.js",
"test/*.js",
"README.md",
"package.json"
]
}
@@ -1,19 +1,19 @@
const path = require("path");
const os = require("os");
const CHECKSUM_MANIFEST = "deepseek-artifacts-sha256.txt";
const CHECKSUM_MANIFEST = "codewhale-artifacts-sha256.txt";
const ASSET_MATRIX = {
linux: {
x64: ["deepseek-linux-x64", "deepseek-tui-linux-x64"],
arm64: ["deepseek-linux-arm64", "deepseek-tui-linux-arm64"],
x64: ["codewhale-linux-x64", "codewhale-tui-linux-x64"],
arm64: ["codewhale-linux-arm64", "codewhale-tui-linux-arm64"],
},
darwin: {
x64: ["deepseek-macos-x64", "deepseek-tui-macos-x64"],
arm64: ["deepseek-macos-arm64", "deepseek-tui-macos-arm64"],
x64: ["codewhale-macos-x64", "codewhale-tui-macos-x64"],
arm64: ["codewhale-macos-arm64", "codewhale-tui-macos-arm64"],
},
win32: {
x64: ["deepseek-windows-x64.exe", "deepseek-tui-windows-x64.exe"],
x64: ["codewhale-windows-x64.exe", "codewhale-tui-windows-x64.exe"],
},
};
@@ -47,7 +47,7 @@ function detectBinaryNames() {
return {
platform,
arch,
deepseek: pair[0],
codewhale: pair[0],
tui: pair[1],
};
}
@@ -55,11 +55,11 @@ function detectBinaryNames() {
function unsupportedBuildHint() {
return [
"No prebuilt binary is available for this platform/architecture combo.",
"You can still run DeepSeek TUI by building from source with Cargo:",
"You can still run codewhale by building from source with Cargo:",
"",
" # Requires Rust 1.88+ (https://rustup.rs)",
" cargo install deepseek-tui-cli --locked # provides `deepseek`",
" cargo install deepseek-tui --locked # provides `deepseek-tui`",
" cargo install codewhale-cli --locked # provides `codewhale`",
" cargo install codewhale-tui --locked # provides `codewhale-tui`",
"",
"Or build from a checkout:",
"",
@@ -3,9 +3,9 @@ function assertSupportedNode() {
const major = Number.parseInt(String(version).split(".")[0], 10);
if (Number.isNaN(major) || major < 18) {
process.stderr.write(
"deepseek-tui: Node.js 18 or newer is required for npm installation. " +
"codewhale: Node.js 18 or newer is required for npm installation. " +
`Current Node.js version is ${version}. ` +
"Please upgrade Node.js and rerun `npm install -g deepseek-tui`.\n",
"Please upgrade Node.js and rerun `npm install -g codewhale`.\n",
);
process.exit(1);
}
@@ -136,16 +136,16 @@ function maxAttempts(context = "runtime", env = process.env) {
}
function binaryPaths() {
const { deepseek, tui } = detectBinaryNames();
const { codewhale, tui } = detectBinaryNames();
const releaseDir = releaseBinaryDirectory();
return {
deepseek: {
asset: deepseek,
target: path.join(releaseDir, process.platform === "win32" ? "deepseek.exe" : "deepseek"),
codewhale: {
asset: codewhale,
target: path.join(releaseDir, process.platform === "win32" ? "codewhale.exe" : "codewhale"),
},
tui: {
asset: tui,
target: path.join(releaseDir, process.platform === "win32" ? "deepseek-tui.exe" : "deepseek-tui"),
target: path.join(releaseDir, process.platform === "win32" ? "codewhale-tui.exe" : "codewhale-tui"),
},
};
}
@@ -166,7 +166,7 @@ function logInfo(message) {
if (isQuietInstall()) {
return;
}
process.stderr.write(`deepseek-tui: ${message}\n`);
process.stderr.write(`codewhale: ${message}\n`);
}
function installFailureHint(error) {
@@ -194,19 +194,19 @@ function installFailureHint(error) {
if (releaseBase) {
return [
"deepseek-tui install hint:",
"codewhale install hint:",
` DEEPSEEK_TUI_RELEASE_BASE_URL is set to ${releaseBase}`,
" Verify that this directory contains deepseek-artifacts-sha256.txt",
" plus the deepseek/deepseek-tui binary assets for your platform.",
" Verify that this directory contains codewhale-artifacts-sha256.txt",
" plus the codewhale/codewhale-tui binary assets for your platform.",
].join("\n");
}
return [
"deepseek-tui install hint:",
"codewhale install hint:",
" The npm package downloads prebuilt binaries from GitHub Releases.",
" If GitHub is unavailable on this network, mirror the release assets and set:",
" DEEPSEEK_TUI_RELEASE_BASE_URL=https://<mirror>/<release-asset-directory>/",
" The directory must contain deepseek-artifacts-sha256.txt and the platform binaries.",
" The directory must contain codewhale-artifacts-sha256.txt and the platform binaries.",
" See docs/INSTALL.md#npm-binary-download-times-out.",
].join("\n");
}
@@ -258,14 +258,14 @@ function createProgressReporter(assetName, totalBytes) {
const render = (final) => {
if (totalBytes && totalBytes > 0) {
const pct = Math.min(100, Math.round((received / totalBytes) * 100));
const line = `deepseek-tui: downloading ${assetName}: ${formatMb(received)} / ${formatMb(totalBytes)} MB (${pct}%)`;
const line = `codewhale: downloading ${assetName}: ${formatMb(received)} / ${formatMb(totalBytes)} MB (${pct}%)`;
if (interactive) {
process.stderr.write(`${line}\r`);
} else {
process.stderr.write(`${line}\n`);
}
} else {
const line = `deepseek-tui: downloading ${assetName}: ${formatMb(received)} MB downloaded`;
const line = `codewhale: downloading ${assetName}: ${formatMb(received)} MB downloaded`;
if (interactive) {
process.stderr.write(`${line}\r`);
} else {
@@ -295,7 +295,7 @@ function createProgressReporter(assetName, totalBytes) {
// Move past the carriage-return line and emit a "done" footer.
process.stderr.write("\n");
}
process.stderr.write(`deepseek-tui: ${assetName} ... done.\n`);
process.stderr.write(`codewhale: ${assetName} ... done.\n`);
},
};
}
@@ -406,7 +406,7 @@ function connectThroughProxy(proxy, targetHost, targetPort, timeoutMs) {
const lines = [
`CONNECT ${targetHost}:${targetPort} HTTP/1.1`,
`Host: ${targetHost}:${targetPort}`,
"User-Agent: deepseek-tui-installer",
"User-Agent: codewhale-installer",
"Proxy-Connection: keep-alive",
];
if (proxy.auth) {
@@ -555,7 +555,7 @@ function httpRequest(rawUrl, opts = {}) {
path: `${url.pathname}${url.search || ""}`,
headers: {
Host: url.host,
"User-Agent": "deepseek-tui-installer",
"User-Agent": "codewhale-installer",
Accept: "*/*",
Connection: "close",
},
@@ -641,7 +641,7 @@ function httpRequest(rawUrl, opts = {}) {
path: rawUrl,
headers: {
Host: url.host,
"User-Agent": "deepseek-tui-installer",
"User-Agent": "codewhale-installer",
Accept: "*/*",
Connection: "close",
...(proxy.auth ? { "Proxy-Authorization": `Basic ${proxy.auth}` } : {}),
@@ -704,7 +704,7 @@ function httpRequest(rawUrl, opts = {}) {
path: `${url.pathname}${url.search || ""}`,
headers: {
Host: url.host,
"User-Agent": "deepseek-tui-installer",
"User-Agent": "codewhale-installer",
Accept: "*/*",
Connection: "close",
},
@@ -1122,7 +1122,7 @@ async function run(options = {}) {
};
await Promise.all([
ensureBinary(paths.deepseek.target, paths.deepseek.asset, version, repo, getChecksums, { context }),
ensureBinary(paths.codewhale.target, paths.codewhale.asset, version, repo, getChecksums, { context }),
ensureBinary(paths.tui.target, paths.tui.asset, version, repo, getChecksums, { context }),
]);
}
@@ -1130,10 +1130,10 @@ async function run(options = {}) {
async function getBinaryPath(name) {
await run({ context: "runtime" });
const paths = binaryPaths();
if (name === "deepseek") {
return paths.deepseek.target;
if (name === "codewhale") {
return paths.codewhale.target;
}
if (name === "deepseek-tui") {
if (name === "codewhale-tui") {
return paths.tui.target;
}
throw new Error(`Unknown binary: ${name}`);
@@ -1158,7 +1158,7 @@ module.exports = {
if (require.main === module) {
run({ context: "install" }).catch((error) => {
console.error("deepseek-tui install failed:", error.message);
console.error("codewhale install failed:", error.message);
const hint = installFailureHint(error);
if (hint) {
console.error(hint);
@@ -66,11 +66,11 @@ function detectBinaryRequiredGlibc(filePath) {
function buildFromSourceHint() {
return [
"You can still run DeepSeek TUI by building from source with Cargo:",
"You can still run codewhale by building from source with Cargo:",
"",
" # Requires Rust 1.88+ (https://rustup.rs)",
" cargo install deepseek-tui-cli --locked # provides `deepseek`",
" cargo install deepseek-tui --locked # provides `deepseek-tui`",
" cargo install codewhale-cli --locked # provides `codewhale`",
" cargo install codewhale-tui --locked # provides `codewhale-tui`",
"",
"Or build from a checkout:",
"",
@@ -9,7 +9,8 @@ function isVersionFlag(args = process.argv.slice(2)) {
function handleVersionFallback(binaryName) {
if (isVersionFlag()) {
const binVersion = pkg.deepseekBinaryVersion || pkg.version;
const binVersion =
pkg.codewhaleBinaryVersion || pkg.deepseekBinaryVersion || pkg.version;
console.log(`${binaryName} (npm wrapper) v${pkg.version}`);
console.log(`binary version: v${binVersion}`);
console.log(`repo: ${pkg.repository?.url || "N/A"}`);
@@ -33,26 +34,26 @@ async function run(binaryName) {
process.exit(result.status ?? 1);
}
async function runDeepseek() {
await run("deepseek");
async function runCodewhale() {
await run("codewhale");
}
async function runDeepseekTui() {
await run("deepseek-tui");
async function runCodewhaleTui() {
await run("codewhale-tui");
}
module.exports = {
run,
runDeepseek,
runDeepseekTui,
runCodewhale,
runCodewhaleTui,
_internal: { isVersionFlag },
};
if (require.main === module) {
const command = process.argv[1] || "";
if (command.includes("tui")) {
runDeepseekTui();
runCodewhaleTui();
} else {
runDeepseek();
runCodewhale();
}
}
@@ -13,7 +13,7 @@ function resolveBinaryVersion() {
const configuredVersion =
process.env.DEEPSEEK_TUI_VERSION ||
process.env.DEEPSEEK_VERSION ||
pkg.deepseekBinaryVersion ||
pkg.codewhaleBinaryVersion || pkg.deepseekBinaryVersion ||
pkg.version;
return String(configuredVersion).trim();
}
@@ -33,7 +33,7 @@ function requestStatus(url, method = "HEAD", redirects = 0) {
{
method,
headers: {
"User-Agent": "deepseek-tui-npm-release-check",
"User-Agent": "codewhale-npm-release-check",
},
},
(res) => {
@@ -71,7 +71,7 @@ async function downloadText(url) {
url,
{
headers: {
"User-Agent": "deepseek-tui-npm-release-check",
"User-Agent": "codewhale-npm-release-check",
},
},
(res) => {
@@ -24,8 +24,8 @@ test("openharmony x64 resolves to linux x64 binaries", () => {
withMockedOs("openharmony", "x64", () => {
const { detectBinaryNames } = require(ARTIFACTS_PATH);
const result = detectBinaryNames();
assert.equal(result.deepseek, "deepseek-linux-x64");
assert.equal(result.tui, "deepseek-tui-linux-x64");
assert.equal(result.codewhale, "codewhale-linux-x64");
assert.equal(result.tui, "codewhale-tui-linux-x64");
});
});
@@ -33,8 +33,8 @@ test("openharmony arm64 resolves to linux arm64 binaries", () => {
withMockedOs("openharmony", "arm64", () => {
const { detectBinaryNames } = require(ARTIFACTS_PATH);
const result = detectBinaryNames();
assert.equal(result.deepseek, "deepseek-linux-arm64");
assert.equal(result.tui, "deepseek-tui-linux-arm64");
assert.equal(result.codewhale, "codewhale-linux-arm64");
assert.equal(result.tui, "codewhale-tui-linux-arm64");
});
});
@@ -52,15 +52,15 @@ test("genuinely unsupported platform throws with raw platform name", () => {
});
test("known platforms are unaffected by alias map", () => {
for (const [platform, arch, expectedDeepseek] of [
["linux", "x64", "deepseek-linux-x64"],
["darwin", "arm64", "deepseek-macos-arm64"],
["win32", "x64", "deepseek-windows-x64.exe"],
for (const [platform, arch, expectedCodewhale] of [
["linux", "x64", "codewhale-linux-x64"],
["darwin", "arm64", "codewhale-macos-arm64"],
["win32", "x64", "codewhale-windows-x64.exe"],
]) {
withMockedOs(platform, arch, () => {
const { detectBinaryNames } = require(ARTIFACTS_PATH);
const result = detectBinaryNames();
assert.equal(result.deepseek, expectedDeepseek);
assert.equal(result.codewhale, expectedCodewhale);
});
}
});
@@ -16,7 +16,7 @@ function sha256(content) {
}
async function makeTempDir(t) {
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "deepseek-install-test-"));
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "codewhale-install-test-"));
t.after(() => fs.promises.rm(dir, { force: true, recursive: true }));
return dir;
}
@@ -69,7 +69,7 @@ test("install failure hint explains release base override for blocked GitHub dow
try {
const error = Object.assign(
new Error(
"fetch https://github.com/Hmbown/DeepSeek-TUI/releases/download/v0.8.19/deepseek-artifacts-sha256.txt failed after 5 attempts:\ngetaddrinfo ENOTFOUND github.com",
"fetch https://github.com/Hmbown/DeepSeek-TUI/releases/download/v0.8.19/codewhale-artifacts-sha256.txt failed after 5 attempts:\ngetaddrinfo ENOTFOUND github.com",
),
{ code: "ENOTFOUND" },
);
@@ -77,7 +77,7 @@ test("install failure hint explains release base override for blocked GitHub dow
const hint = installFailureHint(error);
assert.match(hint, /DEEPSEEK_TUI_RELEASE_BASE_URL/);
assert.match(hint, /deepseek-artifacts-sha256\.txt/);
assert.match(hint, /codewhale-artifacts-sha256\.txt/);
assert.match(hint, /platform binaries/);
assert.match(hint, /#npm-binary-download-times-out/);
} finally {
@@ -100,7 +100,7 @@ test("install failure hint checks configured release base when override is alrea
const hint = installFailureHint(error);
assert.match(hint, /is set to https:\/\/mirror\.example\/deepseek\//);
assert.match(hint, /deepseek-artifacts-sha256\.txt/);
assert.match(hint, /codewhale-artifacts-sha256\.txt/);
assert.doesNotMatch(hint, /If GitHub is unavailable/);
} finally {
if (previous === undefined) {
@@ -113,10 +113,10 @@ test("install failure hint checks configured release base when override is alrea
test("ensureBinary adopts a manually placed target binary after checksum validation", async (t) => {
const dir = await makeTempDir(t);
const target = path.join(dir, process.platform === "win32" ? "deepseek.exe" : "deepseek");
const assetName = process.platform === "win32" ? "deepseek-windows-x64.exe" : "deepseek-linux-x64";
const target = path.join(dir, process.platform === "win32" ? "codewhale.exe" : "codewhale");
const assetName = process.platform === "win32" ? "codewhale-windows-x64.exe" : "codewhale-linux-x64";
const version = "0.8.25";
const content = Buffer.from("manual deepseek binary");
const content = Buffer.from("manual codewhale binary");
let checksumLoads = 0;
await fs.promises.writeFile(target, content, { mode: 0o600 });
@@ -139,8 +139,8 @@ test("ensureBinary adopts a manually placed target binary after checksum validat
test("ensureBinary adopts an official release-named binary placed in downloads", async (t) => {
const dir = await makeTempDir(t);
const target = path.join(dir, process.platform === "win32" ? "deepseek.exe" : "deepseek");
const assetName = process.platform === "win32" ? "deepseek-windows-x64.exe" : "deepseek-linux-x64";
const target = path.join(dir, process.platform === "win32" ? "codewhale.exe" : "codewhale");
const assetName = process.platform === "win32" ? "codewhale-windows-x64.exe" : "codewhale-linux-x64";
const assetPath = path.join(dir, assetName);
const version = "0.8.25";
const content = Buffer.from("official release binary");
@@ -161,8 +161,8 @@ test("ensureBinary adopts an official release-named binary placed in downloads",
test("manual binaries with mismatched checksums are not adopted", async (t) => {
const dir = await makeTempDir(t);
const target = path.join(dir, process.platform === "win32" ? "deepseek.exe" : "deepseek");
const assetName = process.platform === "win32" ? "deepseek-windows-x64.exe" : "deepseek-linux-x64";
const target = path.join(dir, process.platform === "win32" ? "codewhale.exe" : "codewhale");
const assetName = process.platform === "win32" ? "codewhale-windows-x64.exe" : "codewhale-linux-x64";
const content = Buffer.from("wrong binary bytes");
await fs.promises.writeFile(target, content);
@@ -71,7 +71,7 @@ test("optional install only swallows retryable download failures", () => {
false,
);
const badChecksum = new Error("Checksum mismatch for deepseek-linux-x64");
const badChecksum = new Error("Checksum mismatch for codewhale-linux-x64");
badChecksum.nonRetryable = true;
assert.equal(
_internal.shouldIgnoreInstallFailure("install", badChecksum, ["--optional"], {}),
@@ -148,7 +148,7 @@ test("withRetry prints install hint on first retryable failure", async () => {
assert.equal(result, "ok");
assert.equal(attempts, 2);
assert.match(stderr, /deepseek-tui install hint:/);
assert.match(stderr, /codewhale install hint:/);
assert.match(stderr, /#npm-binary-download-times-out/);
} finally {
process.stderr.write = previousWrite;
+9 -83
View File
@@ -1,89 +1,15 @@
# deepseek-tui
# deepseek-tui (deprecated)
Install and run the `deepseek` and `deepseek-tui` binaries from GitHub release artifacts.
## Install
This package has been renamed to **codewhale**. Install that instead:
```bash
npm install -g deepseek-tui
# or
pnpm add -g deepseek-tui
npm uninstall -g deepseek-tui
npm install -g codewhale
```
For project-local usage:
`codewhale` ships the same `codewhale` and `codewhale-tui` binaries plus
deprecation shims under the old `deepseek` / `deepseek-tui` names so existing
scripts keep working through one transition release.
```bash
npm install deepseek-tui
npx deepseek-tui --help
```
`postinstall` tries to download platform binaries into `bin/downloads/` and
exposes `deepseek` and `deepseek-tui` commands. If GitHub release assets are
temporarily unreachable, install continues and the wrapper retries the download
on first run.
## First run
```bash
deepseek login --api-key "YOUR_DEEPSEEK_API_KEY"
deepseek doctor
deepseek
```
The `deepseek` facade and `deepseek-tui` binary share `~/.deepseek/config.toml`
for DeepSeek auth and default model settings. Common TUI commands are available
directly through the facade, including `deepseek doctor`, `deepseek models`,
`deepseek sessions`, and `deepseek resume --last`.
The app talks to DeepSeek's documented OpenAI-compatible Chat Completions API.
Set `DEEPSEEK_BASE_URL` only if you need the China endpoint or DeepSeek beta
features such as strict tool mode, chat prefix completion, or FIM completion.
NVIDIA NIM-hosted DeepSeek V4 Pro is also supported:
```bash
deepseek auth set --provider nvidia-nim --api-key "YOUR_NVIDIA_API_KEY"
deepseek --provider nvidia-nim
```
For a single process, set `DEEPSEEK_PROVIDER=nvidia-nim` and `NVIDIA_API_KEY`
or `NVIDIA_NIM_API_KEY` (with `DEEPSEEK_API_KEY` as a compatibility fallback).
The NIM default model is `deepseek-ai/deepseek-v4-pro` and the default base URL
is `https://integrate.api.nvidia.com/v1`. With `--provider nvidia-nim`,
`--model deepseek-v4-flash` maps to `deepseek-ai/deepseek-v4-flash`.
## Supported platforms
Prebuilt binaries for the GitHub release are downloaded automatically:
- Linux x64
- Linux arm64 (v0.8.8+)
- macOS x64 / arm64
- Windows x64
Other platform/architecture combinations (musl, riscv64, FreeBSD, …) aren't
shipped as prebuilts. Unsupported platforms, checksum failures, and glibc
compatibility problems still fail with a clear error pointing you at
`cargo install deepseek-tui-cli deepseek-tui --locked` and the full
[docs/INSTALL.md](https://github.com/Hmbown/DeepSeek-TUI/blob/main/docs/INSTALL.md)
build-from-source guide.
## Configuration
- Default binary version comes from `deepseekBinaryVersion` in `package.json`.
- Set `DEEPSEEK_TUI_VERSION` or `DEEPSEEK_VERSION` to override the release version.
- Set `DEEPSEEK_TUI_GITHUB_REPO` or `DEEPSEEK_GITHUB_REPO` to override the source repo (defaults to `Hmbown/DeepSeek-TUI`).
- Set `DEEPSEEK_TUI_RELEASE_BASE_URL` to use an internal or mirrored
release-asset directory when GitHub Releases is unavailable. The directory
must contain `deepseek-artifacts-sha256.txt` and the platform binaries.
- Set `DEEPSEEK_TUI_FORCE_DOWNLOAD=1` to force download even when the cached binary is already present.
- Set `DEEPSEEK_TUI_DISABLE_INSTALL=1` to skip install-time download.
- Set `DEEPSEEK_TUI_OPTIONAL_INSTALL=1` to make install-time retryable download
failures warn and exit `0` instead of failing `npm install`.
## Release integrity
- `npm publish` runs a release-asset check to ensure all required binary assets
exist for the target GitHub release before publishing.
- Install-time downloads are verified against the release checksum manifest before
the wrapper marks them executable.
See [docs/REBRAND.md](https://github.com/Hmbown/DeepSeek-TUI/blob/main/docs/REBRAND.md)
for the full migration story.
-8
View File
@@ -1,8 +0,0 @@
#!/usr/bin/env node
const { runDeepseekTui } = require("../scripts/run");
runDeepseekTui().catch((error) => {
console.error("Failed to start deepseek-tui:", error.message);
process.exit(1);
});
-8
View File
@@ -1,8 +0,0 @@
#!/usr/bin/env node
const { runDeepseek } = require("../scripts/run");
runDeepseek().catch((error) => {
console.error("Failed to start deepseek:", error.message);
process.exit(1);
});
+5 -20
View File
@@ -1,11 +1,10 @@
{
"name": "deepseek-tui",
"version": "0.8.40",
"deepseekBinaryVersion": "0.8.40",
"description": "Install and run deepseek and deepseek-tui binaries from GitHub release artifacts.",
"description": "Deprecated. Renamed to `codewhale`. Run `npm install -g codewhale` instead.",
"author": "Hmbown",
"license": "MIT",
"homepage": "https://github.com/Hmbown/DeepSeek-TUI",
"homepage": "https://github.com/Hmbown/DeepSeek-TUI/blob/main/docs/REBRAND.md",
"repository": {
"type": "git",
"url": "git+https://github.com/Hmbown/DeepSeek-TUI.git"
@@ -14,24 +13,13 @@
"url": "https://github.com/Hmbown/DeepSeek-TUI/issues"
},
"keywords": [
"deprecated",
"deepseek",
"cli",
"tui",
"rust",
"binary",
"terminal"
"codewhale"
],
"type": "commonjs",
"bin": {
"deepseek": "bin/deepseek.js",
"deepseek-tui": "bin/deepseek-tui.js"
},
"scripts": {
"release:check": "node scripts/verify-release-assets.js",
"postinstall": "node scripts/install.js --optional",
"prepublishOnly": "node scripts/verify-release-assets.js",
"prepack": "node scripts/install.js",
"test": "node --test test/*.test.js"
"postinstall": "node scripts/deprecation-notice.js"
},
"engines": {
"node": ">=18"
@@ -39,11 +27,8 @@
"publishConfig": {
"access": "public"
},
"preferGlobal": true,
"files": [
"bin/*.js",
"scripts/*.js",
"test/*.js",
"README.md",
"package.json"
]
@@ -0,0 +1,22 @@
#!/usr/bin/env node
const notice = [
"",
" ╭───────────────────────────────────────────────────────────────────╮",
" │ │",
" │ deepseek-tui has been renamed to `codewhale`. │",
" │ │",
" │ Please uninstall this package and install codewhale instead: │",
" │ │",
" │ npm uninstall -g deepseek-tui │",
" │ npm install -g codewhale │",
" │ │",
" │ codewhale ships the same `codewhale` and `codewhale-tui` │",
" │ binaries plus deprecation shims under the old names. See: │",
" │ https://github.com/Hmbown/DeepSeek-TUI/blob/main/docs/REBRAND.md │",
" │ │",
" ╰───────────────────────────────────────────────────────────────────╯",
"",
].join("\n");
process.stderr.write(notice);
+29 -10
View File
@@ -56,29 +56,48 @@ fail=0
echo "Checking published release ${version}..."
if npm_version="$(npm view "deepseek-tui@${version}" version 2>/dev/null)"; then
echo "npm deepseek-tui@${npm_version} is published."
# Canonical post-rebrand npm package.
if npm_version="$(npm view "codewhale@${version}" version 2>/dev/null)"; then
echo "npm codewhale@${npm_version} is published."
else
echo "npm deepseek-tui@${version} is not published." >&2
echo "npm codewhale@${version} is not published." >&2
fail=1
fi
if npm_binary_version="$(npm view "deepseek-tui@${version}" deepseekBinaryVersion 2>/dev/null)"; then
# `codewhaleBinaryVersion` is the new internal version-pin field. Fall back
# to the legacy `deepseekBinaryVersion` field for old/transition packages.
binary_field=""
npm_binary_version=""
if value="$(npm view "codewhale@${version}" codewhaleBinaryVersion 2>/dev/null)" && [[ -n "${value}" ]]; then
binary_field="codewhaleBinaryVersion"
npm_binary_version="${value}"
elif value="$(npm view "codewhale@${version}" deepseekBinaryVersion 2>/dev/null)" && [[ -n "${value}" ]]; then
binary_field="deepseekBinaryVersion"
npm_binary_version="${value}"
fi
if [[ -n "${binary_field}" ]]; then
if [[ "${npm_binary_version}" == "${version}" ]]; then
echo "npm deepseekBinaryVersion=${npm_binary_version}."
echo "npm ${binary_field}=${npm_binary_version}."
elif [[ "${allow_npm_binary_mismatch}" == "1" ]]; then
echo "npm deepseekBinaryVersion=${npm_binary_version} (allowed packaging-only mismatch)."
echo "npm ${binary_field}=${npm_binary_version} (allowed packaging-only mismatch)."
else
echo "npm deepseekBinaryVersion=${npm_binary_version}, expected ${version}." >&2
echo "npm ${binary_field}=${npm_binary_version}, expected ${version}." >&2
fail=1
fi
elif [[ "${allow_npm_binary_mismatch}" == "1" ]]; then
echo "npm deepseekBinaryVersion is absent (allowed packaging-only mismatch)."
echo "npm codewhaleBinaryVersion is absent (allowed packaging-only mismatch)."
else
echo "npm deepseekBinaryVersion is absent for deepseek-tui@${version}." >&2
echo "npm codewhaleBinaryVersion is absent for codewhale@${version}." >&2
fail=1
fi
# Legacy `deepseek-tui` deprecation shim package. Best-effort check —
# absence after the transition release is expected and not fatal.
if legacy_version="$(npm view "deepseek-tui@${version}" version 2>/dev/null)"; then
echo "npm deepseek-tui@${legacy_version} (deprecation shim) is published."
fi
for crate in "${release_crates[@]}"; do
if curl -fsSL "https://crates.io/api/v1/crates/${crate}/${version}" >/dev/null 2>&1; then
echo "crates.io ${crate}@${version} is published."
@@ -89,7 +108,7 @@ for crate in "${release_crates[@]}"; do
done
if [[ "${fail}" == "0" ]]; then
echo "Published release OK: npm deepseek-tui@${version} and ${#release_crates[@]} crates are visible."
echo "Published release OK: npm codewhale@${version} and ${#release_crates[@]} crates are visible."
fi
exit "${fail}"
+15 -6
View File
@@ -5,10 +5,10 @@
# Checks performed:
# 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`. (The npm wrapper directory is
# renamed to `npm/codewhale/` in a follow-up phase; this script will
# be updated then.)
# 2. `npm/codewhale/package.json` `version` matches the workspace
# `version` in the root `Cargo.toml`. (`npm/deepseek-tui/` still
# exists during the transition as a deprecation shim package; its
# version is also checked.)
# 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.
@@ -32,11 +32,20 @@ fi
# 2) Workspace ↔ npm package.json.
workspace_version="$(grep -E '^version = "' Cargo.toml | head -n1 | sed -E 's/^version = "([^"]+)".*/\1/')"
npm_version="$(node -p "require('./npm/deepseek-tui/package.json').version")"
npm_version="$(node -p "require('./npm/codewhale/package.json').version")"
if [[ "${workspace_version}" != "${npm_version}" ]]; then
echo "::error::npm/deepseek-tui/package.json version (${npm_version}) does not match workspace Cargo.toml (${workspace_version})." >&2
echo "::error::npm/codewhale/package.json version (${npm_version}) does not match workspace Cargo.toml (${workspace_version})." >&2
fail=1
fi
# Also pin the legacy deprecation shim package to the same workspace version
# so a stale `deepseek-tui` doesn't ship pointing at a different release.
if [[ -f npm/deepseek-tui/package.json ]]; then
legacy_npm_version="$(node -p "require('./npm/deepseek-tui/package.json').version")"
if [[ "${workspace_version}" != "${legacy_npm_version}" ]]; then
echo "::error::npm/deepseek-tui/package.json version (${legacy_npm_version}) does not match workspace Cargo.toml (${workspace_version})." >&2
fail=1
fi
fi
# 3) Internal path dependency pins.
internal_dep_drift="$(
+4 -4
View File
@@ -8,7 +8,7 @@ const path = require("path");
const { spawn } = require("child_process");
const repoRoot = path.resolve(__dirname, "..", "..");
const packageDir = path.join(repoRoot, "npm", "deepseek-tui");
const packageDir = path.join(repoRoot, "npm", "codewhale");
const prepareAssetsScript = path.join(
repoRoot,
"scripts",
@@ -133,7 +133,7 @@ function parsePackJson(stdout) {
}
async function main() {
const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "deepseek-npm-smoke-"));
const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "codewhale-npm-smoke-"));
const releaseAssetsDir = path.join(tempRoot, "release-assets");
const packDir = path.join(tempRoot, "pack");
const installDir = path.join(tempRoot, "install");
@@ -165,11 +165,11 @@ async function main() {
await runCommand("npm", ["init", "-y"], { cwd: installDir });
await runCommand("npm", ["install", tarball], { cwd: installDir, env });
await runCommand("npx", ["--no-install", "deepseek", "doctor", "--help"], {
await runCommand("npx", ["--no-install", "codewhale", "doctor", "--help"], {
cwd: installDir,
env,
});
await runCommand("npx", ["--no-install", "deepseek-tui", "--help"], {
await runCommand("npx", ["--no-install", "codewhale-tui", "--help"], {
cwd: installDir,
env,
});
@@ -8,7 +8,7 @@ const {
allAssetNames,
CHECKSUM_MANIFEST,
detectBinaryNames,
} = require("../../npm/deepseek-tui/scripts/artifacts");
} = require("../../npm/codewhale/scripts/artifacts");
async function sha256(filePath) {
const content = await fs.readFile(filePath);
@@ -25,16 +25,16 @@ async function main() {
const buildDir = path.resolve(
process.argv[3] || path.join("target", "release"),
);
const { deepseek, tui } = detectBinaryNames();
const { codewhale, tui } = detectBinaryNames();
const isWindows = process.platform === "win32";
const assets = [
{
source: path.join(buildDir, isWindows ? "deepseek.exe" : "deepseek"),
target: deepseek,
source: path.join(buildDir, isWindows ? "codewhale.exe" : "codewhale"),
target: codewhale,
},
{
source: path.join(buildDir, isWindows ? "deepseek-tui.exe" : "deepseek-tui"),
source: path.join(buildDir, isWindows ? "codewhale-tui.exe" : "codewhale-tui"),
target: tui,
},
];
@@ -45,9 +45,9 @@ async function main() {
continue;
}
assets.push({
source: assetName.startsWith("deepseek-tui")
? path.join(buildDir, isWindows ? "deepseek-tui.exe" : "deepseek-tui")
: path.join(buildDir, isWindows ? "deepseek.exe" : "deepseek"),
source: assetName.startsWith("codewhale-tui")
? path.join(buildDir, isWindows ? "codewhale-tui.exe" : "codewhale-tui")
: path.join(buildDir, isWindows ? "codewhale.exe" : "codewhale"),
target: assetName,
});
}