中文:
README.zh-CN.md
Publish anything in one command. Folders, single .html / .md
files, mdbook projects, source code — als packages the input,
uploads it, and prints a public URL.
als auth login # one-time device-flow login
als ./build # publish a folder → https://k7x2qm4j6p.a.ls
als list # see what you've published
als rm k7x2qm4j6p # take it downA single static binary, no runtime dependencies, talks to
api.a.ls (or any compatible deployment).
Download the matching archive from the releases page and
drop the als binary somewhere on your PATH.
# Linux x64 (static musl)
curl -sSL https://github.com/bkhq/als/releases/latest/download/als-linux-x64.tar.gz \
| tar -xz
sudo install -m 0755 ./als /usr/local/bin/als
# macOS / Linux arm64 / Windows: pick the matching archive# als-linux-x64.tar.gz als-linux-arm64.tar.gz# als-darwin-x64.tar.gz als-darwin-arm64.tar.gz# als-windows-x64.zipRequires a recent stable Rust toolchain.
cargo install --git https://github.com/bkhq/als --bin als
# or, in a checkout of this repo:
just build && ./target/release/als --versionals completion bash > /etc/bash_completion.d/als
als completion zsh >"${fpath[1]}/_als"
als completion fish >~/.config/fish/completions/als.fish$ als auth login
To authorize this device, visit:
https://a.ls/verify
and enter the code:
ABCD-EFGH
Waiting for authorization...
✓ Logged in.
$ als ./build
✓ Deployed: https://k7x2qm4j6p.a.ls
Id: k7x2qm4j6p
Name: fox042
Version: 20260516-091500-b8e5d3
Expires: in 7 daysEvery site has two identifiers:
Id— 10-character base32 string, auto-generated by the server, immutable. It IS the URL subdomain.Name— kebab-case lowercase label (a-z,0-9,-). With--name my-projyou pick it explicitly. Without, the CLI mints<word><NNN>— a short noun + three digits, e.g.fox042ormint007. Easier to recall than the id; mutable later viaals site <id|name> --name <new>. The server is the sole arbiter of name uniqueness; if it rejects the candidate, just retry with your own--name.
als auth login is a one-time step per machine. It writes a token to
~/.config/als/config.toml and exits. Every command after that just
works.
Re-running als ./build from the same directory publishes a new
version to the same URL — als remembers the binding locally
(one TOML file per site under ~/.config/als/sites/) so you don't
have to pass --name every time.
als <path> auto-detects the shape:
| Path | What you get |
|---|---|
Folder with index.html | Static site (the folder is zipped and uploaded as-is). |
Folder with book.toml | Rendered mdbook. |
Folder of *.md (no book.toml) | Auto-bootstrapped mdbook — SUMMARY.md is synthesised for you. |
Single .html | One-page site. |
Single .md | One-chapter mdbook. |
.zip archive | Uploaded as-is. |
| Folder of source code | A read-only code viewer (CodeMirror 6 with file tree, tabs, syntax highlighting). |
| Single source file | Same viewer with one file. |
Force the routing if you want with --kind site or --kind code.
als ./build --name myproj
# → https://k7x2qm4j6p.a.ls (the URL stays stable across re-deploys# with the same --name, no matter where you run it from)--name is kebab-case lowercase: a-z, 0-9, -, with no leading
or trailing -. The name is both a server-side uniqueness key and a
filename in your local pin store, so spaces and other punctuation are
rejected at parse time.
als ./build --pass auto # server generates a memorable password
als ./build --pass "letmein123"# or set a literal oneAlready deployed and want to add or rotate a password?
als site k7x2qm4j6p --pass auto # rotate
als site k7x2qm4j6p --no-pass # remove (make public)als ./build --expires 24h # `5m` / `24h` / `7d` / `1y` / `never`
als ./build --expires 2026-12-31T00:00:00Z # absolute RFC 3339
als site k7x2qm4j6p --expires never # adjust after the fact$ als site k7x2qm4j6p # shows version history
$ als site k7x2qm4j6p --version 20260510-103022-a3f1c2
✓ Activated version 20260510-103022-a3f1c2.$ als preview ./docs
Preview at http://127.0.0.1:54123/
press Ctrl+C to stopThe preview is offline — same renderer the upload pipeline uses,
served from a tempdir on 127.0.0.1. Pass --bind 0.0.0.0 to expose
to the LAN.
als list # what's live, with quota footer
als list --state expired # filter by state
als site k7x2qm4j6p # detail view + version history
als rm k7x2qm4j6p # delete (interactive confirm)
als rm --all-expired -y # batch-remove expired sitesals ./build --quiet # prints just the URL
als ./build --json # full envelope on stdout for jq / etc.JSON exit-code matrix is stable; piping to jq is safe.
Drop an als.toml file at the input root to tweak how als <path>
packs and renders the project. All fields are optional; unknown keys
are a hard error so typos surface immediately. The file itself never
ends up in the bundle.
# Authoritative site id — the strongest cross-machine pin. Auto-written# by `als <path>` on every successful deploy, so you almost never set# it by hand. Once committed, any clone of this repo deploys to the# same site without depending on the local pin store.id = "k7x2qm4j6p"# Project identity. Same kebab-case rule the `--name` CLI flag enforces:# `a-z`, `0-9`, `-`, no leading or trailing `-`. Used as a fallback# before `id` has been written (e.g. the very first deploy from a# committed `name`).name = "fibonacci-demo"# Viewer page title for code uploads. Defaults to the directory name.title = "Q3 Fibonacci demo"# Which file the viewer opens on first load (code mode). Path is# relative to the input root. The CLI exits with# `code_default_file_missing` if the target isn't in the bundle.default_file = "src/fibonacci.ts"# Extra ignore patterns layered on top of the built-in defaults# (.git/, node_modules/, target/, .env, *.pem, *.key, …).# Gitignore syntax; `!pattern` re-includes something a default# already excluded.exclude = [
"scratch/**",
"*.log",
"!keep-this.log",
]| Field | Modes | Effect |
|---|---|---|
id | every mode | Pinned site id — strongest anchor. Sent as site_id on POST /api/deploy; the server resolves identity by it. Auto-written by the deploy command on every success. Only --new bypasses it. |
name | every mode | Project identity used when no id has landed yet; sent as project_name. Overridden per-invocation by --name <n>. |
title | code only | <title> and data-title on the generated viewer shell. |
default_file | code only | First file the viewer opens; must exist in the bundled file set. |
exclude | every mode | Extra gitignore patterns. Single source of truth for per-project filtering — there is no separate .alsignore file. |
~/.config/als/
├── config.toml # API URL + bearer token, per-profile blocks
└── sites/ # one TOML per locally-pinned site
├── k7x2qm4j6p_myproj.toml # filename: <id>_<name>.toml
└── m3p4rs5t2k_fox042.toml
A pin file looks like:
name = "myproj"id = "k7x2qm4j6p"path = "/home/alice/build"url = "https://k7x2qm4j6p.a.ls"last_published = "2026-05-15T19:50:00Z"last_content_hash = "a3f1c2..."The filename combines the immutable id with the lowercased name, so
either side of the underscore is enough to locate a pin —
rm ~/.config/als/sites/k7x2qm4j6p_*.toml is the unscripted equivalent
of als unpin k7x2qm4j6p. You rarely need to touch any of these
directly — als auth login, als config set, and als <path>
maintain them for you.
Environment overrides exist for CI usage:
ALS_API # override the active profile's API URL
ALS_TOKEN # override the active profile's bearer token
ALS_PROFILE # select a named profile| Setting | Value |
|---|---|
| API base | https://api.a.ls |
| Homepage | https://a.ls |
| LLM cheatsheet | https://a.ls/llms.txt |
| Per-archive cap | 50 MB |
| Code-mode caps | ≤ 50 files · ≤ 256 KB each · ≤ 2 MB total · ≤ 8 directory levels |
Override the API with ALS_API, als config set default.api <url>,
or by passing --profile <name> to use a different profile.
docs/cli-spec.md— every command, flag, exit code, error sentinel.docs/architecture.md— internal crate map if you want to hack onalsitself.docs/development.md— local build + test workflow.docs/api-reference.md— typedals-apiclient reference for embedders.llms.txt— agent-friendly cheatsheet, also served at https://a.ls/llms.txt.
MIT.