dsh-folder-path 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +160 -2
- package/README_EN.md +179 -0
- package/client/globals.d.ts +30 -0
- package/client/index.ts +323 -0
- package/cordis.patch.yml +21 -0
- package/docs/DETAIL.md +10 -0
- package/docs/DETAIL.zh.md +10 -0
- package/icon.svg +1 -0
- package/lib/client.js +318 -0
- package/lib/index.js +433 -0
- package/lib/types/index.d.ts +110 -0
- package/locale/en.json +7 -0
- package/locale/zh.json +7 -0
- package/package.json +80 -4
- package/scripts/build-client.mts +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cooper Zhuang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,161 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-folder-path
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
DSH(DeepSeek Harness)Web 插件:在输入框里**粘贴一个刚从资源管理器 / Finder 复制来的文件夹**时,
|
|
4
|
+
不再尝试把它当成附件上传(原版会显示「上传失败,点击重试」),而是把它的**完整路径**作为文本插进输入框。
|
|
5
|
+
|
|
6
|
+
- 复制一个文件夹 → `Ctrl+V`(macOS `Cmd+V`)→ 输入框出现 `D:\项目\数据\2026Q1` / `/Users/me/项目/数据`
|
|
7
|
+
- 复制多个文件夹 → 每行一条
|
|
8
|
+
- 普通文本、图片、真实文件的粘贴**完全不受影响**(插件只在粘贴载荷里出现目录时才接管)
|
|
9
|
+
|
|
10
|
+
## 它解决的是什么问题
|
|
11
|
+
|
|
12
|
+
浏览器是沙盒:网页 JavaScript 拿不到本地文件的磁盘路径。把一个文件夹粘进网页时,
|
|
13
|
+
浏览器交出的是一个 `size: 0`、`type: ''` 的 `File`——字节不可读,因此 DSH 原版的上传链路必然失败;
|
|
14
|
+
而路径也根本不在粘贴载荷里(`File.name` 只有文件夹名)。
|
|
15
|
+
|
|
16
|
+
但剪贴板是**进程级**的:运行宿主的那台机器上,剪贴板里此刻装的正是你刚复制的那份文件列表。
|
|
17
|
+
所以本插件由**宿主半边**去读系统剪贴板的文件列表——Windows 走 CF_HDROP,macOS 走 NSPasteboard 的文件 URL——
|
|
18
|
+
再把真实全路径交回浏览器半边。
|
|
19
|
+
|
|
20
|
+
## 平台要求
|
|
21
|
+
|
|
22
|
+
Windows(剪贴板经 PowerShell 5.1 的 `System.Windows.Forms` 读取)或 macOS(系统自带 `osascript`)。
|
|
23
|
+
其他平台宿主返回 `unsupported-platform`,粘贴行为退回原版。
|
|
24
|
+
|
|
25
|
+
## 安装
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
dsh plugin --profile <profile> add dsh-folder-path
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
把 `<profile>` 换成目标 profile(例如桌面版 `desktop`、`dsh web` 用 `web`)。
|
|
32
|
+
|
|
33
|
+
也可以从源码目录安装:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
git clone https://github.com/CooperZhuang/dsh-plugins-cooper-custom.git
|
|
37
|
+
dsh plugin --profile <profile> add ./dsh-plugins-cooper-custom/packages/dsh-folder-path
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
安装后刷新页面即可。Desktop Beta 2.0.18-beta.1(内核 0.2.1-rc.2)起新增的 bundle 行通常能热挂载,
|
|
41
|
+
宿主不必重启;**更早的版本必须重启 `dsh web`**,不确定时重启一次总是安全的。
|
|
42
|
+
|
|
43
|
+
验证是否挂上:`dsh --profile <profile> --dump-config` 里应出现 `# == dsh-folder-path` 层。
|
|
44
|
+
|
|
45
|
+
### 卸载
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
dsh plugin --profile <profile> remove dsh-folder-path
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
删除后重启宿主一次。
|
|
52
|
+
|
|
53
|
+
## 兼容性
|
|
54
|
+
|
|
55
|
+
本包声明的兼容区间是 `engines.dsh: ^0.2.1-rc.2 || ^0.2.2-alpha.2`(Node.js `>=24`)。
|
|
56
|
+
已在 **DSH 0.2.1-rc.2**(npm `@deepseek-ai/dsh` 0.2.1-rc.2)上逐项核对过安装、组合、宿主路由与浏览器半边;
|
|
57
|
+
内核接口一旦变化,最坏结果是插件不再接管粘贴、行为退回原版,而不会破坏原有的粘贴/上传链路。
|
|
58
|
+
本插件只用到内核的这几处:
|
|
59
|
+
|
|
60
|
+
| 内核接口 | 用途 |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `webServer` 服务:`ctx.inject(['webServer'])` + `ctx.webServer.register({ kind: 'exact', … })` | 宿主半注册那条精确路由 |
|
|
63
|
+
| `ctx.effect` / `ctx.logger` | 路由的挂载与清理、剪贴板探测失败时的告警 |
|
|
64
|
+
| 插件入口的 `name` / `apply(ctx, config)` 形状 | 宿主半的加载方式 |
|
|
65
|
+
| `package.json` 的 `dsh.bundle.patch` 与 `dsh.client.platform` | bundle 补丁层与浏览器半的模块登记 |
|
|
66
|
+
| `exports['./client']` + `window.__ModuleLoader__.load({ id, factory })` | 浏览器半的加载方式 |
|
|
67
|
+
|
|
68
|
+
插件**不涉及子代理**,所以内核 0.2.1-rc.2 的子代理约定(`startActivation({provider, request})`、Agent Preset)
|
|
69
|
+
不改变它的任何行为。`test/manifest.test.js` 与 `test/client.test.js` 把上面这份清单钉在测试里。
|
|
70
|
+
|
|
71
|
+
## 使用
|
|
72
|
+
|
|
73
|
+
1. 在资源管理器(Windows)或 Finder(macOS)里选中一个或多个文件夹,`Ctrl+C`(macOS `Cmd+C`)
|
|
74
|
+
2. 回到 DSH Web 输入框,`Ctrl+V`(macOS `Cmd+V`)
|
|
75
|
+
3. 路径作为文本出现在草稿里;解析失败时右下角出现一条提示,草稿不动
|
|
76
|
+
|
|
77
|
+
## 工作原理
|
|
78
|
+
|
|
79
|
+
| 半边 | 位置 | 职责 |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| Host 半 `lib/index.js` | 宿主进程(`webServer` 服务) | 精确路由 `POST /dsh-folder-path/api/v1/clipboard-paths`:读系统剪贴板的文件列表(Windows CF_HDROP / macOS NSPasteboard)、按浏览器报来的文件夹名匹配、返回真实全路径 |
|
|
82
|
+
| Client 半 `lib/client.js` | 浏览器(`__ModuleLoader__` bundle) | document 捕获阶段拦截 `paste`,只有载荷里含**目录**时才接管;向宿主取路径,再把文本按 DSH 自己的粘贴通路插进 composer |
|
|
83
|
+
|
|
84
|
+
插入不是直接改 DOM:客户端补发一个只带 `text/plain` 的合成 `paste` 事件,
|
|
85
|
+
走的是 DSH 编辑器**原生的文本粘贴命令**(草稿状态、命令菜单、撤销历史、已有 chip 都不受影响);
|
|
86
|
+
引擎不允许构造带数据的 `ClipboardEvent` 时,退化为 `document.execCommand('insertText')`。
|
|
87
|
+
|
|
88
|
+
宿主按平台读剪贴板:Windows 用 PowerShell(`System.Windows.Forms.Clipboard`,输出 **base64(UTF-8 JSON)**,
|
|
89
|
+
中文/空格路径不会被重定向进程的代码页破坏);macOS 用 `osascript -l JavaScript` 读 NSPasteboard 的
|
|
90
|
+
`public.file-url` 条目,并合并 Finder 的旧格式 `NSFilenamesPboardType` 列表,避免多选文件夹被折叠成一条。
|
|
91
|
+
目录还是文件由宿主进程自己 `stat` 判断。同一时刻的多次粘贴会合并成一次探测。
|
|
92
|
+
|
|
93
|
+
## 配置
|
|
94
|
+
|
|
95
|
+
插件的配置写在 profile 的补丁层里(`~/.dsh/profiles/web/cordis.patch.yml` 或本包自带的
|
|
96
|
+
`cordis.patch.yml` 会被 `dsh plugin add` 接入),全部可选:
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
- insert:
|
|
100
|
+
- id: folder-path
|
|
101
|
+
name: dsh-folder-path
|
|
102
|
+
config:
|
|
103
|
+
enabled: true # false = 宿主半边空转(客户端因此不再接管,行为退回原版)
|
|
104
|
+
timeoutMs: 5000 # 单次剪贴板探测预算
|
|
105
|
+
powershell: powershell.exe # 也可用环境变量 DSH_FOLDER_PATH_POWERSHELL 覆盖
|
|
106
|
+
osascript: /usr/bin/osascript # 也可用环境变量 DSH_FOLDER_PATH_OSASCRIPT 覆盖
|
|
107
|
+
allowNonLoopback: false # true = 允许远程浏览器读取宿主机剪贴板(见下)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 安全边界
|
|
111
|
+
|
|
112
|
+
- **只读**:路由只读剪贴板,从不打开、复制或上传文件
|
|
113
|
+
- **只接受环回**:默认拒绝非环回对端(`remote-not-supported`)——否则远程浏览器会被喂**宿主机**的路径
|
|
114
|
+
- **同源**:带 `Origin` 的请求必须与 `Host` 一致,否则 403
|
|
115
|
+
- **不猜路径**:宿主报回的文件夹名必须与剪贴板条目对上;对不上就明确失败,绝不返回一个"看起来像"的路径
|
|
116
|
+
- **不缓存**:响应 `cache-control: no-store`
|
|
117
|
+
|
|
118
|
+
## 已知限制
|
|
119
|
+
|
|
120
|
+
- **只支持 Windows 与 macOS**。其它平台宿主返回 `unsupported-platform`,客户端给出提示,行为退回原版。
|
|
121
|
+
- **只处理粘贴,不处理拖放**。浏览器拖放载荷里同样没有磁盘路径,本插件不猜;把文件夹拖进窗口仍是原版行为。
|
|
122
|
+
- **远程访问时读不到你的剪贴板**。通过局域网 IP、端口转发或跳板机访问 `dsh web` 时,剪贴板属于**运行宿主的那台机器**。
|
|
123
|
+
默认直接拒绝;`allowNonLoopback: true` 会读宿主机的剪贴板(只有"浏览器和宿主在同一台机器、但用局域网 IP 打开"时才该打开它)。
|
|
124
|
+
- Windows 依赖 PowerShell 5.1 的 `System.Windows.Forms`;被安全策略挡住时返回 `clipboard-error` 并提示。
|
|
125
|
+
- macOS 依赖系统自带的 `osascript`(读粘贴板不需要 TCC 授权,也不会弹「允许访问剪贴板」);
|
|
126
|
+
只认标准文件复制格式(`public.file-url` / `NSFilenamesPboardType`),自造私有剪贴板格式的 App 读不到。
|
|
127
|
+
|
|
128
|
+
## 开发
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
npm run build # tsc: src/index.ts -> lib/index.js,client/index.ts -> .client-build/index.js;再包成 lib/client.js
|
|
132
|
+
npm test # 先 build,再跑宿主路由 / 浏览器半契约 / 清单契约测试(node --test,无需 DSH 服务器)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
宿主半边源码在 `src/index.ts`(TypeScript,`lib/index.js` 与 `lib/types/` 是 tsc 产物,随仓库提交);
|
|
136
|
+
浏览器半边是 `client/index.ts`(`.client-build/index.js` 是 tsc 产物),由构建脚本包成 `lib/client.js`。
|
|
137
|
+
|
|
138
|
+
浏览器侧调试钩子(devtools 控制台):
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
await __dshFolderPath.probe(['文件夹名']) // 直接问宿主要路径
|
|
142
|
+
__dshFolderPath.insert(document.querySelector('[contenteditable]'), '文本') // 直接试插入
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
改**浏览器半边**后:`npm run build`,然后刷新页面即可,**不必重启宿主**——
|
|
146
|
+
`dsh-client-hmr` 每 500 ms 轮询各客户端产物,重建后重算 revision 并推给页面。
|
|
147
|
+
只有**新增**客户端模块才需要重启宿主(启动时的扫描决定「有哪些客户端模块」)。
|
|
148
|
+
|
|
149
|
+
改**宿主半边**后:必须**重启宿主进程**(宿主走 Node 的 ESM 缓存,运行中的那份代码不会跟着变)。
|
|
150
|
+
Desktop 版要把应用**完全退出**再打开——关窗口不算退出。
|
|
151
|
+
|
|
152
|
+
## 致谢
|
|
153
|
+
|
|
154
|
+
架构参考了同类的 macOS 插件 [jhuanxx44/dsh-paste-path](https://github.com/jhuanxx44/dsh-paste-path)
|
|
155
|
+
(Finder 复制 → 宿主读粘贴板 → Ctrl+V 插路径):环回/同源校验、宿主侧读取系统剪贴板、
|
|
156
|
+
"宿主属于运行 dsh 的那台机器"这条边界都沿用它的判断;本插件把同一思路落到 Windows 剪贴板,
|
|
157
|
+
并把触发方式改为**粘贴事件驱动的目录识别**(Windows 上无粘贴板变更计数可供廉价轮询)。
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT
|
package/README_EN.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# dsh-folder-path
|
|
2
|
+
|
|
3
|
+
A DeepSeek Harness (DSH) Web plugin: paste a **folder** you just copied in Windows Explorer or macOS
|
|
4
|
+
Finder into the composer (`Ctrl+V` / `Cmd+V`) and get its **absolute path as text**, instead of the
|
|
5
|
+
stock "upload failed" dead end.
|
|
6
|
+
|
|
7
|
+
- Copy one folder → `Ctrl+V` (`Cmd+V` on macOS) → the draft gets `D:\work\data\2026Q1` or `/Users/me/work/data`
|
|
8
|
+
- Copy several folders → one path per line
|
|
9
|
+
- Ordinary text, images and real files paste exactly as before (the plugin only takes over when the
|
|
10
|
+
paste payload actually contains a directory)
|
|
11
|
+
|
|
12
|
+
## The problem it solves
|
|
13
|
+
|
|
14
|
+
The browser is a sandbox: page JavaScript never sees a local file's disk path. Pasting a folder hands
|
|
15
|
+
the page a `File` with `size: 0` and `type: ''` — its bytes cannot be read, so DSH's upload path must
|
|
16
|
+
fail — and the path itself is not in the payload at all (`File.name` is just the basename).
|
|
17
|
+
|
|
18
|
+
The clipboard, however, is process-wide: on the machine running the host, it holds exactly the file
|
|
19
|
+
list you just copied. So the **host half** reads the platform clipboard's file list — CF_HDROP on
|
|
20
|
+
Windows, NSPasteboard file URLs on macOS — and hands the real absolute paths back to the browser half.
|
|
21
|
+
|
|
22
|
+
## Requirements
|
|
23
|
+
|
|
24
|
+
Windows (the clipboard is read through PowerShell 5.1 `System.Windows.Forms`) or macOS (system
|
|
25
|
+
`osascript`). Other platforms answer `unsupported-platform` and stock paste behaviour applies.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
dsh plugin --profile <profile> add dsh-folder-path
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Replace `<profile>` with the target profile name (for example `desktop` for the desktop app, `web` for `dsh web`).
|
|
34
|
+
|
|
35
|
+
From a source checkout:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
git clone https://github.com/CooperZhuang/dsh-plugins-cooper-custom.git
|
|
39
|
+
dsh plugin --profile <profile> add ./dsh-plugins-cooper-custom/packages/dsh-folder-path
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then refresh the page. Since Desktop Beta 2.0.18-beta.1 (core 0.2.1-rc.2) a newly added bundle row
|
|
43
|
+
is usually hot-mounted, so the host needs no restart; **earlier versions require restarting
|
|
44
|
+
`dsh web`**. Restarting once is always safe when in doubt.
|
|
45
|
+
|
|
46
|
+
Check it mounted: `dsh --profile <profile> --dump-config` should show a `# == dsh-folder-path` layer.
|
|
47
|
+
|
|
48
|
+
### Uninstall
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
dsh plugin --profile <profile> remove dsh-folder-path
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Restart the host once afterwards.
|
|
55
|
+
|
|
56
|
+
## Compatibility
|
|
57
|
+
|
|
58
|
+
This package declares `engines.dsh: ^0.2.1-rc.2 || ^0.2.2-alpha.2` and Node.js `>=24`. It was checked
|
|
59
|
+
item by item against **DSH 0.2.1-rc.2** (npm `@deepseek-ai/dsh` 0.2.1-rc.2): installing, composing,
|
|
60
|
+
the host route and the browser half all work as before. If a kernel interface changes, the worst case is
|
|
61
|
+
that the plugin stops taking over folder pastes and behaviour falls back to stock — the original
|
|
62
|
+
paste/upload path is never broken. The plugin touches exactly this much of the kernel:
|
|
63
|
+
|
|
64
|
+
| Kernel interface | Used for |
|
|
65
|
+
|---|---|
|
|
66
|
+
| the `webServer` service: `ctx.inject(['webServer'])` + `ctx.webServer.register({ kind: 'exact', … })` | the host half registers its exact route |
|
|
67
|
+
| `ctx.effect` / `ctx.logger` | mounting and cleaning up the route, warning on a failed clipboard probe |
|
|
68
|
+
| the plugin entry's `name` / `apply(ctx, config)` shape | how the host half is loaded |
|
|
69
|
+
| `package.json`'s `dsh.bundle.patch` and `dsh.client.platform` | the bundle patch layer and the browser half's module registration |
|
|
70
|
+
| `exports['./client']` + `window.__ModuleLoader__.load({ id, factory })` | how the browser half is loaded |
|
|
71
|
+
|
|
72
|
+
The plugin involves **no subagents**, so the kernel 0.2.1-rc.2 subagent convention
|
|
73
|
+
(`startActivation({provider, request})`, Agent Presets) changes nothing for it. `test/manifest.test.js`
|
|
74
|
+
and `test/client.test.js` pin the list above in the test suite.
|
|
75
|
+
|
|
76
|
+
## Use
|
|
77
|
+
|
|
78
|
+
1. Select one or more folders in Explorer / Finder and press `Ctrl+C` (`Cmd+C` on macOS)
|
|
79
|
+
2. Return to the DSH Web composer and press `Ctrl+V` (`Cmd+V` on macOS)
|
|
80
|
+
3. The paths appear in the draft; when the host cannot resolve them, a notice appears and the draft is
|
|
81
|
+
left untouched
|
|
82
|
+
|
|
83
|
+
## How it works
|
|
84
|
+
|
|
85
|
+
| Half | Where | Job |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| Host `lib/index.js` | host process (`webServer`) | exact route `POST /dsh-folder-path/api/v1/clipboard-paths`: read the platform clipboard's file list (CF_HDROP on Windows, NSPasteboard on macOS), match the folder names the browser reported, answer with the real absolute paths |
|
|
88
|
+
| Client `lib/client.js` | browser (`__ModuleLoader__` bundle) | capture-phase `paste` interception, only when the payload contains a directory; asks the host, then inserts the text through DSH's own paste path |
|
|
89
|
+
|
|
90
|
+
Insertion does not poke the DOM: the client re-dispatches a synthetic `paste` carrying only
|
|
91
|
+
`text/plain`, which reaches the editor's **native text-paste command** (draft state, command menu,
|
|
92
|
+
undo history and existing chips all stay intact). Engines that refuse to construct a `ClipboardEvent`
|
|
93
|
+
with clipboard data fall back to `document.execCommand('insertText')`.
|
|
94
|
+
|
|
95
|
+
The host reads the clipboard per platform. Windows uses PowerShell
|
|
96
|
+
(`System.Windows.Forms.Clipboard`) and emits **base64(UTF-8 JSON)**, so paths with Chinese characters
|
|
97
|
+
or spaces survive the redirected process's code page. macOS uses `osascript -l JavaScript` to read the
|
|
98
|
+
NSPasteboard's `public.file-url` entries and merges Finder's legacy `NSFilenamesPboardType` list, so a
|
|
99
|
+
multi-selection cannot collapse into one item; directory versus file is decided by the host with
|
|
100
|
+
`stat`. Concurrent pastes share one probe.
|
|
101
|
+
|
|
102
|
+
## Configuration
|
|
103
|
+
|
|
104
|
+
Every knob is optional and lives in the profile patch layer (`~/.dsh/profiles/web/cordis.patch.yml`,
|
|
105
|
+
or this package's own `cordis.patch.yml`, which `dsh plugin add` wires in):
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
- insert:
|
|
109
|
+
- id: folder-path
|
|
110
|
+
name: dsh-folder-path
|
|
111
|
+
config:
|
|
112
|
+
enabled: true # false mounts the host half inert (the client then stops taking over)
|
|
113
|
+
timeoutMs: 5000 # per-probe budget
|
|
114
|
+
powershell: powershell.exe # also settable via DSH_FOLDER_PATH_POWERSHELL
|
|
115
|
+
osascript: /usr/bin/osascript # also settable via DSH_FOLDER_PATH_OSASCRIPT
|
|
116
|
+
allowNonLoopback: false # true = let a remote browser read this host's clipboard
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Security boundary
|
|
120
|
+
|
|
121
|
+
- **Read-only**: the route only reads the clipboard; files are never opened, copied or uploaded
|
|
122
|
+
- **Loopback only** by default (`remote-not-supported`), so a remote browser is never handed the
|
|
123
|
+
*host machine's* paths
|
|
124
|
+
- **Same-origin**: a request carrying `Origin` must match `Host`, else 403
|
|
125
|
+
- **No guessing**: the reported folder name must match a clipboard entry; otherwise the request fails
|
|
126
|
+
loudly instead of returning a plausible-looking wrong path
|
|
127
|
+
- **No caching**: `cache-control: no-store`
|
|
128
|
+
|
|
129
|
+
## Known limitations
|
|
130
|
+
|
|
131
|
+
- **Windows and macOS only.** Other platforms answer `unsupported-platform` and the client shows a notice.
|
|
132
|
+
- **Paste only, not drag-and-drop.** A drop payload carries no disk path either; this plugin does not
|
|
133
|
+
guess, so dropping a folder keeps the stock behaviour.
|
|
134
|
+
- **Remote access cannot reach your clipboard.** Over a LAN address, port forward or jump host the
|
|
135
|
+
clipboard belongs to the machine running the host. That is rejected by default;
|
|
136
|
+
`allowNonLoopback: true` reads the host's clipboard instead (only sensible when the browser and the
|
|
137
|
+
host are the same machine reached through a LAN IP).
|
|
138
|
+
- Windows requires PowerShell 5.1 with `System.Windows.Forms`; if policy blocks it the route answers
|
|
139
|
+
`clipboard-error` and the client explains.
|
|
140
|
+
- macOS uses the system `osascript`; reading the pasteboard needs no TCC grant and raises no
|
|
141
|
+
"allow clipboard access" prompt. Only the standard copy flavours (`public.file-url`,
|
|
142
|
+
`NSFilenamesPboardType`) are recognised — an app that puts files on the pasteboard in a private
|
|
143
|
+
format will not be read.
|
|
144
|
+
|
|
145
|
+
## Development
|
|
146
|
+
|
|
147
|
+
```sh
|
|
148
|
+
npm run build # client/index.ts -> .client-build/index.js -> lib/client.js (wraps the browser half as a __ModuleLoader__ bundle)
|
|
149
|
+
npm test # builds both halves, then host route / browser-half contract / manifest contract tests
|
|
150
|
+
# (node --test, no DSH server needed)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Browser-side debugging hooks (devtools console):
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
await __dshFolderPath.probe(['folder-name']) // ask the host directly
|
|
157
|
+
__dshFolderPath.insert(document.querySelector('[contenteditable]'), 'text')
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
After changing the **browser half**: `npm run build`, then refresh the page — **no host restart
|
|
161
|
+
needed**. `dsh-client-hmr` polls every client bundle every 500 ms, recomputes the revision after a
|
|
162
|
+
rebuild and pushes it to the page. Only **adding** a client module needs a restart (the startup scan
|
|
163
|
+
decides which client modules exist).
|
|
164
|
+
|
|
165
|
+
After changing the **host half**: you must **restart the host process** (the host goes through Node's
|
|
166
|
+
ESM cache, so the running copy of the code does not change with it). In the Desktop app, quit the app
|
|
167
|
+
completely and reopen it — closing the window is not quitting.
|
|
168
|
+
|
|
169
|
+
## Credits
|
|
170
|
+
|
|
171
|
+
The architecture follows the macOS plugin [jhuanxx44/dsh-paste-path](https://github.com/jhuanxx44/dsh-paste-path)
|
|
172
|
+
(copy in Finder → host reads the pasteboard → Ctrl+V inserts the path): its loopback/same-origin
|
|
173
|
+
checks, host-side system-clipboard reads and the "the clipboard belongs to the machine running dsh"
|
|
174
|
+
boundary all carry over. This plugin brings the same idea to the Windows clipboard and triggers on
|
|
175
|
+
the **paste event's directory payload** (Windows offers no cheap pasteboard change counter to poll).
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ambient declarations for the browser half.
|
|
3
|
+
*
|
|
4
|
+
* `client/index.ts` is a classic script (it has no imports or exports), so the
|
|
5
|
+
* bundle wrapper in `scripts/build-client.mts` can embed it inside a
|
|
6
|
+
* `window.__ModuleLoader__.load({ id, factory })` factory unchanged. The DOM
|
|
7
|
+
* types come from the `DOM` lib in `tsconfig.client.json`; only the two
|
|
8
|
+
* page globals this plugin defines or consumes are declared here.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** The troubleshooting hook the browser half installs on `window`. */
|
|
12
|
+
interface FolderPathDebugHook {
|
|
13
|
+
/** The host route the plugin calls. */
|
|
14
|
+
route: string
|
|
15
|
+
/** Ask the host for the full paths behind these folder names. */
|
|
16
|
+
probe(names: string[]): Promise<{ paths: string[]; reason: string | null }>
|
|
17
|
+
/** Insert text through DSH's own paste path. */
|
|
18
|
+
insert(root: HTMLElement, text: string): void
|
|
19
|
+
/** Split a paste payload into directories and files. */
|
|
20
|
+
classify(dataTransfer: DataTransfer): { directories: string[]; files: File[] }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Window {
|
|
24
|
+
/** The DSH client module system; the bundle wrapper registers through it. */
|
|
25
|
+
__ModuleLoader__: {
|
|
26
|
+
load(registration: { id: string; factory: (require: (id: string) => unknown) => unknown }): void
|
|
27
|
+
}
|
|
28
|
+
/** Installed by `apply` so the devtools console can drive the plugin. */
|
|
29
|
+
__dshFolderPath?: FolderPathDebugHook
|
|
30
|
+
}
|