@zorinto/dropvi-cli
v0.6.0
Published
Send, fetch, list and revoke Dropvi transfers from the command line
Maintainers
Readme
@zorinto/dropvi-cli
Send, fetch, list and revoke Dropvi transfers from the command line. Built for people at a terminal and for agents running headless. No runtime dependencies; Node 22.2 or later.
Every transfer dropvi send creates is end-to-end encrypted (format e2e-v1): the contents,
names, types, title, message and sender name are all encrypted before upload, with the key held
only in the share link, never stored on the server. Dropvi still sees the file count and sizes, the
recipient email addresses passed to --to, the expiry and download limit, and, for a protected
transfer, the PIN itself, which the server checks before issuing a download token. Nothing here
claims the metadata above is hidden, or that this is "zero knowledge": it means only that file
contents, names, types and the sender's text are unreadable without the key.
pnpm add -g @zorinto/dropvi-cli # or: pnpm dlx @zorinto/dropvi-cli <command>
dropvi login # once; sending needs a free account (or set DROPVI_TOKEN)
dropvi send report.pdf # prints the share URLCommands
dropvi login Interactive only. Opens dropvi.com/cli/login to approve, stores the token.
dropvi send <path...> Create a transfer and upload. Prints the share URL.
dropvi send --resume <id> Continue an interrupted send. Takes no paths.
dropvi get <id|url> [-o DIR] Download every file in a transfer.
dropvi list The signed-in account's transfers (the 50 most recent).
dropvi rm <id|url> Revoke a transfer and delete its files. Never prompts.
dropvi logout Revoke this CLI's own token and forget it.Global options: --json, --quiet, --no-input, --api <url>, --help, --version (or -V,
-v).
--dry-run is available on send and rm, the two commands that change something; it makes no
network call and redacts the PIN and tokens. On send, the printed request body is the encrypted
one the server would actually receive (ciphertext file sizes, encryptedMeta, keySalt and, for a
protected transfer, pinSalt), with the PIN itself redacted; part geometry follows the ciphertext
size, which is larger than the plaintext.
Anywhere an id is accepted, a full share URL on dropvi.com or a branded subdomain works too.
dropvi --help --json and dropvi <command> --help --json print a manifest of every command and
flag, with types and which flags are secrets, so an agent can check an invocation before running
it.
send
--to <email> (repeatable, or several addresses separated by commas, up to 50), --title, --message, --from-name, --expiry-days <n>,
--max-downloads <n>, --password-stdin, --password-file <path>, --password <pin>,
--resume <id>, --dry-run.
- A directory is sent with its folders, as a browser sends a chosen folder:
dropvi send Photossends every file underPhotos, each in its folder starting withPhotos. The folders are sealed into the encrypted metadata, so Dropvi cannot read them. A symbolic link to a directory, or to a file outside the directory being sent, is refused, and so is a folder more than 16 deep, longer than 255 characters, or with a name another computer cannot recreate (such asCON, a name ending in a dot or a space, or one with a colon, a backslash or a question mark in it). dropvi sendneeds a signed-in account (dropvi login, orDROPVI_TOKEN). Signed out, it exits 3 (not_authenticated) before reading any file or making any request,--dry-runincluded. Dropvi accepts a send without an account only from a browser that has passed its security check.--max-downloadsneeds Business Pro or Teams & Suite.--titleis at most 200 characters,--message2000 and--from-name100, all checked after trimming, before any request. They are sealed into the encrypted metadata, so the API no longer bounds them itself.- SHA-256 and CRC-32 are always computed and sealed into the transfer, so an encrypted transfer
always carries checksums.
--no-hashis no longer accepted: passing it is a usage error. - Earlier versions could send without an account and stored each such transfer's deletion token
in the config file.
dropvi rmstill uses a stored token, then forgets it, so repeating thermreports the transfer asdeleted(exit 5). - Free accounts share an upload budget of 100 GB in any 24 hours with everyone sending from the
same network without an account; when it is used up,
sendexits 7 (rate_limited) at once rather than retrying. Every account also has a limit on the storage its live transfers hold.
Keys and links
dropvi send always prints the full share link, https://dropvi.com/t/<id>#k=<key>, whether or
not --to was given. The key is never stored on the server, only the
link key salt and (for a protected transfer) the PIN salt are. Losing the fragment after # loses
the transfer. Because that link contains the key, treat it (and data.url under --json, which is
the same link) as a secret in its own right, the way you would a password or a token: anyone who
has it can open the transfer, so share it, log it or paste it somewhere only where the intended
recipients, and no one else, will see it.
With --to, the email Dropvi sends those recipients contains the full link with the key, so they
open the files with one click. To write it, dropvi sends the key to Dropvi as each file finishes
uploading; Dropvi uses it only for that email and does not store it, but at that moment Dropvi
could open the transfer, and anyone who can read the email can open it too. To keep the key from
Dropvi entirely, leave out --to and send the printed link yourself. Output is the same either
way: one line with the full link, or data.url and data.key under --json. A branded subdomain
link works the same way.
get
-o, --out <dir> (default ., created if missing), --key-stdin, --password-stdin, --password-file <path>,
--password <pin>.
- Files are downloaded one at a time and each file counts as one download. If a transfer has a
download limit too small for one download per file,
getrefuses before fetching anything and points at the browser ZIP, which counts once. - Every file name from the server is checked. A name with a path separator, a drive letter,
..or a NUL is refused and nothing is downloaded. - Files sent in folders are written into those folders under the output directory. A folder that
would put a file anywhere else (
.., an absolute path, a drive letter, a backslash) is refused before anything is downloaded, and an existing symbolic link where a folder goes is never followed. Each folder is checked just before its file is moved into place, which leaves a short window in which someone else writing to the same output directory could swap a folder for a link, so download into a directory only you can write to. - An existing file is never overwritten without a yes at the prompt. Under
--no-input, or with no terminal, the new file gets a suffix such asreport (1).pdf. - SHA-256 is verified against the checksum the sender's client sealed with each file. A mismatch deletes the file and exits 9.
Every transfer is end-to-end encrypted and needs its key, tried in this order: the #k= fragment of the
share link, then --key-stdin, then DROPVI_KEY. --key-stdin and --password-stdin cannot both
be used, since both read stdin; combine --key-stdin with --password-file or
DROPVI_TRANSFER_PIN, or DROPVI_KEY with --password-stdin. The key is resolved as soon as the
transfer is found, before any PIN attempt or file is downloaded, so a missing key
never spends a PIN attempt: with no key at all, get is a usage error naming --key-stdin. A key
that does not open the transfer, or a malformed one, is wrong_key (exit 2, not retryable). A
tampered, truncated or otherwise inconsistent transfer is integrity (exit 9): if the sealed
metadata, a digest or a size fails to check out, nothing is written at all; if a file's own body
fails to decrypt partway through a multi-file download, only that file's partial download is
removed and files already written earlier in the run are left in place.
The transfer PIN
A PIN or password is 6 to 128 characters; anything shorter or longer is a usage error (exit 2), checked before any request.
Prefer --password-stdin. --password-file and DROPVI_TRANSFER_PIN also work. Plain
--password works with a warning, because the PIN is then visible in process listings and shell
history. Surrounding whitespace is trimmed from every source, matching the browser, so a PIN typed
or pasted with leading or trailing spaces still derives the same key.
On a protected transfer, the PIN itself is sent to Dropvi's server so it can be checked before a
download token is issued; Dropvi stores it only as a salted hash, never as typed. A
resume state file records the link key, key salt and PIN salt, but never the PIN, so send --resume
on a PIN-protected transfer asks for the PIN again, and refuses with wrong_pin before uploading
another byte if it does not match.
Output
stdout carries results; everything else goes to stderr. dropvi send x.zip | pbcopy copies the
URL and nothing else.
Without --json, dropvi list prints a table for a person to read (a header row, then one row per
transfer with its status, file count, size, downloads, whether it has a PIN, and its title where
Dropvi knows one), and dropvi rm prints Deleted <id>. Scripts should parse --json instead;
the text layout is not a contract.
With --json, exactly one object is printed to stdout:
{ "schemaVersion": 1, "ok": true,
"data": { "transferId": "...", "url": "https://dropvi.com/t/...#k=...", "key": "..." } }
{ "schemaVersion": 1, "ok": false,
"error": { "code": "transient", "message": "...", "retryable": true,
"transferId": "...", "resume": "dropvi send --resume ...", "discard": "dropvi rm ..." } }data.url always carries the full link, including the key, whether or not --to was used: handle
it as a secret, the same as a password or a token, since anyone holding it can open the transfer.
data.key repeats the same key on its own, for a script that needs the bare key value apart from
the link.
error.code is the stable contract. resume and discard are present when a send failed after
creating its transfer; resume is left out when the send cannot be continued. An error never
includes the key of the link it concerns, even when the reference passed to dropvi carried one.
Exit codes and error codes
| Exit | Meaning | Agent action |
| ---- | -------------------------------------------------------------------------------- | -------------------------------------- |
| 0 | Success | Continue |
| 1 | Unexpected error | Report, do not retry |
| 2 | Usage error, local validation failed, wrong PIN or wrong key | Fix the invocation |
| 3 | Not authenticated | Re-authenticate |
| 4 | Refused: plan or ownership | Never retry |
| 5 | Gone: not found, deleted, expired, or no longer accepting uploads | Never retry |
| 6 | Limit reached: download quota | Never retry |
| 7 | Rate limited | Retry after the delay, if one is given |
| 8 | Transient failure, retries exhausted | Retry later |
| 9 | Integrity failure: a file or its checksum was altered, cut short or inconsistent | Investigate, do not retry |
| 130 | Interrupted by SIGINT or SIGTERM | Resume if a resume command is given |
| error.code | Exit | Retryable |
| ----------------------- | ---- | -------------------------- |
| usage | 2 | no |
| not_authenticated | 3 | no |
| wrong_pin | 2 | no |
| wrong_key | 2 | no |
| pin_required | 2 | no |
| pin_locked | 7 | no (carries lockedUntil) |
| plan_required | 4 | no |
| forbidden | 4 | no |
| verification_required | 3 | no |
| not_found | 5 | no |
| deleted | 5 | no |
| expired | 5 | no |
| upload_window_closed | 5 | no |
| not_ready | 5 | no |
| quota_exhausted | 6 | no |
| rate_limited | 7 | yes |
| transient | 8 | yes |
| integrity | 9 | no |
| source_changed | 1 | no |
| internal | 1 | no |
| interrupted | 130 | no |
Authentication
DROPVI_TOKEN wins over the stored token. dropvi login stores its token in
$XDG_CONFIG_HOME/dropvi/config.json (default ~/.config/dropvi/config.json; on Windows
%APPDATA%\dropvi\config.json) with mode 0600 in a 0700 directory. The token is only ever sent
as a header and is never printed.
dropvi login signs in through the browser. It shows a code such as KXQR-7MWD and opens
https://dropvi.com/cli/login. Sign in there, type the code and approve it; the CLI then stores its
token. The code is typed rather than put in a link, so nobody can send you a link that approves a
sign-in for their own CLI: approve only a code your own terminal shows. If no browser opens (over
SSH, say), go to that address on any device. A request lasts 10 minutes; a denied or expired one
exits 3.
For scripts and CI, create a token at https://dropvi.com/cli/token and set it as DROPVI_TOKEN.
A token is a full 30-day session. Each token is its own session, separate from the browser's, so revoking one does not sign the browser out.
dropvi logout revokes the token this CLI holds and forgets it. It keeps the stored deletion
tokens, so anonymous transfers stay removable. A token supplied through DROPVI_TOKEN is revoked
server-side, but the CLI cannot unset the variable in your shell and says so.
Deleting the config file on its own revokes nothing. A token you no longer hold cannot be revoked either, because nothing lists an account's sessions, so use a separate account for CI.
Resume
A send writes its state to $XDG_STATE_HOME/dropvi/uploads/<id>.json (default
~/.local/state/dropvi/uploads; on Windows %LOCALAPPDATA%\dropvi\uploads) before uploading a
byte, with mode 0600. It holds the link key, the key salt and, for a protected transfer, the PIN
salt, so it is as sensitive as the share link itself; it never holds the PIN. After a failure or an
interrupt, dropvi send --resume <id> continues from the first part not yet stored, as long as the
files are unchanged; a PIN-protected transfer asks for the PIN again first, and refuses with
wrong_pin before uploading anything if it is not the one the transfer was sent with. The server
prunes an unfinished transfer about 24 hours after it was created, plus an hour for every 5 GiB;
after that only dropvi rm <id> is left. A finished send removes its state file, so --resume on
it is a usage error naming the transfer's keyless share URL: use the full link, including the part
after #, that was printed when the send finished. A resume state file written by a 0.1.x plain
send is unusable after upgrading, since it holds no key; --resume treats it as absent and reports
that the transfer cannot be continued from this machine.
Environment
DROPVI_TOKEN, DROPVI_API, DROPVI_TRANSFER_PIN, DROPVI_KEY, DROPVI_JSON, DROPVI_QUIET and
DROPVI_NO_INPUT. A flag wins over its variable. NO_COLOR and FORCE_COLOR are honoured.
DROPVI_KEY is the last-resort source of the key for dropvi get on an end-to-end encrypted
transfer, tried after the link's own #k= fragment and --key-stdin.
DROPVI_TRANSFER_PIN applies to send as well as get, so a send made with it set is
PIN-protected; send warns when that happens. --api and DROPVI_API must be https, except
http to localhost for local development, because the token travels in a header.
Licence
MIT. See LICENSE.
The licence covers this command-line client only. The Dropvi service it talks to, and the rest of the repository this package is built from, are not open source.
