$ htmlup
Go CLI · GitHub Pages + S3 · MIT

htmlup

Publish HTML to the public web in one command.

zsh — htmlup

# Usage

The shape is always htmlup <provider> publish <path>. Point it at a single .html file or a directory of static assets — directories upload recursively, preserving structure. On success it prints the public URL and exits.

GitHub Pages
# publish a directory to GitHub Pages
htmlup github publish ./site --repo owner/repo

# with options: branch, subdir, custom domain
htmlup github publish ./site --repo owner/repo \
  --branch gh-pages --dir docs --cname example.com
S3 (exposed via CloudFront)
# upload objects to a bucket
htmlup s3 publish ./site --bucket my-bucket

# with key prefix and explicit region
htmlup s3 publish ./site --bucket my-bucket \
  --prefix path/ --region us-east-1
Global flags (every provider)
--dry-run      # enumerate what would upload + the resulting URL; no writes
-v, --verbose  # per-file progress and SDK-level detail

# Two backends

Backends are pluggable behind one Provider interface — adding a target is one new package plus registration, no edits to the publish flow.

GitHub Pages github

Commits your file set to the target branch via the Git Data API, creates the branch if missing, optionally writes a CNAME, and flips Pages on if it isn't already. You get the *.github.io URL back.

S3 s3

One PutObject per file with content-type inferred from the extension. No bucket-policy or website-config mutation — you own public exposure, expected via CloudFront in front of the bucket.

# Stateless by design

htmlup is single-purpose and boring on purpose. No daemon, no local database, no tracking of what was published.

uploads & exits

No lifecycle management. Every invocation publishes and quits. (GitHub Pages expiry, if you want it, is an opt-in cron Action you copy into the target repo — never run by the CLI.)

never stores credentials

htmlup never reads, prompts for, or caches secrets. Each official SDK owns its own credential resolution.

pluggable backends

Every target implements one interface and self-registers. Contributors add a backend without touching core code.

thin commands

Cobra commands parse flags and delegate; the real logic lives in provider packages and tested pure helpers.

# Authentication

Auth is delegated entirely to the official SDKs' standard credential chains. htmlup holds nothing.

GitHub — token resolution order
# 1. $GITHUB_TOKEN  → 2. $GH_TOKEN  → 3. token from the gh CLI
gh auth login   # missing token? actionable error points you here
AWS — default credential chain
# env vars → ~/.aws/credentials → SSO → role / instance creds
# --region only overrides the resolved region

# Also a Claude Code plugin

This repo doubles as a Claude Code plugin marketplace. It publishes the htmlup skill, which teaches Claude how to drive the CLI.

In Claude Code
/plugin marketplace add truehhart/htmlup
/plugin install htmlup@htmlup

// dogfood  This page is docs/index.html in the repo — and it was published to the web by htmlup itself. Turtles all the way down. A page about a tool, served by the tool, about serving pages.