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.html

Publishing 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 url from 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

  1. Ask the owner for an enrolment code

    The owner creates it in the admin: Machines → Add machine. A code works once, for 10 minutes.

  2. 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>

    login makes 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.

  3. Publish

    From then on the key alone authorizes publishing, until the owner revokes the machine. A machine set up earlier with share.mjs needs nothing new: share.mjs still works, and forwards every command to heyadhi.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.woff2

inspect 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.

FlagWhat it does
--generate-passwordMakes a strong password and prints it once as viewer_password. It is not shown again.
--passwordAsks for a password on the terminal without echoing it.
--password-stdinReads a new password from a pipe, for a shell with no terminal.
--remove-passwordOn update only: makes the page public again.
--current-password-stdinReads 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 publish1,000
One publish in total100 MB
index.html10 MB
Any other file50 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.

RouteWhat it does
POST /v1/sites/{name}/publishStep 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}/commitStep 3: switch the page to the new version.
POST /v1/sitesCreate 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/helpRoutes, limits, authentication and errors, as JSON.

Errors

Errors come back as JSON with a message field that says what to do next.

CodeMeaning
401This machine is not enrolled, or was revoked.
403An unsafe path, or an upload the publish id does not allow.
404No such page, or a protected page and the request is not from an enrolled machine.
409The name is already in use, a conflicting update landed first, or files are still missing at the commit.
413Over a size limit.
422Invalid input.
429Too many requests. Wait a minute and retry.
503Briefly unavailable. Retry after the Retry-After header.