🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

桌面外壳 JiafengtTang/dsh-desktop

DeepSeek Harness
JiafengtTang73 天前有提交已提供中文文档扫描:含敏感能力

把 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
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 73 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
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 的绝对路径。
预览分享卡片长什么样 ↓JiafengtTang/dsh-desktop
分享 · DeepSeek Harness Hub
JiafengtTang/dsh-desktop

把 dsh 网页界面变成双击即用的 macOS 应用

回到顶部