Pasty Command line and Pasty CLI
Log in Sign up

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 browser

Install

uv tool install https://pasty.sh/cli/pasty_cli-0.2.0-py3-none-any.whl

That 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 | logout
  • FILE omitted or - reads stdin.

  • --title sets 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 FILE sends a file's current contents up to the document it was opened as.

  • pasty pull FILE replaces a file with the current contents of the document it was opened as.

  • pasty delete TARGET deletes a document. TARGET is 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 new opens a blank document without creating or importing anything on the server; pasty new --no-open only prints its URL.

  • --no-open skips the browser and just prints the URL, for imports and new.

  • A file literally named new, push, pull, delete, login, or logout imports by spelling its path, for example pasty ./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 new and for push; the file's canonical absolute path for pull; and the document id for delete. Everything else (status, errors) goes to stderr.

  • Exit codes: 0 success, including when the browser could not be opened (the URL is still printed); 1 any failure; 2 usage errors.

  • Errors are one clean line, with no tracebacks or HTML. PASTY_DEBUG=1 re-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:

  1. Generate a token at pasty.sh/settings/cli.

  2. Run pasty login and 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 holds

Push 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 id

Deletion cannot be undone, so it asks — but only when there is somebody there to ask:

Where it runs

What happens

A terminal

Delete … ? This cannot be undone. [y/N] on stderr. Anything but y or yes leaves the document alone.

--yes / -y

Straight through, terminal or not.

A pipe, a script, an agent

Refused, with a message naming --yes.

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