Quick start: the client
plandrop pushes a finished static HTML document to a unique, secure hostname on your
network and gives you a link to share. The client is an npx CLI; the hosting is served
with zero server-side logic.
You need a running plandrop stack and its domain (e.g. https://plandrop.example.com).
If you want to run the stack yourself, see Hosting on localhost.
At a glance
# in the directory holding your finished document
npx plandrop create --domain https://plandrop.example.com
npx plandrop upload ./planfile.html
# → share the printed https://<host>.plandrop.example.com/ link
Commands
| Command | What it does |
|---|---|
create |
Mint a new host (a unique hostname + passphrase) and write them to a .plandrop file in the current directory. |
upload <path> [remote] |
Push a file or a directory (recursively) to your host over authenticated WebDAV. |
rotate |
Change the host's passphrase (the old one stops working immediately). |
remove |
Delete the host and its content, and remove the local .plandrop. |
create
Run create in the directory you want to associate with a host:
npx plandrop create --domain https://plandrop.example.com
It calls the control plane, receives a generated host label and passphrase, and
writes a .plandrop file here. It prints the shareable URL and a reminder that the file
holds your passphrase.
If a .plandrop already exists in this directory, create refuses unless you pass
--force (which mints a new host and replaces the file).
Where the domain comes from
You can give the domain explicitly, or let the client resolve it. Precedence, highest first:
| Source | Example |
|---|---|
--domain flag |
--domain https://plandrop.example.com |
PLANDROP_DOMAIN env var |
export PLANDROP_DOMAIN=https://plandrop.example.com |
Nearest .plandrop (walking up from the current dir) |
the domain field of a parent directory's .plandrop |
| Per-user config | ~/.config/plandrop/config.json → { "domain": "https://plandrop.example.com" } |
| Interactive prompt / piped stdin | typed at a TTY, or echo https://plandrop.example.com | npx plandrop create |
A bare hostname (no scheme) defaults to https://. An explicit http://… or https://…
URI — including a port — is used as given. The full URI is stored in .plandrop.
upload
Upload a single file (served at its name under the host root):
npx plandrop upload ./planfile.html # → /planfile.html
npx plandrop upload ./planfile.html index.html # → /index.html (served at the bare URL)
Upload a whole directory, preserving its structure verbatim:
npx plandrop upload ./site
It's a plain static host: files are served at their paths, and index.html answers the
bare directory URL. What you upload is what's served.
rotate
npx plandrop rotate
Asks the control plane for a new passphrase and updates .plandrop in place. The old
passphrase stops authorizing writes immediately; reads are unaffected.
remove
npx plandrop remove
Deletes the host (its content and credentials) and removes the local .plandrop. After
this, the host URL returns 404 and writes are rejected.
The .plandrop file
A small JSON file written at mode 0600 in the directory where you ran create:
{
"domain": "https://plandrop.example.com",
"host": "<generated-label>",
"passphrase": "<generated-passphrase>"
}
Subsequent commands (upload, rotate, remove) find the nearest .plandrop by walking
up from the current directory — so you can run them from anywhere inside the project.
Do not commit
.plandrop. It holds your host's passphrase. Add it to.gitignore.
Targeting another host explicitly
Any argument of 8 characters or more in front of the command is treated as a host label
that overrides the one in .plandrop (the passphrase still comes from .plandrop):
npx plandrop <host-label> upload ./planfile.html
Command names are all shorter than 8 characters, so the length of the first argument is enough to tell a command from a host label.