Appearance
Local setup
Prerequisites
- Node.js 22.12 or later. That is the
enginesrange in the rootpackage.json; the Docker image and the maintainers use Node 24. - pnpm 10, through Corepack. The root
package.jsonpinspnpm@10.20.0inpackageManager, and Corepack fetches that version for you. - Git. On Windows, Git Bash and PowerShell both work; see the Windows notes.
Wireface Core, the face engine, is already in the repo (vendor/wireface-core). You only need its own repo, next to this one as ../core, to update that copy (see Wireface Core).
sh
corepack enable
pnpm installThe first build
Build everything once before you start the dev servers:
sh
pnpm buildThis runs build in every package under packages/ and apps/ that has one: the widget (loader, frame and manifest.json), the npm package, the admin panel and the server. pnpm dev rebuilds the widget's loader and frame as you work, but not its manifest.json (the version and SRI hash the admin panel shows for the pinned snippet), and the server's own /admin/ needs apps/admin/dist. For the docs at /docs/ on the server, also run pnpm docs:build.
A widget build also copies the loader into packages/widget/releases/. In development it replaces the copy there (with a warning) when the loader changed, so check git status before you commit: that change belongs in a release only. See Released loaders.
pnpm dev
sh
pnpm devThe root dev script runs three packages' dev scripts in parallel:
| Package | Script | What it does |
|---|---|---|
@wireface/server | node --watch --watch-preserve-output --import tsx src/main.ts | Runs the server from source on port 8800. Node restarts it when any file it imports changes, workspace packages included. |
@wireface/widget | vite build --watch for the loader and for the frame | Rebuilds packages/widget/dist on every change. The server serves the new files straight away: reload the page. |
@wireface/admin | vite | The admin panel with hot reload at http://127.0.0.1:5180/admin/. It proxies /api and /v1 (WebSockets included), /core, /frame, /embed, /widget, /docs and /healthz to the server on 127.0.0.1:8800, so cookies, the widget and the face are same-origin, as in production. |
Open http://127.0.0.1:5180/admin/ to work on the admin panel. http://localhost:8800/admin/ serves the last built copy (pnpm --filter @wireface/admin build; the server picks up a rebuild on the next page load).
To work on the docs, run pnpm docs:dev. It regenerates the reference pages, then starts VitePress (on port 5173, or the next free one) with the docs under /docs/.
Configuration
The server reads .env from its working directory (apps/server when pnpm runs it), then from the repo root. Neither replaces a variable that is already set in the environment, and an empty value counts as not set. Copy the example to the repo root and fill in what you need:
sh
cp .env.example .envNothing in it is required for development. Every variable is listed in Environment variables; the ones that matter in development are these:
| Variable | In development |
|---|---|
NODE_ENV | Defaults to development: readable logs (pino-pretty), every request logged, the widget socket accepted from localhost pages, http:// webhook URLs allowed, source maps served. Tests use test. See Production mode. |
DATA_DIR | Defaults to ./data, relative to the server's working directory: apps/server/data. It holds wireface.db, files/ and master.key. Stop the server and delete the folder to start again from nothing. |
WIREFACE_MASTER_KEY | Leave it empty: the first start generates DATA_DIR/master.key and logs a warning saying so. The database's sealed keys can't be read without that file, so keep the two together. |
SETUP_TOKEN | Leave it empty to get a random one (see below), or set your own (8 characters or more). |
PORT, HOST | 8800 and 0.0.0.0. The admin dev server's proxy expects 8800. |
On the first start the log shows the setup token:
text
First run: open http://localhost:8800/admin/setup and enter this setup token: ...The token is kept in the database until setup is done, so a restart prints the same one. Setup creates the owner account and the workspace; the quick start walks through it.
Provider keys and the mock providers
When setup creates the workspace, the server connects every key it finds in ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY and ELEVENLABS_API_KEY. That happens once. After it, add or change keys in the admin panel under Providers.
To work offline, or without spending anything, run the mock providers and point the server at them:
sh
pnpm --filter @wireface/mock-providers startThey listen on 127.0.0.1:8899 (set MOCK_PORT for another port) and print the PROVIDER_BASE_URL_* and PROVIDER_WS_URL_* lines to add to .env. Restart pnpm dev after adding them. Any API key then works, except one containing bad (rejected) or broke (out of credit). What the mock models answer is described in Testing.
Blank your real keys for the server before setup, or setup imports them into this workspace: they would be stored in your development database and used against the real APIs once the PROVIDER_* lines are gone. Since .env never overrides the environment, OPENAI_API_KEY= in .env is not enough if your shell sets the key. Blank them for the command instead:
sh
# Git Bash
ANTHROPIC_API_KEY= OPENAI_API_KEY= GEMINI_API_KEY= ELEVENLABS_API_KEY= pnpm devpowershell
# PowerShell, for this session
$env:ANTHROPIC_API_KEY = ''; $env:OPENAI_API_KEY = ''; $env:GEMINI_API_KEY = ''; $env:ELEVENLABS_API_KEY = ''
pnpm devTrying the chat on a page
The bot editor's preview in the admin panel shows the unpublished draft. To see a published bot on a page of its own, serve one of the examples:
sh
node examples/serve.mjs plain-html --host http://localhost:8800 --bot pk_your_bot_idIt serves the page at http://localhost:5500 (--port to change it). Add that origin to the bot's allowed origins first (Security in the bot editor). examples/README.md lists the examples.
Windows notes
tsx watchhangs. The server'sdevscript isnode --watch --watch-preserve-output --import tsxrather thantsx watch, becausetsx watchfreezes at startup when stdin is a pipe, which is whatpnpm --parallelgives each process (tsx's watcher reads stdin). Keep it that way.Port 5173 is avoided. It is Vite's default, and other local apps use it (Wireface Core's demo, the simulator), so the admin dev server uses 5180. It sets
strictPort: if 5180 is taken, it stops with an error instead of moving to another port, which the proxy setup and these docs would not expect.Use 127.0.0.1, not localhost, with curl. In Git Bash
localhostcan resolve to the IPv6 address::1first, and the admin dev server only listens on127.0.0.1:shcurl http://127.0.0.1:8800/healthzLine endings. Biome formats with LF. With
core.autocrlf=true(the Git for Windows default) a checkout gets CRLF files andbiome checkreports every one of them. Setgit config core.autocrlf inputin this repo, and make scripts that write files use\n.Stalled connections. On some Windows machines connections to
127.0.0.1stall for a few seconds now and then. A test that fails with a timeout may pass when run again: see Flaky runs.
A production build
sh
pnpm build
pnpm docs:build # optional: the docs at /docs/
NODE_ENV=production pnpm startpnpm start runs node dist/main.js in apps/server: the tsdown bundle, with npm dependencies left external (the native ones, better-sqlite3 and sharp, must be) and the migrations read from apps/server/drizzle. It finds the built widget, admin panel, docs and face engine in the repo. Without NODE_ENV=production it runs in development mode. Running it for real is covered in Docker.