feat: add npm package and update install docs

- Add npm/deepseek-tui package that downloads prebuilt binaries from
  GitHub releases (supports macOS, Linux, Windows)
- Published as deepseek-tui@0.3.28 on npmjs.com
- Update README to feature npm as primary install method
- Add npm badge
This commit is contained in:
Hunter Bown
2026-03-03 09:57:56 -06:00
parent f269147286
commit f2881e7e3d
10 changed files with 336 additions and 1 deletions
+1
View File
@@ -55,6 +55,7 @@ AI_HANDOFF.md
result.json
count_deps.py
project_overhaul_prompt.md
prompt_for_release_agent.md
.codex/
docs/rlm-paper.txt
+14 -1
View File
@@ -4,6 +4,7 @@ A terminal-native TUI and CLI for [DeepSeek](https://platform.deepseek.com) mode
[![CI](https://github.com/Hmbown/DeepSeek-TUI/actions/workflows/ci.yml/badge.svg)](https://github.com/Hmbown/DeepSeek-TUI/actions/workflows/ci.yml)
[![crates.io](https://img.shields.io/crates/v/deepseek-tui)](https://crates.io/crates/deepseek-tui)
[![npm](https://img.shields.io/npm/v/deepseek-tui)](https://www.npmjs.com/package/deepseek-tui)
<p align="center">
<img src="assets/hero.png" alt="DeepSeek CLI" width="800">
@@ -25,17 +26,29 @@ Three modes:
## Install
```bash
# Recommended — no Rust toolchain needed
npm install -g deepseek-tui
```
This downloads prebuilt binaries for your platform (macOS, Linux, Windows). After install, both `deepseek` and `deepseek-tui` commands are available.
<details>
<summary>Other install methods</summary>
```bash
# From crates.io (requires Rust 1.85+)
cargo install deepseek-tui --locked
# Or from source
# From source
git clone https://github.com/Hmbown/DeepSeek-TUI.git
cd DeepSeek-TUI
cargo install --path crates/tui --locked # TUI (interactive terminal)
cargo install --path crates/cli --locked # CLI (dispatcher + server)
```
</details>
## Setup
Create `~/.deepseek/config.toml`:
+1
View File
@@ -0,0 +1 @@
bin/downloads/
+31
View File
@@ -0,0 +1,31 @@
# deepseek-tui
This package installs the `deepseek` and `deepseek-tui` binaries from the
`DeepSeek-TUI` GitHub release artifacts and exposes them as Node-compatible
console entry points.
## Install
```bash
npm install deepseek-tui
# or
pnpm add deepseek-tui
```
This runs `postinstall`, downloads the platform-specific binaries for version
`0.3.28`, and makes `deepseek` and `deepseek-tui` available on your PATH.
## Supported platforms
- Linux x64
- macOS x64 / arm64
- Windows x64
## Notes
- Binaries come directly from release assets in
`https://github.com/Hmbown/DeepSeek-TUI/releases`.
- Set `DEEPSEEK_VERSION` to install a different release version (defaults to package version).
- Set `DEEPSEEK_GITHUB_REPO` to override the repo source (defaults to `Hmbown/DeepSeek-TUI`).
- 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.
+8
View File
@@ -0,0 +1,8 @@
#!/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
@@ -0,0 +1,8 @@
#!/usr/bin/env node
const { runDeepseek } = require("../scripts/run");
runDeepseek().catch((error) => {
console.error("Failed to start deepseek:", error.message);
process.exit(1);
});
+42
View File
@@ -0,0 +1,42 @@
{
"name": "deepseek-tui",
"version": "0.3.28",
"description": "Install and run deepseek and deepseek-tui binaries 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": [
"deepseek",
"cli",
"tui",
"rust",
"binary",
"terminal"
],
"type": "commonjs",
"bin": {
"deepseek": "bin/deepseek.js",
"deepseek-tui": "bin/deepseek-tui.js"
},
"scripts": {
"postinstall": "node scripts/install.js",
"prepack": "node scripts/install.js"
},
"engines": {
"node": ">=18"
},
"preferGlobal": true,
"files": [
"bin/*.js",
"scripts/*.js",
"README.md",
"package.json"
]
}
+53
View File
@@ -0,0 +1,53 @@
const path = require("path");
const os = require("os");
const ASSET_MATRIX = {
linux: {
x64: ["deepseek-linux-x64", "deepseek-tui-linux-x64"],
default: ["deepseek-linux-x64", "deepseek-tui-linux-x64"],
},
darwin: {
x64: ["deepseek-macos-x64", "deepseek-tui-macos-x64"],
arm64: ["deepseek-macos-arm64", "deepseek-tui-macos-arm64"],
default: ["deepseek-macos-x64", "deepseek-tui-macos-x64"],
},
win32: {
x64: ["deepseek-windows-x64.exe", "deepseek-tui-windows-x64.exe"],
default: ["deepseek-windows-x64.exe", "deepseek-tui-windows-x64.exe"],
},
};
function detectBinaryNames() {
const platform = os.platform();
const arch = os.arch();
const defaults = ASSET_MATRIX[platform];
if (!defaults) {
throw new Error(`Unsupported platform: ${platform}`);
}
const pair = defaults[arch] || defaults.default;
return {
platform,
arch,
deepseek: pair[0],
tui: pair[1],
};
}
function executableName(base, platform) {
return platform === "win32" ? `${base}.exe` : base;
}
function releaseAssetUrl(baseName, version, repo = "Hmbown/DeepSeek-TUI") {
return `https://github.com/${repo}/releases/download/v${version}/${baseName}`;
}
function releaseBinaryDirectory() {
return path.join(__dirname, "..", "bin", "downloads");
}
module.exports = {
detectBinaryNames,
executableName,
releaseAssetUrl,
releaseBinaryDirectory,
};
+142
View File
@@ -0,0 +1,142 @@
const fs = require("fs");
const https = require("https");
const http = require("http");
const { mkdir, chmod, stat, rename, readFile, writeFile } = fs.promises;
const { createWriteStream } = fs;
const { pipeline } = require("stream/promises");
const path = require("path");
const {
detectBinaryNames,
releaseAssetUrl,
releaseBinaryDirectory,
} = require("./artifacts");
function resolvePackageVersion() {
const pkg = require("../package.json");
return process.env.DEEPSEEK_TUI_VERSION || process.env.DEEPSEEK_VERSION || pkg.version;
}
function resolveRepo() {
return process.env.DEEPSEEK_TUI_GITHUB_REPO || process.env.DEEPSEEK_GITHUB_REPO || "Hmbown/DeepSeek-TUI";
}
function binaryPaths() {
const { deepseek, tui } = detectBinaryNames();
const releaseDir = releaseBinaryDirectory();
return {
deepseek: {
asset: deepseek,
target: path.join(releaseDir, process.platform === "win32" ? "deepseek.exe" : "deepseek"),
},
tui: {
asset: tui,
target: path.join(releaseDir, process.platform === "win32" ? "deepseek-tui.exe" : "deepseek-tui"),
},
};
}
async function httpGet(url) {
const client = url.startsWith("https:") ? https : http;
const response = await new Promise((resolve, reject) => {
client.get(url, (res) => {
const status = res.statusCode || 0;
if (status >= 300 && status < 400 && res.headers.location) {
resolve({ redirect: res.headers.location, response: null });
return;
}
if (status !== 200) {
reject(new Error(`Request failed with status ${status}: ${url}`));
return;
}
resolve({ redirect: null, response: res });
}).on("error", reject);
});
return response;
}
async function download(url, destination) {
const resolved = await httpGet(url);
if (resolved.redirect) {
return download(resolved.redirect, destination);
}
await mkdir(path.dirname(destination), { recursive: true });
await pipeline(resolved.response, createWriteStream(destination));
}
async function readLocalVersion(file) {
return readFile(file, "utf8").catch(() => "");
}
async function fileExists(file) {
try {
const result = await stat(file);
return result.isFile();
} catch {
return false;
}
}
async function ensureBinary(targetPath, assetName, version, repo) {
const marker = `${targetPath}.version`;
const downloadIfNeeded =
process.env.DEEPSEEK_TUI_FORCE_DOWNLOAD === "1" || process.env.DEEPSEEK_FORCE_DOWNLOAD === "1";
if (!downloadIfNeeded) {
const existing = await fileExists(targetPath);
if (existing) {
const markerVersion = await readLocalVersion(marker);
if (markerVersion === String(version)) {
return targetPath;
}
}
}
const url = releaseAssetUrl(assetName, version, repo);
const destination = `${targetPath}.download`;
await download(url, destination);
if (process.platform !== "win32") {
await chmod(destination, 0o755);
}
await rename(destination, targetPath);
await writeFile(marker, String(version), "utf8");
return targetPath;
}
async function run() {
if (process.env.DEEPSEEK_TUI_DISABLE_INSTALL === "1" || process.env.DEEPSEEK_DISABLE_INSTALL === "1") {
return;
}
const version = resolvePackageVersion();
const repo = resolveRepo();
const paths = binaryPaths();
const releaseDir = releaseBinaryDirectory();
await mkdir(releaseDir, { recursive: true });
await Promise.all([
ensureBinary(paths.deepseek.target, paths.deepseek.asset, version, repo),
ensureBinary(paths.tui.target, paths.tui.asset, version, repo),
]);
}
async function getBinaryPath(name) {
await run();
const paths = binaryPaths();
if (name === "deepseek") {
return paths.deepseek.target;
}
if (name === "deepseek-tui") {
return paths.tui.target;
}
throw new Error(`Unknown binary: ${name}`);
}
module.exports = {
getBinaryPath,
run,
};
if (require.main === module) {
run().catch((error) => {
console.error("deepseek-tui install failed:", error.message);
process.exit(1);
});
}
+36
View File
@@ -0,0 +1,36 @@
const { spawnSync } = require("child_process");
const { getBinaryPath } = require("./install");
async function run(binaryName) {
const binaryPath = await getBinaryPath(binaryName);
const result = spawnSync(binaryPath, process.argv.slice(2), {
stdio: "inherit",
});
if (result.error) {
throw result.error;
}
process.exit(result.status ?? 1);
}
async function runDeepseek() {
await run("deepseek");
}
async function runDeepseekTui() {
await run("deepseek-tui");
}
module.exports = {
run,
runDeepseek,
runDeepseekTui,
};
if (require.main === module) {
const command = process.argv[1] || "";
if (command.includes("tui")) {
runDeepseekTui();
} else {
runDeepseek();
}
}