The hosti CLI
hosti pushes bundles, sets their sharing state and prints their links. It talks HTTP to your catalog and never opens the database.
Install
The CLI needs Node 24.21.0 or later. Each CLI release is a hosti-cli.tgz attached to a cli-v<version> release on GitHub:
github.com/JakubSzwajka/hosti/releases
Install the latest one:
npm install -g https://github.com/JakubSzwajka/hosti/releases/latest/download/hosti-cli.tgz
From a checkout of the repository, pack and install it yourself:
pnpm --filter @hosti/cli run pack:tgz
npm install -g ./apps/cli/hosti-cli.tgz
Log in
hosti login https://hosti.example
hosti login asks the owner for a push token by browser approval:
- The CLI makes a push token and a polling secret on your machine and sends only their SHA-256 digests.
- It prints a link to
/connect/<id>and a short code, such asKX4F-9QLM, each on its own line, and tries to open the link in a browser. - The owner opens the link, logs in if needed, checks that the page shows the same code, and approves.
- The CLI writes
{"url","token"}to~/.config/hosti.json, or$XDG_CONFIG_HOME/hosti.json, with mode0600. It prints the token's name and scopes, never the token.
A connection lasts ten minutes. If the owner denies it, it runs out, or the server forgot it after a restart, the CLI exits 1 and says to run hosti login again.
hosti login https://hosti.example --name "ci runner" # default: <user>@<hostname>
hosti login https://hosti.example --allow-delete # ask for delete too
hosti whoami # the URL, the token name and its scopes
hosti logout # forget the saved login
A new login asks for the publish and share scopes, and for delete only with --allow-delete. The owner may grant less than you asked for, never more.
logout only edits the local file. A push token cannot revoke itself, so it keeps working until the owner revokes it on /tokens.
Commands
This is the CLI's own usage text, from hosti --help:
hosti login <url> [--name N] [--allow-delete]
hosti whoami
hosti logout
hosti push <path> --slug <slug> [--title T] [--collection C]
[--allow-absolute]
hosti ls [--collection C]
hosti share <slug> --mode private|link|pin [--pin 4821]
hosti rotate <slug>
hosti rm <slug> [--yes]
hosti open <slug> [--open]
hosti prune <slug>
push
hosti push ./out --slug squad-2026 --title "Squad 2026" --collection reports
push takes a directory or one .html file. It leaves out .git, node_modules, .DS_Store and ._ sidecars. --title sets the name shown in the catalog; without it the bundle is named after its slug. --collection puts the bundle in a collection. A later push that names none leaves both as they are.
The folder needs index.html at its root. A folder with exactly one root .html file and no index.html has that file stored as index.html. The server refuses anything over 50 MB compressed, 2000 files, or 20 MB per file.
A reference that starts with a slash asks for the root of the domain, so it will 404 under the bundle's path. push warns about each one and still goes through. It never rewrites your files. --allow-absolute skips the check.
$ hosti push ./fixtures/root-absolute --slug repo-atlas
warning 5 root-absolute references will 404 under /v/repo-atlas/
about.html:4 <script src="/assets/nav.js">
index.html:6 <link rel="stylesheet" href="/assets/atlas.css" />
fix them, or push anyway with --allow-absolute
pushed revision 1
private nothing answers at the share URL
admin https://hosti.example/b/repo-atlas
ls
Lists bundles as a table: slug, revision, collection, last update, sharing state and share link. --collection C shows one collection. The sharing column prints private, link or pin, the same words the catalog uses.
share
hosti share squad-2026 --mode link
hosti share squad-2026 --mode pin --pin 4821
hosti share squad-2026 --mode private
share is the only way the CLI moves the sharing state. --mode pin needs --pin unless the bundle already holds a pin. The CLI refuses --pin with any other mode before a request leaves. Nothing ever prints the digits of a pin.
rotate
hosti rotate <slug> mints a fresh share slug. The old URL stops answering. It leaves the sharing state and the pin alone.
rm
hosti rm <slug> deletes a bundle with every revision and its share link. It asks first. With no terminal attached to ask, it refuses unless you pass --yes. It needs the delete scope.
open
Prints where a bundle can be read: its share link, or its owner-only catalog page while it is private. The URL is always the last line, alone, so hosti open x | tail -1 is a URL. --open also hands it to the browser. An unknown slug exits 1.
prune
Trims a bundle to the newest few revisions and prints which it kept and which it removed. The count is the server's HOSTI_KEEP_REVISIONS. The CLI has no flag for it.
Settings
Each setting resolves in this order:
--urland--tokenHOSTI_URLandHOSTI_TOKEN- the saved login in
~/.config/hosti.json, or$XDG_CONFIG_HOME/hosti.json
hosti login and hosti logout warn when HOSTI_URL or HOSTI_TOKEN is set, because those override the saved login.
Exit codes
0- Done.
1- The server refused, or the command failed. The reason is on the last line, in the server's own words where it gave one.
2- A usage error, or a missing setting. The message names what to set.