Bundle
dsh-copy-file-path
DSH plugin: adds 复制相对路径 / 复制绝对路径 (copy relative / absolute path) to the delivered-file card menu of the dsh web UI. / dsh Web 交付文件卡片的「⋯」菜单增加复制相对路径与复制绝对路径。
- Source
- Meowrium
- License
- MIT
- Updated
- Updated 5 hours ago
Readme
# dsh-copy-file-path
[中文说明 / Chinese README](./README.zh.md)
Adds **Copy relative path** and **Copy absolute path** to the delivered-file card menu of the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`) web UI.
At the end of a turn, dsh shows the files that turn delivered as cards. Each card has an **Open** button and a **⋯** menu holding **Open in default app** and **Show in File Explorer** / **Open containing folder**. This plugin appends two more rows to that menu:
| Row | Copies |
| --- | --- |
| 复制相对路径 / Copy relative path | the path relative to the session workspace |
| 复制绝对路径 / Copy absolute path | the absolute host path |
A click copies the value and shows a short confirmation pill with what was copied.
```
⋯ ┌──────────────────────────┐
│ ↗ Open in default app │ ← shipped by dsh-client-ui-deliverables
│ ▤ Show in File Explorer │
│ ⧉ Copy relative path │ ← this plugin
│ ⧉ Copy absolute path │ ← this plugin
└──────────────────────────┘
```
## Install
```sh
dsh plugin --profile web add github:Meowrium/dsh-copy-file-path
```
Then restart `dsh web` and hard-refresh the page (`Ctrl+Shift+R`) — the host mounts the bundle at startup and the browser shell rebuilds its client modules.
## Why this needs a plugin
Those menu rows are a hard-coded `items` array inside `@deepseek-ai/dsh-client-ui-deliverables`, rendered by the `@deepseek-ai/dsh-client-ui-primitives` `Menu` (a `role="menu"` list portalled to `document.body`). There is no slot, registry or hook for extra rows, and a shadowing plugin would have to reimplement the whole card.
So this plugin post-processes the DOM instead — the same technique as `dsh-mermaid`: no core patch, no monkey-patching of React internals. It watches for that portal, and while a delivery card's chevron is expanded (`aria-expanded="true"`) it clones the last row, relabels it, gives it a copy icon and owns the click. The injected rows are plain DOM: the click handler stops propagation so the React root never sees the event, and the popup is dismissed through the same outside-`pointerdown` path a real click elsewhere would use.
## Where the two paths come from
Nothing is guessed and nothing is rewritten — every value is taken from what dsh itself already has:
| Value | Source |
| --- | --- |
| Absolute path | the card's preview-button `title`, i.e. exactly what dsh resolves and opens (`resolveWorkspacePath(cwd, file.path)`). Copied verbatim, separators untouched. |
| Relative path | the path the turn declared to the `present` tool, recovered from the card's own `aria-label`s (`{path} 的更多文件操作` / `More file actions for {path}`). If the declaration was itself absolute, it is recomputed against the workspace root. |
| Workspace root | derived from that (absolute, declared) pair — exact by construction. The client session store (`sessions.list`) is only the fallback, for cards whose labels carry no declared path. |
If a value cannot be established, that row is simply not offered — an unusable path is never copied.
## Compatibility
- `dsh` web UI with delivered-file cards. Tested against **0.1.6-alpha.1** on Windows; not verified against older lines (the `dsh.client` / bundle shape is shared with the 0.1.5 line, but that is untested here).
- No runtime dependencies. The browser half uses the DOM plus the optional `locale` and `sessions` client services: `inject` is empty, so the plugin still loads — English labels and card-derived workspace root — when either service is absent.
## Tests
Both harnesses run without a dsh instance:
```sh
node test/paths.test.mjs # 35 assertions on the path helpers
```
`test/fixture.html` reproduces the delivery card plus the primitives menu portal (structure read from the shipped 0.1.6-alpha.1 code), loads `lib/client.js` through a stub module loader and runs 23 checks in a real browser: injection and label language, both copied values, menu dismissal, no event leakage into the React container, a negative control (an unrelated popup gets nothing), idempotence, and full teardown. Open the file in a browser — results render on the page and in `document.title`.
## Limitations
- Only the delivered-file card menu is covered. The "Files changed" chips and the sidebar file tree have no menu of their own, so they are untouched.
- The menu belongs to a shipped component: a future dsh release that changes the card markup may need this plugin updated. The detector degrades by injecting nothing rather than by mislabelling an unrelated menu.
- Copies text only: no file is read, opened or transmitted, and there is no host access at all (the host half is deliberately inert).
## Uninstall
```sh
dsh plugin --profile web remove dsh-copy-file-path
```
## License
MIT
Install
dsh plugin --profile web add github:Meowrium/dsh-copy-file-path
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-copy-file-path from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.