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

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