DSH Plugins Marketplace

DSH Plugins

Plugins

/

Terminal & Clients

/

dsh-tailscale-remote

d

dsh-tailscale-remote

Manifest valid

Publishes the DSH Web GUI on your tailnet whenever DSH runs, so a phone anywhere can connect, and removes the route on exit. The phone link lives in Settings -> General -> Phone access with one-click copy and key rotation: rotating revokes the old link and every session signed with it. The same link is in the loginUrl field of ~/.dsh/tailscale-remote.json.

UI (client)hasBundlePatch

dsh-tailscale-remote

打开 DSH 就把本机 Web GUI 通过 Tailscale Serve 暴露到你的 tailnet 上,手机在任何网络下 打开一个网址就能用;DSH 退出时把这条路由撤掉。手机链接直接显示在 **「设置 → 通用 → 手机连接」**里,点一下就能复制。

给 DeepSeek Harness(DSH)用的插件。 只需要「随时随地用手机连上自己电脑上的 DSH」这一件事,不引入任何第三方中转。

先看这个:手机怎么进

最省事的办法:DSH 里打开「设置 → 通用」,往下找到「手机连接」那一行,里面就有链接, 点一下「复制」,发到手机上打开一次即可。 那一行还能随时「更换密钥」——万一链接泄露, 换了之后旧链接和所有旧登录一起失效。

不想开 DSH 界面时,读状态文件也一样(字段是 loginUrl):

# 装好并重启 DSH 后,执行(Windows 用 PowerShell 见下)
cat ~/.dsh/tailscale-remote.json

读里面 loginUrl 那个值,形如:

https://<你的机器>.<tailnet>.ts.net:7259/remote/<持久密钥>

在手机浏览器打开它一次(会自动跳转并登录),然后存成书签/主屏幕图标 —— 这条链接跨重启不变,以后点一下就进。

Windows PowerShell 读法:

Get-Content "$env:USERPROFILE\.dsh\tailscale-remote.json"

就这么两步:拿到 loginUrl → 手机打开一次并存书签。下面都是细节。

DSH 的 Web GUI 要求一次登录令牌,没令牌只显示一句 dsh web authentication required; ...。DSH Desktop 又从不打印那行链接,令牌还每次重启都换 —— 所以插件给你一条存一次就永久用的入口。

安装

dsh plugin --profile <你的 profile> add dsh-tailscale-remote

装完重启 DSH(新包需要重启才会加载)。之后每次开 DSH 它都会自己跑起来。

也可以从本地目录装:

dsh plugin --profile <profile> add -w /path/to/dsh-tailscale-remote

它做什么

启动时:

  1. 确认 tailscale 真的在跑(用 tailscale status --json 验证 BackendState, 不是相信 tailscale up 的退出码 —— 见下文)。
  2. 把 https://<你的机器>.<tailnet>.ts.net:7259 代理到 DSH 自己的 Web 端口 (端口从 webServer 服务上读,不写死)。
  3. 把 <你的机器>.<tailnet>.ts.net:7259 加进 DSH /api 的 Host 信任列表。
  4. 打印手机可用的网址,并写一份到 $DSH_HOME/tailscale-remote.json。

退出时:只回滚本次运行自己做过的事——撤掉 7259 的路由、移除信任项;只有 tailscale 本来就是关着、由 DSH 拉起来的情况下才 tailscale down。

例外:如果 7259 上已经有一条精确指向本实例 DSH 端口的路由(几乎只可能是上次 DSH 崩溃/被强杀留下的),插件会把它认领下来,退出时一并撤掉 —— 否则崩溃恢复 之后「关 DSH 就关掉」会永远失效,路由一直留在那儿。

为什么必须动信任列表

tailscale serve 保留客户端的 Host 头(实测:从 tailnet 地址访问时,后端收到的 host 就是 xxx.ts.net:7259)。而 DSH 的 /api fence 要求 Host 是回环地址或命中 trustedHosts,否则一律 403 —— 表现为手机上页面能打开、但一发消息就报错。 插件在运行时把该 authority 推进 connection.trustedHosts(这是个可变数组,fence 每次 请求都读它),所以不需要改 profile 的 cordis.yml。

手机上怎么用(重点:怎么拿到登录链接)

DSH 的 Web GUI 默认要求一次登录令牌才让你进去,没有令牌就只显示一句话:

dsh web authentication required; reopen the URL printed by dsh web.

麻烦的地方在于:DSH Desktop 从不打印这行链接(它走 IPC,不走 dsh web),而令牌又是 进程级的、每次重启都换。所以本插件给你三个拿链接的地方,任选其一。

① 设置页里的「手机连接」行(推荐)

设置 → 通用,滚到下面有「手机连接」这一行:

  • 里面是手机登录链接,一个只读输入框 + 「复制」按钮,点一下就能发到手机上。
  • 旁边有「更换密钥」:点一下会先让你确认,确认后旧密钥与所有旧登录一起失效。
    • 换的到底是什么:登录链接(/remote/<密钥>)只负责换取一张会话 cookie。 密钥泄露后,如果对方已经用它登录过,他手里那张 cookie 是另一把签名密钥签的、 有效期 10 年 —— 只换登录密钥完全不影响它。所以这里的「更换密钥」会同时换掉 登录密钥和会话签名密钥,让泄露的密钥真正失效。
    • 代价:所有设备(包括你自己的手机)都会被登出,需要用新链接重新登录一次。 点按钮的这台设备会被自动补发一张新 cookie、不掉线,所以在电脑上操作最顺手。
    • 想只换链接、不踢设备?设 revokeSessionsOnRotate: false(此时泄露的旧 cookie 仍然有效,只适合「我确定没人用过那条链接」的情况)。
  • 还没就绪时会直接告诉你卡在哪一步(比如「Tailscale 没在运行」),而不是给你一个空框。

这一行只在你打开设置页时才请求一次状态(外加窗口重新获得焦点时刷新一次), 不轮询、不挂定时器。它注册在 DSH 自己的 /api 上,所以只有已登录的浏览器能读。

② 稳定登录入口(存一次书签就够)

插件会在 DSH 自己的 webserver 上注册一条路由:

https://<你的机器>.<tailnet>.ts.net:7259/remote/<持久密钥>

打开它就会 302 跳转到带令牌的地址、种下 cookie,然后你就进 GUI 了。

这条链接的密钥存在状态文件里,跨重启不变 —— 所以手机上存一次书签,以后点一下就进。

密钥本身就是访问门槛:不知道它的人拿不到会话(和 DSH 自己的令牌同一级别)。 不需要的话可以设 loginRoute: false 关掉。

③ 状态文件(永远可以查到)

任何一次启动后,下面这个文件里都有当前可用的链接:

# 三个字段:loginUrl(稳定入口)、url(一次性令牌链接)、plainUrl(干净地址)
cat ~/.dsh/tailscale-remote.json

Windows PowerShell:

Get-Content "$env:USERPROFILE\.dsh\tailscale-remote.json"

拿 loginUrl 那个值,在手机上打开即可。它同时也会打到 DSH 的日志里。

登录之后

cookie 种下后,直接访问干净地址 https://<机器>.<tailnet>.ts.net:7259/ 就能进, 可以「添加到主屏幕」当 App 用。

cookie 有效期默认 10 年(见下面的 cookieMaxAgeDays)—— DSH 原版是 30 天,对 「手机随时连」太短,插件启动时会把 connection 上那个活的 maxAgeMilliseconds 直接改长。所以正常情况下你只需要登录一次。

手机上好不好用:实测可用。DSH 0.2.0-rc.2 的 Web 前端确实没有专门的移动端外壳 (JS 无 matchMedia,CSS 里只有 prefers-reduced-motion、没有宽度断点),但它的三栏是 弹性布局,在手机宽度下会自然收窄,日常查看会话、发消息、看结果都没问题,不会挤成一条。 想要更像 App 的体验(抽屉侧栏、底部导航)需要另外写客户端插件。

前置条件

  • 本机已安装并登录 Tailscale。
  • 该 tailnet 已启用 HTTPS 证书(Tailscale 管理台 → DNS → HTTPS Certificates)。 没启用时 tailscale serve 会失败,日志里会提示。

配置

字段默认说明
enabledtrue关掉则插件完全不动作。
httpsPort7259tailnet 上的 HTTPS 端口。
connectOnStarttruetailscale 没在跑时是否把它拉起来。
serveOnStarttrue是否建立 Serve 路由。
teardownOnExittrue退出时是否回滚。
trustedHoststrue是否把该 authority 加进 /api 信任列表。
loginRoutetrue是否注册 /remote/<持久密钥> 这个稳定登录入口(设置页那一行的「更换密钥」也依赖它)。
revokeSessionsOnRotatetrue换密钥时是否同时吊销所有已登录的会话。默认开:密钥泄露时对方可能已用旧密钥换到一张 10 年 cookie,只换登录密钥拦不住他。关掉则只换链接、所有设备继续登录。
cookieMaxAgeDays3650登录 cookie 有效期(天)。0 = 不插手 DSH 的 30 天默认值。
binaryPath自动探测tailscale CLI 可执行文件路径。
guiPath自动探测Tailscale 桌面客户端路径(Windows 默认 tailscale-ipn.exe)。
readyTimeoutMs8000等 tailscale up 生效的预算。
guiReadyTimeoutMs25000启动桌面客户端后等 backend 就绪的预算。
stateFile$DSH_HOME/tailscale-remote.json状态文件位置。

关于「永久不过期」

DSH 把 cookie 有效期上限写死在 connection 的 cookieMaxAgeDays 里,并且校验时要求 expiresAt - issuedAt <= maxAgeMilliseconds —— 所以自己造一个超长 cookie 是无效的, 必须改这个上限。本插件启动时会直接改 connection.browserAuth.maxAgeMilliseconds, 默认给 10 年(3650 天),够用且不会溢出 JS 安全整数范围。

想更长就把它调大(比如 36500 = 100 年);0 表示完全不动 DSH 的默认 30 天。

为什么不能只调 tailscale up

在 Windows 上,如果你从托盘把 Tailscale 完全退出,tailscaled 服务还在跑,但 backend 处于 NoState(tailscale status 报 Tailscale is starting. Please wait.)。此时 tailscale up 会「立刻 exit 0、不输出任何东西、也什么都不做」(实测),backend 永远 不会变成 Running —— 只看退出码就会误判成功,然后因为读不到 DNS 名而静默放弃。

所以插件每一步都用 tailscale status --json 验证 BackendState,而不是相信退出码: tailscale up 之后 backend 还不是 Running,就改为启动桌面客户端 (tailscale-ipn.exe),它才会真正初始化 backend(实测启动后立刻 Running + Online)。 桌面客户端以 detached 方式启动,不会随 DSH 退出而被带走。

如果连客户端都拉不起来(例如需要交互式登录),插件会留下明确提示让你手动打开 Tailscale, 并且不会谎报成功去配一条连不上的路由。

发布(维护者)

包名 dsh-tailscale-remote 已发布到 npm。package.json 里写了 publishConfig (access: public + registry: https://registry.npmjs.org/),所以直接 npm publish 不带任何参数就会发到正确的地方——即使本机 ~/.npmrc 指向的是镜像站。

后续版本自动发布:推送到 main 时 .github/workflows/publish.yml 会先跑测试再 npm publish——但只有 package.json 里的 version 是 registry 上还没有的版本时才发。 所以发版流程就是两步:

  1. 改 package.json 的 version。
  2. 推送到 main。

这次运行要么发出这个版本,要么报告「该版本已在线上」并以成功结束。 没改版本号的推送是 no-op,不是失败——所以每个要发出去的改动都必须自己带一次版本号 变更;只改文档的提交同样会触发工作流,同样 no-op。

认证用 npm Trusted Publishing(OIDC):GitHub 给这个 job 签一个短时 token,npm 拿它换 一个只对这一个包有效的发布 token。仓库里没有任何 NPM_TOKEN secret。 工作流刻意 不用 actions/setup-node 的 registry-url 输入——它会往 ~/.npmrc 写 //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN},而该变量没定义时这个占位符会 原样留下、被 npm 当作 token 发出去。registry 交给 publishConfig 就够了。

一次性配置:绑定 Trusted Publisher

Trusted Publisher 只能绑到 registry 上已存在的包,所以首次发布是手工做的 (在检出目录里跑了一次 npm publish),从第二个版本起才轮到工作流接管。在 npmjs.com 上: 包 → Settings → Trusted Publisher → GitHub Actions,填

字段值
RepositoryCkarefulon/dsh-tailscale-remote
Workflow filenamepublish.yml
Environment留空

文件名必须逐字一致——npm 把授权绑在它上面,写错会表现为发布报 ENEEDAUTH。

安全边界

这条路由只对你的 tailnet 可见(Tailscale Serve 的默认范围,不是 Funnel),也就是说 只有登录到你这个 tailnet 的设备能连上。DSH 自己的浏览器鉴权(30 天签名 cookie)仍然 生效,两层是叠加的。

设置页那一行背后的两个端点注册在 DSH 自己的 /api 前缀路由上 (connection.fetch),因此直接白拿 DSH 的 Host/Origin fence 与浏览器 cookie 鉴权: 未登录的请求在到达插件之前就被 401 挡掉了。它们也刻意不用 webServer.register({ kind: 'exact', path: '/api/...' }) —— webserver 的 match() 先查 exact 表,那样会抢先命中并遮掉整条 /api 路由,把 fence 和鉴权一起绕过去。

端点作用
GET /api/dsh-tailscale-remote/state当前阶段、手机链接、能否换密钥、换密钥会不会踢设备。不缓存。
POST /api/dsh-tailscale-remote/rotate换一把新登录密钥,并(默认)吊销所有已登录的会话;给发起方补发一张新 cookie。

「更换密钥」到底吊销了什么

DSH 的浏览器鉴权有两把独立的密钥,别把它们混为一谈:

作用换了它会怎样
登录密钥(本插件的 /remote/<密钥>)换取会话 cookie 的入口旧链接立刻 404
会话签名密钥(connection.browserAuth.secret,存在 credentials 的 client-connection/browser-session 记录里)给会话 cookie 签名所有已发出的 cookie 立刻验不过

只换登录密钥是不够的:泄露的链接如果已经被用过,对方手里那张 cookie 由第二把密钥 签名、有效期 10 年,换第一把对它毫无影响。所以默认两把一起换(revokeSessionsOnRotate)。

实现上两处都要改,缺一不可:credentials 记录(持久,否则 DSH 一重启就把旧密钥读回来、 旧 cookie 复活)和活着的 browserAuth.secret(立即生效,它按引用逐字比较签名)。 写入后会回读确认:写错记录键或被只读来源遮蔽时 modifyRecord 会「成功返回但记录没变」, 不校验的话就是「报成功、实则没换」。

想彻底踢掉已登录设备也可以直接去改 DSH 自己的凭据 —— 那等于把第二把密钥换掉,效果一样, 但会一并影响其它依赖它的东西;用本插件这一行更精确,而且会顺手把新链接给你。

插件不会覆盖 7259 上已有的、指向别处的 Serve 配置:检测到冲突就只记日志、不动手。 唯一会接管的情况是发现一条指向另一个回环端口的陈旧路由(通常是上次 DSH 换了端口 留下的)。

Comments

Loading…

From the same category

dsh-TUI

by ccch1mneyyy

DSH 官方公众号收录的 TUI 补位插件:Claude Code 风,鲸鱼顶栏/实时状态/流式思考/双击 Esc 回滚/上下文进度+TPS。npm 一键装。 DSH official WeChat featured TUI plugin — Claude Code style: whale bar, live status, streaming thoughts, double-Esc rol

Terminal & ClientsManifest valid

★ 4.8k

↓ 7.2k/wk

MIT

TypeScript

Oct 19, 2026

dsh plugin --profile terminal add @deepseek-harness-tui/dsh-tui

by strukto-ai

The World's First Virtual Terminal for AI Agents

Terminal & ClientsManifest valid

★ 3.7k

↓ 227/wk

Apache-2.0

TypeScript

Oct 19, 2026

dsh plugin --profile agent add @struktoai/mirage-dsh

by bowenliang123

The best DeepSeek Harness plugin for context insight and management, with context dashboard / browser / sidebar and context command, for context statistics, composition, breakdown, evolution details,

Tools & CapabilitiesUI & ExperienceTerminal & ClientsManifest valid

★ 2.3k

↓ 39.4k/wk

Apache-2.0

TypeScript

Oct 19, 2026

dsh plugin --profile web add dsh-context

by huiliyi37

官方 DeepSeek Harness 的交互式终端 UI 插件:自研 ANSI 极简交互渲染、流式 Markdown/工具卡、16+ 主题、slash 命令与选择器、输入历史与本地偏好持久化、LSP 诊断、memory记忆,很丝滑的开发体验。

Terminal & ClientsManifest valid

★ 287

↓ 319/wk

Apache-2.0

TypeScript

Sep 30, 2026

dsh plugin --profile terminal add @huiliyi37/dsh-tianshu-tui

by Hilbert-beinghappy

面向 DeepSeek Harness 的 Claude Code 风格终端界面,支持 Windows、macOS 与 Linux,兼容透明终端、VS Code 主题和自定义配色。

Terminal & ClientsManifest valid

★ 196

↓ 108/wk

MIT

TypeScript

Oct 13, 2026

dsh plugin --profile web add seektty

by c3cvld7aag

富江DSH完美破甲|富江破甲 DSH 本地插件,内置提示词规则,支持 Windows 一键安装、环境自动准备与一键卸载,全中文使用说明。

Terminal & ClientsManifest valid

★ 145

JavaScript

Oct 3, 2026

dsh plugin --profile web add fujiang-armor