These guides are primarily for agent readers. They explain how agents use Sails. If you are a person, start at sails.app or Add Sails.

File transfer

upload and download print a one-time browser URL. The person uses their own machine. You never need their local disk, and you should not paste binaries into chat.

There is no upload --to. The path is the first argument. It is relative to the current working directory and must stay under $HOME.

MCP write_file / read_file are UTF-8 text only. Use transfer for CSVs, zips, images, and anything awkward to paste.

Done looks like

  • In: upload poll ID prints success and the file exists at the path you named.
  • Out: the person opens the URL once and the browser saves the file (or a .tar.gz for a directory). No poll step.

Worked example: get a spreadsheet in

Job: a human has q2-leads.csv on their laptop. You need it at inbox/q2-leads.csv.

upload inbox/q2-leads.csv

Expected shape:

Open this URL in your browser to upload (repository members):
  https://…/sails/transfer/…
Target: ~/inbox/q2-leads.csv
Transfer session: 3f2a9c1e-…

Send that URL. They sign in as a repository member and drop the file. Then:

upload poll 3f2a9c1e-…

When poll returns success, the file is in the live workspace and autosave has it.

Do not pass --wait from bash_exec. That call buffers until the upload finishes, so you never see the URL.

Hand a file out:

download inbox/q2-leads.csv
# one-time URL; first successful GET consumes it

download ./site
# browser saves site.tar.gz

Commands

upload help
download help
CommandWhat it does
upload PATHOne-time URL for a single file at PATH
upload PATH --dirOne-time URL for a directory (folder picker or .tar / .tar.gz / .tgz)
upload poll IDBlock until that upload finishes, fails, or expires
upload PATH --waitBlock in one command — avoid inside bash_exec
download PATHOne-time URL; directories become .tar.gz

Common flag: --timeout SECS (default 900). After that the session expires; run the command again.

Auth: the person must be signed in as a repository member. Hard size limit: 100 MiB. Uploads write into the live session and trigger autosave.

Failure modes

What you seeWhat it meansWhat to do
You used upload --to DIRThat flag does not existupload DIR/filename or upload DIR --dir
Browser asks to sign in / 403Transfer URLs are for repository membersThey must be signed in as a member of this repo
Link dead on second openSingle-use (download completes on first GET)Run upload / download again
upload poll times outNobody finished the browser step, or --timeout elapsedNew upload PATH, new URL
Path rejectedOutside $HOME / the active repo mountUse a path under the workspace
100 MiB exceededHard limitSplit the archive, or curl -o a public URL

When not to use this

JobUse instead
Small text already in chatMCP write_file / edit_file
File is already on the public internetcurl -o path https://…
API key, token, DSNsecret open — see Store a secret
Public URL for a folderPublish a site
Another repository should copy a folderShare a directory with another repo

How to: Send a file.