diff --git a/README.zh-CN.md b/README.zh-CN.md index 6c9ec90..6dae14c 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -203,7 +203,10 @@ token-watcher extra-roots remove claude-code ~/mirror/home-pc/claude/projects **Node ≥ 22.13**(`node:sqlite` 自该版本起免 `--experimental-sqlite` flag,零原生依赖)。 -**dsh 源需要系统 `zstd`**(`brew install zstd`)。dsh 的会话快照是追加式多帧写入, +**dsh 源需要系统 `zstd`**。dsh 的会话快照是追加式多帧写入,必须用外部 zstd CLI 解压: +- macOS:`brew install zstd` +- Windows:`scoop install zstd` 或 `choco install zstd`,或从 [facebook/zstd releases](https://github.com/facebook/zstd/releases) 下载 `zstd.exe` 加入 PATH +- Linux:`apt-get install -y zstd` / `dnf install -y zstd` / `pacman -S zstd` 单个文件可达数千帧,而 Node 内置的 zstd 只解第一帧就结束且不报错,因此这里以外部 `zstd` 为准,仅在确认文件只含单帧时才回落内置实现。未安装且遇到多帧文件时,该源会在 健康自检里报错,而不是静默少算。其余数据源不需要它。 diff --git a/src/doctor.js b/src/doctor.js index 298779b..fc24d36 100644 --- a/src/doctor.js +++ b/src/doctor.js @@ -4,6 +4,7 @@ import { join } from 'node:path'; import { homedir } from 'node:os'; import { SOURCES, DB_PATH, DATA_DIR, EXTRA_ROOTS, isOffline } from './config.js'; import { isUnderRoot } from './scanner.js'; +import { zstdInstallHint } from './zstd.js'; import { computeHealth } from './server.js'; import { zcodeQuotaTargets } from './zcodeQuota.js'; import { readClaudeOauthToken } from './claudeUsage.js'; @@ -34,7 +35,7 @@ export async function runDoctor(store, scannerStats, { log = console.log } = {}) try { execFileSync('zstd', ['--version'], { stdio: 'ignore', timeout: 3000 }); return true; } catch { return false; } })(); - log(` ${zstd ? '✓' : '⚠'} 系统 zstd(dsh 多帧会话必需):${zstd ? '可用' : '未找到,brew install zstd'}`); + log(` ${zstd ? '✓' : '⚠'} 系统 zstd(dsh 多帧会话必需):${zstd ? '可用' : '未找到,' + zstdInstallHint()}`); } log('数据库'); diff --git a/src/zstd.js b/src/zstd.js index 9ae21d1..b82798b 100644 --- a/src/zstd.js +++ b/src/zstd.js @@ -7,6 +7,25 @@ const execFileP = promisify(execFile); const ZSTD_BINS = ['zstd', '/opt/homebrew/bin/zstd', '/usr/local/bin/zstd', '/usr/bin/zstd']; const ZSTD_MAGIC = Buffer.from([0x28, 0xb5, 0x2f, 0xfd]); +/** + * 跨平台「如何安装 zstd」提示。dsh 多帧会话必须用外部 zstd CLI 解压, + * 但旧报错只给了 macOS 的 `brew install zstd`,Windows/Linux 用户无解。 + * 这里按 process.platform 给出对应包管理器命令或官方二进制下载地址。 + */ +export function zstdInstallHint() { + switch (process.platform) { + case 'win32': + return '请安装 zstd:scoop install zstd 或 choco install zstd;也可从 https://github.com/facebook/zstd/releases 下载 zstd.exe 并加入 PATH'; + case 'darwin': + return '请安装 zstd:brew install zstd'; + case 'linux': + return '请安装 zstd:apt-get install -y zstd(Debian/Ubuntu)| dnf install -y zstd(Fedora)| pacman -S zstd(Arch)'; + default: + return '请通过系统包管理器安装 zstd'; + } +} + + /** Decompress a complete zstd stream, including concatenated frames. */ export async function decompressZstd(path, { bins = ZSTD_BINS } = {}) { for (const bin of bins) { @@ -35,11 +54,11 @@ export async function decompressZstd(path, { bins = ZSTD_BINS } = {}) { } catch { /* A magic-like sequence inside the frame payload is not a frame boundary. */ } idx = buf.indexOf(ZSTD_MAGIC, idx + 1); } - if (multi) throw new Error(`${path} 含多个 zstd 帧,Node 内置实现只能解第一帧。请安装 zstd:brew install zstd`); + if (multi) throw new Error(`${path} 含多个 zstd 帧,Node 内置实现只能解第一帧。${zstdInstallHint()}`); return zlib.zstdDecompressSync(buf); } - throw new Error('zstd 不可用:PATH 与常见安装路径下均无 zstd,请执行 brew install zstd'); + throw new Error(`zstd 不可用:PATH 与常见安装路径下均无 zstd。${zstdInstallHint()}`); } /**