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

手机漫游 winlin926/dsh-roam

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

扫码在手机浏览器继续电脑上的会话

30.8/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/23 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add winlin926/dsh-roam
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么[目录]—
dsh 原生插件 · market
暂无时间戳·判据 →
装得上吗[实装验证]!
实装验证未通过(unknown),装前请到仓库确认最近更新与 issue
L4 · 真实安装等级说明 →
数据截至 2026/10/24·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/03·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 12 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

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

验证判据与等级说明 →

可信度证据

⚠︎ 实装验证未通过(unknown · 2026/10/24) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 59 处,展示前 59 条)
  • 读取本地文件
    src/bridge/acme-certificate.ts:65
    const certificate = new X509Certificate(await readFile(path, 'utf8'))
  • 写入本地文件
    src/bridge/acme-certificate.ts:181
    await mkdir(request.challengeDir, { recursive: true })
  • 写入本地文件
    src/bridge/acme-certificate.ts:182
    await writeFile(join(request.challengeDir, challenge.token), keyAuthorization)
  • 删除本地文件
    src/bridge/acme-certificate.ts:185
    await unlink(join(request.challengeDir, challenge.token)).catch(() => undefined)
  • 读取本地文件
    src/bridge/acme-certificate.ts:194
    const key = await readFile(path)
  • 写入本地文件
    src/bridge/acme-certificate.ts:198
    await mkdir(stateDir, { recursive: true })
  • 写入本地文件
    src/bridge/acme-certificate.ts:230
    await mkdir(join(path, '..'), { recursive: true })
  • 写入本地文件
    src/bridge/acme-certificate.ts:232
    await writeFile(temporary, body, { mode })
  • 写入本地文件
    src/bridge/acme-certificate.ts:233
    await rename(temporary, path)
  • 读取本地文件
    src/bridge/cloudflared-tunnel.ts:63
    return resolved && existsSync(resolved) ? resolved : null
  • 读取本地文件
    src/bridge/cloudflared-tunnel.ts:82
    const stream = createReadStream(path)
  • 读取本地文件
    src/bridge/cloudflared-tunnel.ts:98
    if (options.customPath && existsSync(options.customPath)) {
  • 读取本地文件
    src/bridge/cloudflared-tunnel.ts:113
    if (existsSync(targetBinaryPath)) {
  • 写入本地文件
    src/bridge/cloudflared-tunnel.ts:117
    await mkdir(options.cacheDir, { recursive: true })
  • 读取本地文件
    src/bridge/cloudflared-tunnel.ts:120
    for (const entry of await readdir(options.cacheDir)) {
  • 删除本地文件
    src/bridge/cloudflared-tunnel.ts:122
    await rm(join(options.cacheDir, entry), { force: true }).catch(() => undefined)
  • 删除本地文件
    src/bridge/cloudflared-tunnel.ts:134
    await rm(tempDownloadPath, { force: true }).catch(() => undefined)
  • 删除本地文件
    src/bridge/cloudflared-tunnel.ts:142
    await rm(tempDownloadPath, { force: true }).catch(() => undefined)
  • 删除本地文件
    src/bridge/cloudflared-tunnel.ts:144
    await rm(targetBinaryPath, { force: true }).catch(() => undefined)
  • 写入本地文件
    src/bridge/cloudflared-tunnel.ts:150
    await chmod(targetBinaryPath, 0o755)
  • 删除本地文件
    src/bridge/cloudflared-tunnel.ts:155
    await rm(tempDownloadPath, { force: true }).catch(() => undefined)
  • 删除本地文件
    src/bridge/cloudflared-tunnel.ts:189
    await rm(destination, { force: true }).catch(() => undefined)
  • 写入本地文件
    src/bridge/cloudflared-tunnel.ts:220
    const fileStream = createWriteStream(destination)
  • 执行外部命令来自配置/变量
    src/bridge/cloudflared-tunnel.ts:279
    const child = spawn(binaryPath, ['tunnel', '--url', `http://127.0.0.1:${port}`], {
  • 读取本地文件
    src/bridge/lan-mobile-bridge.ts:846
    const body = await readFile(path)
  • 读取本地文件
    src/bridge/lan-mobile-bridge.ts:1199
    body = await readFile(join(dirname(logoPath), `icon-${size}.png`))
  • 发起网络请求
    src/bridge/lan-mobile-bridge.ts:1441
    const response = await fetch(url, {
  • 发起网络请求
    src/bridge/lan-mobile-bridge.ts:1459
    const send = async (cookie: string | undefined): Promise<Response> => fetch(url, {
  • 读取本地文件
    src/bridge/lan-mobile-bridge.ts:2044
    const body = await readFile(join(directory, name))
  • 读取本地文件
    src/bridge/pairing-pin-store.ts:30
    const parsed = JSON.parse(readFileSync(path, 'utf8')) as {
  • 写入本地文件
    src/bridge/pairing-pin-store.ts:51
    mkdirSync(dirname(path), { recursive: true })
  • 写入本地文件
    src/bridge/pairing-pin-store.ts:52
    writeFileSync(temporary, `${JSON.stringify(document, undefined, 2)}\n`, { mode: 0o600 })
  • 写入本地文件
    src/bridge/pairing-pin-store.ts:55
    renameSync(temporary, path)
  • 读取本地文件
    src/bridge/pairing-pin-store.ts:59
    if (existsSync(temporary)) unlinkSync(temporary)
  • 删除本地文件
    src/bridge/pairing-pin-store.ts:59
    if (existsSync(temporary)) unlinkSync(temporary)
  • 执行外部命令固定命令
    src/bridge/pairing-pin-store.ts:71
    execFileSync(
  • 读取本地文件
    src/bridge/pinggy-tunnel.ts:32
    return resolved && existsSync(resolved) ? resolved : null
  • 读取本地文件
    src/bridge/pinggy-tunnel.ts:81
    if (existsSync(options.identityPath)) return options.identityPath
  • 写入本地文件
    src/bridge/pinggy-tunnel.ts:82
    await mkdir(dirname(options.identityPath), { recursive: true })
  • 读取本地文件
    src/bridge/pinggy-tunnel.ts:91
    if (!existsSync(options.identityPath)) {
  • 读取本地文件
    src/bridge/pinggy-tunnel.ts:100
    if (existsSync(sibling)) return sibling
  • 读取本地文件
    src/bridge/pinggy-tunnel.ts:119
    if (!existsSync(sshPath)) throw new Error(`OpenSSH client does not exist: ${sshPath}`)
  • 写入本地文件
    src/bridge/pinggy-tunnel.ts:121
    await mkdir(dirname(knownHostsPath), { recursive: true })
  • 执行外部命令来自配置/变量
    src/bridge/pinggy-tunnel.ts:131
    const child = spawn(
  • 读取本地文件
    src/bridge/public-url-settings.ts:83
    raw = JSON.parse(readFileSync(path, 'utf8'))
  • 写入本地文件
    src/bridge/public-url-settings.ts:105
    await mkdir(directory, { recursive: true })
  • 写入本地文件
    src/bridge/public-url-settings.ts:110
    await writeFile(temporary, `${JSON.stringify(document, undefined, 2)}\n`, 'utf8')
  • 写入本地文件
    src/bridge/public-url-settings.ts:111
    await rename(temporary, path)
  • 删除本地文件
    src/bridge/public-url-settings.ts:113
    await rm(temporary, { force: true }).catch(() => undefined)
  • 读取本地文件
    src/bridge/public-url-settings.ts:135
    const raw = JSON.parse(readFileSync(path, 'utf8')) as { initialPin?: unknown }
  • 发起网络请求
    src/client.js:157
    return await fetch(url, { ...options, signal: controller.signal })
  • 写入剪贴板
    src/client.js:163
    async function writeClipboard(value) {
  • 写入剪贴板
    src/client.js:166
    await navigator.clipboard.writeText(value)
  • 写入剪贴板
    src/client.js:296
    const copied = await writeClipboard(value)
  • 读取本地文件
    src/index.js:22
    return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version
  • 发起网络请求
    src/index.js:281
    fetch(url, {
  • 读取本地文件
    src/index.js:315
    return existsSync(light) && existsSync(dark) ? { light, dark } : undefined
  • 发起网络请求
    src/mobile/api.js:8
    response = await fetch('/api/rpc', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ method, payload }) })
  • 发起网络请求
    src/mobile/app.js:929
    const response = await fetch('/api/status', { cache: 'no-store' })
扫描于 2026/11/3(1 天前) · 范围 全包扫描(包内未声明入口) · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

社区实装还没有人打卡 · 装过的第一个就是你
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-client-locale@deepseek-ai/dsh-client-runtime
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

把电脑上的 DeepSeek Harness 会话漫游到手机:浏览器扫码即用,AI-HUB App 共用同一地址和配对码。

README

由 DeepSeek 最新模型翻译生成
dsh-roam 把电脑上正在运行的 DeepSeek Harness 会话带到手机浏览器:扫码即用,不安装 App,不依赖 AI-HUB,也不用把会话搬到云端。 1.0 起只保留浏览器客户端。AI-HUB 兼容版本停在 0.3.1,见 legacy/aihub。 它解决什么问题 DeepSeek Harness 的工作台在电脑上,人离开电脑后,正在跑的会话就不方便继续。dsh-roam 给官方 DSH 加一条本地优先的移动接入层: - 手机浏览器零安装:扫码后看会话列表、继续对话、新建会话、切换模型与预设、发送、取消、回答提问。 - 不绑定中转账号:局域网直连;公网可用 Cloudflare 临时隧道,也可以接自己的固定域名和 frpc。 - 不复制整个桌面:手机端只做会话操作,设置、插件、终端和文件管理仍留在电脑上。 - 默认不敞开门:设置面板只接受本机回环请求;二维码令牌一次性、5 分钟有效;公网入口必须输入 6 位连接密码。 它不是远程桌面,也不会把 DSH 的完整界面投到手机上。
展开完整文档(README 剩余 1 节)
dsh-roam

把电脑上正在运行的 DeepSeek Harness 会话带到手机浏览器:扫码即用,不安装 App,不依赖 AI-HUB,也不用把会话搬到云端。

1.0 起只保留浏览器客户端。AI-HUB 兼容版本停在 0.3.1,见 legacy/aihub。

它解决什么问题

DeepSeek Harness 的工作台在电脑上,人离开电脑后,正在跑的会话就不方便继续。dsh-roam 给官方 DSH 加一条本地优先的移动接入层:

- 手机浏览器零安装:扫码后看会话列表、继续对话、新建会话、切换模型与预设、发送、取消、回答提问。
- 不绑定中转账号:局域网直连;公网可用 Cloudflare 临时隧道,也可以接自己的固定域名和 frpc。
- 不复制整个桌面:手机端只做会话操作,设置、插件、终端和文件管理仍留在电脑上。
- 默认不敞开门:设置面板只接受本机回环请求;二维码令牌一次性、5 分钟有效;公网入口必须输入 6 位连接密码。

它不是远程桌面,也不会把 DSH 的完整界面投到手机上。

和旧版本的区别

| 能力 | 1.0 浏览器版 | 0.3.x AI-HUB 兼容版 |
| 手机浏览器扫码 | ✅ | ✅ |
| AI-HUB 原生 App | ❌ 已移除 | ✅ |
| dsh-mobile-plugin 依赖 | ❌ 已移除 | ✅ |
| 设备台账 / App 在线状态 | ❌ 已移除 | ✅ |
| 独立的桌面配对页 | ❌ 已移除,统一进设置面板 | ✅ |
| 适用分支 | main | legacy/aihub |

如果你仍在使用 AI-HUB App,请固定在 0.3.1,不要升级到 1.0。

快速开始

要求:DeepSeek Harness 0.2.x、Node.js 22.19+,电脑和手机在同一 Wi-Fi(首次配对)。

dsh plugin --profile web add dsh-roam@1.0.0

重启 DSH,打开 设置 → 连接手机:

1. 手机相机扫描 同一 Wi-Fi 里的二维码。
2. 浏览器打开会话列表,选择会话继续。
3. 二维码 5 分钟有效,过期或使用一次后点 换一张。

任意网络

两种方式,任选一种:

临时隧道:在 任意网络设置 里点 开启临时隧道。插件优先使用 cloudflared 快速隧道,失败时切到 pinggy。地址每次开启都会变化,适合临时外出。

固定域名:有自己的域名和 frpc / SakuraFrp 时,填 https://你的域名 并保存。要求:

- 域名直接解析到隧道节点,不要套 CDN;
- 开一条 HTTP 隧道(80 端口)用于 Let's Encrypt 验证和跳转,一条 HTTPS 隧道(443 端口)指向本机 43128;
- frpc 的「自动 HTTPS」填同一个域名,并在自定义设置里加 auto_https_mode = http;
- 面板里点 申请证书,插件会把 <域名>.crt / <域名>.key 写进 frpc 工作目录。证书 90 天有效,剩 30 天时自动续期;续期后需要重启隧道才会加载新证书。

固定公网地址只接受 https://。只要请求带有 Forwarded、X-Forwarded-、X-Real-IP 或 CF- 转发头,就按公网入口校验密码;配置固定公网域名后,所有配对也一律要求密码。安全判定不依赖可被改写的 Host。

临时隧道每次开启都会换域名,手机需要重新扫码;需要长期使用请配置固定域名。

安全边界

- /phone-bridge/ 管理接口只接受本机回环请求,并校验 Host 与同源;面板不会把管理接口暴露到公网。
- 局域网二维码里是 32 字节一次性令牌,5 分钟有效,配对后作废。
- 公网入口必须输入 6 位连接密码;连续输错会触发限速。带转发头的请求和配置了固定域名的实例一律按公网入口处理。
- 浏览器会话只认服务端签发的 Cookie,不按来源 IP 放行,也不会把客户端自带的 Cookie 提升为会话。会话 30 天绝对过期,插件重启后需要重新扫码。
- 修改连接密码会让已连接的浏览器立即失效。
- 本版本没有应用层端到端加密。 公网路径的 TLS 在 Cloudflare / Pinggy / frps 或反向代理处终止,运营方理论上能看到会话明文。对内容保密有更高要求时,请使用自己控制的固定域名与反向代理,或只在可信局域网内使用。

数据与网络

| 项目 | 位置 / 行为 |
| 数据目录 | %USERPROFILE%\.dsh\dsh-roam(设置 DSH_HOME 时跟随) |
| 设置 | settings.json |
| 连接密码 | mobile-pairing-pin.json,仅本机可读 |
| 证书状态 | acme/ |
| cloudflared | 首次开启临时隧道时下载到数据目录的 bin/ |
| 端口 | 默认 43128,被占用时自动顺延 |

网络请求只发生在:开启临时隧道时(Cloudflare / Pinggy)、申请或续期证书时(Let's Encrypt)。没有遥测,没有账号体系,不把会话内容上传到插件自己的服务器。

常见问题

手机打不开二维码里的地址:确认同一 Wi-Fi、电脑防火墙放行实际端口,并确认手机没有连访客网络。端口不是 43128 时以面板显示为准。

公网一直转圈:多半是 cloudflared 下载失败,换网络或改用固定域名。临时隧道地址每次开启都会变,出门前重新扫一次。

二维码提示失效:浏览器令牌 5 分钟过期,面板会自动换码;也可以点 换一张。

升级后 App 连不上:1.0 已移除 AI-HUB。需要 App 请回退 0.3.1 或使用 legacy/aihub 分支。

升级后浏览器要求重新扫码:浏览器会话只存在内存中,插件升级或 DSH 重启后需要重新扫码;这是为了不让旧 Cookie 长期有效。

开发

pnpm install
pnpm build # 清空 lib,tsc 编译 src/,client.js 原样复制
pnpm test # 先构建,再跑全部测试

- src/ 是唯一源码:index.js(宿主入口)、client.js(设置面板,浏览器经典脚本)、bridge/.ts(桥接、隧道、证书、手机页面)。
- lib/ 是构建产物,不提交、不手改。测试跑的是 lib/,所以改完源码必须先构建。
- src/client.js 不能加顶层 import / export,否则设置页白屏。

发布前检查:

pnpm test
npm pack --dry-run

测试状态

- 已验证:Windows 11、DSH 0.2.0-rc.2、Android 14(API 34)浏览器。
- 未实测:iOS / iPadOS Safari、Firefox、低端 Android。插件没有平台锁定,遇到问题请附系统版本和浏览器版本提 issue。
- 公网验证使用固定域名 + frpc + Let's Encrypt 证书;临时隧道由 cloudflared 提供。

版本记录

- npm:https://www.npmjs.com/package/dsh-roam
- Releases:https://github.com/winlin926/dsh-roam/releases
- AI-HUB 兼容版:0.3.1 / legacy/aihub

许可证

MIT,Copyright (c) 2026 winlin926。第三方组件与许可证见 THIRD_PARTY_NOTICES.md。
预览分享卡片长什么样 ↓winlin926/dsh-roam
分享 · DeepSeek Harness Hub
winlin926/dsh-roam

扫码在手机浏览器继续电脑上的会话

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部