Commands
Complete reference · v1 · positional grammar
zz-drop has two verb families — upload (s, default) and download (d) — plus a small set of single-letter atomic commands. Most v1 invocations are positional; scriptable mode (--json / --quiet) is live as of 0.9.0 and adds a small set of global flags before the verb — see the scriptable section below for the full contract.
Quick reference
UPLOAD
zz <file>... upload (default verb)
zz s <file>... [<dir>/] explicit alias (trailing-/ → remote dir)
zz sx <file> [<dir>/] + zstd compress
zz sa <dir> [<prefix>] bulk top-level
zz sar <dir> [<prefix>] recursive
zz sax <dir> [<prefix>] → one .tar.zst
zz sarx <dir> [<prefix>] recursive bundle
DOWNLOAD
zz d <name>... [<dir>/] download raw (trailing-/ → local dir)
zz dx <name> [<dir>/] + decompress (or extract bundle)
zz da [<dest> [<prefix>]] all top-level
zz dar [<dest> [<prefix>]] recursive
ATOMIC
zz q lock the agent
zz w wipe local state (asks for confirmation)
zz z unlock the local container
zz c open the configuration TUI (zz-tui)
zz f doctor / diagnostics
Conventions
Trailing slash → destination directory
On upload and download, the last positional argument with a trailing / is treated as the destination directory; all other positionals are sources. Without a trailing slash, every argument is a source and the destination is the default (remote root for upload, current working directory for download).
zz s a.md b.md docs/ # a.md, b.md → docs/ on remote
zz d report.pdf invoice.pdf ./out/ # two remote files → ./out/ locally
Modifier set semantics
The letters after s and d form an unordered set. sax, sxa, xas, and sxa are all the same command — recursive-bundle upload. Only valid modifiers are accepted; everything else is a usage error.
a— bulk (apply to a directory's contents)r— recursive (only meaningful witha)x— compress on upload, decompress / extract on downloade— encrypt (file-content E2EE) — v1.1 roadmap
Set order does not matter: zz sarx . ≡ zz sxar . ≡ zz xars ..
Upload
zz <file>... — default upload
Upload one or more files to the active profile's remote storage. zz file.md and zz s file.md are equivalent; the bare form is the headline ergonomic.
zz notes.md
uploaded notes.md 4.1 KiB → work-nc · nextcloud.zz-drop.net/zz-drop
zz sx <file> [<dir>/] — compressed upload
Apply zstd compression and upload as <name>.zst. The output line shows the saved-percentage of the compressed payload relative to the original.
zz sx readme.md
uploaded readme.md.zst 3.2 KiB (78% compressed) → work-nc · nextcloud.zz-drop.net/zz-drop
zz sa <dir> [<prefix>] — bulk top-level
Upload every top-level file in <dir>. Subdirectories, hidden files (leading .) and symlinks are skipped silently. With r (zz sar), the walk is recursive and relative paths are preserved on the remote.
zz sax <dir> [<prefix>] — bundle to one archive
Pack the directory into a single <dirname>.tar.zst and upload it as one file. zz sarx does the same recursively. Useful for atomic snapshots — no half-uploaded state if the network drops mid-walk.
Download
zz d <name>... [<dir>/] — download
Download one or more remote files. Basic globs (*, ?) are supported on the leaf name only; no recursive **, no character classes.
zz d report.pdf
downloaded report.pdf 1.2 MiB ← work-nc · nextcloud.zz-drop.net/zz-drop
zz dx <name> [<dir>/] — download + decompress
If the file ends in .zst, the decompressed sibling is written alongside (the original .zst is kept). If it is a .tar.zst archive, the contents are extracted into a sibling directory.
zz da, zz dar — bulk download
Download every top-level file (da) or recursively (dar) from the remote root or a sub-prefix. Bulk decompress (dax, darx) is not yet wired — invoking it returns exit code 3 (not implemented) until v1.1.
Atomic commands
zz z — unlock
Decrypt the local profiles-local.zz container, spawn the agent, and make the chosen alias active. With no argument it uses the cached default (or prompts to pick if the container holds more than one alias).
zz z
passphrase (profiles-local.zz): ••••••••••
unlocked · work-nc · nextcloud.zz-drop.net/zz-drop
zz q — lock
Stop the agent and zero the in-memory profile. Idempotent: a no-op when already locked, exits 0.
zz q
locked
zz w — wipe
Erase all local zz-drop state: container files, config, runtime dir, agent socket, agent token. Asks for confirmation interactively (literal wipe). In a non-TTY context it refuses unless ZZ_DROP_CONFIRM_WIPE=yes is set.
zz c — open the TUI
Launch zz-tui for setup and configuration. Requires a terminal; a non-TTY context exits 2. Returns 127 if the zz-tui binary is not on PATH.
zz f — doctor
Read-only health check: paths, container presence, agent state, build identity. Never decrypts, never writes, always exits 0 — safe to call from monitoring scripts.
Help & completion
zz --help
Print the cheat sheet shown above. The layout adapts to the terminal width; below 72 columns it switches to a compact rendering.
zz --setup-completions · zz --check-completions
Brew and the curl | sh installer wire SACS automatically. For any other install path — cargo install --git, source build, or to fix a setup that didn't quite take — the one-shot command does the same thing:
zz --setup-completions # auto-detect $SHELL, write file + rc block
zz --setup-completions zsh # force a specific shell
zz --check-completions # read-only status report
zz --setup-completions --uninstall # remove cleanly
The setup writes a single delimited block to ~/.zshrc / ~/.bashrc between # >>> zz-drop SACS >>> and # <<< zz-drop SACS <<<. Framework-aware (oh-my-zsh, prezto, zinit, antibody, antidote, znap, zimfw, zplug — when one is detected, compinit is left to the framework). Idempotent: re-running with identical content is a no-op; changed content updates in place. Both flags support --json / --quiet.
The raw zz --completions {bash|zsh|fish} flag still emits a script on stdout for advanced piping; --setup-completions is the recommended path.
Exit codes
| Code | Constant | Meaning |
|---|---|---|
| 0 | EXIT_OK | Success |
| 2 | EXIT_USAGE | Bad argument, unknown command, or non-TTY required interactive prompt |
| 3 | EXIT_NOT_IMPLEMENTED | Feature gated off in this build (e.g. bulk decompress) |
| 5 | EXIT_AGENT_UNREACHABLE | Agent locked, socket missing, or build mismatch |
| 6 | EXIT_PROFILE_MISSING | No container at the expected path |
| 7 | EXIT_DECRYPT_FAILED | Wrong passphrase or corrupted container |
| 8 | EXIT_WIPE_CANCELLED | Wipe confirmation declined |
| 9 | EXIT_PROVIDER_ERROR | Upstream upload or download error |
| 10 | EXIT_AGENT_LOCKED | Agent reachable but profile locked; scriptable mode never auto-unlocks |
| 11 | EXIT_PASSPHRASE_FILE_INSECURE | --passphrase-file mode > 0600 or owner mismatch |
| 12 | EXIT_COMPLETIONS_FAILED | --setup-completions couldn't write the file or rc block (or --check-completions reports anything other than wired) |
| 127 | EXIT_TUI_NOT_FOUND | zz-tui not on PATH (raised by zz c) |
Environment variables
| Variable | Effect | Default |
|---|---|---|
NO_COLOR | Disable ANSI colors (no-color.org) | unset |
CLICOLOR=0 | Disable ANSI colors (BSD convention) | unset |
FORCE_COLOR=1 | Force colors even without a TTY | unset |
COLUMNS | Override detected terminal width for help layout | auto-detect |
ZZ_DROP_CONFIRM_WIPE=yes | Auto-confirm zz w in non-TTY contexts | unset (refuse) |
Output format
Every successful upload, download, or unlock emits one line in the same shape:
<verb> <name> <bytes> <arrow> <alias> · <target>
The arrow disambiguates direction (→ for upload, ← for download). Errors mirror the format with failed as the verb plus a short reason, and are written to stderr while successes go to stdout.
Scriptable mode
zz has a stable scriptable contract: --json emits one NDJSON event per line on stdout, --quiet emits one minimal text line per result. Schema version "1" is frozen from 1.0.0 onward; new fields and events are additive, breaking changes bump v to "2". The contract is documented in docs/scriptable.md and constrained by the JSON Schema at docs/scriptable/zz-drop-output.v1.json.
Global flags
Accepted before the verb on any invocation. --quiet and --json are mutually exclusive.
| Flag | Effect |
|---|---|
--json | NDJSON on stdout, schema v: "1". |
--quiet | One terse text line per result. No ANSI. |
--passphrase-file <path> | Read passphrase from file. Strict mode ≤ 0600, owner = current UID, max 4 KiB. |
--alias <name> | Pre-select alias, skip the picker. |
--local / --remote | Force one container when both exist. |
--yes | Auto-confirm zz w in scriptable mode. |
Environment variables
Applied when no command-line flag overrides them. Precedence: flag > env > default.
| Variable | Values | Default |
|---|---|---|
ZZ_OUTPUT | text | json | text |
ZZ_PASSPHRASE_FILE | path | unset |
ZZ_ALIAS | alias string | unset |
ZZ_CONTAINER | local | remote | unset |
ZZ_CONFIG_DIR | absolute path | OS default |
SSL_CERT_FILE | path to PEM bundle | unset (OS trust store) |
ZZ_OUTPUT=quiet is rejected (quiet is flag-only). ZZ_PASSPHRASE=<value> is not supported by design — env values leak via /proc, ps eww, container debug. ZZ_CONFIG_DIR redirects the entire state tree to <root>/{config,cache,runtime}. SSL_CERT_FILE is the standard OpenSSL trust-set override: when set, only the PEM certificates in that file are trusted for outbound HTTPS — useful behind a corporate TLS-inspection proxy when the corp CA isn't installed system-wide. If it is set but unreadable or contains no certificate, zz-drop fails closed (trusts nothing, every connection fails) rather than silently falling back to the OS trust store.
Exit code / reason mapping
Each reason on a failed event maps 1:1 to a process exit code. Stable from 1.0.0 onward.
reason | Exit | Meaning |
|---|---|---|
usage | 2 | flag / arg parse error or unsupported combination |
not_implemented | 3 | recognized verb gated off in this build |
agent_unreachable | 5 | socket present but rpc failed |
profile_missing | 6 | no profiles-local.zz / profiles-remote.zz |
decrypt_failed | 7 | wrong passphrase or corrupted container |
wipe_cancelled | 8 | operator declined wipe (text mode) |
provider_error | 9 | upstream provider failed |
agent_locked | 10 | agent up but profile locked — scriptable mode never auto-unlocks |
passphrase_file_permissions | 11 | passphrase file mode > 0600 or owner mismatch |
interactive_required | 2 | zz w without --yes |
interactive_only | 2 | zz c / setup — no scriptable surface |
alias_ambiguous | 2 | multiple aliases, no --alias / ZZ_ALIAS / cache |
container_ambiguous | 2 | both containers exist, no --local / --remote |
CI cookbook
The minimum viable pattern — unlock from a file at job start, lock on exit, fail on per-file errors:
export ZZ_OUTPUT=json
export ZZ_CONFIG_DIR=/run/secrets/zz-drop
export ZZ_PASSPHRASE_FILE=/run/secrets/zz.pass
export ZZ_ALIAS=ci-bot
zz z | jq -e '.event=="unlocked"' >/dev/null
trap 'zz q' EXIT
zz s artifact.zip \
| tee >(jq -c 'select(.event=="failed")' >&2) \
| jq -e 'select(.event=="batch_summary").exit_code==0' >/dev/null
Three gotchas worth pinning: (1) no auto-unlock in scriptable mode — locked agent fails with agent_locked / exit 10; (2) destructive verbs need --yes — zz w rejects otherwise; (3) quote globs — zz d 'reports/*.pdf' expands server-side, the shell would expand locally without quotes.