Hawt CLI

Command-line tools for publishing static sites on hawt.site. This repository builds two binaries:

  • hawt-site (hs): the site publishing CLI. Start here.
  • hawt: the wider Hawt Cloud suite CLI (see below).

Both share one credential store, so logging in with either logs you in to both.

Round Trip in 60 Seconds

Install, create an account, publish a site, look at it, take it down:

# 1. Install hawt-site and the `hs` symlink into ~/.local/bin
curl -fsSL https://api.hawtcloud.com/install-hawt-site-cli.sh | sh

# 2. Onboard: redeem your invite (this saves your token)
hs register -u bob_dev -i hawt_inv_9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c
✔ Registered as bob_dev
✔ Token saved to /Users/bob/.config/hawt/config.json

  This token is your only credential — there are no passwords and no
  recovery. Save a copy somewhere safe:

    hawt_pat_...

  Next: hs deploy ./your-site
# 3. Deploy
mkdir my-site && echo '<h1>Hello, hawt.site</h1>' > my-site/index.html
hs deploy ./my-site
🔥 Uploading ./my-site (182 B)...

✔ Deployed

   URL:        https://hawt.site/hawt/VdUdSS1
   Site ID:    VdUdSS1
   Files:      1
   Size:       25 B
   Expires:    13 days
# 4. Look at it, then clean up
open https://hawt.site/hawt/VdUdSS1
hs rm VdUdSS1

You don't have to configure an endpoint. https://api.hawtcloud.com/v1 is compiled in.

Everything hs Can Do

Command Description
hs register Redeem an invite code and create an account
hs login Save a Personal Access Token
hs whoami Show the current identity and configuration
hs deploy <path> Deploy a file or directory as a static site
hs list List your deployed sites
hs info <site-id> Show details for one site
hs rm <site-id> Delete a site

Every command accepts these global flags:

Flag Description
--json Machine-readable JSON on stdout; the API response body as-is where there is one
-q, --quiet Just the essential value (a URL, a site ID, a username)
--no-color Disable ANSI styling (also honors NO_COLOR and a non-TTY stdout)
--version Version, commit, and build date

--json and --quiet can't be combined.

Onboarding

Have an invite? Register. The email is optional unless the invite is bound to one:

hs register -u bob_dev -i hawt_inv_... -e bob@example.com

Registration returns your Personal Access Token exactly once. There are no passwords and no recovery. If you lose the token, an admin has to mint you a fresh invite. The raw token is printed only to an interactive terminal, and in --json output so a script can capture it.

Already have a token? Log in. The token is checked against the API before it is saved, so a bad paste fails right away:

hs login                  # prompts with echo disabled
echo "$PAT" | hs login    # CI: reads one line from stdin

Or skip the config file entirely:

export HAWT_CLOUD_API_KEY=hawt_pat_...

Something not working? hs whoami is the first thing to run. It shows who you are, which credential is in use and where it came from, the endpoint, and the config file path:

$ hs whoami
🔥 Hawt Site

   Logged in as:  bob_dev (bob@example.com)
   API key:       hawt...4b4c  (config file)
   Endpoint:      https://api.hawtcloud.com/v1  (default)
   Config file:   /Users/bob/.config/hawt/config.json

Deploying

hs deploy ./dist                    # → https://hawt.site/hawt/<id>
hs deploy ./dist --slug portfolio   # → https://hawt.site/hawt/portfolio
hs deploy ./dist --user-scope       # → https://hawt.site/bob_dev/<id>
hs deploy index.html                # a single file
hs deploy site.zip                  # an existing zip, uploaded as-is
open "$(hs deploy ./dist -q)"       # -q prints only the URL
  • Directories lose their top-level folder, so dist/index.html is served at the root of the site.

  • Uploads are capped at 50 MB. Oversized payloads are rejected locally, before anything is sent, with exit code 5 (the same as the server's 413). Each account has a 500 MB storage quota.

  • Sites expire after 14 days.

  • You can't redeploy in place yet. To reuse a slug, remove the old site first:

    hs rm portfolio -y && hs deploy ./dist --slug portfolio
    

Uploads over 1 MB show a progress bar on an interactive terminal.

Managing Sites

hs list                          # table of your sites
hs list --limit 10 --offset 10   # paginate (limit 1100, default 50)
hs info VdUdSS1                  # full details
hs rm VdUdSS1                    # asks for confirmation on a terminal
hs rm VdUdSS1 -y                 # no prompt
$ hs list
🔥 Deployed Sites

  SITE ID     URL                                 FILES  SIZE     EXPIRES  STATUS
  portfolio   https://hawt.site/hawt/portfolio    12     1.4 MB   13 days  active
  VdUdSS1     https://hawt.site/hawt/VdUdSS1      1      25 B     13 days  active

Showing 2 of 2 total sites.
$ hs info VdUdSS1
🔥 Site Details
   Site ID:    VdUdSS1
   URL:        https://hawt.site/hawt/VdUdSS1
   Status:     active
   Owner:      bob_dev
   Path Type:  hawt
   Files:      1
   Size:       25 B
   Created:    Sun, 13 Sep 2026 16:10:00 UTC
   Expires:    Sun, 27 Sep 2026 16:10:00 UTC (13 days)

hs rm never prompts when stdin isn't a terminal or when --json is set, so it won't hang in a pipeline.

Scripting

Results go to stdout. Progress, warnings, and errors go to stderr. Under --json, stdout is the API response body as-is:

# Deploy and capture fields
hs deploy ./dist --json | jq -r '.url, .expires_at'

# Delete every site you own
hs list -q | xargs -n1 hs rm -y

# Find sites expiring within two days (macOS date; on Linux: date -u -d +2days)
hs list --json | jq -r --arg t "$(date -u -v+2d +%FT%TZ)" \
  '.sites[] | select(.expires_at < $t) | .site_id'

# Capture the one-time token at registration
TOKEN=$(hs register -u ci_bot -i "$INVITE" --json | jq -r .api_token)

Under --json, errors are also JSON on stderr: {"error": "...", "status": <int|null>}. Exit codes tell failures apart:

Code Meaning
0 Success (including a declined confirmation)
1 Unexpected internal error
2 Usage error: bad flags or arguments
3 Local filesystem error: path missing, unreadable, config unwritable
4 Authentication error: no credential, or 401
5 API rejected the request: 400, 403, 404, 409, 413, or the local size check that stands in for a 413
6 Network error: DNS, connection refused, timeout

Configuration

Credentials live in ~/.config/hawt/config.json (mode 0600), shared with hawt. hs writes only api_key and preserves every other field.

Setting Precedence, highest first
Credential HAWT_CLOUD_API_KEY, then api_key in the config file
Endpoint --api-url (hidden, for development and self-hosting), then HAWT_CLOUD_API_URL, then api_url in the config file, then the compiled https://api.hawtcloud.com/v1

Whenever the endpoint isn't the default, every command prints a banner to stderr so a local deploy is never mistaken for a production one:

using endpoint http://localhost:8000/v1 (HAWT_CLOUD_API_URL)

The design and full behavior of hs are specified in docs/specs/SPEC-hs-cli.md.

Installation Options

# Installer: hawt-site + hs symlink into ~/.local/bin
curl -fsSL https://api.hawtcloud.com/install-hawt-site-cli.sh | sh
# HAWT_INSTALL_DIR=/somewhere to install elsewhere; HAWT_VERSION=vX.Y.Z to pin

# Go toolchain
go install git.hawt.cloud/hawtcloud/hawt-cli/cmd/hawt-site@latest
go install git.hawt.cloud/hawtcloud/hawt-cli/cmd/hawt@latest

# From a checkout
just build             # both binaries → bin/
just build-hawt-site   # → bin/hawt-site
just build-hawt        # → bin/hawt

Prebuilt archives for each release are described in Release Assets.

The hawt Suite CLI

hawt is the entry point for the wider Hawt Cloud suite. Its site commands overlap with hs. hawt site upload is the longer form of hs deploy.

# Authentication & configuration
hawt auth login                      # prompt for a token
hawt auth info                       # show the active session
hawt register --username bob_dev --email bob@example.com --invite-code hawt_inv_...
hawt config set token hawt_pat_...
hawt config set api-url https://api.hawtcloud.com/v1

# Sites
hawt site upload ./dist                             # → /hawt/<site_id>
hawt site upload ./dist --slug portfolio --user-scope
hawt site list --limit 10 --offset 0
hawt site info <site_id>                            # alias: get
hawt site delete <site_id>

HAWT_CLOUD_API_KEY overrides the saved token for hawt as well.

Running Unit Tests

just test        # go test -v ./...

The Hurl HTTP integration suite exercises the API, not these CLIs, and stays in the hawt-site repository.

Release Assets

Each published release carries one archive per CLI per platform, built by .gitea/workflows/release-cli.yml. The naming is a stable contract that install-hawt-site-cli.sh and any other downloader can rely on:

<cli>_<tag>_<goos>_<goarch>.tar.gz
<cli>_<tag>_<goos>_<goarch>.tar.gz.sha256
Field Values
<cli> hawt, hawt-site
<tag> the release tag, e.g. v0.4.0
<goos> darwin, linux
<goarch> amd64, arm64

Eight archives per release. Each holds exactly the one binary it is named for, placed at the archive root — no wrapper directory, so installing is an untar straight into a bin directory:

TAG=v0.4.0
BASE=https://git.hawt.cloud/hawtcloud/hawt-cli/releases/download/${TAG}
NAME=hawt_${TAG}_linux_amd64.tar.gz

curl -fsSLO "${BASE}/${NAME}"
curl -fsSLO "${BASE}/${NAME}.sha256"
sha256sum -c "${NAME}.sha256"
tar -xzf "${NAME}" -C ~/.local/bin

The .sha256 file records the bare archive name, so sha256sum -c works from whichever directory you downloaded into (on macOS, shasum -a 256 -c).

hs is not a separate asset: it is a symlink to hawt-site that the installer creates. The two CLIs are built from a single tag and share a credential store, but they install independently — take only the one you need.

Binaries are static (CGO_ENABLED=0), built with -trimpath, and stamped with the tag, short commit, and build date:

$ hawt --version
hawt v0.4.0 (commit b32d43a, built 2026-09-12T14:19:09Z)

Layout

hawt-cli/
├── cmd/hawt/          → binary: hawt       (suite CLI)
├── cmd/hawt-site/     → binary: hawt-site  (installed as hs)
├── hawtcmd/           hawt commands
├── hscmd/             hs commands
└── internal/
    ├── client/        HTTP client and typed API/network errors
    ├── config/        config file and credential resolution
    ├── ui/            lipgloss brand palette and formatting
    └── zipper/        payload compression
S
Description
Command line tool for interacting with hawt.site and hawt.cloud services.
Readme MIT
161 KiB
v0.1.0
Latest
2026-09-12 20:31:05 -07:00
Languages
Go 98.9%
Just 1.1%