Перейти к основному содержимому
C

dsh-cross-device-upload

cmoyuer/dsh-cross-device-upload

dsh 插件:把手机 / 平板 / 第二台电脑上的文件一键投递到会话——插件自带独立端口接收上传(不走 dsh 登录鉴权,只认上传令牌),文件进入输入框正上方的待添加浮窗,点一下即加入输入栏。

Установка

dsh plugin --profile web add github:cmoyuer/dsh-cross-device-upload

README

dsh-cross-device-upload · 跨设备上传

手机、平板、第二台电脑上的文件,一键投递到正在对话的 dsh 会话里。

npm npm downloads License Platform Dependencies AI-generated

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 层应用,不需要手工编辑组合树。

快速开始

  1. dsh 里打开 设置 → 跨设备上传,点「新建令牌」,备注写「我的手机」;

  2. 页面下方会按本机每个地址各给一条命令,复制其中适合的那条,到设备 B 执行:

    curl -X POST -F "file=@photo.jpg" "http://10.126.126.2:3088/api/session/upload?token=xxxx"
    
  3. 回到 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 脚本。

一次性准备
  1. 安装 Termux 与配套的 Termux:API 应用(两个都要装,建议用 F-Droid 版;Google Play 上的 Termux 已停止更新)。

  2. 在 Termux 里装依赖:

    pkg update && pkg install termux-api curl
    

    termux-api 提供 termux-toast,上传成功/失败时会弹一条提示;不装也能用,只是没有任何提示。

  3. 给 Termux:API 授权:系统设置 → 应用 → Termux:API → 权限,或首次调用时按弹窗允许。

  4. 手机与 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_PORTexit 1
非图片(默认设置下)打印「已跳过」→ exit 0(跳过不算失败)

失败时下载目录里的临时副本不会被删除,避免丢文件。

可调参数

脚本顶部都能改,也都可以用环境变量临时覆盖:

变量默认说明
DSH_HOST10.126.126.2dsh 主机的局域网地址(不是 127.0.0.1,那在 Termux 里指手机自己)
DSH_PORT3088与设置页里的端口保持一致
UPLOAD_TOKEN需填写上传令牌
ONLY_IMAGES1设为 0 则任意文件都传(PDF、文档、压缩包…)
SUCCESS_WAIT3成功后停留秒数;设 0 立即退出
FAIL_WAIT6失败后停留秒数
MAX_TIME300单次上传最长秒数(大文件或慢网请调大)
DELETE_AFTER_UPLOAD1成功后是否清理下载目录里的临时副本
排查
  • 网络或传输失败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/uploadmultipart/form-data(字段 file)或裸字节;返回 {ok:true,id,name,size,mediaType,receivedAt,pending}
GET/api/session/upload返回用法说明与当前待添加数量,用于连通性自检

令牌依次从 X-Upload-TokenAuthorization: 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
  • 浏览器侧接口里 tokenssettingsremove 只接受回环来源,局域网内的其他设备无法读取或修改令牌。
  • 设置页与 /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 进程树的安全重启助手,健康检查失败会自动写入 disabled overlay 回滚。
  • 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.docksettings.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)上的监听与防火墙行为、极高并发或超大文件、以及把这个端口暴露到公网的使用方式(不建议这样做)。

许可

MIT

Похожие плагины