# dsh-file-path

[中文说明](./README.zh-CN.md)

A DeepSeek Harness (`dsh`) plugin that lets the Web composer accept files the
built-in attachment path does not support — PDFs, archives, Office documents,
any binary — by turning them into **workspace path references** instead of
uploading them as message attachments.

This is a community plugin. It is not an official DeepSeek plugin and is not
affiliated with or endorsed by DeepSeek.

## Why

dsh's version-one attachment path accepts PNG, JPEG, WebP, and GIF only.
Dropping a PDF into the composer currently announces "only images are
supported" and drops the file. Browsers do not expose the absolute host path of
a dragged `File` for security reasons, so a web page cannot know where the file
came from — even when the page is served by a local CLI.

`dsh-file-path` closes the gap using the local dsh host itself:

1. You drag or paste a non-image file into the composer.
2. The plugin's browser half reads the file bytes and calls a local
   `filePathBridge/importFile` RPC on the dsh process.
3. The host half writes the file below the session workspace
   (`.dsh-files/<name>` by default, with collision-free `-1`, `-2` suffixes).
4. The composer inserts the resulting workspace-relative path as plain text.
   The model reads it with its ordinary filesystem tools.

A paperclip button in the composer tool row also opens a dialog for manually
typing an absolute or workspace-relative path, so you can reference files the
model can already reach without copying them at all.

## Install

Pack and install the built tarball:

```sh
npm run build
npm pack
dsh plugin --profile web add ./dsh-file-path-0.1.0.tgz
```

Or install a published version:

```sh
dsh plugin --profile web add dsh-file-path
```

Or install from git (the `prepare` script builds the artifacts; pnpm ≥10 asks
you to allow that build once, and dsh prints the exact `allowBuilds` snippet):

```sh
dsh plugin --profile web add github:<you>/dsh-file-path#<commit-sha>
```

Then boot dsh web as usual:

```sh
dsh --profile web --port 3080
```

> The Web profile already contains the Web app bundle. For a custom profile,
> make sure `@deepseek-ai/dsh-web-app` is in `dsh.profile.bundles` before
> `dsh-file-path`.

## Usage

- **Drag or paste** a non-image file anywhere on the page. The import dialog
  lists the file and the target location, then "Import and insert paths" copies
  it into the workspace and inserts its path.
- Click the **paperclip button** in the composer tool row to insert a manually
  typed path (absolute or relative to the session workspace).
- Supported images (PNG/JPEG/WebP/GIF) keep the normal dsh image behavior. A
  mixed drop routes images to the default composer intake and opens the import
  dialog only for the rest.

## Configuration

The bundle patch inserts one host row. Override its config in the profile's
`cordis.patch.yml` or any later layer:

```yaml
- id: file-path
  config:
    importSubdir: .dsh-files   # single relative directory segment
    maxFileBytes: 104857600    # decoded bytes per imported file
```

- `importSubdir` must be one relative segment (no `/`, `\`, `..`). Invalid
  values fail the plugin load loudly.
- `maxFileBytes` is a positive integer. The browser half shows oversized files
  before upload; the host enforces the same limit authoritatively.

## How it is built

- **Host half** (`lib/index.js`): a self-contained Cordis service registered as
  `ctx.filePathBridge`. It registers strict Typert Remote descriptors for
  `filePathBridge/importFile` and `filePathBridge/describe`, so wire input is
  schema-validated on both sides. `importFile` resolves the live session's
  workspace, decodes the base64 payload, writes bytes below `importSubdir` with
  a temporary-file-plus-rename publication path, and returns the
  workspace-relative path.
- **Browser half** (`lib/client.js`): a `dsh.client` module that mounts the two
  Remote methods, registers locale dictionaries, and contributes a small
  control to the `conversation.input.right` slot. Capture-phase document
  listeners take over drops and file-only pastes that contain a non-image;
  all-image batches pass through untouched, and images inside a mixed batch are
  re-dispatched to the default composer listener.

## Security notes

- The RPC only writes into the live session's own workspace, under the
  configured single-segment `importSubdir`; path traversal through the file
  name is stripped.
- The dsh Web transport's loopback/browser-trust fence still applies to
  `/api/filePathBridge/*`.
- Installing a plugin means executing its code inside the local dsh process.
  Review the source and pin versions just as you would for any plugin.

## Development

Requires Node `>=22.19`.

```sh
npm install
npm run check        # typecheck + tests + build
npm test             # host service and composer control tests
npm pack             # built tarball
```

The build script (`scripts/build.mjs`) emits the ESM host half and the
`window.__ModuleLoader__.load` CJS-factory browser half; `tsc` emits the
declaration files under `lib/types`.

## License

[MIT](./LICENSE)
