- Accueil
- Plugins
- Intégrations et accès distant
- dsh-cross-device-upload
dsh-cross-device-upload
cmoyuer/dsh-cross-device-upload
Plugin dsh pour envoyer en un clic des fichiers d’un téléphone, d’une tablette ou d’un autre PC vers une session. Réception sur un port dédié, sans authentification de connexion dsh : seul le jeton d’envoi est vérifié. Le fichier attend dans un panneau flottant au-dessus de la saisie et s’ajoute d’un clic.
Installer
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)上的监听与防火墙行为、极高并发或超大文件、以及把这个端口暴露到公网的使用方式(不建议这样做)。
许可
Plugins associés
dsh-web (dsh-ssh)
zhu1090093659/dsh-web
dsh-web (dsh-remote-web-ui)
zhu1090093659/dsh-web
dsh-web-ui (dsh-ssh)
zhu1090093659/dsh-web-ui
dsh-web-ui (dsh-remote-web-ui)
zhu1090093659/dsh-web-ui