deepseek-tui 0.3.29 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,6 +20,23 @@ npx deepseek-tui --help
20
20
  `postinstall` downloads platform binaries into `bin/downloads/` and exposes
21
21
  `deepseek` and `deepseek-tui` commands.
22
22
 
23
+ ## First run
24
+
25
+ ```bash
26
+ deepseek login --api-key "YOUR_DEEPSEEK_API_KEY"
27
+ deepseek doctor
28
+ deepseek
29
+ ```
30
+
31
+ The `deepseek` facade and `deepseek-tui` binary share `~/.deepseek/config.toml`
32
+ for DeepSeek auth and default model settings. Common TUI commands are available
33
+ directly through the facade, including `deepseek doctor`, `deepseek models`,
34
+ `deepseek sessions`, and `deepseek resume --last`.
35
+
36
+ The app talks to DeepSeek's documented OpenAI-compatible Chat Completions API.
37
+ Set `DEEPSEEK_BASE_URL` only if you need the China endpoint or DeepSeek beta
38
+ features such as strict tool mode, chat prefix completion, or FIM completion.
39
+
23
40
  ## Supported platforms
24
41
 
25
42
  - Linux x64
@@ -40,3 +57,7 @@ Other platform/architecture combinations are not supported and will fail during
40
57
 
41
58
  - `npm publish` runs a release-asset check to ensure all required binary assets
42
59
  exist for the target GitHub release before publishing.
60
+ - Install-time downloads are verified against the release checksum manifest before
61
+ the wrapper marks them executable.
62
+ - Set `DEEPSEEK_TUI_RELEASE_BASE_URL` to point the installer at a local or
63
+ staged release-asset directory for smoke tests.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "deepseek-tui",
3
- "version": "0.3.29",
4
- "deepseekBinaryVersion": "0.3.28",
3
+ "version": "0.4.4",
4
+ "deepseekBinaryVersion": "0.4.4",
5
5
  "description": "Install and run deepseek and deepseek-tui binaries from GitHub release artifacts.",
6
6
  "author": "Hmbown",
7
7
  "license": "MIT",
@@ -1,6 +1,8 @@
1
1
  const path = require("path");
2
2
  const os = require("os");
3
3
 
4
+ const CHECKSUM_MANIFEST = "deepseek-artifacts-sha256.txt";
5
+
4
6
  const ASSET_MATRIX = {
5
7
  linux: {
6
8
  x64: ["deepseek-linux-x64", "deepseek-tui-linux-x64"],
@@ -40,8 +42,22 @@ function executableName(base, platform) {
40
42
  return platform === "win32" ? `${base}.exe` : base;
41
43
  }
42
44
 
45
+ function releaseBaseUrl(version, repo = "Hmbown/DeepSeek-TUI") {
46
+ const override =
47
+ process.env.DEEPSEEK_TUI_RELEASE_BASE_URL || process.env.DEEPSEEK_RELEASE_BASE_URL;
48
+ if (override) {
49
+ const trimmed = String(override).trim();
50
+ return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
51
+ }
52
+ return `https://github.com/${repo}/releases/download/v${version}/`;
53
+ }
54
+
43
55
  function releaseAssetUrl(baseName, version, repo = "Hmbown/DeepSeek-TUI") {
44
- return `https://github.com/${repo}/releases/download/v${version}/${baseName}`;
56
+ return new URL(baseName, releaseBaseUrl(version, repo)).toString();
57
+ }
58
+
59
+ function checksumManifestUrl(version, repo = "Hmbown/DeepSeek-TUI") {
60
+ return releaseAssetUrl(CHECKSUM_MANIFEST, version, repo);
45
61
  }
46
62
 
47
63
  function releaseBinaryDirectory() {
@@ -58,10 +74,18 @@ function allAssetNames() {
58
74
  return Array.from(new Set(names));
59
75
  }
60
76
 
77
+ function allReleaseAssetNames() {
78
+ return [...allAssetNames(), CHECKSUM_MANIFEST];
79
+ }
80
+
61
81
  module.exports = {
62
82
  allAssetNames,
83
+ allReleaseAssetNames,
84
+ CHECKSUM_MANIFEST,
85
+ checksumManifestUrl,
63
86
  detectBinaryNames,
64
87
  executableName,
65
88
  releaseAssetUrl,
89
+ releaseBaseUrl,
66
90
  releaseBinaryDirectory,
67
91
  };
@@ -1,12 +1,14 @@
1
1
  const fs = require("fs");
2
2
  const https = require("https");
3
3
  const http = require("http");
4
- const { mkdir, chmod, stat, rename, readFile, writeFile } = fs.promises;
4
+ const crypto = require("crypto");
5
+ const { mkdir, chmod, stat, rename, readFile, unlink, writeFile } = fs.promises;
5
6
  const { createWriteStream } = fs;
6
7
  const { pipeline } = require("stream/promises");
7
8
  const path = require("path");
8
9
 
9
10
  const {
11
+ checksumManifestUrl,
10
12
  detectBinaryNames,
11
13
  releaseAssetUrl,
12
14
  releaseBinaryDirectory,
@@ -69,6 +71,19 @@ async function download(url, destination) {
69
71
  await pipeline(resolved.response, createWriteStream(destination));
70
72
  }
71
73
 
74
+ async function downloadText(url) {
75
+ const resolved = await httpGet(url);
76
+ if (resolved.redirect) {
77
+ return downloadText(resolved.redirect);
78
+ }
79
+ const chunks = [];
80
+ resolved.response.setEncoding("utf8");
81
+ for await (const chunk of resolved.response) {
82
+ chunks.push(chunk);
83
+ }
84
+ return chunks.join("");
85
+ }
86
+
72
87
  async function readLocalVersion(file) {
73
88
  return readFile(file, "utf8").catch(() => "");
74
89
  }
@@ -82,7 +97,45 @@ async function fileExists(file) {
82
97
  }
83
98
  }
84
99
 
85
- async function ensureBinary(targetPath, assetName, version, repo) {
100
+ function parseChecksumManifest(text) {
101
+ const checksums = new Map();
102
+ for (const line of text.split(/\r?\n/)) {
103
+ const trimmed = line.trim();
104
+ if (!trimmed) {
105
+ continue;
106
+ }
107
+ const match = trimmed.match(/^([a-fA-F0-9]{64})\s+\*?(.+)$/);
108
+ if (!match) {
109
+ throw new Error(`Invalid checksum manifest line: ${trimmed}`);
110
+ }
111
+ checksums.set(match[2], match[1].toLowerCase());
112
+ }
113
+ return checksums;
114
+ }
115
+
116
+ async function sha256File(filePath) {
117
+ const content = await readFile(filePath);
118
+ return crypto.createHash("sha256").update(content).digest("hex");
119
+ }
120
+
121
+ async function verifyChecksum(filePath, assetName, checksums) {
122
+ const expected = checksums.get(assetName);
123
+ if (!expected) {
124
+ throw new Error(`Checksum manifest is missing ${assetName}`);
125
+ }
126
+ const actual = await sha256File(filePath);
127
+ if (actual !== expected) {
128
+ throw new Error(
129
+ `Checksum mismatch for ${assetName}: expected ${expected}, got ${actual}`,
130
+ );
131
+ }
132
+ }
133
+
134
+ async function loadChecksums(version, repo) {
135
+ return parseChecksumManifest(await downloadText(checksumManifestUrl(version, repo)));
136
+ }
137
+
138
+ async function ensureBinary(targetPath, assetName, version, repo, checksums) {
86
139
  const marker = `${targetPath}.version`;
87
140
  const downloadIfNeeded =
88
141
  process.env.DEEPSEEK_TUI_FORCE_DOWNLOAD === "1" || process.env.DEEPSEEK_FORCE_DOWNLOAD === "1";
@@ -91,13 +144,20 @@ async function ensureBinary(targetPath, assetName, version, repo) {
91
144
  if (existing) {
92
145
  const markerVersion = await readLocalVersion(marker);
93
146
  if (markerVersion === String(version)) {
147
+ await verifyChecksum(targetPath, assetName, checksums);
94
148
  return targetPath;
95
149
  }
96
150
  }
97
151
  }
98
152
  const url = releaseAssetUrl(assetName, version, repo);
99
- const destination = `${targetPath}.download`;
153
+ const destination = `${targetPath}.${process.pid}.${Date.now()}.download`;
100
154
  await download(url, destination);
155
+ try {
156
+ await verifyChecksum(destination, assetName, checksums);
157
+ } catch (error) {
158
+ await unlink(destination).catch(() => {});
159
+ throw error;
160
+ }
101
161
  if (process.platform !== "win32") {
102
162
  await chmod(destination, 0o755);
103
163
  }
@@ -115,10 +175,11 @@ async function run() {
115
175
  const paths = binaryPaths();
116
176
  const releaseDir = releaseBinaryDirectory();
117
177
  await mkdir(releaseDir, { recursive: true });
178
+ const checksums = await loadChecksums(version, repo);
118
179
 
119
180
  await Promise.all([
120
- ensureBinary(paths.deepseek.target, paths.deepseek.asset, version, repo),
121
- ensureBinary(paths.tui.target, paths.tui.asset, version, repo),
181
+ ensureBinary(paths.deepseek.target, paths.deepseek.asset, version, repo, checksums),
182
+ ensureBinary(paths.tui.target, paths.tui.asset, version, repo, checksums),
122
183
  ]);
123
184
  }
124
185
 
@@ -1,6 +1,11 @@
1
1
  const https = require("https");
2
2
  const http = require("http");
3
- const { allAssetNames, releaseAssetUrl } = require("./artifacts");
3
+ const {
4
+ allAssetNames,
5
+ allReleaseAssetNames,
6
+ checksumManifestUrl,
7
+ releaseAssetUrl,
8
+ } = require("./artifacts");
4
9
 
5
10
  const pkg = require("../package.json");
6
11
 
@@ -58,10 +63,59 @@ async function verifyAsset(url, label) {
58
63
  }
59
64
  }
60
65
 
66
+ async function downloadText(url) {
67
+ const client = url.startsWith("https:") ? https : http;
68
+ return new Promise((resolve, reject) => {
69
+ client
70
+ .get(
71
+ url,
72
+ {
73
+ headers: {
74
+ "User-Agent": "deepseek-tui-npm-release-check",
75
+ },
76
+ },
77
+ (res) => {
78
+ const status = res.statusCode || 0;
79
+ if (status >= 300 && status < 400 && res.headers.location) {
80
+ const next = new URL(res.headers.location, url).toString();
81
+ resolve(downloadText(next));
82
+ return;
83
+ }
84
+ if (status !== 200) {
85
+ reject(new Error(`Request failed with status ${status}: ${url}`));
86
+ res.resume();
87
+ return;
88
+ }
89
+ const chunks = [];
90
+ res.setEncoding("utf8");
91
+ res.on("data", (chunk) => chunks.push(chunk));
92
+ res.on("end", () => resolve(chunks.join("")));
93
+ },
94
+ )
95
+ .on("error", reject);
96
+ });
97
+ }
98
+
99
+ function parseChecksumManifest(text) {
100
+ const checksums = new Map();
101
+ for (const line of text.split(/\r?\n/)) {
102
+ const trimmed = line.trim();
103
+ if (!trimmed) {
104
+ continue;
105
+ }
106
+ const match = trimmed.match(/^([a-fA-F0-9]{64})\s+\*?(.+)$/);
107
+ if (!match) {
108
+ throw new Error(`Invalid checksum manifest line: ${trimmed}`);
109
+ }
110
+ checksums.set(match[2], match[1].toLowerCase());
111
+ }
112
+ return checksums;
113
+ }
114
+
61
115
  async function run() {
62
116
  const version = resolveBinaryVersion();
63
117
  const repo = resolveRepo();
64
- const assets = allAssetNames();
118
+ const assets = allReleaseAssetNames();
65
119
 
66
120
  console.log(`Verifying ${assets.length} release assets for ${repo}@v${version}...`);
67
121
  for (const asset of assets) {
@@ -69,6 +123,14 @@ async function run() {
69
123
  await verifyAsset(url, asset);
70
124
  console.log(` ok ${asset}`);
71
125
  }
126
+ const checksums = parseChecksumManifest(
127
+ await downloadText(checksumManifestUrl(version, repo)),
128
+ );
129
+ for (const asset of allAssetNames()) {
130
+ if (!checksums.has(asset)) {
131
+ throw new Error(`Checksum manifest is missing ${asset}`);
132
+ }
133
+ }
72
134
  console.log("Release assets verified.");
73
135
  }
74
136