Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place
1# Wisp CLI 2 3A 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. 4 5## Why? 6 7The 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. 8 9## Features 10 11- Deploy static sites directly to your AT Protocol repo 12- Supports both OAuth and app password authentication 13- Preserves directory structure and file integrity 14 15## Soon 16 17-- Host sites 18-- Manage and delete sites 19-- Metrics and logs for self hosting. 20 21## Installation 22 23### From Source 24 25```bash 26cargo build --release 27``` 28 29Check out the build scripts for cross complation using nix-shell. 30 31The binary will be available at `target/release/wisp-cli`. 32 33## Usage 34 35### Commands 36 37The CLI supports three main commands: 38- **deploy**: Upload a site to your PDS (default command) 39- **pull**: Download a site from a PDS to a local directory 40- **serve**: Serve a site locally with real-time firehose updates 41 42### Basic Deployment 43 44Deploy the current directory: 45 46```bash 47wisp-cli nekomimi.pet --path . --site my-site 48``` 49 50Deploy a specific directory: 51 52```bash 53wisp-cli alice.bsky.social --path ./dist/ --site my-site 54``` 55 56Or use the explicit `deploy` subcommand: 57 58```bash 59wisp-cli deploy alice.bsky.social --path ./dist/ --site my-site 60``` 61 62### Pull a Site 63 64Download a site from a PDS to a local directory: 65 66```bash 67wisp-cli pull alice.bsky.social --site my-site --path ./downloaded-site 68``` 69 70This will download all files from the site to the specified directory. 71 72### Serve a Site Locally 73 74Serve a site locally with real-time updates from the firehose: 75 76```bash 77wisp-cli serve alice.bsky.social --site my-site --path ./site --port 8080 78``` 79 80This will: 811. Download the site to the specified path 822. Start a local server on the specified port (default: 8080) 833. Watch the firehose for updates and automatically reload files when changed 84 85### Authentication Methods 86 87#### OAuth (Recommended) 88 89By default, the CLI uses OAuth authentication with a local loopback server: 90 91```bash 92wisp-cli alice.bsky.social --path ./my-site --site my-site 93``` 94 95This will: 961. Open your browser for authentication 972. Save the session to a file (default: `/tmp/wisp-oauth-session.json`) 983. Reuse the session for future deployments 99 100Specify a custom session file location: 101 102```bash 103wisp-cli alice.bsky.social --path ./my-site --site my-site --store ~/.wisp-session.json 104``` 105 106#### App Password 107 108For headless environments or CI/CD, use an app password: 109 110```bash 111wisp-cli alice.bsky.social --path ./my-site --site my-site --password YOUR_APP_PASSWORD 112``` 113 114**Note:** When using `--password`, the `--store` option is ignored. 115 116## Command-Line Options 117 118### Deploy Command 119 120``` 121wisp-cli [deploy] [OPTIONS] <INPUT> 122 123Arguments: 124 <INPUT> Handle (e.g., alice.bsky.social), DID, or PDS URL 125 126Options: 127 -p, --path <PATH> Path to the directory containing your static site [default: .] 128 -s, --site <SITE> Site name (defaults to directory name) 129 --store <STORE> Path to auth store file (only used with OAuth) [default: /tmp/wisp-oauth-session.json] 130 --password <PASSWORD> App Password for authentication (alternative to OAuth) 131 --directory Enable directory listing mode for paths without index files 132 --spa Enable SPA mode (serve index.html for all routes) 133 -y, --yes Skip confirmation prompts (automatically accept warnings) 134 -h, --help Print help 135 -V, --version Print version 136``` 137 138### Pull Command 139 140``` 141wisp-cli pull [OPTIONS] --site <SITE> <INPUT> 142 143Arguments: 144 <INPUT> Handle (e.g., alice.bsky.social) or DID 145 146Options: 147 -s, --site <SITE> Site name (record key) 148 -p, --path <PATH> Output directory for the downloaded site [default: .] 149 -h, --help Print help 150``` 151 152### Serve Command 153 154``` 155wisp-cli serve [OPTIONS] --site <SITE> <INPUT> 156 157Arguments: 158 <INPUT> Handle (e.g., alice.bsky.social) or DID 159 160Options: 161 -s, --site <SITE> Site name (record key) 162 -p, --path <PATH> Output directory for the site files [default: .] 163 -P, --port <PORT> Port to serve on [default: 8080] 164 -h, --help Print help 165``` 166 167## How It Works 168 1691. **Authentication**: Authenticates using OAuth or app password 1702. **File Processing**: 171 - Recursively walks the directory tree 172 - Skips hidden files (starting with `.`) 173 - Detects MIME types automatically 174 - Compresses files with gzip 175 - Base64 encodes compressed content 1763. **Upload**: 177 - Uploads files as blobs to your PDS 178 - Processes up to 5 files concurrently 179 - Creates a `place.wisp.fs` record with the site manifest 1804. **Deployment**: Site is immediately available at `https://sites.wisp.place/{did}/{site-name}` 181 182## File Processing 183 184All files are automatically: 185 186- **Compressed** with gzip (level 9) 187- **Base64 encoded** to bypass PDS content sniffing 188- **Uploaded** as `application/octet-stream` blobs 189- **Stored** with original MIME type metadata 190 191The hosting service automatically decompresses non HTML/CSS/JS files when serving them. 192 193## Limitations 194 195- **Max file size**: 100MB per file (after compression) (this is a PDS limit, but not enforced by the CLI in case yours is higher) 196- **Max file count**: 2000 files 197- **Site name** must follow AT Protocol rkey format rules (alphanumeric, hyphens, underscores) 198 199## Deploy with CI/CD 200 201### GitHub Actions 202 203```yaml 204name: Deploy to Wisp 205on: 206 push: 207 branches: [main] 208 209jobs: 210 deploy: 211 runs-on: ubuntu-latest 212 steps: 213 - uses: actions/checkout@v3 214 215 - name: Setup Node 216 uses: actions/setup-node@v3 217 with: 218 node-version: '25' 219 220 - name: Install dependencies 221 run: npm install 222 223 - name: Build site 224 run: npm run build 225 226 - name: Download Wisp CLI 227 run: | 228 curl -L https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli 229 chmod +x wisp-cli 230 231 - name: Deploy to Wisp 232 env: 233 WISP_APP_PASSWORD: ${{ secrets.WISP_APP_PASSWORD }} 234 run: | 235 ./wisp-cli alice.bsky.social \ 236 --path ./dist \ 237 --site my-site \ 238 --password "$WISP_APP_PASSWORD" 239``` 240 241### Tangled.org 242 243```yaml 244when: 245 - event: ['push'] 246 branch: ['main'] 247 - event: ['manual'] 248 249engine: 'nixery' 250 251clone: 252 skip: false 253 depth: 1 254 submodules: false 255 256dependencies: 257 nixpkgs: 258 - nodejs 259 - coreutils 260 - curl 261 github:NixOS/nixpkgs/nixpkgs-unstable: 262 - bun 263 264environment: 265 SITE_PATH: 'dist' 266 SITE_NAME: 'my-site' 267 WISP_HANDLE: 'your-handle.bsky.social' 268 269steps: 270 - name: build site 271 command: | 272 export PATH="$HOME/.nix-profile/bin:$PATH" 273 274 # regenerate lockfile 275 rm package-lock.json bun.lock 276 bun install @rolldown/binding-linux-arm64-gnu --save-optional 277 bun install 278 279 # build with vite 280 bun node_modules/.bin/vite build 281 282 - name: deploy to wisp 283 command: | 284 # Download Wisp CLI 285 curl https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli 286 chmod +x wisp-cli 287 288 # Deploy to Wisp 289 ./wisp-cli \ 290 "$WISP_HANDLE" \ 291 --path "$SITE_PATH" \ 292 --site "$SITE_NAME" \ 293 --password "$WISP_APP_PASSWORD" 294``` 295 296### Generic Shell Script 297 298```bash 299# Use app password from environment variable 300wisp-cli alice.bsky.social --path ./dist --site my-site --password "$WISP_APP_PASSWORD" 301``` 302 303## Output 304 305Upon successful deployment, you'll see: 306 307``` 308Deployed site 'my-site': at://did:plc:abc123xyz/place.wisp.fs/my-site 309Available at: https://sites.wisp.place/did:plc:abc123xyz/my-site 310``` 311 312### Dependencies 313 314- **jacquard**: AT Protocol client library 315- **clap**: Command-line argument parsing 316- **tokio**: Async runtime 317- **flate2**: Gzip compression 318- **base64**: Base64 encoding 319- **walkdir**: Directory traversal 320- **mime_guess**: MIME type detection 321 322## License 323 324MIT License 325 326## Contributing 327 328Just 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. 329 330## Links 331 332- **Website**: https://wisp.place 333- **Main Repository**: https://tangled.org/@nekomimi.pet/wisp.place-monorepo 334- **AT Protocol**: https://atproto.com 335- **Jacquard Library**: https://tangled.org/@nonbinary.computer/jacquard 336 337## Support 338 339For issues and questions: 340- Check the main wisp.place documentation 341- Open an issue in the main repository