Bundle
dsh-file-download
Download Agent-delivered files straight from the DSH Web GUI: a Session-header button with per-file and ZIP-bundle browser downloads, built for cloud hosts that have no local file manager.
- Source
- lanyunshijian
- License
- MIT
- Updated
- Updated 4 days ago
Readme
# dsh-file-download
English | [中文](README.zh.md)
> Download the files an Agent delivers, straight from the DSH Web GUI.
## Why this exists
DSH's Web GUI can already *open* a delivered file on the Host desktop. That is
exactly the wrong affordance when the Host is a cloud server: there is no local
folder to open. `dsh-file-download` adds the missing transport — the browser
downloads the file instead.
It lists every file the Agent declared through the `present` tool in the current
Session, and hands each file (or all of them as one ZIP) to the browser's own
download manager. Nothing is held in JavaScript memory, so a large deliverable
streams straight through.
## Use it
1. Install the bundle into a profile (see *Install* below).
2. Open a Session in the Web GUI and refresh the page.
3. The Session header shows a **Download deliverables** button with a count
badge. Open it to see the declared files with their sizes and descriptions.
4. Press **Download** on one file, or **Download all as ZIP** when several files
are available.
Files appear in the panel only when the Agent declares them with `present`; a
Session that never declared a file shows an empty state with that hint.
## Install
```bash
# from npm
dsh plugin --profile web add dsh-file-download
# or from a local checkout
dsh plugin --profile web add /path/to/dsh-file-download
```
The package carries no runtime dependency beyond Node built-ins, so the bundle
installs without a build step or a network fetch.
## Routes
Three authenticated routes are claimed on Connection's `/api` fence, so they
inherit browser-cookie authentication plus Host/Origin trust. A fourth route is
an unauthenticated operational probe.
| Route | Purpose |
| --- | --- |
| `GET /api/dsh-file-download.list?sessionId=<id>` | JSON listing: declared path, name, size, description, and a `missing` flag |
| `GET\|HEAD /api/dsh-file-download.file?sessionId=<id>&seq=<n>&index=<m>` | One declared file as an attachment |
| `GET\|HEAD /api/dsh-file-download.bundle?sessionId=<id>` | Every available declared file as one ZIP |
| `GET /dsh-file-download/health` | `{ ok: true, plugin, version }` for deployment probes (no Session data) |
## Safety properties
- **Coordinates, not paths.** The browser never sends a filesystem path. It
sends a `present` event coordinate (`seq` + `index`); the Host re-reads the
Session log, recovers the declared path, resolves it through the composed
filesystem, and only then opens it. An arbitrary path cannot be requested.
- **No host paths in responses.** Listings expose only the declared path, name,
size, and description.
- **Downloads are attachments.** `Content-Disposition` uses an ASCII fallback
plus RFC 5987 `filename*`, so non-ASCII names survive every browser. Responses
are sent with `x-content-type-options: nosniff`.
- **No `Content-Length` on single files.** A delivered file may change between
the stat and the stream; a stale length would hang the browser, so the
response is chunked instead.
- **Archive entry names are sanitized.** Traversal segments, absolute roots, and
control characters are removed, and duplicate names get a numeric suffix.
- **Zero dependencies.** The ZIP writer is built on `node:zlib` alone, so the
plugin loads in deployments whose profile does not ship an archiver.
## Limits
- The listing is derived from `present` declarations in the Session log; files
the Agent wrote but never declared are not listed.
- A single file at or above 4 GiB, an archive at or above 4 GiB, or more than
65535 entries fails loudly: this writer implements no Zip64.
- Downloaded content is streamed as the Agent left it; the plugin neither
transforms nor re-encodes files.
## Layout
| Path | Role |
| --- | --- |
| `lib/index.js` | Host half: route registration, Session-log reading, file streaming |
| `lib/zip.js` | Dependency-free streaming ZIP writer |
| `lib/client.js` | Browser half: header control, listing panel, download handoff |
| `cordis.patch.yml` | Bundle patch inserting the plugin row into the profile |
| `test/host.test.mjs` | Behavior specs (routes, headers, archive validity) |
| `PUBLISHING.md` | Release checklist and community submission guide |
| `CHANGELOG.md` | Release history |
| `evidence/` | Verification report and raw logs for this build |
| `.github/workflows/test.yml` | CI: syntax check, specs, packaging smoke test |
## License
MIT
Install
dsh plugin --profile web add github:lanyunshijian/dsh-file-download
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-file-download from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.