deepseek-tui 0.3.28 → 0.3.32
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 +27 -12
- package/package.json +7 -1
- package/scripts/artifacts.js +44 -6
- package/scripts/install.js +72 -6
- package/scripts/verify-release-assets.js +140 -0
package/README.md
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
# deepseek-tui
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
`DeepSeek-TUI` GitHub release artifacts and exposes them as Node-compatible
|
|
5
|
-
console entry points.
|
|
3
|
+
Install and run the `deepseek` and `deepseek-tui` binaries from GitHub release artifacts.
|
|
6
4
|
|
|
7
5
|
## Install
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
|
-
npm install deepseek-tui
|
|
8
|
+
npm install -g deepseek-tui
|
|
11
9
|
# or
|
|
12
|
-
pnpm add deepseek-tui
|
|
10
|
+
pnpm add -g deepseek-tui
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
For project-local usage:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install deepseek-tui
|
|
17
|
+
npx deepseek-tui --help
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`postinstall` downloads platform binaries into `bin/downloads/` and exposes
|
|
21
|
+
`deepseek` and `deepseek-tui` commands.
|
|
17
22
|
|
|
18
23
|
## Supported platforms
|
|
19
24
|
|
|
@@ -21,11 +26,21 @@ This runs `postinstall`, downloads the platform-specific binaries for version
|
|
|
21
26
|
- macOS x64 / arm64
|
|
22
27
|
- Windows x64
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
Other platform/architecture combinations are not supported and will fail during install.
|
|
30
|
+
|
|
31
|
+
## Configuration
|
|
25
32
|
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
- Set `
|
|
29
|
-
- Set `DEEPSEEK_GITHUB_REPO` to override the repo source (defaults to `Hmbown/DeepSeek-TUI`).
|
|
33
|
+
- Default binary version comes from `deepseekBinaryVersion` in `package.json`.
|
|
34
|
+
- Set `DEEPSEEK_TUI_VERSION` or `DEEPSEEK_VERSION` to override the release version.
|
|
35
|
+
- Set `DEEPSEEK_TUI_GITHUB_REPO` or `DEEPSEEK_GITHUB_REPO` to override the source repo (defaults to `Hmbown/DeepSeek-TUI`).
|
|
30
36
|
- Set `DEEPSEEK_TUI_FORCE_DOWNLOAD=1` to force download even when the cached binary is already present.
|
|
31
37
|
- Set `DEEPSEEK_TUI_DISABLE_INSTALL=1` to skip install-time download.
|
|
38
|
+
|
|
39
|
+
## Release integrity
|
|
40
|
+
|
|
41
|
+
- `npm publish` runs a release-asset check to ensure all required binary assets
|
|
42
|
+
exist for the target GitHub release before publishing.
|
|
43
|
+
- Install-time downloads are verified against the release checksum manifest before
|
|
44
|
+
the wrapper marks them executable.
|
|
45
|
+
- Set `DEEPSEEK_TUI_RELEASE_BASE_URL` to point the installer at a local or
|
|
46
|
+
staged release-asset directory for smoke tests.
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepseek-tui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.32",
|
|
4
|
+
"deepseekBinaryVersion": "0.3.31",
|
|
4
5
|
"description": "Install and run deepseek and deepseek-tui binaries from GitHub release artifacts.",
|
|
5
6
|
"author": "Hmbown",
|
|
6
7
|
"license": "MIT",
|
|
@@ -26,12 +27,17 @@
|
|
|
26
27
|
"deepseek-tui": "bin/deepseek-tui.js"
|
|
27
28
|
},
|
|
28
29
|
"scripts": {
|
|
30
|
+
"release:check": "node scripts/verify-release-assets.js",
|
|
29
31
|
"postinstall": "node scripts/install.js",
|
|
32
|
+
"prepublishOnly": "node scripts/verify-release-assets.js",
|
|
30
33
|
"prepack": "node scripts/install.js"
|
|
31
34
|
},
|
|
32
35
|
"engines": {
|
|
33
36
|
"node": ">=18"
|
|
34
37
|
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
35
41
|
"preferGlobal": true,
|
|
36
42
|
"files": [
|
|
37
43
|
"bin/*.js",
|
package/scripts/artifacts.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
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"],
|
|
7
|
-
|
|
9
|
+
// arm64: ["deepseek-linux-arm64", "deepseek-tui-linux-arm64"], // Uncomment when binaries are available
|
|
8
10
|
},
|
|
9
11
|
darwin: {
|
|
10
12
|
x64: ["deepseek-macos-x64", "deepseek-tui-macos-x64"],
|
|
11
13
|
arm64: ["deepseek-macos-arm64", "deepseek-tui-macos-arm64"],
|
|
12
|
-
default: ["deepseek-macos-x64", "deepseek-tui-macos-x64"],
|
|
13
14
|
},
|
|
14
15
|
win32: {
|
|
15
16
|
x64: ["deepseek-windows-x64.exe", "deepseek-tui-windows-x64.exe"],
|
|
16
|
-
default: ["deepseek-windows-x64.exe", "deepseek-tui-windows-x64.exe"],
|
|
17
17
|
},
|
|
18
18
|
};
|
|
19
19
|
|
|
@@ -22,9 +22,14 @@ function detectBinaryNames() {
|
|
|
22
22
|
const arch = os.arch();
|
|
23
23
|
const defaults = ASSET_MATRIX[platform];
|
|
24
24
|
if (!defaults) {
|
|
25
|
-
|
|
25
|
+
const supported = Object.keys(ASSET_MATRIX).map(p => `'${p}'`).join(', ');
|
|
26
|
+
throw new Error(`Unsupported platform: ${platform}. Supported platforms: ${supported}`);
|
|
27
|
+
}
|
|
28
|
+
const pair = defaults[arch];
|
|
29
|
+
if (!pair) {
|
|
30
|
+
const supported = Object.keys(defaults).map(a => `'${a}'`).join(', ');
|
|
31
|
+
throw new Error(`Unsupported architecture: ${arch} on platform ${platform}. Supported architectures: ${supported}`);
|
|
26
32
|
}
|
|
27
|
-
const pair = defaults[arch] || defaults.default;
|
|
28
33
|
return {
|
|
29
34
|
platform,
|
|
30
35
|
arch,
|
|
@@ -37,17 +42,50 @@ function executableName(base, platform) {
|
|
|
37
42
|
return platform === "win32" ? `${base}.exe` : base;
|
|
38
43
|
}
|
|
39
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
|
+
|
|
40
55
|
function releaseAssetUrl(baseName, version, repo = "Hmbown/DeepSeek-TUI") {
|
|
41
|
-
return
|
|
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);
|
|
42
61
|
}
|
|
43
62
|
|
|
44
63
|
function releaseBinaryDirectory() {
|
|
45
64
|
return path.join(__dirname, "..", "bin", "downloads");
|
|
46
65
|
}
|
|
47
66
|
|
|
67
|
+
function allAssetNames() {
|
|
68
|
+
const names = [];
|
|
69
|
+
for (const platformAssets of Object.values(ASSET_MATRIX)) {
|
|
70
|
+
for (const pair of Object.values(platformAssets)) {
|
|
71
|
+
names.push(pair[0], pair[1]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return Array.from(new Set(names));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function allReleaseAssetNames() {
|
|
78
|
+
return [...allAssetNames(), CHECKSUM_MANIFEST];
|
|
79
|
+
}
|
|
80
|
+
|
|
48
81
|
module.exports = {
|
|
82
|
+
allAssetNames,
|
|
83
|
+
allReleaseAssetNames,
|
|
84
|
+
CHECKSUM_MANIFEST,
|
|
85
|
+
checksumManifestUrl,
|
|
49
86
|
detectBinaryNames,
|
|
50
87
|
executableName,
|
|
51
88
|
releaseAssetUrl,
|
|
89
|
+
releaseBaseUrl,
|
|
52
90
|
releaseBinaryDirectory,
|
|
53
91
|
};
|
package/scripts/install.js
CHANGED
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const https = require("https");
|
|
3
3
|
const http = require("http");
|
|
4
|
-
const
|
|
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,
|
|
13
15
|
} = require("./artifacts");
|
|
16
|
+
const pkg = require("../package.json");
|
|
14
17
|
|
|
15
18
|
function resolvePackageVersion() {
|
|
16
|
-
const
|
|
17
|
-
|
|
19
|
+
const configuredVersion =
|
|
20
|
+
process.env.DEEPSEEK_TUI_VERSION ||
|
|
21
|
+
process.env.DEEPSEEK_VERSION ||
|
|
22
|
+
pkg.deepseekBinaryVersion ||
|
|
23
|
+
pkg.version;
|
|
24
|
+
return String(configuredVersion).trim();
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
function resolveRepo() {
|
|
@@ -64,6 +71,19 @@ async function download(url, destination) {
|
|
|
64
71
|
await pipeline(resolved.response, createWriteStream(destination));
|
|
65
72
|
}
|
|
66
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
|
+
|
|
67
87
|
async function readLocalVersion(file) {
|
|
68
88
|
return readFile(file, "utf8").catch(() => "");
|
|
69
89
|
}
|
|
@@ -77,7 +97,45 @@ async function fileExists(file) {
|
|
|
77
97
|
}
|
|
78
98
|
}
|
|
79
99
|
|
|
80
|
-
|
|
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) {
|
|
81
139
|
const marker = `${targetPath}.version`;
|
|
82
140
|
const downloadIfNeeded =
|
|
83
141
|
process.env.DEEPSEEK_TUI_FORCE_DOWNLOAD === "1" || process.env.DEEPSEEK_FORCE_DOWNLOAD === "1";
|
|
@@ -86,6 +144,7 @@ async function ensureBinary(targetPath, assetName, version, repo) {
|
|
|
86
144
|
if (existing) {
|
|
87
145
|
const markerVersion = await readLocalVersion(marker);
|
|
88
146
|
if (markerVersion === String(version)) {
|
|
147
|
+
await verifyChecksum(targetPath, assetName, checksums);
|
|
89
148
|
return targetPath;
|
|
90
149
|
}
|
|
91
150
|
}
|
|
@@ -93,6 +152,12 @@ async function ensureBinary(targetPath, assetName, version, repo) {
|
|
|
93
152
|
const url = releaseAssetUrl(assetName, version, repo);
|
|
94
153
|
const destination = `${targetPath}.download`;
|
|
95
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
|
+
}
|
|
96
161
|
if (process.platform !== "win32") {
|
|
97
162
|
await chmod(destination, 0o755);
|
|
98
163
|
}
|
|
@@ -110,10 +175,11 @@ async function run() {
|
|
|
110
175
|
const paths = binaryPaths();
|
|
111
176
|
const releaseDir = releaseBinaryDirectory();
|
|
112
177
|
await mkdir(releaseDir, { recursive: true });
|
|
178
|
+
const checksums = await loadChecksums(version, repo);
|
|
113
179
|
|
|
114
180
|
await Promise.all([
|
|
115
|
-
ensureBinary(paths.deepseek.target, paths.deepseek.asset, version, repo),
|
|
116
|
-
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),
|
|
117
183
|
]);
|
|
118
184
|
}
|
|
119
185
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const https = require("https");
|
|
2
|
+
const http = require("http");
|
|
3
|
+
const {
|
|
4
|
+
allAssetNames,
|
|
5
|
+
allReleaseAssetNames,
|
|
6
|
+
checksumManifestUrl,
|
|
7
|
+
releaseAssetUrl,
|
|
8
|
+
} = require("./artifacts");
|
|
9
|
+
|
|
10
|
+
const pkg = require("../package.json");
|
|
11
|
+
|
|
12
|
+
function resolveBinaryVersion() {
|
|
13
|
+
const configuredVersion =
|
|
14
|
+
process.env.DEEPSEEK_TUI_VERSION ||
|
|
15
|
+
process.env.DEEPSEEK_VERSION ||
|
|
16
|
+
pkg.deepseekBinaryVersion ||
|
|
17
|
+
pkg.version;
|
|
18
|
+
return String(configuredVersion).trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function resolveRepo() {
|
|
22
|
+
return process.env.DEEPSEEK_TUI_GITHUB_REPO || process.env.DEEPSEEK_GITHUB_REPO || "Hmbown/DeepSeek-TUI";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function requestStatus(url, method = "HEAD", redirects = 0) {
|
|
26
|
+
if (redirects > 10) {
|
|
27
|
+
throw new Error(`Too many redirects while checking ${url}`);
|
|
28
|
+
}
|
|
29
|
+
const client = url.startsWith("https:") ? https : http;
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const req = client.request(
|
|
32
|
+
url,
|
|
33
|
+
{
|
|
34
|
+
method,
|
|
35
|
+
headers: {
|
|
36
|
+
"User-Agent": "deepseek-tui-npm-release-check",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
(res) => {
|
|
40
|
+
const status = res.statusCode || 0;
|
|
41
|
+
const location = res.headers.location;
|
|
42
|
+
res.resume();
|
|
43
|
+
if (status >= 300 && status < 400 && location) {
|
|
44
|
+
const next = new URL(location, url).toString();
|
|
45
|
+
resolve(requestStatus(next, method, redirects + 1));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
resolve(status);
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
req.on("error", reject);
|
|
52
|
+
req.end();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function verifyAsset(url, label) {
|
|
57
|
+
let status = await requestStatus(url, "HEAD");
|
|
58
|
+
if (status === 403 || status === 405) {
|
|
59
|
+
status = await requestStatus(url, "GET");
|
|
60
|
+
}
|
|
61
|
+
if (status < 200 || status >= 400) {
|
|
62
|
+
throw new Error(`${label} returned HTTP ${status} (${url})`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
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
|
+
|
|
115
|
+
async function run() {
|
|
116
|
+
const version = resolveBinaryVersion();
|
|
117
|
+
const repo = resolveRepo();
|
|
118
|
+
const assets = allReleaseAssetNames();
|
|
119
|
+
|
|
120
|
+
console.log(`Verifying ${assets.length} release assets for ${repo}@v${version}...`);
|
|
121
|
+
for (const asset of assets) {
|
|
122
|
+
const url = releaseAssetUrl(asset, version, repo);
|
|
123
|
+
await verifyAsset(url, asset);
|
|
124
|
+
console.log(` ok ${asset}`);
|
|
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
|
+
}
|
|
134
|
+
console.log("Release assets verified.");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
run().catch((error) => {
|
|
138
|
+
console.error("Release asset verification failed:", error.message);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
});
|