Skip to content
dsh.fish
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

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source