claude up some atproto stuff
0

Configure Feed

Select the types of activity you want to include in your feed.

wisp skill: directive auth + serving-model + custom-domain flow

Rewrites the auth section from a "two ways" menu into a directive (run the
command, it opens the browser; don't pre-login or offer app-passwords unless
headless). Adds a serving-model section (SPA mode, _redirects, custom 404,
root-relative rewriter) and its hard limit — static files only, no per-request
OG/meta injection, so per-page link previews need an external renderer while
display pages don't. Documents the async custom-domain claim→DNS→verify→add-site
flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

+35 -4
+35 -4
skills/wisp/SKILL.md
··· 27 27 - `<script>` and `<style>` contents are **not** rewritten (the rewriter is a tag scanner that copies raw-text elements through unchanged), so inline JS is safe. 28 28 - **SvelteKit**: `paths.relative = true` in `svelte.config.js` is the cleanest setup — emit relative asset paths and let the browser resolve them. Prerender where you can; for SPA fallback use `--spa`. 29 29 30 + ## serving model — static files only, no per-request rendering 31 + 32 + wisp's hosting-service serves **static files** from the PDS with a few routing features, and **nothing dynamic per request**. Know these before architecting: 33 + 34 + - **SPA mode** (`--spa` at deploy): unmatched routes serve the SPA fallback file (`index.html`). So client-side routes like `/l/<id>` work — the SPA reads `location.pathname` and renders. Note: with SPA mode, **every** unmatched route returns the *same* HTML. 35 + - **`_redirects` file** (Netlify-style): supports splat patterns (`/old/*`), `:named` path/query params, rewrites (200), redirects (301/302), and custom 404 targets. 36 + - **custom 404**: via `_redirects` or the site `custom404` setting. 37 + - **root-relative rewriter**: the only HTML transformation — rewrites `/`-rooted `src`/`href`/etc. under the base path (see subpath serving above). It does **not** read or inject `<meta>`, `<title>`, or any content. 38 + 39 + **Hard limit: there is no templating and no per-request `<meta>`/OG injection.** Confirmed in `apps/hosting-service` source. Consequences: 40 + 41 + - **Per-page link previews are impossible on wisp alone.** Crawlers (Bluesky's cardyb, Discord, etc.) don't run JS — they read OG tags from the raw HTML. Under SPA mode every URL returns identical `index.html`, so every share unfurls to the *same* card. You can bake one **generic** OG card into `index.html`, but a **per-item** preview image requires an external renderer (a small CF Worker that returns per-item HTML, or pre-generated per-item static files — which a browser-only app can't deploy). 42 + - **Human-facing per-item display pages are fine** without any of that: SPA route + client fetch renders the item, logged-out, zero extra infra. Don't conflate "display page" (client-side, free) with "link preview" (needs server HTML). 43 + 30 44 ## domains 31 45 32 46 ```bash ··· 35 49 wispctl domain add-site <handle> --domain example.com --site my-site 36 50 ``` 37 51 38 - ## auth 52 + Always pass `--domain` (and `--json` for parseable output) — these subcommands prompt interactively for a domain otherwise, which hangs a non-interactive agent. 53 + 54 + ### custom domains are a 4-step, partly-async flow 55 + 56 + 1. **claim** — `wispctl domain claim <handle> --domain doodl.example.com --json` returns the records to create: 57 + - `txtName` / `txtValue` → a **TXT** record (the DID, proving ownership) 58 + - `cnameTarget` → a **CNAME** at the domain, pointed at `<challengeId>.dns.wisp.place` 59 + 2. **set DNS** — create both records. Keep the CNAME **DNS-only (unproxied)** so wisp's host terminates TLS for the custom domain. 60 + 3. **wait for verification** — wisp's verifier polls DNS on **its own cadence**; verification is **not** instant and is **not** triggered by you. `wispctl domain status <handle> --domain ... --json` reports `"verified": false` / `"pendingVerification"` until it flips. Re-running `claim` does **not** force a re-check (same `challengeId` comes back). Poll `status` until `verified: true`; don't block on it — tell the user it's pending on wisp's side. 61 + 4. **add-site** — only **after** `verified: true`. Running `add-site` early fails with `Custom domain not found or not verified`. Then `deploy` as usual; the site is reachable at the custom domain. 62 + 63 + ## auth — just run the command 64 + 65 + **Default behavior: run the authed command directly. It opens a browser for AT Protocol OAuth, the user signs in, and the session is stored (per-directory sqlite at `~/.config/wispctl/state.sqlite`, overridable with `--db`). Subsequent commands reuse it silently.** 66 + 67 + This applies to `deploy`, `domain claim`, `domain add-site`, `domain status`, `list`, etc. — any of them triggers the browser inline on first use. Do **not** treat auth as a separate prerequisite step: 68 + 69 + - Do **not** ask the user to run `wispctl login` first — `login` exists only if someone wants to authenticate ahead of time; it is never required. 70 + - Do **not** ask the user for a Bluesky app password, and do **not** offer the headless path, unless you are in a genuinely headless context (CI, cron, no browser available). Offering it interactively just adds a decision the user didn't need. 39 71 40 - Two ways to authenticate (sessions are stored per directory in a sqlite db at `~/.config/wispctl/state.sqlite`, overridable with `--db`): 72 + When you're about to deploy or claim a domain, the correct move is to run the command and let the browser pop. `wispctl logout` (or `--all`) clears the stored session. 41 73 42 - - **Interactive OAuth** — `wispctl login <handle>` opens a browser for AT Protocol OAuth and stores the session. `deploy` and other commands also trigger this inline on first use if no session exists. `wispctl logout` (or `--all`) clears it. 43 - - **Headless** — pass `--password <app-password>` to `deploy` (and other authed commands) to skip the browser entirely. Use a Bluesky **app password**, not the account password — ideal for CI. (Confirmed against sibling repos: `wisp-cli deploy "$WISP_DID" --path ./dist --site "$NAME" --password "$WISP_APP_PASSWORD"`.) 74 + **Headless / CI only:** pass `--password <app-password>` (a Bluesky **app password**, not the account password) to skip the browser. Example: `wispctl deploy "$WISP_HANDLE" --path ./dist --site "$NAME" --password "$WISP_APP_PASSWORD"`. 44 75 45 76 ## other commands 46 77