# Development setup

> How to run the Moonspell server, the web app, Nova and the edge Worker locally from a public clone, and what does and does not work without the private app and CLI.

This page is for working on the hosted product's public parts: the server, Nova, the web app, the edge Worker, the contracts and the specs. To build games with the engine, you do not need any of this: start with [Quickstart](https://docs.moonspell.app/start/quickstart.md).

## What works from a public clone

| Works | Does not work without the private parts |
|---|---|
| The server and the web app at `http://localhost:3000` | Worker runs: the sandbox snapshot needs the private bundling CLI (`npm run snapshot:pack` stops with one sentence) |
| Accounts, the feed, the shared-game page, the play page | `npm run starter:upload`, `npm run android:backfill`, `npm run web:backfill` (they export games) |
| Chatting with Nova (with an Anthropic API key) | The iOS and Android apps and the App Clip |
| The edge Worker under `wrangler dev`: the proxy, live progress, the meter | Generated phone contracts (`npm run contracts` writes the web files only) |
| `npm run typecheck`, `npm run lint`, `npm test`, `npm run build` | |
| `npm run contracts`, `npm run parity`, `npm run db:migrate` | |

Nova answers and starts runs locally, but without a sandbox snapshot a run fails at its sandbox step, and Nova treats it as a failure on the platform's side (she retries once, then gives up). See [What is open and what is private](https://docs.moonspell.app/start/open-source.md).

## Prerequisites

- Node 24 (`engines` in the root `package.json`) and npm.
- A Postgres database. The server uses `@neondatabase/serverless`, so a Neon database or branch fits best. Use a database of your own, never one a deployment uses.
- An Anthropic API key, for Nova.
- Optional: a Vercel Blob token (chat pictures and uploads), and a Cloudflare account for `wrangler`.

## The server

```bash
npm install
cp .env.example .env.local      # then fill it in (below)
npm run db:migrate              # applies db/migrations to DATABASE_URL
npm run dev                     # next dev on :3000: the web app, the API, Nova at /eve/v1/*, the workflows
```

`npm run dev` sets `NODE_OPTIONS=--network-family-autoselection-attempt-timeout=2000`, which gives each address of a host two seconds to connect instead of Node's default, so database calls do not time out on slow networks.

The smallest `.env.local` for the web app and Nova:

| Variable | Value |
|---|---|
| `DATABASE_URL` | Your database's connection string |
| `APP_SESSION_SECRET` | Any long random string |
| `INTERNAL_API_SECRET` | Any long random string |
| `ANTHROPIC_API_KEY` | Your key; Nova needs it |
| `PUBLIC_BASE_URL` | `http://localhost:3000` (`.env.example` has the hosted value) |
| `CRESCENT_EDGE_URL`, `REALTIME_SECRET` | Empty, or a local edge (below) |
| `CRESCENT_REQUIRE_SIGN_IN` | `off`, unless you set up Google sign-in with `GOOGLE_WEB_CLIENT_ID` |
| `BLOB_READ_WRITE_TOKEN` | Optional: picture uploads in the chat |

`CRESCENT_APP_URL` defaults to `http://localhost:3000`; set it when `next dev` runs on another port. Every other variable in `.env.example` is for real runs or the hosted product, and each has a comment. [Deploy](https://docs.moonspell.app/moonspell/deploy.md) lists them by step.

Locally the web app runs games on the other loopback name: open the app on `http://localhost:3000` and games load from `http://127.0.0.1:3000`, or the other way round (`src/web/play.ts`).

## The edge Worker

```bash
cd edge
npm install
npm run typecheck
printf 'REALTIME_SECRET=any-local-test-secret\n' > .dev.vars          # git-ignored, never commit
npx wrangler dev --local --var ORIGIN_URL:http://localhost:3000       # proxies to next dev, serves /rt/* on :8787
```

To send the server's events through it, set `CRESCENT_EDGE_URL=http://localhost:8787` and the same `REALTIME_SECRET` in `.env.local`. A sandbox runs on the provider's network and cannot reach a Worker on your machine, so live events from a real run need a deployed edge.

To try the meter against a stand-in for Anthropic, also pass `--var ANTHROPIC_UPSTREAM:http://localhost:<port>` and put `ANTHROPIC_API_KEY` and `INTERNAL_API_SECRET` in `.dev.vars`.

After editing `edge/wrangler.jsonc`, run `npx wrangler types` without a `.dev.vars` present, and commit the regenerated `worker-configuration.d.ts`.

## Contracts, specs and docs

```bash
npm run contracts -- --check                      # generated web files are current
npm run parity                                    # what each app is missing
npm --prefix docs-site run start -- --port 3100   # this site
```

See [Three apps, one product](https://docs.moonspell.app/moonspell/three-apps.md).

## Troubleshooting

| Symptom | Cause |
|---|---|
| Every edge page answers 503 | `ORIGIN_URL` is unset or still the placeholder |
| The edge answers 508 | `ORIGIN_URL` points at the Worker itself |
| `/rt/*` answers 503 | `REALTIME_SECRET` is not set on the edge |
| The app site association file answers 502 | The origin returned something that is not JSON, often a deployment-protection page |
| A run fails at once with "No Claude credential" | No `ANTHROPIC_API_KEY` where the workflow runs |
| A run fails with "DAYTONA_SNAPSHOT is not configured" | No sandbox snapshot; expected in a public clone |
| Making a game answers `sign_in_required` | Sign-in is on: set `CRESCENT_REQUIRE_SIGN_IN=off` locally, or sign in with Google |
