# DSH Desktop DSH 的本地桌面端:菜单栏常驻 + 原生窗口 + 升级管家。 **不打包任何官方代码**——它只托管你本机 npm 安装的 `dsh web`,并通过 npm 跟随官方升级。 ## 设计 ``` 菜单栏 App(Electron) ├─ Supervisor attach 已有 dsh(:3080 健康时)/ spawn 并守护自己的子进程 ├─ Window BrowserWindow → http://127.0.0.1:(官方 UI,绝不内置副本) └─ Updater npm view 盯发版 → 一键 npm i -g 升级 → 优雅重启 → 冒烟失败自动回滚 ``` 产品方案见 [PRODUCT-PLAN.zh.md](PRODUCT-PLAN.zh.md)。 ## 版本管理 - 壳版本(DSH Desktop 自身)遵循 SemVer:`MAJOR` 形态/破坏性变更、`MINOR` 新功能、`PATCH` 修复 - 发版流程:`npm run bump -- minor|patch|major` → 在 [CHANGELOG.md](CHANGELOG.md) 登记变更 → `npm run dist` (.app 的 Info.plist 与 DMG 文件名自动携带新版本号) - dsh 本体的版本独立于壳版本,由升级管家经 npm 管理;两者都可见于托盘"关于 DSH Desktop" ## 开发 ```sh cd app npm install # 仅一个 devDependency:electron(42.3.3,复用本机缓存) npm run icons # 生成图标:官方鲸鱼路径 → PNG/icns(纯 Node 光栅化 + iconutil) npm start # 启动 ``` ## 打包 ```sh cd app npm run dist # → ../dist/DSH Desktop.app(手工构建 bundle:electron dist + 源码,无网络依赖) # → ../dist/DSH-Desktop-<版本>-arm64.dmg(拖拽安装:app + Applications 快捷方式) ``` 安装:打开 DMG,把 DSH Desktop 拖进 Applications 文件夹即完成。已做 ad-hoc 签名,首次打开若仍被拦: 右键 → 打开(或 `xattr -dr com.apple.quarantine "/Applications/DSH Desktop.app"`)。 ## 和 Chrome PWA(应用程序文件夹里的 "DeepSeek Harness")的区别 Chrome 装的 "DeepSeek Harness" 是一个 PWA 快捷方式:一个指向 `http://127.0.0.1:3080/` 的 Chrome 窗口。它**不会启动 dsh**——终端里没有 `dsh web` 在跑,点开就是连接失败; 服务挂了它也只是白屏。DSH Desktop 是进程监督者:点图标自动拉起并守护服务, 菜单栏看状态、崩溃自动重启、npm 发版一键升级(失败自动回滚)。装好 DSH Desktop 后 PWA 可以删掉(两者指向同一个本地服务,功能上前者完全覆盖后者)。 ## 使用 - 启动后菜单栏出现图标;主窗口加载本地 dsh Web UI,关窗 = 隐藏(服务继续跑),退出走托盘菜单(优雅停机) - 如果终端里已有 `dsh web` 在 3080:直接 attach 复用,不重复拉起 - 托盘菜单:打开 DSH / 检查更新 / 升级 / 重启服务 / 停止·启动 / 日志 / 开机自启 / 退出 - 快捷键 `⌘⇧D` 显示/隐藏窗口 - 日志:`~/Library/Application Support/DSH Desktop/dsh-web.log`(npm 独立缓存在同级 `npm-cache/`) - 发现新版本:通知 + 托盘菜单出现"⬆ 升级";升级后服务起不来会自动回滚旧版本并通知 ## 测试钩子(环境变量) | 变量 | 作用 | |---|---| | `DSH_DESKTOP_SMOKE=1` | 就绪后自动退出并打印状态(冒烟测试) | | `DSH_DESKTOP_NO_ATTACH=1` | 不 attach,强制 spawn 自己的子进程 | | `DSH_DESKTOP_USER_DATA=dir` | 覆盖 Electron 用户数据目录 | | `DSH_DESKTOP_DSH_BIN=path` | 指定 dsh 二进制路径 | | `DSH_DESKTOP_PORT=3080` | 覆盖默认端口 | ```sh # 冒烟(attach 路径) DSH_DESKTOP_SMOKE=1 ./node_modules/.bin/electron . # 冒烟(spawn 路径,会自己拉一个 dsh 实例) DSSH_DESKTOP_SMOKE=1 DSH_DESKTOP_NO_ATTACH=1 ./node_modules/.bin/electron . ``` ## 已验证 / 待你在真实环境验证 已在本机端到端验证: - attach 路径:探测 `__DSH_BOOT__` → 复用 3080 现有实例,退出时不碰它 - spawn 路径:固定端口冲突 → 自动降级 `--port 0` → 子进程端口解析(stdout + lsof 兜底)→ 就绪 → SIGTERM 优雅退出(exit 0)→ 无残留进程 - 崩溃退避重启逻辑(1s/5s/30s/60s) - 版本读取(package.json 解析) - npm 发版探测(独立 cache,发现 0.1.1-rc.2 > 0.1.0-rc.6) - 打包后的 .app 冒烟通过;DMG 挂载后从卷内直接运行冒烟通过 - Finder 式最小 PATH(launchd 环境)下的二进制定位与子进程 PATH 增强(dsh/npm 的 node shebang 可解析) 沙箱内无法实测:`npm install -g` 升级与自动回滚(需要写 `~/.local`,见下)。 装好 .app 后点一次"检查更新 → 升级到 0.1.1-rc.2"即完成最终验证。 ## 环境备注 - 本机 `~/.npm` 缓存存在 root 属主文件(历史上 sudo npm 的遗留),已让 npm 报 EPERM。 桌面端所有 npm 操作走独立 cache 目录不受影响;如想根治: `sudo chown -R 501:20 ~/.npm` - dsh 升级通道是 npm dist-tag(`latest`/`next`),当前默认跟 `latest`。