···11---
22-title: Wisp CLI
22+title: Wisp CLI v1.0.0
33description: Command-line tool for deploying static sites to the AT Protocol
44---
5566-`wispctl` deploys static sites to your AT Protocol account from the terminal. Supports incremental updates, OAuth and app password auth, and a local dev server with live firehose updates.
77-88-## Installation
66+**Deploy static sites to the AT Protocol**
971010-```bash
1111-npm install -g wispctl
1212-```
1313-1414-Then use `wispctl` anywhere:
88+The Wisp CLI is a command-line tool for deploying static websites directly to your AT Protocol account. Host your sites on wisp.place with full ownership and control, backed by the decentralized AT Protocol.
1591616-```bash
1717-wispctl deploy your-handle.bsky.social --path ./dist --site my-site
1818-```
1010+## Features
19112020-## Quick Deploy
1212+- **Deploy**: Push static sites directly from your terminal
1313+- **Pull**: Download sites from the PDS for development or backup
1414+- **Serve**: Run a local server with real-time firehose updates
1515+- **Authenticate** with app password or OAuth
1616+- **Incremental updates**: Only upload changed files
21172222-No install needed — use `npm create wisp` to deploy directly:
2323-2424-```bash
2525-npm create wisp your-handle.bsky.social --path ./dist --site my-site
2626-```
2727-2828-Or with `npx`:
2929-3030-```bash
3131-npx wispctl deploy your-handle.bsky.social --path ./dist --site my-site
3232-```
3333-3434-## Deploying a Site
3535-3636-```bash
3737-wispctl deploy your-handle.bsky.social --path ./dist --site my-site
3838-```
3939-4040-Your site will be at `https://sites.wisp.place/your-handle/my-site`.
4141-4242-The CLI tracks files by content hash (CID), so subsequent deploys only upload what actually changed. First deploy uploads everything; after that, deploys complete in seconds when only a few files differ.
1818+## Downloads
43194444-## Authentication
2020+<div class="downloads">
45214646-OAuth is the default — it opens your browser and saves a session to `/tmp/wisp-oauth-session.json`. For CI/CD or headless environments, use an app password instead:
2222+<h2>Download v1.0.0</h2>
47234848-```bash
4949-wispctl deploy your-handle.bsky.social \
5050- --path ./dist \
5151- --site my-site \
5252- --password YOUR_APP_PASSWORD
5353-```
2424+<a href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-darwin" class="download-link" download="">
54255555-Generate app passwords from your AT Protocol account settings. Don't use your main password.
2626+<span class="platform">macOS (Apple Silicon):</span> wisp-cli-aarch64-darwin
56275757-## Domain Management
2828+</a>
58295959-```bash
6060-# Claim a wisp.place subdomain
6161-wispctl domain claim-subdomain your-handle.bsky.social --subdomain alice
3030+<a href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-darwin" class="download-link" download="">
62316363-# Claim a custom domain
6464-wispctl domain claim your-handle.bsky.social --domain example.com
3232+<span class="platform">macOS (Intel):</span> wisp-cli-x86_64-darwin
65336666-# Check domain status
6767-wispctl domain status your-handle.bsky.social --domain example.com
3434+</a>
68356969-# Attach a site to a domain
7070-wispctl domain add-site your-handle.bsky.social --domain example.com --site mysite
3636+<a href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-linux" class="download-link" download="">
71377272-# Delete a domain or site
7373-wispctl domain delete your-handle.bsky.social --domain example.com
7474-wispctl site delete your-handle.bsky.social --site mysite
7575-```
3838+<span class="platform">Linux (ARM64):</span> wisp-cli-aarch64-linux
76397777-```bash
7878-wispctl list domains your-handle.bsky.social
7979-wispctl list sites your-handle.bsky.social
8080-```
4040+</a>
81418282-## Pulling a Site
4242+<a href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux" class="download-link" download="">
83438484-Download a site from the PDS to your local machine:
4444+<span class="platform">Linux (x86_64):</span> wisp-cli-x86_64-linux
85458686-```bash
8787-wispctl pull your-handle.bsky.social --site my-site --path ./my-site
8888-```
4646+</a>
89479090-## Local Dev Server
4848+<h3 style="margin-top: 1.5rem; margin-bottom: 0.5rem;">SHA-256 Checksums</h3>
91499292-Serve a site locally with real-time updates from the firehose:
5050+<pre style="font-size: 0.75rem; padding: 1rem;" class="language-bash" tabindex="0"><code class="language-bash">
5151+06544b3a3e27a4b8d7b3a46a39fb7205cf90b3061e19fe533b090facd604f375 wisp-cli-aarch64-darwin
5252+9ec523e3ceef927b37adc52d449dcd9e13ea84fa49b0b77f0d5932c94cfe262e wisp-cli-x86_64-darwin
5353+42a262668e13dce36173a4096cdc2b22358b805cf192335f84534c7f695d395b wisp-cli-aarch64-linux
5454+589ee59f3959ddfbc12fea38d2bcb91701f1362f560ae6fd506bebea3150e2cc wisp-cli-x86_64-linux
5555+</code></pre>
93569494-```bash
9595-wispctl serve your-handle.bsky.social --site my-site
9696-wispctl serve your-handle.bsky.social --site my-site --port 3000
9797-wispctl serve your-handle.bsky.social --site my-site --spa # serve index.html for all routes
9898-wispctl serve your-handle.bsky.social --site my-site --directory # directory listing
9999-```
5757+</div>
10058101101-## CI/CD
5959+## CI/CD Integration
1026010361Deploy automatically on every push using Tangled Spindle:
10462···1127011371dependencies:
11472 nixpkgs:
7373+ - nodejs
11574 - coreutils
11675 - curl
11776 - glibc
···12786 - name: build site
12887 command: |
12988 export PATH="$HOME/.nix-profile/bin:$PATH"
8989+9090+ # you may need to regenerate the lockfile due to nixery being weird
9191+ # rm package-lock.json bun.lock
13092 bun install
9393+13194 bun run build
1329513396 - name: deploy to wisp
13497 command: |
135135- curl -fsSL https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wispctl
136136- chmod +x wispctl
137137- ./wispctl deploy \
9898+ # Download Wisp CLI
9999+ curl https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli
100100+ chmod +x wisp-cli
101101+102102+ # Deploy to Wisp
103103+ ./wisp-cli \
138104 "$WISP_HANDLE" \
139105 --path "$SITE_PATH" \
140106 --site "$SITE_NAME" \
141107 --password "$WISP_APP_PASSWORD"
142108```
143109144144-Set `WISP_APP_PASSWORD` as a secret in your Tangled Spindle repository settings.
110110+**Note:** Set `WISP_APP_PASSWORD` as a secret in your Tangled Spindle repository settings. Generate an app password from your AT Protocol account settings.
145111146146-## File Processing
112112+## Basic Usage
147113148148-Files are gzip-compressed at level 9 and uploaded as `application/octet-stream` blobs with the original MIME type stored in the manifest. They may also be base64-encoded to bypass content sniffing on legacy reference PDS. The hosting service handles decompression transparently.
114114+### Deploy a Site
149115150150-Common build artifacts like `.git`, `node_modules`, and `.env` are excluded automatically. Customize this with a [`.wispignore` file](/file-filtering).
116116+```bash
117117+# Download and make executable
118118+curl -O https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-darwin
119119+chmod +x wisp-cli-aarch64-darwin
151120152152-## Limits
121121+# Deploy your site
122122+./wisp-cli-aarch64-darwin deploy your-handle.bsky.social \
123123+ --path ./dist \
124124+ --site my-site
125125+```
153126154154-- Max file size: 100 MB (after compression)
155155-- Max total size: 300 MB per site
156156-- Max files: 1,000 per site
157157-- Site name: alphanumeric, hyphens, underscores (AT Protocol rkey format)
127127+Your site will be available at: `https://sites.wisp.place/your-handle/my-site`
128128+129129+### Domain Management
130130+131131+```bash
132132+# Claim a custom domain
133133+./wisp-cli domain claim your-handle.bsky.social --domain example.com
134134+135135+# Claim a subdomain
136136+./wisp-cli domain claim-subdomain your-handle.bsky.social --subdomain alice
158137159159-## Command Reference
138138+# Check domain status
139139+./wisp-cli domain status your-handle.bsky.social --domain example.com
160140161161-### deploy
141141+# Attach a site to a domain
142142+./wisp-cli domain add-site your-handle.bsky.social --domain example.com --site mysite
162143144144+# Delete a domain or site
145145+./wisp-cli domain delete your-handle.bsky.social --domain example.com
146146+./wisp-cli site delete your-handle.bsky.social --site mysite
163147```
164164-wispctl deploy [OPTIONS] <INPUT>
165148166166-Arguments:
167167- <INPUT> Handle (e.g., alice.bsky.social), DID, or PDS URL
149149+### List Domains & Sites
168150169169-Options:
170170- -p, --path <PATH> Path to site directory [default: .]
171171- -s, --site <SITE> Site name (defaults to directory name)
172172- --store <STORE> OAuth session file [default: /tmp/wisp-oauth-session.json]
173173- --password <PASSWORD> App password
151151+```bash
152152+./wisp-cli list domains your-handle.bsky.social
153153+./wisp-cli list sites your-handle.bsky.social
174154```
175155176176-### pull
156156+### Options
157157+158158+Use an alternate proxy service DID:
177159160160+```bash
161161+./wisp-cli list domains your-handle.bsky.social --service did:web:example.com
178162```
179179-wispctl pull [OPTIONS] --site <SITE> <INPUT>
180163181181-Arguments:
182182- <INPUT> Handle or DID
164164+### Pull a Site from PDS
183165184184-Options:
185185- -s, --site <SITE> Site name to download
186186- -p, --path <PATH> Output directory [default: .]
166166+Download a site from the PDS to your local machine:
167167+168168+```bash
169169+# Pull a site to a specific directory
170170+wisp-cli pull your-handle.bsky.social \
171171+ --site my-site \
172172+ --path ./my-site
173173+174174+# Pull to current directory
175175+wisp-cli pull your-handle.bsky.social \
176176+ --site my-site
187177```
188178189189-### serve
179179+### Serve a Site Locally with Real-Time Updates
180180+181181+Run a local server that monitors the firehose for real-time updates:
182182+183183+```bash
184184+# Serve on http://localhost:8080 (default)
185185+wisp-cli serve your-handle.bsky.social \
186186+ --site my-site
190187188188+# Serve on a custom port
189189+wisp-cli serve your-handle.bsky.social \
190190+ --site my-site \
191191+ --port 3000
192192+193193+# Enable SPA mode (serve index.html for all routes)
194194+wisp-cli serve your-handle.bsky.social \
195195+ --site my-site \
196196+ --spa
197197+198198+# Enable directory listing for paths without index files
199199+wisp-cli serve your-handle.bsky.social \
200200+ --site my-site \
201201+ --directory
191202```
192192-wispctl serve [OPTIONS] --site <SITE> <INPUT>
193203194194-Arguments:
195195- <INPUT> Handle or DID
204204+Downloads site, serves it, and watches firehose for live updates!
196205197197-Options:
198198- -s, --site <SITE> Site name
199199- -p, --path <PATH> Site files directory [default: .]
200200- -P, --port <PORT> Port [default: 8080]
201201- --spa Serve index.html for all routes
202202- --directory Directory listing for paths without index files
206206+## Authentication
207207+208208+### OAuth (Recommended)
209209+210210+The CLI uses OAuth by default, opening your browser for secure authentication:
211211+212212+```bash
213213+wisp-cli deploy your-handle.bsky.social --path ./dist --site my-site
203214```
204215205205-## Binary Downloads
216216+This creates a session stored locally (default: `/tmp/wisp-oauth-session.json`).
206217207207-Pre-built binaries are available if you can't use npm.
218218+### App Password
208219209209-<div class="downloads">
220220+For headless environments or CI/CD, use an app password:
210221211211-<h2>Download v1.0.0</h2>
222222+```bash
223223+wisp-cli deploy your-handle.bsky.social \
224224+ --path ./dist \
225225+ --site my-site \
226226+ --password YOUR_APP_PASSWORD
227227+```
212228213213-<a href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-darwin" class="download-link" download="">
229229+**Generate app passwords** from your AT Protocol account settings.
214230215215-<span class="platform">macOS (Apple Silicon):</span> wisp-cli-aarch64-darwin
231231+## File Processing
216232217217-</a>
233233+The CLI handles all file processing automatically to ensure reliable storage and delivery. Files are compressed with gzip at level 9 for optimal size reduction, then base64 encoded to bypass PDS content sniffing restrictions. Everything is uploaded as `application/octet-stream` blobs while preserving the original MIME type as metadata. When serving your site, the hosting service automatically decompresses non-HTML/CSS/JS files, ensuring your content is delivered correctly to visitors.
218234219219-<a href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-darwin" class="download-link" download="">
235235+**File Filtering**: The CLI automatically excludes common files like `.git`, `node_modules`, `.env`, and other development artifacts. Customize this with a [`.wispignore` file](/file-filtering).
220236221221-<span class="platform">macOS (Intel):</span> wisp-cli-x86_64-darwin
237237+## Incremental Updates
222238223223-</a>
239239+The CLI tracks file changes using CID-based content addressing to minimize upload times and bandwidth usage. On your first deploy, all files are uploaded to establish the initial site. For subsequent deploys, the CLI compares content-addressed CIDs to detect which files have actually changed, uploading only those that differ from the previous version. This makes fast iterations possible even for large sites, with deploys completing in seconds when only a few files have changed.
224240225225-<a href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-linux" class="download-link" download="">
241241+## Limits
226242227227-<span class="platform">Linux (ARM64):</span> wisp-cli-aarch64-linux
243243+- **Max file size**: 100MB per file (after compression)
244244+- **Max total size**: 300MB per site
245245+- **Max files**: 1000 files per site
246246+- **Site name**: Must follow AT Protocol rkey format (alphanumeric, hyphens, underscores)
228247229229-</a>
248248+## Command Reference
230249231231-<a href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux" class="download-link" download="">
250250+### Deploy Command
232251233233-<span class="platform">Linux (x86_64):</span> wisp-cli-x86_64-linux
252252+```bash
253253+wisp-cli deploy [OPTIONS] <INPUT>
234254235235-</a>
255255+Arguments:
256256+ <INPUT> Handle (e.g., alice.bsky.social), DID, or PDS URL
236257237237-<h3 style="margin-top: 1.5rem; margin-bottom: 0.5rem;">SHA-256 Checksums</h3>
258258+Options:
259259+ -p, --path <PATH> Path to site directory [default: .]
260260+ -s, --site <SITE> Site name (defaults to directory name)
261261+ --store <STORE> OAuth session file path [default: /tmp/wisp-oauth-session.json]
262262+ --password <PASSWORD> App password for authentication
263263+ -h, --help Print help
264264+```
238265239239-<pre style="font-size: 0.75rem; padding: 1rem;" class="language-bash" tabindex="0"><code class="language-bash">
240240-06544b3a3e27a4b8d7b3a46a39fb7205cf90b3061e19fe533b090facd604f375 wisp-cli-aarch64-darwin
241241-9ec523e3ceef927b37adc52d449dcd9e13ea84fa49b0b77f0d5932c94cfe262e wisp-cli-x86_64-darwin
242242-42a262668e13dce36173a4096cdc2b22358b805cf192335f84534c7f695d395b wisp-cli-aarch64-linux
243243-589ee59f3959ddfbc12fea38d2bcb91701f1362f560ae6fd506bebea3150e2cc wisp-cli-x86_64-linux
244244-</code></pre>
266266+### Pull Command
245267246246-</div>
268268+```bash
269269+wisp-cli pull [OPTIONS] --site <SITE> <INPUT>
247270248248-## Building from Source
271271+Arguments:
272272+ <INPUT> Handle or DID
249273250250-The CLI is written in TypeScript and supports both Node.js and Bun runtimes. Run directly with Bun during development, or build a Node.js-compatible bundle for distribution.
274274+Options:
275275+ -s, --site <SITE> Site name to download
276276+ -p, --path <PATH> Output directory [default: .]
277277+ -h, --help Print help
278278+```
279279+280280+### Serve Command
281281+282282+```bash
283283+wisp-cli serve [OPTIONS] --site <SITE> <INPUT>
284284+285285+Arguments:
286286+ <INPUT> Handle or DID
287287+288288+Options:
289289+ -s, --site <SITE> Site name to serve
290290+ -p, --path <PATH> Site files directory [default: .]
291291+ -P, --port <PORT> Port to serve on [default: 8080]
292292+ --spa Enable SPA mode (serve index.html for all routes)
293293+ --directory Enable directory listing mode for paths without index files
294294+ -h, --help Print help
295295+```
296296+297297+## Development
298298+299299+The CLI is written in Rust using the Jacquard AT Protocol library. To build from source:
251300252301```bash
253302git clone https://tangled.org/@nekomimi.pet/wisp.place-monorepo
254303cd cli
255255-bun install
304304+cargo build --release
305305+```
306306+307307+Built binaries are available in `target/release/`.
256308257257-# Run directly with Bun
258258-bun run index.ts
309309+## Related
259310260260-# Build a Node.js bundle (outputs to dist/)
261261-bun run build
262262-node dist/index.js
263263-```
311311+- [place.wisp.fs](/lexicons/place-wisp-fs) - Site manifest lexicon
312312+- [place.wisp.subfs](/lexicons/place-wisp-subfs) - Subtree records for large sites
313313+- [AT Protocol](https://atproto.com) - The decentralized protocol powering Wisp