Publish a file. Edit it later.
Every gist gets a 16-character address that is the only key to it. Spreadsheets and JSON stay editable in place afterwards, and nothing you publish needs an account.
Install
$ curl -fsSL https://gist.pub/install | bash
Or cargo install gpub. The installer verifies a checksum before it will run anything, and gpub upgrade replaces the binary later.
Agents can load the whole thing as a skill: gpub --skill prints it to stdout, and gpub install-skill writes it to ~/.claude/skills/gpub.
Publishing
$ gpub publish notes.md https://gist.pub/k3f9x2mqp7ad4nrt
publish writes the URL to stdout and nothing else, so it composes: URL=$(gpub publish report.md). Progress and warnings go to stderr.
The URL is the only access control. It contains an unguessable 80-bit secret, and anyone holding it can read the gist. Do not publish anything you would mind a stranger reading.
$ gpub publish notes.md --update https://gist.pub/k3f9x2mqp7ad4nrt $ gpub remove https://gist.pub/k3f9x2mqp7ad4nrt $ gpub list
--update replaces the content and keeps the URL; files the new version does not contain are dropped, and the kind may change. Without it, every publish creates a new address. remove unpublishes; cached copies clear within a minute.
- Hidden files are skipped, so a stray .env or .git is never published. --all includes them.
- .gitignore is respected when publishing a folder.
- --dry-run shows exactly what would be uploaded, and sends nothing.
- Limits: 4 MB per file, 100 MB and 2000 files per gist.
Markdown
$ gpub publish notes.md https://gist.pub/k3f9x2mqp7ad4nrt
Rendered to a styled page with syntax-highlighted code. The source stays readable at <url>.md. The title comes from the first heading unless you pass --title.
HTML and static sites
$ gpub publish page.html # one file, served as written $ gpub publish ./site/ # a folder, served as a site $ gpub publish build.tar.gz # .tar.gz, .tgz, .tar or .zip of one
A folder wants an index.html at its root; without one, gist.pub generates a file listing. Inside a site, relative links work (./style.css, img/logo.png) — root-absolute ones (/style.css) do not, because a gist lives under a path rather than at the domain root.
CSV and TSV
$ gpub publish data.csv https://gist.pub/2p6bxhk4mtn9wqz0
Rendered as a sortable table with a sticky header. It is not a snapshot: cells, rows and columns can be changed afterwards and the address never moves.
$ gpub set <url> B2 42 # one cell — A1, row 1 is the header $ gpub set <url> 3 Ada 12 # a whole row $ gpub set <url> C role eng ops # a whole column, header first $ gpub set <url> + Kate 5 # append a row $ gpub del <url> B2 # CLEAR a cell $ gpub del <url> 3 # remove a row, closing the gap $ gpub del <url> B # remove a column
- Row 1 is the header, so B2 is the first data row — and row 1 cannot be deleted.
- Setting past the edge grows the sheet, which is how you add to it.
- del on a cell clears it. Removing one cell would shift its neighbours, which is never what was meant.
- The original is at
<url>.csvor<url>.tsv, and either extension converts.
JSON
$ gpub publish config.json https://gist.pub/7hq1nvw3zx8k5rjm
Rendered as a collapsible tree, with counts on every node so a closed one still says how big it is. Invalid JSON is rejected at publish time with the parser’s message.
$ gpub set <url> .port 8080 # a number
$ gpub set <url> .name staging # a string
$ gpub set <url> .ver 1.20 --string # a string that looks like a number
$ gpub set <url> .servers[0].host b # into a list
$ gpub set <url> . '{"fresh":true}' # replace the document
$ gpub del <url> .debug # remove a key- Paths are jq-style:
.key,[0],.["quoted"], and.for the whole document. The leading dot is required — it is what separates.b2from the cellB2. - A path that is not there is created, along with the objects and lists it implies. Adding and modifying are the same command.
- Writing through a value of the wrong shape is refused, so a typo cannot silently discard a list.
- Values parse as JSON when they can, and are strings otherwise.
gpub publish config.json --editablemakes the document writable by anyone who has the URL —set,del, and the API work with no token at all, which turns the gist into a tiny shared data store. Only the content is open: replacing, renaming, or removing the gist still requires its owner. The URL becomes the write credential, so share it accordingly.
Raw files
https://gist.pub/fg65842wfcy4za79.json the JSON itself https://gist.pub/fg65842wfcy4za79.md the markdown source https://gist.pub/fg65842wfcy4za79.csv the sheet as CSV https://gist.pub/fg65842wfcy4za79.tsv the same, tab-separated https://gist.pub/@ada/roster.csv under a name
This is the form to fetch when a program is reading the gist rather than a person. It is generated from the current content, so it reflects every edit. The longer <url>/source.json form still works and means the same thing.
Who can edit
The first publish registers an identity and stores one edit token at ~/.config/gpub/identity.json. That token authorises every later edit and removal. gpub token roll replaces it if it leaks, and --token acts as a different identity.
Editing something you do not own answers 404, not 403 — an address must not be probeable.
Signing in
$ gpub login # prints a code, waits for a browser to approve it $ gpub whoami $ gpub logout
Publishing never needs an account. Signing in claims everything the machine already published, and afterwards any signed-in machine can edit any of it. Sign in with GitHub or a link sent by email.
Readable addresses
$ gpub name <url> release-notes # → gist.pub/@you/release-notes $ gpub name <url> --clear
A name needs a handle to live under, so it requires signing in. A name is guessable and the 16-character address is not — naming a gist is what makes it findable by anyone who guesses it. Only name what you would not mind being found. The secret address keeps working either way.
Over HTTP
Against https://app.gist.pub, with Authorization: Bearer <edit token>:
PUT /api/gists/<id>/cells/B2 {"value": "42"}
PUT /api/gists/<id>/rows/3 {"values": ["Ada", "12"]}
POST /api/gists/<id>/rows {"values": ["Kate", "5"]}
DELETE /api/gists/<id>/rows/3
PUT /api/gists/<id>/columns/B {"values": ["size", "12", "8"]}
DELETE /api/gists/<id>/columns/B
PUT /api/gists/<id>/doc/.port {"value": 8080}
DELETE /api/gists/<id>/doc/.debug
GET /api/gists everything you ownExit codes from gpub: 0 published, 1 bad usage or rejected input, 2 network or server error (worth retrying), 3 over a limit.