Overview
@storagesdk/cli ships two binary aliases — storage (primary) and storagesdk (for searchability) — both pointing at the same script. After install, both are on your PATH and resolve to the same binary.
The CLI is built on the same @storagesdk/adapters registry the library uses, so any adapter that works in code works from the shell with no extra wiring — just set the adapter-native env vars and pick it with --adapter <name>.
Install
# global
npm install -g @storagesdk/cli
# or one-shot via npx (no install)
npx @storagesdk/cli adapters
# or pin to a project
npm install --save-dev @storagesdk/cli
Use storage or storagesdk — both work; storage is shorter, storagesdk is unambiguous for shell history or grep.
Adapter discovery
storage adapters lists every adapter the registry knows about; pass a name to see its env vars (with required/optional flags + backend-native fallbacks).
storage adapters
fs
s3
r2
minio
tigris
azure
gcs
vercel
github
webdav
backblaze
spaces
wasabi
supabase
linode
fly
railway
Run `storage adapters <name>` to see env vars.
storage adapters tigris
Env vars for tigris:
TIGRIS_BUCKET required
TIGRIS_ACCESS_KEY_ID required
TIGRIS_SECRET_ACCESS_KEY required
TIGRIS_ENDPOINT optional
TIGRIS_FORCE_PATH_STYLE optional
Backend-native fallbacks show up next to the adapter-prefixed var:
storage adapters s3
Env vars for s3:
S3_BUCKET required
S3_ACCESS_KEY_ID optional fallback: AWS_ACCESS_KEY_ID
S3_SECRET_ACCESS_KEY optional fallback: AWS_SECRET_ACCESS_KEY
S3_REGION optional fallback: AWS_REGION
S3_ENDPOINT optional
S3_FORCE_PATH_STYLE optional
If you already have AWS_* set in your environment, the S3 adapter picks them up automatically — no duplication.
Pick an adapter once
Every command takes --adapter <name>. If you’ll be running several in a row, set STORAGE_ADAPTER once and skip the flag:
export STORAGE_ADAPTER=tigris
export TIGRIS_BUCKET=my-bucket
export TIGRIS_ACCESS_KEY_ID=…
export TIGRIS_SECRET_ACCESS_KEY=…
storage ls photos/
Output format
The CLI detects whether stdout is a terminal and chooses the format. Piping always yields machine-readable JSON; in a terminal you get aligned text. Both can be forced via flags:
storage ls photos/ # human (in a terminal)
storage ls photos/ | jq # JSON (piped to jq)
storage ls photos/ --json # force JSON
storage ls photos/ --no-json # force human
Errors go to stderr with a clear message and exit code; piped consumers get clean output on stdout.
storage adapters foo
# ✗ Unknown adapter 'foo'.
# Available: fs, s3, r2, minio, tigris, azure, gcs, vercel,
# github, webdav, backblaze, spaces, wasabi, supabase,
# linode, fly, railway
# (exit code 1)
Errors
StorageError becomes a clean stderr message + a per-code hint and exit 1:
NotFound— check the path and that it exists in the selected adapter.Unauthorized— check the adapter env vars (storage adapters <name>).InvalidArgument— check the command arguments.Conflict— the target already exists or conflicts with another resource.NotSupported— this adapter doesn’t support the operation.
Where to next
- Reads:
ls,stat,cat. - Writes:
cp,mv,rm. - Signed URLs:
sign download(GET) andsign upload(PUT/POST). - Snapshots and forks:
snapshotsandforkscover listing, creation, removal, and the--snapshot/--forkscoping flags. - AI agents:
mcpboots a stdio Model Context Protocol server so Claude Desktop, Cursor, or any MCP host can drive every verb.
Write commands print a confirmation line to stderr in human mode so stdout stays clean for piped data (e.g. cp storage://config.json -); JSON mode emits { action, from?, to?, path? } on stdout so it composes with jq.