# Wisp CLI
A command-line tool for deploying static sites to your AT Protocol repo to be served on [wisp.place](https://wisp.place), an AT indexer to serve such sites.
## Why?
The PDS serves as a way to verfiably, cryptographically prove that you own your site. That it was you (or at least someone who controls your account) who uploaded it. It is also a manifest of each file in the site to ensure file integrity. Keeping hosting seperate ensures that you could move your site across other servers or even serverless solutions to ensure speedy delievery while keeping it backed by an absolute source of truth being the manifest record and the blobs of each file in your repo.
## Features
- Deploy static sites directly to your AT Protocol repo
- Supports both OAuth and app password authentication
- Preserves directory structure and file integrity
## Soon
-- Host sites
-- Manage and delete sites
-- Metrics and logs for self hosting.
## Installation
### From Source
```bash
cargo build --release
```
Check out the build scripts for cross complation using nix-shell.
The binary will be available at `target/release/wisp-cli`.
## Usage
### Basic Deployment
Deploy the current directory:
```bash
wisp-cli nekomimi.ppet --path . --site my-site
```
Deploy a specific directory:
```bash
wisp-cli alice.bsky.social --path ./dist/ --site my-site
```
### Authentication Methods
#### OAuth (Recommended)
By default, the CLI uses OAuth authentication with a local loopback server:
```bash
wisp-cli alice.bsky.social --path ./my-site --site my-site
```
This will:
1. Open your browser for authentication
2. Save the session to a file (default: `/tmp/wisp-oauth-session.json`)
3. Reuse the session for future deployments
Specify a custom session file location:
```bash
wisp-cli alice.bsky.social --path ./my-site --site my-site --store ~/.wisp-session.json
```
#### App Password
For headless environments or CI/CD, use an app password:
```bash
wisp-cli alice.bsky.social --path ./my-site --site my-site --password YOUR_APP_PASSWORD
```
**Note:** When using `--password`, the `--store` option is ignored.
## Command-Line Options
```
wisp-cli [OPTIONS]
Arguments:
Handle (e.g., alice.bsky.social), DID, or PDS URL
Options:
-p, --path Path to the directory containing your static site [default: .]
-s, --site Site name (defaults to directory name)
--store Path to auth store file (only used with OAuth) [default: /tmp/wisp-oauth-session.json]
--password App Password for authentication (alternative to OAuth)
-h, --help Print help
-V, --version Print version
```
## How It Works
1. **Authentication**: Authenticates using OAuth or app password
2. **File Processing**:
- Recursively walks the directory tree
- Skips hidden files (starting with `.`)
- Detects MIME types automatically
- Compresses files with gzip
- Base64 encodes compressed content
3. **Upload**:
- Uploads files as blobs to your PDS
- Processes up to 5 files concurrently
- Creates a `place.wisp.fs` record with the site manifest
4. **Deployment**: Site is immediately available at `https://sites.wisp.place/{did}/{site-name}`
## File Processing
All files are automatically:
- **Compressed** with gzip (level 9)
- **Base64 encoded** to bypass PDS content sniffing
- **Uploaded** as `application/octet-stream` blobs
- **Stored** with original MIME type metadata
The hosting service automatically decompresses non HTML/CSS/JS files when serving them.
## Limitations
- **Max file size**: 100MB per file (after compression) (this is a PDS limit, but not enforced by the CLI in case yours is higher)
- **Max file count**: 2000 files
- **Site name** must follow AT Protocol rkey format rules (alphanumeric, hyphens, underscores)
## Deploy with CI/CD
### GitHub Actions
```yaml
name: Deploy to Wisp
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '25'
- name: Install dependencies
run: npm install
- name: Build site
run: npm run build
- name: Download Wisp CLI
run: |
curl -L https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli
chmod +x wisp-cli
- name: Deploy to Wisp
env:
WISP_APP_PASSWORD: ${{ secrets.WISP_APP_PASSWORD }}
run: |
./wisp-cli alice.bsky.social \
--path ./dist \
--site my-site \
--password "$WISP_APP_PASSWORD"
```
### Tangled.org
```yaml
when:
- event: ['push']
branch: ['main']
- event: ['manual']
engine: 'nixery'
clone:
skip: false
depth: 1
submodules: false
dependencies:
nixpkgs:
- nodejs
- coreutils
- curl
github:NixOS/nixpkgs/nixpkgs-unstable:
- bun
environment:
SITE_PATH: 'dist'
SITE_NAME: 'my-site'
WISP_HANDLE: 'your-handle.bsky.social'
steps:
- name: build site
command: |
export PATH="$HOME/.nix-profile/bin:$PATH"
# regenerate lockfile
rm package-lock.json bun.lock
bun install @rolldown/binding-linux-arm64-gnu --save-optional
bun install
# build with vite
bun node_modules/.bin/vite build
- name: deploy to wisp
command: |
# Download Wisp CLI
curl https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli
chmod +x wisp-cli
# Deploy to Wisp
./wisp-cli \
"$WISP_HANDLE" \
--path "$SITE_PATH" \
--site "$SITE_NAME" \
--password "$WISP_APP_PASSWORD"
```
### Generic Shell Script
```bash
# Use app password from environment variable
wisp-cli alice.bsky.social --path ./dist --site my-site --password "$WISP_APP_PASSWORD"
```
## Output
Upon successful deployment, you'll see:
```
Deployed site 'my-site': at://did:plc:abc123xyz/place.wisp.fs/my-site
Available at: https://sites.wisp.place/did:plc:abc123xyz/my-site
```
### Dependencies
- **jacquard**: AT Protocol client library
- **clap**: Command-line argument parsing
- **tokio**: Async runtime
- **flate2**: Gzip compression
- **base64**: Base64 encoding
- **walkdir**: Directory traversal
- **mime_guess**: MIME type detection
## License
MIT License
## Contributing
Just don't give me entirely claude slop especailly not in the PR description itself. You should be responsible for code you submit and aware of what it even is you're submitting.
## Links
- **Website**: https://wisp.place
- **Main Repository**: https://tangled.org/@nekomimi.pet/wisp.place-monorepo
- **AT Protocol**: https://atproto.com
- **Jacquard Library**: https://tangled.org/@nonbinary.computer/jacquard
## Support
For issues and questions:
- Check the main wisp.place documentation
- Open an issue in the main repository