···11-test/
22-.DS_STORE
33-jacquard/
44-binaries/
55-# Generated by Cargo
66-# will have compiled files and executables
77-debug
88-target
11+test-site
22+# dependencies (bun install)
33+node_modules
941010-# These are backup files generated by rustfmt
1111-**/*.rs.bk
55+# output
66+out
77+dist
88+*.tgz
1291313-# MSVC Windows builds of rustc generate these, which store debugging information
1414-*.pdb
1010+# code coverage
1111+coverage
1212+*.lcov
15131616-# Generated by cargo mutants
1717-# Contains mutation testing data
1818-**/mutants.out*/
1414+# logs
1515+logs
1616+_.log
1717+report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
19182020-# RustRover
2121-# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
2222-# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
2323-# and can be added to the global gitignore or merged into this file. For a more nuclear
2424-# option (not recommended) you can uncomment the following to ignore the entire idea folder.
2525-#.idea/
1919+# dotenv environment variable files
2020+.env
2121+.env.development.local
2222+.env.test.local
2323+.env.production.local
2424+.env.local
2525+2626+# caches
2727+.eslintcache
2828+.cache
2929+*.tsbuildinfo
3030+3131+# IntelliJ based IDEs
3232+.idea
3333+3434+# Finder (MacOS) folder config
3535+.DS_Store
+106
cli/CLAUDE.md
···11+22+Default to using Bun instead of Node.js.
33+44+- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
55+- Use `bun test` instead of `jest` or `vitest`
66+- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
77+- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
88+- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
99+- Use `bunx <package> <command>` instead of `npx <package> <command>`
1010+- Bun automatically loads .env, so don't use dotenv.
1111+1212+## APIs
1313+1414+- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
1515+- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
1616+- `Bun.redis` for Redis. Don't use `ioredis`.
1717+- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
1818+- `WebSocket` is built-in. Don't use `ws`.
1919+- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
2020+- Bun.$`ls` instead of execa.
2121+2222+## Testing
2323+2424+Use `bun test` to run tests.
2525+2626+```ts#index.test.ts
2727+import { test, expect } from "bun:test";
2828+2929+test("hello world", () => {
3030+ expect(1).toBe(1);
3131+});
3232+```
3333+3434+## Frontend
3535+3636+Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
3737+3838+Server:
3939+4040+```ts#index.ts
4141+import index from "./index.html"
4242+4343+Bun.serve({
4444+ routes: {
4545+ "/": index,
4646+ "/api/users/:id": {
4747+ GET: (req) => {
4848+ return new Response(JSON.stringify({ id: req.params.id }));
4949+ },
5050+ },
5151+ },
5252+ // optional websocket support
5353+ websocket: {
5454+ open: (ws) => {
5555+ ws.send("Hello, world!");
5656+ },
5757+ message: (ws, message) => {
5858+ ws.send(message);
5959+ },
6060+ close: (ws) => {
6161+ // handle close
6262+ }
6363+ },
6464+ development: {
6565+ hmr: true,
6666+ console: true,
6767+ }
6868+})
6969+```
7070+7171+HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
7272+7373+```html#index.html
7474+<html>
7575+ <body>
7676+ <h1>Hello, world!</h1>
7777+ <script type="module" src="./frontend.tsx"></script>
7878+ </body>
7979+</html>
8080+```
8181+8282+With the following `frontend.tsx`:
8383+8484+```tsx#frontend.tsx
8585+import React from "react";
8686+import { createRoot } from "react-dom/client";
8787+8888+// import .css files directly and it works
8989+import './index.css';
9090+9191+const root = createRoot(document.body);
9292+9393+export default function Frontend() {
9494+ return <h1>Hello, world!</h1>;
9595+}
9696+9797+root.render(<Frontend />);
9898+```
9999+100100+Then, run index.ts
101101+102102+```sh
103103+bun --hot ./index.ts
104104+```
105105+106106+For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`.
cli/Cargo.lock
rust-cli/Cargo.lock
cli/Cargo.toml
rust-cli/Cargo.toml
+6-332
cli/README.md
···11-# Wisp CLI
22-33-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.
44-55-## Why?
66-77-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.
88-99-## Features
1010-1111-- Deploy static sites directly to your AT Protocol repo
1212-- Supports both OAuth and app password authentication
1313-- Preserves directory structure and file integrity
1414-1515-## Soon
11+# cli
1621717--- Host sites
1818--- Manage and delete sites
1919--- Metrics and logs for self hosting.
2020-2121-## Installation
2222-2323-### From Source
33+To install dependencies:
244255```bash
2626-cargo build --release
2727-```
2828-2929-Check out the build scripts for cross complation using nix-shell.
3030-3131-The binary will be available at `target/release/wisp-cli`.
3232-3333-## Usage
3434-3535-### Commands
3636-3737-The CLI supports three main commands:
3838-- **deploy**: Upload a site to your PDS (default command)
3939-- **pull**: Download a site from a PDS to a local directory
4040-- **serve**: Serve a site locally with real-time firehose updates
4141-4242-### Basic Deployment
4343-4444-Deploy the current directory:
4545-4646-```bash
4747-wisp-cli nekomimi.pet --path . --site my-site
4848-```
4949-5050-Deploy a specific directory:
5151-5252-```bash
5353-wisp-cli alice.bsky.social --path ./dist/ --site my-site
5454-```
5555-5656-Or use the explicit `deploy` subcommand:
5757-5858-```bash
5959-wisp-cli deploy alice.bsky.social --path ./dist/ --site my-site
6060-```
6161-6262-### Pull a Site
6363-6464-Download a site from a PDS to a local directory:
6565-6666-```bash
6767-wisp-cli pull alice.bsky.social --site my-site --path ./downloaded-site
6868-```
6969-7070-This will download all files from the site to the specified directory.
7171-7272-### Serve a Site Locally
7373-7474-Serve a site locally with real-time updates from the firehose:
7575-7676-```bash
7777-wisp-cli serve alice.bsky.social --site my-site --path ./site --port 8080
7878-```
7979-8080-This will:
8181-1. Download the site to the specified path
8282-2. Start a local server on the specified port (default: 8080)
8383-3. Watch the firehose for updates and automatically reload files when changed
8484-8585-### Authentication Methods
8686-8787-#### OAuth (Recommended)
8888-8989-By default, the CLI uses OAuth authentication with a local loopback server:
9090-9191-```bash
9292-wisp-cli alice.bsky.social --path ./my-site --site my-site
9393-```
9494-9595-This will:
9696-1. Open your browser for authentication
9797-2. Save the session to a file (default: `/tmp/wisp-oauth-session.json`)
9898-3. Reuse the session for future deployments
9999-100100-Specify a custom session file location:
101101-102102-```bash
103103-wisp-cli alice.bsky.social --path ./my-site --site my-site --store ~/.wisp-session.json
104104-```
105105-106106-#### App Password
107107-108108-For headless environments or CI/CD, use an app password:
109109-110110-```bash
111111-wisp-cli alice.bsky.social --path ./my-site --site my-site --password YOUR_APP_PASSWORD
112112-```
113113-114114-**Note:** When using `--password`, the `--store` option is ignored.
115115-116116-## Command-Line Options
117117-118118-### Deploy Command
119119-120120-```
121121-wisp-cli [deploy] [OPTIONS] <INPUT>
122122-123123-Arguments:
124124- <INPUT> Handle (e.g., alice.bsky.social), DID, or PDS URL
125125-126126-Options:
127127- -p, --path <PATH> Path to the directory containing your static site [default: .]
128128- -s, --site <SITE> Site name (defaults to directory name)
129129- --store <STORE> Path to auth store file (only used with OAuth) [default: /tmp/wisp-oauth-session.json]
130130- --password <PASSWORD> App Password for authentication (alternative to OAuth)
131131- --directory Enable directory listing mode for paths without index files
132132- --spa Enable SPA mode (serve index.html for all routes)
133133- -y, --yes Skip confirmation prompts (automatically accept warnings)
134134- -h, --help Print help
135135- -V, --version Print version
136136-```
137137-138138-### Pull Command
139139-140140-```
141141-wisp-cli pull [OPTIONS] --site <SITE> <INPUT>
142142-143143-Arguments:
144144- <INPUT> Handle (e.g., alice.bsky.social) or DID
145145-146146-Options:
147147- -s, --site <SITE> Site name (record key)
148148- -p, --path <PATH> Output directory for the downloaded site [default: .]
149149- -h, --help Print help
150150-```
151151-152152-### Serve Command
153153-154154-```
155155-wisp-cli serve [OPTIONS] --site <SITE> <INPUT>
156156-157157-Arguments:
158158- <INPUT> Handle (e.g., alice.bsky.social) or DID
159159-160160-Options:
161161- -s, --site <SITE> Site name (record key)
162162- -p, --path <PATH> Output directory for the site files [default: .]
163163- -P, --port <PORT> Port to serve on [default: 8080]
164164- -h, --help Print help
165165-```
166166-167167-## How It Works
168168-169169-1. **Authentication**: Authenticates using OAuth or app password
170170-2. **File Processing**:
171171- - Recursively walks the directory tree
172172- - Skips hidden files (starting with `.`)
173173- - Detects MIME types automatically
174174- - Compresses files with gzip
175175- - Base64 encodes compressed content
176176-3. **Upload**:
177177- - Uploads files as blobs to your PDS
178178- - Processes up to 5 files concurrently
179179- - Creates a `place.wisp.fs` record with the site manifest
180180-4. **Deployment**: Site is immediately available at `https://sites.wisp.place/{did}/{site-name}`
181181-182182-## File Processing
183183-184184-All files are automatically:
185185-186186-- **Compressed** with gzip (level 9)
187187-- **Base64 encoded** to bypass PDS content sniffing
188188-- **Uploaded** as `application/octet-stream` blobs
189189-- **Stored** with original MIME type metadata
190190-191191-The hosting service automatically decompresses non HTML/CSS/JS files when serving them.
192192-193193-## Limitations
194194-195195-- **Max file size**: 100MB per file (after compression) (this is a PDS limit, but not enforced by the CLI in case yours is higher)
196196-- **Max file count**: 2000 files
197197-- **Site name** must follow AT Protocol rkey format rules (alphanumeric, hyphens, underscores)
198198-199199-## Deploy with CI/CD
200200-201201-### GitHub Actions
202202-203203-```yaml
204204-name: Deploy to Wisp
205205-on:
206206- push:
207207- branches: [main]
208208-209209-jobs:
210210- deploy:
211211- runs-on: ubuntu-latest
212212- steps:
213213- - uses: actions/checkout@v3
214214-215215- - name: Setup Node
216216- uses: actions/setup-node@v3
217217- with:
218218- node-version: '25'
219219-220220- - name: Install dependencies
221221- run: npm install
222222-223223- - name: Build site
224224- run: npm run build
225225-226226- - name: Download Wisp CLI
227227- run: |
228228- curl -L https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli
229229- chmod +x wisp-cli
230230-231231- - name: Deploy to Wisp
232232- env:
233233- WISP_APP_PASSWORD: ${{ secrets.WISP_APP_PASSWORD }}
234234- run: |
235235- ./wisp-cli alice.bsky.social \
236236- --path ./dist \
237237- --site my-site \
238238- --password "$WISP_APP_PASSWORD"
66+bun install
2397```
2408241241-### Tangled.org
242242-243243-```yaml
244244-when:
245245- - event: ['push']
246246- branch: ['main']
247247- - event: ['manual']
248248-249249-engine: 'nixery'
250250-251251-clone:
252252- skip: false
253253- depth: 1
254254- submodules: false
255255-256256-dependencies:
257257- nixpkgs:
258258- - nodejs
259259- - coreutils
260260- - curl
261261- github:NixOS/nixpkgs/nixpkgs-unstable:
262262- - bun
263263-264264-environment:
265265- SITE_PATH: 'dist'
266266- SITE_NAME: 'my-site'
267267- WISP_HANDLE: 'your-handle.bsky.social'
268268-269269-steps:
270270- - name: build site
271271- command: |
272272- export PATH="$HOME/.nix-profile/bin:$PATH"
273273-274274- # regenerate lockfile
275275- rm package-lock.json bun.lock
276276- bun install @rolldown/binding-linux-arm64-gnu --save-optional
277277- bun install
278278-279279- # build with vite
280280- bun node_modules/.bin/vite build
281281-282282- - name: deploy to wisp
283283- command: |
284284- # Download Wisp CLI
285285- curl https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli
286286- chmod +x wisp-cli
287287-288288- # Deploy to Wisp
289289- ./wisp-cli \
290290- "$WISP_HANDLE" \
291291- --path "$SITE_PATH" \
292292- --site "$SITE_NAME" \
293293- --password "$WISP_APP_PASSWORD"
294294-```
295295-296296-### Generic Shell Script
99+To run:
2971029811```bash
299299-# Use app password from environment variable
300300-wisp-cli alice.bsky.social --path ./dist --site my-site --password "$WISP_APP_PASSWORD"
301301-```
302302-303303-## Output
304304-305305-Upon successful deployment, you'll see:
306306-307307-```
308308-Deployed site 'my-site': at://did:plc:abc123xyz/place.wisp.fs/my-site
309309-Available at: https://sites.wisp.place/did:plc:abc123xyz/my-site
1212+bun run index.ts
31013```
31114312312-### Dependencies
313313-314314-- **jacquard**: AT Protocol client library
315315-- **clap**: Command-line argument parsing
316316-- **tokio**: Async runtime
317317-- **flate2**: Gzip compression
318318-- **base64**: Base64 encoding
319319-- **walkdir**: Directory traversal
320320-- **mime_guess**: MIME type detection
321321-322322-## License
323323-324324-MIT License
325325-326326-## Contributing
327327-328328-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.
329329-330330-## Links
331331-332332-- **Website**: https://wisp.place
333333-- **Main Repository**: https://tangled.org/@nekomimi.pet/wisp.place-monorepo
334334-- **AT Protocol**: https://atproto.com
335335-- **Jacquard Library**: https://tangled.org/@nonbinary.computer/jacquard
336336-337337-## Support
338338-339339-For issues and questions:
340340-- Check the main wisp.place documentation
341341-- Open an issue in the main repository
1515+This project was created using `bun init` in bun v1.3.5. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
···11-import type { BlobRef } from "@atproto/lexicon";
11+import type { BlobRef } from "@atproto/api";
22import type { Directory, File } from "@wisp/lexicons/types/place/wisp/fs";
33import { CID } from 'multiformats/cid';
44import { sha256 } from 'multiformats/hashes/sha2';
···11+test/
22+.DS_STORE
33+jacquard/
44+binaries/
55+# Generated by Cargo
66+# will have compiled files and executables
77+debug
88+target
99+1010+# These are backup files generated by rustfmt
1111+**/*.rs.bk
1212+1313+# MSVC Windows builds of rustc generate these, which store debugging information
1414+*.pdb
1515+1616+# Generated by cargo mutants
1717+# Contains mutation testing data
1818+**/mutants.out*/
1919+2020+# RustRover
2121+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
2222+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
2323+# and can be added to the global gitignore or merged into this file. For a more nuclear
2424+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
2525+#.idea/
+341
rust-cli/README.md
···11+# Wisp CLI
22+33+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.
44+55+## Why?
66+77+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.
88+99+## Features
1010+1111+- Deploy static sites directly to your AT Protocol repo
1212+- Supports both OAuth and app password authentication
1313+- Preserves directory structure and file integrity
1414+1515+## Soon
1616+1717+-- Host sites
1818+-- Manage and delete sites
1919+-- Metrics and logs for self hosting.
2020+2121+## Installation
2222+2323+### From Source
2424+2525+```bash
2626+cargo build --release
2727+```
2828+2929+Check out the build scripts for cross complation using nix-shell.
3030+3131+The binary will be available at `target/release/wisp-cli`.
3232+3333+## Usage
3434+3535+### Commands
3636+3737+The CLI supports three main commands:
3838+- **deploy**: Upload a site to your PDS (default command)
3939+- **pull**: Download a site from a PDS to a local directory
4040+- **serve**: Serve a site locally with real-time firehose updates
4141+4242+### Basic Deployment
4343+4444+Deploy the current directory:
4545+4646+```bash
4747+wisp-cli nekomimi.pet --path . --site my-site
4848+```
4949+5050+Deploy a specific directory:
5151+5252+```bash
5353+wisp-cli alice.bsky.social --path ./dist/ --site my-site
5454+```
5555+5656+Or use the explicit `deploy` subcommand:
5757+5858+```bash
5959+wisp-cli deploy alice.bsky.social --path ./dist/ --site my-site
6060+```
6161+6262+### Pull a Site
6363+6464+Download a site from a PDS to a local directory:
6565+6666+```bash
6767+wisp-cli pull alice.bsky.social --site my-site --path ./downloaded-site
6868+```
6969+7070+This will download all files from the site to the specified directory.
7171+7272+### Serve a Site Locally
7373+7474+Serve a site locally with real-time updates from the firehose:
7575+7676+```bash
7777+wisp-cli serve alice.bsky.social --site my-site --path ./site --port 8080
7878+```
7979+8080+This will:
8181+1. Download the site to the specified path
8282+2. Start a local server on the specified port (default: 8080)
8383+3. Watch the firehose for updates and automatically reload files when changed
8484+8585+### Authentication Methods
8686+8787+#### OAuth (Recommended)
8888+8989+By default, the CLI uses OAuth authentication with a local loopback server:
9090+9191+```bash
9292+wisp-cli alice.bsky.social --path ./my-site --site my-site
9393+```
9494+9595+This will:
9696+1. Open your browser for authentication
9797+2. Save the session to a file (default: `/tmp/wisp-oauth-session.json`)
9898+3. Reuse the session for future deployments
9999+100100+Specify a custom session file location:
101101+102102+```bash
103103+wisp-cli alice.bsky.social --path ./my-site --site my-site --store ~/.wisp-session.json
104104+```
105105+106106+#### App Password
107107+108108+For headless environments or CI/CD, use an app password:
109109+110110+```bash
111111+wisp-cli alice.bsky.social --path ./my-site --site my-site --password YOUR_APP_PASSWORD
112112+```
113113+114114+**Note:** When using `--password`, the `--store` option is ignored.
115115+116116+## Command-Line Options
117117+118118+### Deploy Command
119119+120120+```
121121+wisp-cli [deploy] [OPTIONS] <INPUT>
122122+123123+Arguments:
124124+ <INPUT> Handle (e.g., alice.bsky.social), DID, or PDS URL
125125+126126+Options:
127127+ -p, --path <PATH> Path to the directory containing your static site [default: .]
128128+ -s, --site <SITE> Site name (defaults to directory name)
129129+ --store <STORE> Path to auth store file (only used with OAuth) [default: /tmp/wisp-oauth-session.json]
130130+ --password <PASSWORD> App Password for authentication (alternative to OAuth)
131131+ --directory Enable directory listing mode for paths without index files
132132+ --spa Enable SPA mode (serve index.html for all routes)
133133+ -y, --yes Skip confirmation prompts (automatically accept warnings)
134134+ -h, --help Print help
135135+ -V, --version Print version
136136+```
137137+138138+### Pull Command
139139+140140+```
141141+wisp-cli pull [OPTIONS] --site <SITE> <INPUT>
142142+143143+Arguments:
144144+ <INPUT> Handle (e.g., alice.bsky.social) or DID
145145+146146+Options:
147147+ -s, --site <SITE> Site name (record key)
148148+ -p, --path <PATH> Output directory for the downloaded site [default: .]
149149+ -h, --help Print help
150150+```
151151+152152+### Serve Command
153153+154154+```
155155+wisp-cli serve [OPTIONS] --site <SITE> <INPUT>
156156+157157+Arguments:
158158+ <INPUT> Handle (e.g., alice.bsky.social) or DID
159159+160160+Options:
161161+ -s, --site <SITE> Site name (record key)
162162+ -p, --path <PATH> Output directory for the site files [default: .]
163163+ -P, --port <PORT> Port to serve on [default: 8080]
164164+ -h, --help Print help
165165+```
166166+167167+## How It Works
168168+169169+1. **Authentication**: Authenticates using OAuth or app password
170170+2. **File Processing**:
171171+ - Recursively walks the directory tree
172172+ - Skips hidden files (starting with `.`)
173173+ - Detects MIME types automatically
174174+ - Compresses files with gzip
175175+ - Base64 encodes compressed content
176176+3. **Upload**:
177177+ - Uploads files as blobs to your PDS
178178+ - Processes up to 5 files concurrently
179179+ - Creates a `place.wisp.fs` record with the site manifest
180180+4. **Deployment**: Site is immediately available at `https://sites.wisp.place/{did}/{site-name}`
181181+182182+## File Processing
183183+184184+All files are automatically:
185185+186186+- **Compressed** with gzip (level 9)
187187+- **Base64 encoded** to bypass PDS content sniffing
188188+- **Uploaded** as `application/octet-stream` blobs
189189+- **Stored** with original MIME type metadata
190190+191191+The hosting service automatically decompresses non HTML/CSS/JS files when serving them.
192192+193193+## Limitations
194194+195195+- **Max file size**: 100MB per file (after compression) (this is a PDS limit, but not enforced by the CLI in case yours is higher)
196196+- **Max file count**: 2000 files
197197+- **Site name** must follow AT Protocol rkey format rules (alphanumeric, hyphens, underscores)
198198+199199+## Deploy with CI/CD
200200+201201+### GitHub Actions
202202+203203+```yaml
204204+name: Deploy to Wisp
205205+on:
206206+ push:
207207+ branches: [main]
208208+209209+jobs:
210210+ deploy:
211211+ runs-on: ubuntu-latest
212212+ steps:
213213+ - uses: actions/checkout@v3
214214+215215+ - name: Setup Node
216216+ uses: actions/setup-node@v3
217217+ with:
218218+ node-version: '25'
219219+220220+ - name: Install dependencies
221221+ run: npm install
222222+223223+ - name: Build site
224224+ run: npm run build
225225+226226+ - name: Download Wisp CLI
227227+ run: |
228228+ curl -L https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli
229229+ chmod +x wisp-cli
230230+231231+ - name: Deploy to Wisp
232232+ env:
233233+ WISP_APP_PASSWORD: ${{ secrets.WISP_APP_PASSWORD }}
234234+ run: |
235235+ ./wisp-cli alice.bsky.social \
236236+ --path ./dist \
237237+ --site my-site \
238238+ --password "$WISP_APP_PASSWORD"
239239+```
240240+241241+### Tangled.org
242242+243243+```yaml
244244+when:
245245+ - event: ['push']
246246+ branch: ['main']
247247+ - event: ['manual']
248248+249249+engine: 'nixery'
250250+251251+clone:
252252+ skip: false
253253+ depth: 1
254254+ submodules: false
255255+256256+dependencies:
257257+ nixpkgs:
258258+ - nodejs
259259+ - coreutils
260260+ - curl
261261+ github:NixOS/nixpkgs/nixpkgs-unstable:
262262+ - bun
263263+264264+environment:
265265+ SITE_PATH: 'dist'
266266+ SITE_NAME: 'my-site'
267267+ WISP_HANDLE: 'your-handle.bsky.social'
268268+269269+steps:
270270+ - name: build site
271271+ command: |
272272+ export PATH="$HOME/.nix-profile/bin:$PATH"
273273+274274+ # regenerate lockfile
275275+ rm package-lock.json bun.lock
276276+ bun install @rolldown/binding-linux-arm64-gnu --save-optional
277277+ bun install
278278+279279+ # build with vite
280280+ bun node_modules/.bin/vite build
281281+282282+ - name: deploy to wisp
283283+ command: |
284284+ # Download Wisp CLI
285285+ curl https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli
286286+ chmod +x wisp-cli
287287+288288+ # Deploy to Wisp
289289+ ./wisp-cli \
290290+ "$WISP_HANDLE" \
291291+ --path "$SITE_PATH" \
292292+ --site "$SITE_NAME" \
293293+ --password "$WISP_APP_PASSWORD"
294294+```
295295+296296+### Generic Shell Script
297297+298298+```bash
299299+# Use app password from environment variable
300300+wisp-cli alice.bsky.social --path ./dist --site my-site --password "$WISP_APP_PASSWORD"
301301+```
302302+303303+## Output
304304+305305+Upon successful deployment, you'll see:
306306+307307+```
308308+Deployed site 'my-site': at://did:plc:abc123xyz/place.wisp.fs/my-site
309309+Available at: https://sites.wisp.place/did:plc:abc123xyz/my-site
310310+```
311311+312312+### Dependencies
313313+314314+- **jacquard**: AT Protocol client library
315315+- **clap**: Command-line argument parsing
316316+- **tokio**: Async runtime
317317+- **flate2**: Gzip compression
318318+- **base64**: Base64 encoding
319319+- **walkdir**: Directory traversal
320320+- **mime_guess**: MIME type detection
321321+322322+## License
323323+324324+MIT License
325325+326326+## Contributing
327327+328328+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.
329329+330330+## Links
331331+332332+- **Website**: https://wisp.place
333333+- **Main Repository**: https://tangled.org/@nekomimi.pet/wisp.place-monorepo
334334+- **AT Protocol**: https://atproto.com
335335+- **Jacquard Library**: https://tangled.org/@nonbinary.computer/jacquard
336336+337337+## Support
338338+339339+For issues and questions:
340340+- Check the main wisp.place documentation
341341+- Open an issue in the main repository