把 dsh 网页界面变成双击即用的 macOS 应用
27.5/100
综合分
2
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/9/30 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add JiafengtTang/dsh-desktop该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:待复验
是什么
dsh 原生插件 · dev
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 73 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 98 处,展示前 84 条)
- 读取本地文件electron/main.js:90
const dirs = fs.readdirSync(npxRoot) - 读取本地文件electron/main.js:95
const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf8')) - 读取本地文件electron/main.js:209
const code = fs.readFileSync(path.join(__dirname, '..', 'renderer', 'inject.js'), 'utf8') - 写入本地文件electron/main.js:218
fs.mkdirSync(userData, { recursive: true }) - 读取本地文件electron/main.js:219
if (!fs.existsSync(settingsFile)) { - 写入本地文件electron/main.js:220
fs.writeFileSync(settingsFile, JSON.stringify(settings.all, null, 2)) - 写入本地文件electron/main.js:363
fs.mkdirSync(path.dirname(logPath), { recursive: true }) - 写入本地文件electron/main.js:402
fs.mkdirSync(path.dirname(logPath), { recursive: true }) - 写入本地文件electron/main.js:658
fs.mkdirSync(path.dirname(bootstrapLogPath), { recursive: true }) - 写入本地文件electron/main.js:659
fs.appendFileSync(bootstrapLogPath, line + '\n') - 读取本地文件electron/settings.js:30
const raw = fs.readFileSync(this.file, 'utf8') - 写入本地文件electron/settings.js:59
fs.mkdirSync(path.dirname(this.file), { recursive: true }) - 写入本地文件electron/settings.js:60
fs.writeFileSync(this.file, JSON.stringify(this.data, null, 2)) - 执行外部命令固定命令electron/backend.js:36
const where = spawnSync('where.exe', ['node'], { encoding: 'utf8' }) - 读取本地文件electron/backend.js:39
if (line && fs.existsSync(line)) return line - 读取本地文件electron/backend.js:47
if (candidate && fs.existsSync(candidate)) return candidate - 执行外部命令固定命令electron/backend.js:51
const which = spawnSync('which', ['node'], { encoding: 'utf8' }) - 读取本地文件electron/backend.js:54
if (line && fs.existsSync(line)) return line - 读取本地文件electron/backend.js:57
if (fs.existsSync(candidate)) return candidate - 写入本地文件electron/backend.js:78
fs.mkdirSync(dir, { recursive: true }) - 写入本地文件electron/backend.js:89
fs.writeFileSync(shim, '@echo off\r\n"' + systemNode + '" "' + pnpmMjs + '" %*\r\n') - 写入本地文件electron/backend.js:91
fs.writeFileSync(shim, '@echo off\r\nset ELECTRON_RUN_AS_NODE=1\r\n"' + process.execPath + '" "' + pnpmMjs + '" %*\r\n') - 写入本地文件electron/backend.js:100
fs.writeFileSync(shim, '#!/bin/sh\nexec ' + shellQuoteForSh(systemNode) + ' ' + shellQuoteForSh(pnpmMjs) + ' "$@"\n') - 写入本地文件electron/backend.js:102
fs.writeFileSync(shim, '#!/bin/sh\nELECTRON_RUN_AS_NODE=1 exec ' + shellQuoteForSh(process.execPath) + ' ' + shellQuoteForSh(pnpmMjs) + ' "$@"\n') - 写入本地文件electron/backend.js:104
fs.chmodSync(shim, 0o755) - 读取本地文件electron/backend.js:166
if (fs.existsSync(runtimeBin) && runtimeNode) { - 执行外部命令来自配置/变量electron/backend.js:183
const probe = spawnSync(npx, ['--version'], { encoding: 'utf8' }) - 执行外部命令来自配置/变量electron/backend.js:214
this.child = spawn(cmd, args, { - 读取本地文件electron/remoteBackend.js:72
if (fs.existsSync(candidate)) return candidate - 执行外部命令固定命令electron/remoteBackend.js:74
const where = spawnSync('where.exe', ['ssh'], { encoding: 'utf8' }) - 读取本地文件electron/remoteBackend.js:77
if (line && fs.existsSync(line)) return line - 读取本地文件electron/remoteBackend.js:81
if (fs.existsSync('/usr/bin/ssh')) return '/usr/bin/ssh' - 执行外部命令固定命令electron/remoteBackend.js:82
const which = spawnSync('which', ['ssh'], { encoding: 'utf8' }) - 读取本地文件electron/remoteBackend.js:161
try { pkg = JSON.parse(fs.readFileSync(manifest, 'utf8')) } catch { } - 执行外部命令来自配置/变量electron/remoteBackend.js:239
const child = spawn(resolveSsh(), args, { - 执行外部命令来自配置/变量electron/remoteBackend.js:322
this.child = spawn(resolveSsh(), args, { - 写入本地文件electron/remoteBackend.js:335
fs.mkdirSync(path.dirname(this.logPath), { recursive: true }) - 写入本地文件electron/remoteBackend.js:336
this._logStream = fs.createWriteStream(this.logPath, { flags: 'a' }) - 执行外部命令来自配置/变量electron/remoteBackend.js:406
const child = spawn(resolveSsh(), args, { stdio: ['ignore', 'pipe', 'pipe'] }) - 执行外部命令来自配置/变量electron/remoteBackend.js:448
const child = spawn(resolveSsh(), args, { stdio: ['ignore', 'pipe', 'pipe'] }) - 发起网络请求electron/remoteBackend.js:571
const res = await fetch(url, { - 发起网络请求electron/remoteBackend.js:602
const res = await fetch(url, { - 读取本地文件electron/connections.js:20
const raw = fs.readFileSync(this.file, 'utf8') - 写入本地文件electron/connections.js:35
fs.mkdirSync(path.dirname(this.file), { recursive: true }) - 写入本地文件electron/connections.js:36
fs.writeFileSync(this.file, JSON.stringify(this.data, null, 2)) - 读取本地文件electron/sshconfig.js:41
text = fs.readFileSync(file, 'utf8') - 执行外部命令固定命令electron/bootstrap.js:26
? spawnSync('where.exe', [name], { encoding: 'utf8' }) - 执行外部命令固定命令electron/bootstrap.js:27
: spawnSync('which', [name], { encoding: 'utf8' }) - 读取本地文件electron/bootstrap.js:32
if (trimmed && fs.existsSync(trimmed)) return trimmed - 读取本地文件electron/bootstrap.js:57
try { text = fs.readFileSync(patchPath, 'utf8') } catch { } - 写入本地文件electron/bootstrap.js:64
fs.mkdirSync(path.dirname(patchPath), { recursive: true }) - 写入本地文件electron/bootstrap.js:65
fs.writeFileSync(patchPath, text ? text + '\n\n' + managed + '\n' : managed + '\n') - 读取本地文件electron/bootstrap.js:74
try { text = fs.readFileSync(patchPath, 'utf8') } catch { return } - 删除本地文件electron/bootstrap.js:82
if (text.trim() === '') fs.unlinkSync(patchPath) - 写入本地文件electron/bootstrap.js:83
else fs.writeFileSync(patchPath, text + '\n') - 读取本地文件electron/bootstrap.js:94
if (!fs.existsSync(source)) return false - 读取本地文件electron/bootstrap.js:96
if (fs.existsSync(dest)) return true - 读取本地文件electron/bootstrap.js:98
return fs.existsSync(path.join(dest, 'package.json')) - 写入本地文件electron/bootstrap.js:141
fs.mkdirSync(path.dirname(patchPath), { recursive: true }) - 写入本地文件electron/bootstrap.js:142
fs.writeFileSync(patchPath, text ? text + '\n\n' + block + '\n' : block + '\n') - 删除本地文件electron/bootstrap.js:159
if (text.trim() === '') fs.unlinkSync(patchPath) - 写入本地文件electron/bootstrap.js:160
else fs.writeFileSync(patchPath, text + '\n') - 执行外部命令来自配置/变量electron/bootstrap.js:223
const child = spawn(cmd, args, { env }) - 发起网络请求electron/updater.js:14
const req = https.get(url, { - 执行外部命令固定命令electron/updater.js:58
const child = spawn('/usr/bin/curl', ['-L', '--fail', '--silent', '--show-error', url, '-o', dest], { - 读取本地文件electron/updater.js:71
return JSON.parse(fs.readFileSync(stateFile, 'utf8')) - 写入本地文件electron/updater.js:79
fs.mkdirSync(path.dirname(stateFile), { recursive: true }) - 写入本地文件electron/updater.js:80
fs.writeFileSync(stateFile, JSON.stringify(state, null, 2)) - 执行外部命令固定命令electron/plugins.js:15
? spawnSync('where.exe', [name], { encoding: 'utf8' }) - 执行外部命令固定命令electron/plugins.js:16
: spawnSync('which', [name], { encoding: 'utf8' }) - 读取本地文件electron/plugins.js:21
if (trimmed && fs.existsSync(trimmed)) return trimmed - 读取本地文件electron/plugins.js:25
if (fs.existsSync(candidate)) return candidate - 执行外部命令来自配置/变量electron/plugins.js:29
const result = spawnSync(shell, ['-lc', 'command -v ' + name + ' 2>/dev/null'], { encoding: 'utf8' }) - 读取本地文件electron/plugins.js:32
if (line && fs.existsSync(line)) return line - 读取本地文件electron/plugins.js:68
return JSON.parse(fs.readFileSync(manifest, 'utf8')) - 发起网络请求electron/plugins.js:105
return fetch(url, { headers: { Accept: 'application/vnd.npm.install-v1+json' } }) - 发起网络请求electron/plugins.js:112
return fetch(url, { - 读取本地文件electron/plugins.js:144
text = fs.readFileSync(wsPath, 'utf8') - 写入本地文件electron/plugins.js:155
fs.writeFileSync(wsPath, text) - 执行外部命令来自配置/变量electron/plugins.js:164
const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'], ...opts }) - 读取本地文件electron/plugins.js:212
if (c && fs.existsSync(path.join(dir, c))) return true - 写入本地文件electron/plugins.js:263
fs.mkdirSync(vendorDir, { recursive: true }) - 写入本地文件electron/plugins.js:265
fs.copyFileSync(path.join(tmp, tarball), persistedTarball) - 删除本地文件electron/plugins.js:271
try { fs.rmSync(tmp, { recursive: true, force: true }) } catch {}
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 网页界面转macOS应用
- 双击即用
- 远程SSH项目
为 DeepSeek Harness 打造的 macOS 原生桌面外壳,支持 Codex 风格的远程 SSH 项目。
README
由 DeepSeek 最新模型翻译生成DSH Desktop
一个面向 DeepSeek Harness(dsh)的原生 macOS 桌面外壳。
DeepSeek Harness 自带一个 Web UI,通常需要从终端启动并在浏览器中打开。DSH Desktop 将其变成一个双击即可运行的桌面应用,并叠加了你在 Codex Desktop 等桌面编码代理中所期望的原生便利功能:
- 托管后端 —— 自动启动/停止/重启 dsh web 服务器,无需终端或 npx。
- 原生窗口 + 菜单 —— 真正的应用窗口、标准 macOS 菜单栏,以及可用的复制/粘贴/全选快捷键(普通浏览器会替你处理这些)。
- 菜单栏 / 托盘常驻 —— 后端在后台持续运行;可从托盘显示/隐藏窗口、打开新窗口或退出。
- 多窗口 —— 针对同一个正在运行的后端打开多个窗口。
- 通知 —— “后端就绪”和“后端意外停止”。
- 全局快捷键 —— Cmd+Shift+D 切换窗口显示(可配置)。
- 设置持久化 —— 窗口位置尺寸、端口、快捷键等会保存到磁盘,并可作为 JSON 编辑。
- 日志记录 —— 后端的 stdout/stderr 会被捕获到日志文件中,便于调试。
展开完整文档(README 剩余 1 节)
DSH Desktop 一个面向 DeepSeek Harness(dsh)的原生 macOS 桌面外壳。 DeepSeek Harness 自带一个 Web UI,通常需要从终端启动并在浏览器中打开。DSH Desktop 将其变成一个双击即可运行的桌面应用,并叠加了你在 Codex Desktop 等桌面编码代理中所期望的原生便利功能: - 托管后端 —— 自动启动/停止/重启 dsh web 服务器,无需终端或 npx。 - 原生窗口 + 菜单 —— 真正的应用窗口、标准 macOS 菜单栏,以及可用的复制/粘贴/全选快捷键(普通浏览器会替你处理这些)。 - 菜单栏 / 托盘常驻 —— 后端在后台持续运行;可从托盘显示/隐藏窗口、打开新窗口或退出。 - 多窗口 —— 针对同一个正在运行的后端打开多个窗口。 - 通知 —— “后端就绪”和“后端意外停止”。 - 全局快捷键 —— Cmd+Shift+D 切换窗口显示(可配置)。 - 设置持久化 —— 窗口位置尺寸、端口、快捷键等会保存到磁盘,并可作为 JSON 编辑。 - 日志记录 —— 后端的 stdout/stderr 会被捕获到日志文件中,便于调试。 - 远程连接(SSH) —— 在远程服务器上运行代理并将 UI 隧道回本地,因此文件读写和命令都针对远程项目执行(相当于 Codex 的 remote-devbox 功能)。 - dsh-web-ui 集成 —— 首次本地启动时,应用会安装 dsh-web-ui 插件套件(任务看板、Git 图谱、SSH 面板、皮肤中心、Blue Fantasy 鲸娘皮肤及相关插件),并自动启用 Blue Fantasy 皮肤。 - DeepSeek 账单徽章 —— 内置的 DeepSeek-Harness-billing-plugin 会在会话头部显示你真实的 DeepSeek 账户余额以及按模型估算的剩余任务数。 为什么选择 DSH Desktop - 真正的 macOS 桌面应用 —— 双击即可启动,拥有 Dock 图标、原生菜单、托盘、通知和全局快捷键。无需为了保持 harness 存活而一直开着终端会话。 - 以 Codex 的方式开发远程项目 —— 通过 SSH 连接到服务器,借助父级/根目录导航浏览并选择远程项目目录,然后就像项目在本地一样继续编码。工作区条目会显示它们是本地还是远程,并带有连接状态指示。 - 鲸娘桌面身份 —— 内置的 Blue Fantasy 皮肤和自定义应用图标将标准 Web UI 转变为独特的 macOS 桌面体验。 - 自动包含插件套件 —— 首次本地启动时会为你安装并加载任务看板、SSH 面板、Git 图谱、皮肤中心及相关 dsh-web-ui 插件。 - 账户余额一目了然 —— 配置好 DeepSeek API 密钥后,每个会话头部都会显示你的实时余额,以及当前模型还能运行多少任务的估算值。 - 日常使用的安全默认值 — 后端受管理且可重启,日志会被捕获,远程端口会自动选择,因此不同的 dsh 实例不会冲突。 窗口内的一切都是真正的 DeepSeek Harness Web UI,因此其完整功能集——会话、计划、目标、子代理、终端、文件编辑、技能、工作区以及仅追加的 Trajectory 视图——均可原样工作。 原生“选择工作区”文件夹选择器也可用,因为后端运行在你的 Mac 上(通过 directory-picker-native osascript 后端)。 要求 - macOS(Apple Silicon 或 Intel) - Node.js ≥ 20(用于运行 dsh 后端;如果 PATH 中没有 node,应用会回退到 Electron 自带的 Node) 后端使用真实的 Node 运行时启动。应用会先在 PATH 中查找 node,然后在 /usr/local/bin、/opt/homebrew/bin、/opt/local/bin 和 /usr/bin 中查找,接着通过登录 shell 查找,最后才回退到 Electron 捆绑的 Node。这一点很重要,因为 Finder 启动应用时使用的 PATH 很精简,会省略常见的 Node 安装位置。 从源码运行 cd dsh-desktop npm install npm start 构建可分发的应用 npm run pack # 在 dist/ 中生成未打包的 .app npm run dist # 在 dist/ 中生成 .dmg + .zip 在没有代码签名身份的情况下构建: CSC_IDENTITY_AUTO_DISCOVERY=false npm run dist 首次运行 1. 启动应用。它会启动后端并打开 DeepSeek Harness UI。 2. 在 UI 中的 Settings → Models 添加模型 API 密钥(DeepSeek 或任何兼容 OpenAI 的端点)。 3. 选择一个工作区文件夹,然后启动一个会话。 计费徽章读取同一个 DeepSeek 密钥。它会在会话打开后出现在会话标题中;如果密钥或端点不可用,徽章会显示不可用状态,而不是余额。 远程连接(SSH) 这与 Codex 连接到远程 devbox 的方式类似:DSH Desktop 不在本地运行代理,而是在远程机器上的远程项目目录内启动 dsh web,然后打开 SSH 隧道并在本地加载远程 UI。 因此,读取、写入、测试和 shell 命令都在服务器上执行。 要添加一个连接,请打开左侧边栏中的 远程连接(或 DSH Desktop → Connections…)并填写: - Name — 显示标签。 - SSH host alias — 来自本地 ~/.ssh/config 的条目(推荐;它会为你处理用户、密钥和跳板主机),或者直接填写 Host / User / Port / Identity file。 - Remote project directory — 服务器上的绝对路径(可选;如果留空,连接会从远程主目录开始)。 - Remote dsh command — 默认为 npx -y @deepseek-ai/dsh@0.1.0-rc.6;如果它已经 安装在远程 PATH 中,请改为 dsh。 - Remote port — 默认为 0(自动)。应用会选择一个高位临时端口,并将其隧道到本地空闲端口,因此不会与其他 dsh 实例冲突。 远程主机上的要求: - 通过密钥或本地 ssh-agent 进行 SSH 访问(密码提示被有意禁用,以实现非交互式连接)。 - Node.js ≥ 20,以及 npx(默认)或预装的 dsh。 - 使用默认的 0 时,不需要固定的远程端口。 在首次远程连接时,应用还会将计费插件安装到远程配置文件中,因此远程项目也会像本地一样显示会话头部的 DeepSeek 余额徽章。 在连接之前,点击 Test connection 以验证 SSH、Node 和项目目录,然后点击 Use 以激活它。应用通过 SSH 将 127.0.0.1: → 127.0.0.1: 进行隧道转发;dsh 的 /api 信任围栏已经接受回环主机,因此该隧道不需要额外的身份验证。 与 Codex 相同的非交互式 shell 注意事项也适用:如果你的远程 Node 位于非标准位置(nvm 等),请确保它位于远程登录 shell 加载的 PATH 上(例如 .profile/.bash_profile)。出于这个原因,连接是通过配置的 Remote shell(默认 bash)作为登录 shell 启动的。 配置 设置位于应用的数据文件夹中(macOS: ~/Library/Application Support/DSH Desktop/settings.json)。使用 DSH Desktop → Settings… 来显示和编辑该文件。编辑后重新启动。 | 键 | 默认值 | 含义 | | port | 0 | 后端端口;0 让操作系统选择一个空闲端口。 | | host | 127.0.0.1 | 绑定主机。 | | dshHome | "" | 空 = 与 CLI 共享 ~/.dsh;设置一个路径以隔离。 | | shortcut | CommandOrControl+Shift+D | 全局显示/隐藏快捷键。 | | notifications | true | 显示桌面通知。 | | autoRestart | true | 如果后端意外退出,则重新启动它。 | 环境变量覆盖: - DSH_DESKTOP_NODE — 用于运行后端的 node 二进制文件的绝对路径。 - DSH_HOME — 当设置了 dshHome 时被忽略;否则作为后端的 home。 架构 electron/main.js 启动应用并协调各个部分。electron/backend.js 在本地生成 @deepseek-ai/dsh(dsh web --port )并监视其 stdout 以获取就绪行 dsh web: http://127.0.0.1:,然后将该 URL 加载到窗口中。对于远程连接,electron/remoteBackend.js 则生成 ssh,带有本地端口转发和一个在远程项目目录中运行 dsh web 的远程命令,然后将就绪行映射到本地隧道 URL。 dsh UI 由后端通过回环 HTTP 提供服务,dsh 的 /api 信任 围栏已经接受这一点。 dsh 仓库记录了一种更深入的集成(“Electron 通过 file:// 加载构建 文件,并通过 IPC 桥发送 fetch 请求”)。这是一个计划中的未来优化;这里使用的回环 HTTP 方法对于本地桌面应用来说在功能上是等效的,并且简单得多。 说明 - DeepSeek Harness 是一个早期开发者预览版(0.1.0-rc.6),具有 - 预期会有破坏兼容性的变更。版本已固定在 package.json 中;请谨慎升级。 - 该应用有意在你关闭窗口后继续在菜单栏中运行(类似 Codex)。使用 Cmd+Q 或托盘菜单退出。 - 并非 DeepSeek 官方产品。所有 DeepSeek Harness 代码均采用 MIT 许可证。 故障排除 - 窗口打开后显示黑屏 — 后端无法启动。请查看 Help → Open Backend Logs 中的日志(或 ~/Library/Application Support/DSH Desktop/logs/backend.log)。最常见原因是未找到可用的 node;请安装 Node.js ≥ 20 或将 DSH_DESKTOP_NODE 设置为 node 的绝对路径。
预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
JiafengtTang/dsh-desktop
把 dsh 网页界面变成双击即用的 macOS 应用
相关插件 · 开发 / 工程
隐私简历生成器reactive-resume/reactive-resume
Reactive Resume 已迁至 GitHub 上的…
★ 45,125已汉化MCP
71.7
AI 架构文档生成器sopaco/deepwiki-rs
🚀 Litho 已进化为 Terrain —— 给你的 AI 智能体一张会呼吸的代码库地图。
★ 3,157已汉化DSH
70.8
长时任务执行闭环AMAP-ML/LongHorizon-Harness
面向 Computer-Use Agent 的 Loop Engineering
★ 1,764已汉化DSH
70.5
AI 编程纪律方法包GanyuanRan/Aegis
让 AI 编程 agent 变得可信:少返工、更安全、说“完成”前先给证据。
★ 1,384已汉化DSH
69.6
多代理编排运行时Enderfga/claw-orchestrator
面向编程智能体的运行时。把 Claude Code、Codex、Antigravity、Grok…
★ 586已汉化MCP
69.4
记忆进化插件csyangwen/dsh-memory-evolve
dsh-memory-evolve 使用场景指南
★ 461已汉化DSH
67.9