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.

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

CodeConstantMeaning
0EXIT_OKSuccess
2EXIT_USAGEBad argument, unknown command, or non-TTY required interactive prompt
3EXIT_NOT_IMPLEMENTEDFeature gated off in this build (e.g. bulk decompress)
5EXIT_AGENT_UNREACHABLEAgent locked, socket missing, or build mismatch
6EXIT_PROFILE_MISSINGNo container at the expected path
7EXIT_DECRYPT_FAILEDWrong passphrase or corrupted container
8EXIT_WIPE_CANCELLEDWipe confirmation declined
9EXIT_PROVIDER_ERRORUpstream upload or download error
10EXIT_AGENT_LOCKEDAgent reachable but profile locked; scriptable mode never auto-unlocks
11EXIT_PASSPHRASE_FILE_INSECURE--passphrase-file mode > 0600 or owner mismatch
12EXIT_COMPLETIONS_FAILED--setup-completions couldn't write the file or rc block (or --check-completions reports anything other than wired)
127EXIT_TUI_NOT_FOUNDzz-tui not on PATH (raised by zz c)

Environment variables

VariableEffectDefault
NO_COLORDisable ANSI colors (no-color.org)unset
CLICOLOR=0Disable ANSI colors (BSD convention)unset
FORCE_COLOR=1Force colors even without a TTYunset
COLUMNSOverride detected terminal width for help layoutauto-detect
ZZ_DROP_CONFIRM_WIPE=yesAuto-confirm zz w in non-TTY contextsunset (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.

FlagEffect
--jsonNDJSON on stdout, schema v: "1".
--quietOne 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 / --remoteForce one container when both exist.
--yesAuto-confirm zz w in scriptable mode.

Environment variables

Applied when no command-line flag overrides them. Precedence: flag > env > default.

VariableValuesDefault
ZZ_OUTPUTtext | jsontext
ZZ_PASSPHRASE_FILEpathunset
ZZ_ALIASalias stringunset
ZZ_CONTAINERlocal | remoteunset
ZZ_CONFIG_DIRabsolute pathOS default
SSL_CERT_FILEpath to PEM bundleunset (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.

reasonExitMeaning
usage2flag / arg parse error or unsupported combination
not_implemented3recognized verb gated off in this build
agent_unreachable5socket present but rpc failed
profile_missing6no profiles-local.zz / profiles-remote.zz
decrypt_failed7wrong passphrase or corrupted container
wipe_cancelled8operator declined wipe (text mode)
provider_error9upstream provider failed
agent_locked10agent up but profile locked — scriptable mode never auto-unlocks
passphrase_file_permissions11passphrase file mode > 0600 or owner mismatch
interactive_required2zz w without --yes
interactive_only2zz c / setup — no scriptable surface
alias_ambiguous2multiple aliases, no --alias / ZZ_ALIAS / cache
container_ambiguous2both 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 --yeszz w rejects otherwise; (3) quote globszz d 'reports/*.pdf' expands server-side, the shell would expand locally without quotes.