---
name: filehub-transfer
description: >-
  Upload and download files to the 烛龙智元 (DragonAI) filehub at files.dragonai.tech.
  Use whenever you need to store a local file in the company file relay or fetch one back —
  e.g. share a build artifact, dataset, report, log, model, or large media file with teammates,
  or retrieve a file someone uploaded. Auth is "password-as-namespace": any access password
  (口令) opens its own private space; the SAME password is needed to read those files back or
  to share them. If no password is configured, ASK THE USER for it, then pass it via
  --password or the FILEHUB_PASSWORD env var. Files stream DIRECTLY to Aliyun OSS via presigned
  URLs, so there is no size or bandwidth limit; large files upload in parts and identical content
  is de-duplicated (instant upload). Ships a zero-dependency Python CLI (filehub.py, stdlib only).
---

# filehub-transfer

A skill for uploading/downloading files to **烛龙智元 · 文件中转站** (`https://files.dragonai.tech`).
The bundled `filehub.py` is a self-contained CLI (Python 3.8+, **standard library only** — no pip
installs needed). File bytes go client→OSS directly; the backend only signs URLs.

## Authentication: password-as-namespace (口令即空间)

There is **no fixed login**. Any non-empty password deterministically opens its **own isolated
private space**. Re-entering the same password returns to the same files; a different password is a
different, empty space. To share files, the uploader and downloader must use the **same password**.

> Practical consequence: a typo in the password silently lands you in a *different empty space* — it
> never errors. Use `status` to confirm which space you're in (it reports a short fingerprint and
> whether the space is new/empty).

### The handshake an agent should follow

1. **Probe**: run `python filehub.py status`.
2. If you get **exit code 3** (and a line starting `AUTH_REQUIRED`, or JSON `"code":"auth_required"`),
   **stop and ask the user** for the filehub access password (口令). Do **not** guess one.
3. **Retry** with the password the user gives you, either:
   ```bash
   python filehub.py status --password '<口令the user gave>'
   # or, to set it for the whole session:
   export FILEHUB_PASSWORD='<口令>'
   ```
4. `status` will then print the space fingerprint and whether it's empty. You're connected — proceed
   with `upload` / `download` / `ls`, passing `--password` (or relying on the exported env var).

The password flag works on **every** command, so once you have it you can go straight to the action:
`python filehub.py upload ./report.pdf --password '<口令>'`.

## Setup

`filehub.py` should sit next to this file. If it's missing, fetch it (stdlib only, nothing to install):
```bash
curl -fsSL https://files.dragonai.tech/skill/filehub.py -o filehub.py
```
Optional: `FILEHUB_URL` / `--url` to point at a different host (default `https://files.dragonai.tech`).

## Commands

```bash
# 连接自检 —— 第一步先跑这个:确认服务器可达、口令是否就绪、进入了哪个空间。
python filehub.py status
python filehub.py status --password '<口令>' --json     # JSON 输出便于解析

# 上传(自动选单次直传 / 大文件分片 / 秒传)。默认把对象 key 打到 stdout。
python filehub.py upload ./report.pdf --password '<口令>'
python filehub.py upload ./build.zip --folder "构建产物/2026/"      # 放到指定目录
python filehub.py upload ./a.bin --name 备份.bin                    # 上传时重命名

# 下载(key 来自 upload 的输出或 ls)
python filehub.py download "构建产物/2026/build.zip" ./build.zip

# 列举(可带目录前缀,以 / 结尾)
python filehub.py ls
python filehub.py ls "构建产物/2026/" --json

# 删除 / 新建文件夹
python filehub.py rm "构建产物/2026/旧.zip"
python filehub.py mkdir "新目录/子目录"

# 断点续传相关
python filehub.py upload-status ./big.zip            # 查某文件的续传进度(不上传)
python filehub.py uploads                            # 列出所有未完成的分片上传
python filehub.py abort "big.zip"                    # 放弃一个未完成的分片上传
python filehub.py upload ./big.zip --restart         # 忽略旧进度,从头重传
```

## Resumable uploads & lifecycle (断点续传)

Large files (> 100 MB) upload in 8 MB parts. The CLI **persists resume state** (the multipart
`uploadId` + part size) to `~/.filehub/uploads/` (override with `FILEHUB_STATE_DIR`), so an
interrupted upload continues instead of restarting. The flow is simple and idempotent:

1. If `upload` is cut off (process killed, network drop, timeout), it exits with **code 4** and prints
   a resumable summary (`code:"incomplete", resumable:true, partsUploaded/partsTotal, pct`).
2. **Just run the exact same `upload` command again.** It detects the saved state, asks the server
   which parts already arrived (authoritative), uploads only the missing ones, then completes.
3. Check progress without uploading: `upload-status <file>` →
   `{pending, partsUploaded, partsTotal, pct, resumable}`.
4. See everything in flight: `uploads` → server-side incomplete uploads + local resume records.
5. Give up on a stuck upload: `abort <key>` (frees the partial parts + clears local state).

Resume is keyed by `(space, folder+name, size)` and validated against the file's size+mtime; if the
file changed it restarts cleanly. Use `--restart` to force a fresh upload. Identical content is still
de-duplicated first (instant upload), so re-running after a completed upload is near-instant anyway.

### Progress / process events

Progress goes to **stderr**; the final result goes to **stdout**. With `--json`, stderr becomes a
**JSONL event stream** (one object per line, each with an `event` field) and stdout is the single
final JSON object. Event types: `init`, `part` (carries `partsDone/partsTotal/pct`), `retry`
(transient failure + backoff wait — i.e. "waiting", not "stuck"), `resume`, `session_expired`,
`instant`, `complete`.

## How an agent should use it

- **Always `status` first** when you don't know if a password is configured. Exit code 3 = you need
  to ask the user for the password; any other failure is a real error.
- To **store a file**: run `upload`, capture stdout (the object `key`), and report/pass that key on.
  The key is the file's address; downloads and links use it. With `--json` you get
  `{"ok":true,"key":...,"mode":"single|multipart|instant","size":...}`.
- To **retrieve a file**: you need its `key` (from a previous upload, from `ls`, or from a teammate).
  Run `download <key> <localpath>`. A 404 with a hint usually means either the key is wrong **or you
  are in the wrong space (wrong password)** — re-check with `ls` / `status`.
- `upload` is idempotent-friendly: re-uploading identical content is near-instant (server-side copy,
  0 bytes transferred). Uploading different content to the same name overwrites it.
- **Parsing**: human output goes to stdout (key) / stderr (progress). Pass `--json` to get a single
  JSON object on stdout for any command — preferred when you parse results programmatically.

### Exit codes (the contract)

| code | meaning | what the agent should do |
|------|---------|--------------------------|
| `0` | success | continue |
| `1` | generic error (network / OSS / bad args) | read stderr (and `hint`), fix and retry |
| `3` | **auth required** — no password, or session invalid | **ask the user for the password**, then retry with `--password` |
| `4` | **upload interrupted but resumable** | re-run the **same** `upload` command to resume (or `abort` to give up) |

## Notes & limits

- **No size limit.** Files > 100 MB upload in 8 MB parts automatically; ≤ 100 MB use a single direct
  PUT. Each part is retried on transient failure.
- **Filenames** may contain Chinese/spaces.
- **Security** = password strength. Treat the password as a secret; never hard-code it into committed
  files — pass it via `--password` at call time or the `FILEHUB_PASSWORD` env var.
- **TLS**: the CLI verifies the server certificate. On a host without a CA bundle, set
  `FILEHUB_INSECURE=1` or pass `--insecure` (test only).
- **Tuning**: `FILEHUB_SINGLE_MAX=<bytes>` overrides the single-vs-multipart threshold.
- Full HTTP API reference (to integrate without the CLI): https://files.dragonai.tech/docs/

## Files in this skill

- `SKILL.md` — this file
- `filehub.py` — the CLI (`python filehub.py status | upload | download | ls | rm | mkdir`)
