Publish one HTML file, or a built folder. It becomes a live page at its own address.
share.heyadhi.com turns an HTML file and the files it references, or a whole built folder, into a live page at https://{name}.share.heyadhi.com/. It is a personal service: only machines the owner has enrolled can publish, and there are no accounts and no signup. Pages are public unless they are published with a viewer password.
curl -fsSLO https://share.heyadhi.com/heyadhi.mjs
node heyadhi.mjs share publish q4-launch-plan page.htmlPublishing works only from an enrolled machine. New machine? Start with the setup steps below.
If you are an agent
This page is written so you can follow it. Four rules:
- Publish only when the owner has asked you to publish. Writing an HTML file is not permission to put it online.
- Give the owner the exact
urlfrom the command's output. Never build or guess a URL. - Never send, paste or print this machine's private key. It never leaves the machine.
- Add a viewer password only when the owner asks for one.
The same instructions, packaged: /SKILL.md is an Agent Skill (install it as the short stub it describes, so your copy never goes stale), /llms.txt is the plain-text summary, and /v1/help describes the API as JSON.
Set up a machine
Ask the owner for an enrolment code
The owner creates it in the admin: Machines → Add machine. A code works once, for 10 minutes.
Download the client and enrol this machine
Needs Node 22 or newer.
mkdir -p ~/.local/share/heyadhi-share && cd ~/.local/share/heyadhi-share curl -fsSLO https://share.heyadhi.com/heyadhi.mjs node heyadhi.mjs login <code>loginmakes this machine's key, in~/.config/heyadhi/ed25519.pem, and enrols it. The private key never leaves this machine. You download the client once: before every command it checks for a newer version and replaces itself.Publish
From then on the key alone authorizes publishing, until the owner revokes the machine. A machine set up earlier with
share.mjsneeds nothing new:share.mjsstill works, and forwards every command toheyadhi.mjs.
Publish a page
node heyadhi.mjs share publish q4-launch-plan page.html
node heyadhi.mjs share publish q4-launch-plan dist/A file brings the web files it references that sit beside it. A folder (with index.html at its top, such as a Vite dist/) brings every web file in it. Hidden files, node_modules, files named like secrets (.env, *.pem, *.key, id_rsa, credentials*.json, token*.json) and anything outside the folder are never sent; the output lists them in refused. It prints JSON:
{
"site_id": "q4-launch-plan",
"url": "https://q4-launch-plan.share.heyadhi.com/",
"status": "created",
"files": 3,
"uploaded": 3
}The page is live at url straight away. Each page is its own browser origin, so scripts, storage, cookies and same-origin requests work as they would on any site. Only files the server does not have yet are uploaded, and a publish that hits a network error retries safely and never creates a duplicate page.
Files
A reference to a file that is not there is listed in refs_missing; a file that was found but is not a web file inside the folder is listed in refused. To add one file to a live page:
node heyadhi.mjs share inspect <name>
node heyadhi.mjs share asset <name> ./fonts/Inter.woff2 --path fonts/Inter.woff2inspect lists every referenced file as present or missing. --path is where the file lives on the page and defaults to the file name. Paths must be relative: no .., no leading /.
Update and delete
node heyadhi.mjs share update <name> page.html
node heyadhi.mjs share delete <name>update replaces the page at the same URL; visitors in the middle of loading the old version still get its files for an hour. delete unpublishes the page, and its name is free for a new one.
Viewer password
Pages are public by default. A password makes the page ask visitors for it before showing any of its files.
| Flag | What it does |
|---|---|
--generate-password | Makes a strong password and prints it once as viewer_password. It is not shown again. |
--password | Asks for a password on the terminal without echoing it. |
--password-stdin | Reads a new password from a pipe, for a shell with no terminal. |
--remove-password | On update only: makes the page public again. |
--current-password-stdin | Reads a protected page's current password from a pipe. A machine's key alone neither reads nor changes a protected page: pull, inspect, update and asset need it. |
An update with no password flag keeps the current password. Never put a password on the command line or in a URL.
Limits
| Files in one publish | 1,000 |
|---|---|
| One publish in total | 100 MB |
| index.html | 10 MB |
| Any other file | 50 MB |
API
The client wraps this HTTP API. Every write carries a one-use token: the machine asks for a challenge, signs it with its Ed25519 key, and trades the signature for the token.
| Route | What it does |
|---|---|
POST /v1/sites/{name}/publish | Step 1: the manifest of paths and SHA-256 hashes. Answers the hashes the server is missing and a publish id. |
PUT /v1/blobs/{sha256} | Step 2: one missing file, with the publish id. |
POST /v1/sites/{name}/publish/{id}/commit | Step 3: switch the page to the new version. |
POST /v1/sites | Create a page from {"site_id", "html_content", "password"?} (ht-ml.app compatible). |
GET /v1/sites/{name} | Status, HTML digest, and each referenced file as present or missing (of a protected page, only its status and dates). |
PUT /v1/sites/{name} | Replace the HTML; set, rotate or remove the password. |
POST /v1/sites/{name}/assets?relative_path=… | Upload one file. |
DELETE /v1/sites/{name} | Unpublish. The name is free again afterwards. |
GET /v1/help | Routes, limits, authentication and errors, as JSON. |
Errors
Errors come back as JSON with a message field that says what to do next.
| Code | Meaning |
|---|---|
401 | This machine is not enrolled, or was revoked. |
403 | An unsafe path, or an upload the publish id does not allow. |
404 | No such page, or a protected page and the request is not from an enrolled machine. |
409 | The name is already in use, a conflicting update landed first, or files are still missing at the commit. |
413 | Over a size limit. |
422 | Invalid input. |
429 | Too many requests. Wait a minute and retry. |
503 | Briefly unavailable. Retry after the Retry-After header. |