Command line and Pasty CLI
pasty README.md imports a local Markdown file into Pasty and opens it in your browser; cat notes.md | pasty reads stdin. pasty new opens a new blank document without an import. --no-open prints the URL without a browser, which is the form scripts and agents want.
pasty README.md # create a Pasty document and open it in your browser
pasty push README.md # send your local edits up to that document
pasty pull README.md # bring your browser edits back into the file
pasty delete README.md # delete the document (asks first; the file stays)
cat notes.md | pasty # stdin works too
pasty new # open a new blank document
some-agent | pasty --no-open # scripts/agents: print the URL, skip the browserInstall
uv tool install https://pasty.sh/cli/pasty_cli-0.2.0-py3-none-any.whlThat puts pasty on your PATH. pipx install https://pasty.sh/cli/pasty_cli-0.2.0-py3-none-any.whl works too. Either way it needs Python 3.10 or newer, and nothing else: the CLI has no dependencies.
To upgrade, run the install command from this page again, with --force if the version in the URL hasn't changed. pasty --version says which one you have.
Usage
pasty [FILE] [--title TITLE] [--no-open]
pasty push [--force] FILE
pasty pull [--force] FILE
pasty delete [--yes] TARGET
pasty new | login | logoutFILEomitted or-reads stdin.--titlesets the document title. Otherwise the file name is used, then the first level-one heading, and an untitled document shows as "Untitled document".pasty push FILEsends a file's current contents up to the document it was opened as.pasty pull FILEreplaces a file with the current contents of the document it was opened as.pasty delete TARGETdeletes a document.TARGETis a document URL, a document id, or a linked file. It asks first;--yes(or-y) skips the question and is required when stdin is not a terminal.pasty newopens a blank document without creating or importing anything on the server;pasty new --no-openonly prints its URL.--no-openskips the browser and just prints the URL, for imports andnew.A file literally named
new,push,pull,delete,login, orlogoutimports by spelling its path, for examplepasty ./new.
The output contract
Agents and scripts can rely on this:
stdout carries exactly one line: the primary result. That is the document URL for an import, for
newand forpush; the file's canonical absolute path forpull; and the document id fordelete. Everything else (status, errors) goes to stderr.Exit codes:
0success, including when the browser could not be opened (the URL is still printed);1any failure;2usage errors.Errors are one clean line, with no tracebacks or HTML.
PASTY_DEBUG=1re-raises for a traceback.
Authentication
No account needed: an anonymous import prints a link that opens the document in the editor, and creating an account there claims it. To import straight into your account:
Generate a token at pasty.sh/settings/cli.
Run
pasty loginand paste it (input is hidden).
pasty logout removes the saved token. PASTY_API_TOKEN overrides it; prefer pasty login for interactive use, because an inline environment variable lands in your shell history. Config lives at ~/.config/pasty/config.json ($XDG_CONFIG_HOME honoured; %APPDATA%\pasty\config.json on Windows), written with mode 0600.
Pulling edits back
pasty plan.md sends the file up; pasty pull plan.md brings the browser's version back down, replacing the file in place.
An authenticated file import records which document the file became, in ~/.config/pasty/links.json, along with a hash of the bytes it uploaded. That hash is what makes pull safe: if the file changed locally since it was opened in Pasty, pull refuses, because it has no way to merge and overwriting would silently lose the local work. --force replaces the file anyway. A file that has been deleted counts as changed; --force recreates it.
Pull writes through a temporary file in the same directory and renames it over the original, so an interrupted pull leaves the original untouched. A symlink keeps its link and its target is what changes. The file's permissions are preserved.
What is not linked, and so cannot be pulled:
stdin imports, because there is no file to write back to;
anonymous imports, because the document is a 7-day stash rather than yours to read back (the CLI says so, and suggests
pasty login);a file that has since been moved or renamed, because its old path is what was recorded. Import it again to relink.
Pushing edits up
pasty push plan.md is pull's mirror image: it sends the file's current contents up to the document it was opened as. The title is left alone — and not merely because the CLI does not send one: the push endpoint itself writes Markdown and nothing else, so a document is named in the browser and a file name can never become a rename.
pasty plan.md # opens /d/<id>
# edit plan.md locally, or let an agent rewrite it
pasty push plan.md # the browser now shows what the file holdsPush is as careful as pull, in the other direction. Every link records the revision the file was last synced at, and the push names it: if the document changed in Pasty since then, push refuses, rather than writing over whatever was typed in the browser. The message says the two ways out — pasty pull FILE to take the Pasty version, or pasty push --force FILE to overwrite it. A forced push is not destructive either: the server archives the version it displaced, exactly as Keep mine does in the browser.
A link recorded before push existed has no revision to build on, so push asks for --force rather than guessing. Pulling once records one.
Pushing into a document somebody is editing live is fine: their tabs notice within a poll and reload into the pushed text. Nothing is merged — push replaces the document's Markdown with the file's.
The same things that cannot be pulled cannot be pushed, and for the same reasons: stdin imports, anonymous imports, and files that have since moved.
Deleting
pasty delete removes a document for good. Its share and edit links stop working for everyone holding them, and the local file is left exactly where it is.
pasty delete plan.md # a linked file
pasty delete https://pasty.sh/d/<id> # the URL the CLI printed
pasty delete <id> # or just the idDeletion cannot be undone, so it asks — but only when there is somebody there to ask:
Where it runs | What happens |
|---|---|
A terminal |
|
| Straight through, terminal or not. |
A pipe, a script, an agent | Refused, with a message naming |
The last row is the one worth explaining. Assuming yes would make every un-flagged automated call a silent permanent deletion; assuming no would make it a mystery. Refusing is the only answer that says what to do next, and it means --yes is an explicit statement of destructive intent rather than noise you paste out of habit. The question goes to stderr, so the output contract is unchanged.
The browser asks too, in its own way — ⌘K → Delete document → confirm.
A delete prints the document id, and forgets every links.json entry that pointed at it, so nothing is left pointing at a document that no longer exists. Deleting something already gone — or something that was never in this account — is an error, not a silent success. A cancelled delete exits non-zero and prints nothing on stdout: the document is still there, and a caller reading the exit code must not conclude otherwise.
Self-hosting
PASTY_BASE_URL points the CLI at another Pasty server (the default is https://pasty.sh). When set during pasty login, it is saved alongside the token, because a token is only meaningful against the server that minted it.
PASTY_BASE_URL=http://127.0.0.1:8000 pasty README.md --no-open