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### Basic Deployment
36
37Deploy the current directory:
38
39```bash
40wisp-cli nekomimi.ppet --path . --site my-site
41```
42
43Deploy a specific directory:
44
45```bash
46wisp-cli alice.bsky.social --path ./dist/ --site my-site
47```
48
49### Authentication Methods
50
51#### OAuth (Recommended)
52
53By default, the CLI uses OAuth authentication with a local loopback server:
54
55```bash
56wisp-cli alice.bsky.social --path ./my-site --site my-site
57```
58
59This will:
601. Open your browser for authentication
612. Save the session to a file (default: `/tmp/wisp-oauth-session.json`)
623. Reuse the session for future deployments
63
64Specify a custom session file location:
65
66```bash
67wisp-cli alice.bsky.social --path ./my-site --site my-site --store ~/.wisp-session.json
68```
69
70#### App Password
71
72For headless environments or CI/CD, use an app password:
73
74```bash
75wisp-cli alice.bsky.social --path ./my-site --site my-site --password YOUR_APP_PASSWORD
76```
77
78**Note:** When using `--password`, the `--store` option is ignored.
79
80## Command-Line Options
81
82```
83wisp-cli [OPTIONS] <INPUT>
84
85Arguments:
86 <INPUT> Handle (e.g., alice.bsky.social), DID, or PDS URL
87
88Options:
89 -p, --path <PATH> Path to the directory containing your static site [default: .]
90 -s, --site <SITE> Site name (defaults to directory name)
91 --store <STORE> Path to auth store file (only used with OAuth) [default: /tmp/wisp-oauth-session.json]
92 --password <PASSWORD> App Password for authentication (alternative to OAuth)
93 -h, --help Print help
94 -V, --version Print version
95```
96
97## How It Works
98
991. **Authentication**: Authenticates using OAuth or app password
1002. **File Processing**:
101 - Recursively walks the directory tree
102 - Skips hidden files (starting with `.`)
103 - Detects MIME types automatically
104 - Compresses files with gzip
105 - Base64 encodes compressed content
1063. **Upload**:
107 - Uploads files as blobs to your PDS
108 - Processes up to 5 files concurrently
109 - Creates a `place.wisp.fs` record with the site manifest
1104. **Deployment**: Site is immediately available at `https://sites.wisp.place/{did}/{site-name}`
111
112## File Processing
113
114All files are automatically:
115
116- **Compressed** with gzip (level 9)
117- **Base64 encoded** to bypass PDS content sniffing
118- **Uploaded** as `application/octet-stream` blobs
119- **Stored** with original MIME type metadata
120
121The hosting service automatically decompresses non HTML/CSS/JS files when serving them.
122
123## Limitations
124
125- **Max file size**: 100MB per file (after compression) (this is a PDS limit, but not enforced by the CLI in case yours is higher)
126- **Max file count**: 2000 files
127- **Site name** must follow AT Protocol rkey format rules (alphanumeric, hyphens, underscores)
128
129## Deploy with CI/CD
130
131### GitHub Actions
132
133```yaml
134name: Deploy to Wisp
135on:
136 push:
137 branches: [main]
138
139jobs:
140 deploy:
141 runs-on: ubuntu-latest
142 steps:
143 - uses: actions/checkout@v3
144
145 - name: Setup Node
146 uses: actions/setup-node@v3
147 with:
148 node-version: '25'
149
150 - name: Install dependencies
151 run: npm install
152
153 - name: Build site
154 run: npm run build
155
156 - name: Download Wisp CLI
157 run: |
158 curl -L https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli
159 chmod +x wisp-cli
160
161 - name: Deploy to Wisp
162 env:
163 WISP_APP_PASSWORD: ${{ secrets.WISP_APP_PASSWORD }}
164 run: |
165 ./wisp-cli alice.bsky.social \
166 --path ./dist \
167 --site my-site \
168 --password "$WISP_APP_PASSWORD"
169```
170
171### Tangled.org
172
173```yaml
174when:
175 - event: ['push']
176 branch: ['main']
177 - event: ['manual']
178
179engine: 'nixery'
180
181clone:
182 skip: false
183 depth: 1
184 submodules: false
185
186dependencies:
187 nixpkgs:
188 - nodejs
189 - coreutils
190 - curl
191 github:NixOS/nixpkgs/nixpkgs-unstable:
192 - bun
193
194environment:
195 SITE_PATH: 'dist'
196 SITE_NAME: 'my-site'
197 WISP_HANDLE: 'your-handle.bsky.social'
198
199steps:
200 - name: build site
201 command: |
202 export PATH="$HOME/.nix-profile/bin:$PATH"
203
204 # regenerate lockfile
205 rm package-lock.json bun.lock
206 bun install @rolldown/binding-linux-arm64-gnu --save-optional
207 bun install
208
209 # build with vite
210 bun node_modules/.bin/vite build
211
212 - name: deploy to wisp
213 command: |
214 # Download Wisp CLI
215 curl https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli
216 chmod +x wisp-cli
217
218 # Deploy to Wisp
219 ./wisp-cli \
220 "$WISP_HANDLE" \
221 --path "$SITE_PATH" \
222 --site "$SITE_NAME" \
223 --password "$WISP_APP_PASSWORD"
224```
225
226### Generic Shell Script
227
228```bash
229# Use app password from environment variable
230wisp-cli alice.bsky.social --path ./dist --site my-site --password "$WISP_APP_PASSWORD"
231```
232
233## Output
234
235Upon successful deployment, you'll see:
236
237```
238Deployed site 'my-site': at://did:plc:abc123xyz/place.wisp.fs/my-site
239Available at: https://sites.wisp.place/did:plc:abc123xyz/my-site
240```
241
242### Dependencies
243
244- **jacquard**: AT Protocol client library
245- **clap**: Command-line argument parsing
246- **tokio**: Async runtime
247- **flate2**: Gzip compression
248- **base64**: Base64 encoding
249- **walkdir**: Directory traversal
250- **mime_guess**: MIME type detection
251
252## License
253
254MIT License
255
256## Contributing
257
258Just 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.
259
260## Links
261
262- **Website**: https://wisp.place
263- **Main Repository**: https://tangled.org/@nekomimi.pet/wisp.place-monorepo
264- **AT Protocol**: https://atproto.com
265- **Jacquard Library**: https://tangled.org/@nonbinary.computer/jacquard
266
267## Support
268
269For issues and questions:
270- Check the main wisp.place documentation
271- Open an issue in the main repository