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:
@@ -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
|
||||
|
||||
@@ -4,6 +4,7 @@ A terminal-native TUI and CLI for [DeepSeek](https://platform.deepseek.com) mode
|
||||
|
||||
[](https://github.com/Hmbown/DeepSeek-TUI/actions/workflows/ci.yml)
|
||||
[](https://crates.io/crates/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`:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
bin/downloads/
|
||||
@@ -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.
|
||||
Executable
+8
@@ -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);
|
||||
});
|
||||
Executable
+8
@@ -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);
|
||||
});
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user