- Home
- Plugin
- Strumenti e capacità
- dsh-cross-device-upload
dsh-cross-device-upload
cmoyuer/dsh-cross-device-upload
dsh 插件:把手机 / 平板 / 第二台电脑上的文件一键投递到会话——插件自带独立端口接收上传(不走 dsh 登录鉴权,只认上传令牌),文件进入输入框正上方的待添加浮窗,点一下即加入输入栏。
Installazione
dsh plugin --profile web add github:cmoyuer/dsh-cross-device-uploadREADME
dsh-cross-device-upload · 跨设备上传
把手机、平板、第二台电脑上的文件,一键投递到正在对话的 dsh 会话里。
npm 包页 · 更新日志 · MIT 许可 · AI 生成声明
演示视频
▶ 观看演示(约 10 秒,1080×720) —— 一台设备把文件传过来,另一台设备的 dsh 里出现待添加浮窗,点「添加」进入输入栏。
GitHub 页面会内嵌播放;npm 包页不能嵌视频,点链接到 GitHub 观看。
截图

待添加浮窗 —— 挂在 conversation.input.dock,只有待添加池非空时才出现。每行给出缩略图、文件名、来源令牌备注、大小、时间与来源 IP;「添加」把它送进当前会话的输入栏,「全部添加」一次收下池里所有项,「×」连文件一起丢弃。

在手机上分享即上传 —— 装上 dev/termux-upload.sh 之后,从文件管理器分享到 Termux 就会走一遍上传:打印文件名、大小与待添加区数量,随后自动退出(配置见下方「Termux 分享即上传」)。

设置页 —— 改监听地址与端口(即时重绑)、管理上传令牌、复制本机各地址的上传命令。命令里 token 一律是 your-token 占位,因此设置页截图可以安全外发。
这个插件解决什么
在电脑前跟 agent 对话,要发的图片却在手机里;或者第二台机器上刚生成的文件想丢进当前会话。dsh 本身没有「从别的设备送东西进来」的通道,先传网盘再下载显然更麻烦。
这个插件做的事很小:开一个只认令牌的端口收下文件,放进输入框正上方的浮窗,你点一下「添加」,它就进了输入栏。
- 收文件的端口不经过 dsh 的登录鉴权,所以手机上的
curl、快捷指令、Termux 分享脚本都能直接发 - 待添加的东西在你点「添加」之前不会进入对话,agent 也读不到
- 图片成为图片附件(agent 能看图),其他文件走文件附件通道
- 零第三方依赖,客户端半边手写模块格式、没有构建步骤
工作方式
手机 / 平板 / 第二台电脑
│ POST /api/session/upload?token=…
▼
┌─────────────────────────────┐
│ 插件自带的 HTTP 监听 │ 默认 0.0.0.0:3088
│ 校验令牌 → 落盘 → 待添加池 │ 与 dsh 自己的 3080 互不影响
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ dsh web(浏览器) │
│ ┌───────────────────────┐ │
│ │📥 跨设备上传 · 1 个待添加│ │ ← 输入框正上方的浮窗
│ │[缩略图] photo.jpg 添加 ×│ │
│ └───────────────────────┘ │
│ ┌───────────────────────┐ │
│ │ 输入栏(附件已就位) │ │
│ └───────────────────────┘ │
└─────────────────────────────┘
浮窗挂在 dsh 的 conversation.input.dock 上,只有待添加池非空时才出现,因此平时不占任何版面。
特性
| 独立监听 | 自带 HTTP 服务,默认 0.0.0.0:3088;端口与监听地址可在设置页修改,改完即时重绑,不需要重启 dsh |
| 令牌门禁 | 支持多个令牌,每个带备注(即来源名);令牌 32 位随机,SHA-256 后定长比较;可重命名、复制、删除 |
| 图片优先 | png / jpeg / webp / gif 走图片附件,其余类型走文件回执通道,两者都能进输入栏 |
| 待添加池 | 全局池,任意会话可见;TTL 默认 24 小时,过期自动清理;「×」即时删除文件 |
| 零依赖 | Host 只用 node: 内置模块,Client 只用宿主提供的 React;pnpm add 不会多装任何东西 |
安装
装完需要重启一次 dsh web(服务端插件在启动时装配)。之后的端口改动都在设置页即时生效。
从 npm(推荐)
dsh plugin --profile web add dsh-cross-device-upload
从 GitHub 或本地目录
dsh plugin --profile web add github:CMoyuer/dsh-cross-device-upload
# 或者
git clone https://github.com/CMoyuer/dsh-cross-device-upload.git
dsh plugin --profile web add ./dsh-cross-device-upload
dsh plugin add 会在 profile 目录里执行 pnpm,并按实际安装结果校正 dsh.profile.bundles;包内自带 cordis.patch.yml,会被 loader 作为 patch 层应用,不需要手工编辑组合树。
快速开始
-
dsh 里打开 设置 → 跨设备上传,点「新建令牌」,备注写「我的手机」;
-
页面下方会按本机每个地址各给一条命令,复制其中适合的那条,到设备 B 执行:
curl -X POST -F "file=@photo.jpg" "http://10.126.126.2:3088/api/session/upload?token=xxxx" -
回到 dsh 页面,输入框上方出现浮窗(3 秒内),点「添加」进输入栏,或点「×」丢弃。
用法
curl
# 令牌放 query
curl -X POST -F "file=@photo.jpg" "http://<主机IP>:3088/api/session/upload?token=xxxx"
# 令牌放请求头(推荐:不会留在 shell 历史与进程列表里)
curl -X POST -H "X-Upload-Token: xxxx" -F "file=@photo.jpg" \
"http://<主机IP>:3088/api/session/upload"
# 也接受 Authorization: Bearer
curl -X POST -H "Authorization: Bearer xxxx" --data-binary @report.pdf \
"http://<主机IP>:3088/api/session/upload?name=report.pdf"
不带令牌或令牌错误 → 401;文件超过上限 → 413。用浏览器直接 GET 该地址(带令牌)会返回一段用法 JSON,可用来确认端口是否通。
手机 / 平板:Termux 分享即上传
Android 上可以把系统的「分享」直接接到这个上传接口:在文件管理器或相册里选中文件 → 分享 → Termux,脚本自动上传并给出结果。仓库里的 dev/termux-upload.sh 就是这样一个 Termux:API file-editor 脚本。
一次性准备
-
安装 Termux 与配套的 Termux:API 应用(两个都要装,建议用 F-Droid 版;Google Play 上的 Termux 已停止更新)。
-
在 Termux 里装依赖:
pkg update && pkg install termux-api curltermux-api提供termux-toast,上传成功/失败时会弹一条提示;不装也能用,只是没有任何提示。 -
给 Termux:API 授权:系统设置 → 应用 → Termux:API → 权限,或首次调用时按弹窗允许。
-
手机与 dsh 主机在同一局域网;跨网段或走隧道时,把脚本里的
DSH_HOST指向实际可达的地址。
部署脚本
mkdir -p ~/bin
nano ~/bin/termux-file-editor # 粘贴 dev/termux-upload.sh 的内容
chmod +x ~/bin/termux-file-editor
~/bin/termux-file-editor 是 Termux 处理「分享单个文件」的固定入口(见官方文档 Intents and Hooks);处理 URL 分享的是同目录下的 termux-url-opener,两者互不影响。
把脚本顶部的 UPLOAD_TOKEN 换成你的令牌(dsh → 设置 → 跨设备上传 → 该行「复制」)。不想让令牌明文留在脚本里,也可以改成从环境变量读:
echo 'export UPLOAD_TOKEN=你的令牌' >> ~/.bashrc
怎么用
文件管理器 / 相册里选中文件 → 分享 → Termux。脚本自己跑完、弹提示、然后退出,不需要再按回车。
脚本行为
| 情况 | 结果 |
|---|---|
| 上传成功 | 打印文件名、大小与待添加区数量 → 弹提示 → 停留 3 秒自动退出(exit 0) |
| 令牌无效 / 超限被拒 | 打印服务端返回的 error 与中文 hint → 弹提示 → 停留 6 秒 → exit 1 |
| 连不上主机 | 提示网络失败并回显当前 DSH_HOST:DSH_PORT → exit 1 |
| 非图片(默认设置下) | 打印「已跳过」→ exit 0(跳过不算失败) |
失败时下载目录里的临时副本不会被删除,避免丢文件。
可调参数
脚本顶部都能改,也都可以用环境变量临时覆盖:
| 变量 | 默认 | 说明 |
|---|---|---|
DSH_HOST | 10.126.126.2 | dsh 主机的局域网地址(不是 127.0.0.1,那在 Termux 里指手机自己) |
DSH_PORT | 3088 | 与设置页里的端口保持一致 |
UPLOAD_TOKEN | 需填写 | 上传令牌 |
ONLY_IMAGES | 1 | 设为 0 则任意文件都传(PDF、文档、压缩包…) |
SUCCESS_WAIT | 3 | 成功后停留秒数;设 0 立即退出 |
FAIL_WAIT | 6 | 失败后停留秒数 |
MAX_TIME | 300 | 单次上传最长秒数(大文件或慢网请调大) |
DELETE_AFTER_UPLOAD | 1 | 成功后是否清理下载目录里的临时副本 |
排查
网络或传输失败→DSH_HOST要填主机的局域网地址(Termux 里的127.0.0.1是手机自己);确认主机防火墙放行该端口、手机与主机在同一网段。服务端拒绝(HTTP 401 · unauthorized)→ 令牌不对或已被删除,回设置页重新复制一个。服务端拒绝(HTTP 413 · too-large)→ 超过单文件上限(默认 64 MB),见设置页一节。- 上传成功但页面没有浮窗 → 浮窗是全局池,任意会话都会显示;确认页面已刷新(首次安装客户端插件后需要刷新一次)。
其他设备
任何能发 HTTP 的东西都行:iOS 快捷指令(获取 URL 内容 + 表单文件)、Android 的 Tasker/HTTP Request、Python 的 requests、NAS 上的同步脚本等。请求体支持 multipart/form-data(字段名 file)或裸字节(用 ?name= 或 X-File-Name 指定文件名)。
设置页
设置 → 跨设备上传
| 项 | 说明 |
|---|---|
| 监听地址 | 0.0.0.0(局域网可访问)/ 127.0.0.1(仅本机) |
| 端口 | 默认 3088;改完即时重绑,若新地址不可用会自动退回旧地址并报错 |
| 上传令牌 | 每个令牌一行:名称(可「重命名」)、令牌值(可「复制」)、「删除」 |
| 上传命令 | 按本机每个地址各给一条命令,token 以 your-token 占位——页面从不打印真实令牌 |
单文件上限(默认 64 MB)与待添加保留时长(默认 24 小时)不占界面,需要时走同源接口(即时生效):
curl -X PATCH http://127.0.0.1:3080/remote/cross-device-upload/settings \
-H "content-type: application/json" \
-d "{\"maxFileBytes\":134217728,\"ttlHours\":48}"
接口
外部上传(只认令牌,不走 dsh 登录)
| 方法 | 路径 | 说明 |
|---|---|---|
POST | /api/session/upload | multipart/form-data(字段 file)或裸字节;返回 {ok:true,id,name,size,mediaType,receivedAt,pending} |
GET | /api/session/upload | 返回用法说明与当前待添加数量,用于连通性自检 |
令牌依次从 X-Upload-Token、Authorization: Bearer、?token= 读取。路径、端口、监听地址都可在设置页改。
同源管理接口(浏览器侧,前缀 /remote/cross-device-upload)
| 方法 | 路径 | 允许来源 | 说明 |
|---|---|---|---|
GET | /items | 任何页面来源 | 待添加列表(新→旧) |
GET | /blob/<id> | 任何页面来源 | 文件字节,供缩略图与转 File 使用 |
POST | /remove | 仅回环 | 删除一项(含文件) |
GET/POST/PATCH/DELETE | /tokens | 仅回环 | 令牌列表 / 新建 / 改名·停用·轮换 / 删除 |
GET/PATCH | /settings | 仅回环 | 读取 / 修改监听与限额 |
GET | /status | 任何页面来源 | 运行状态、本机可用地址、最后一条错误 |
「仅回环」指本机浏览器,或本机反向代理转发的页面;因此局域网里的其他机器即使能打开 dsh,也读不到、改不了你的令牌配置。
数据与清理
~/.dsh/storages/dsh-cross-device-upload/ # Windows: %USERPROFILE%\.dsh\storages\...
├── settings.json 监听地址、端口、单文件上限、保留时长
├── tokens.json 上传令牌与备注
├── items.json 待添加项元数据
└── files/ 上传的文件字节
- 点浮窗「×」→ 记录与文件立即删除;
- 超过保留时长(默认 24 小时)→ 由内置清理器删除,进程每 10 分钟扫一次;
- 上传的字节不会被读进对话,除非你在浮窗里点了「添加」。
安全
- 外部只有一个入口:
POST/GET /api/session/upload?token=…。令牌 32 位随机、SHA-256 后定长比较,用timingSafeEqual判定;怀疑泄露就「删除」重建。 - 这个端口没有 dsh 的登录保护,安全性完全由上传令牌承担。只在
0.0.0.0上暴露你愿意暴露的东西;只想给本机脚本用,就把监听地址改成127.0.0.1。 - 浏览器侧接口里
tokens、settings、remove只接受回环来源,局域网内的其他设备无法读取或修改令牌。 - 设置页与
/status都不返回真实令牌值(命令中用your-token占位),避免截图或录屏泄露。
常见问题
传完了浮窗没出现?
先确认端口通:带令牌 GET http://<主机IP>:3088/api/session/upload,返回 JSON 即通。若不通,检查主机防火墙是否放行该端口、设备是否在同一网段、设置页里的监听地址是否为 0.0.0.0。浮窗是全局池,任意会话都会显示。
为什么设置页不显示真实令牌?
故意的:命令里一律用 your-token 占位,需要令牌时点该行的「复制」。这样设置页截图可以安全外发。
端口 3088 被占用? 设置页改端口即可,即时生效。绑定失败时插件会退回原地址并明确报错,不会静默失效。
能传多大的文件?
默认 64 MB。改 maxFileBytes 即可(见上文接口示例);手机拍照的原图通常在 3–10 MB,无需调整。
改了端口/上限要重启 dsh 吗? 不用。只有首次安装(或升级插件本身)需要重启一次。
支持哪些平台?
Host 只用 node: 内置模块,客户端半边只用宿主提供的 React,设计上与平台无关;本项目在 Windows 上完整验证,dev/termux-upload.sh 面向 Android/Termux。
开发
node dev/preflight.mjs # Host:假 ctx 挂载 + 真实 HTTP 端到端,输出 PREFLIGHT_OK
node dev/client-check.mjs # 客户端:模块格式、两个 slot 注册、inject face
node dev/settings-render-check.mjs # 设置页渲染回归(当前 UI 决策逐条断言)
node dev/verify-deployment.mjs # 部署后验证:roster、bundle 内容、监听、局域网可达
node dev/audit-dead-code.mjs # 静态死代码审查(未使用的 CSS 类与顶层绑定)
bash dev/termux-upload-test.sh # Termux 上传脚本的回归测试(mock curl,16 项)
preflight覆盖:导出结构、监听启动、令牌门禁(401)、multipart(含中文文件名)、同源接口读取/删除、字节一致性、设置持久化、端口占用时的重绑失败与自动回滚、dispose 关闭监听。dev/restart-web.ps1/dev/start-restart.ps1:脱离 dsh 进程树的安全重启助手,健康检查失败会自动写入disabledoverlay 回滚。dev/seed-pending-item.mjs:造一条真实的待添加项,用于肉眼验收浮窗。
结构
lib/index.js Host 半边:独立监听、令牌与待添加池、同源 API、数据落盘
lib/client.js Client 半边:输入框上方浮窗(conversation.input.dock)
与设置页(settings.section)——手写模块格式,无构建步骤
cordis.patch.yml 把插件挂进 web profile 组合树
dev/ 开发与验证脚本(不随 npm 包发布)
兼容性
- 面向 dsh web(
--profile web);组合方式遵循 dsh 的 plugin + bundle patch 约定。 - 浏览器侧依赖宿主提供的
conversation.input.dock与settings.section两个 slot,以及ctx.conversation.createDrafts。缺失时会优雅降级:浮窗仍显示,但「添加」会明确提示当前版本不支持直接加入输入栏。 - 数据格式带
version字段,后续版本可以就地升级。
AI 生成声明
本插件的代码与文档由 AI(DeepSeek Harness 中的编码 agent)生成,作者负责提出需求、审阅与验证。
已经做过的验证(都可以复跑,见 开发):
- Host 端到端自检(24 项):导出结构、独立监听启动、令牌门禁(401)、multipart 上传(含中文文件名)、同源接口读取与删除、字节一致性、设置持久化、端口占用时的重绑失败与自动回滚、dispose 关闭监听;
- 客户端(20 + 26 项):模块格式与两个 slot 注册、inject face 降级、设置页渲染回归;
- 部署后验证(16 项):浏览器 roster、客户端 bundle 内容、独立监听状态、局域网地址可达;
- Termux 上传脚本(16 项 + 真实服务端实测):成功与失败两条路径、临时文件保留策略。
没有覆盖、需要你自行评估的部分:Windows 之外平台(Linux/macOS)上的监听与防火墙行为、极高并发或超大文件、以及把这个端口暴露到公网的使用方式(不建议这样做)。
许可
Plugin correlati
archify (deepseek-harness)
tt-a1i/archify
WeKnora (dsh-weknora)
tencent/weknora
weknora
tencent/weknora
BrowserSkill (dsh-plugin-browserskill)
tencent/browserskill