Getting started
Clone, install, configure, and run AdStack locally, plus the commands you will use every day.
Requirements
- Bun. The project uses Bun as package manager and runtime. Never
npm,npx,yarn, orpnpm. Usebunxfor global CLIs. - Node 22.12 or newer, only for the docs site in
docs/. - Access to the Cloudflare account and the Railway staging database.
Install and run
bun install
bun run dev
bun run dev runs scripts/dev.ts, not a raw vite command. The dev server shares the staging database, so local development reads and writes the same rows staging does. There is no separate local database.
Environment
Secrets live in .env for the app and .dev.vars for the Workers, which is the Cloudflare convention. Nothing secret belongs on a command line or in a commit.
Two prefixes, two meanings:
| Prefix | Visibility | Notes |
|---|---|---|
VITE_ |
Public, in the client bundle | Feature flags and public URLs |
| no prefix | Server only | Cloudflare bindings and secrets, rotatable without redeploy |
Worker secrets are set per worker with wrangler secret put <NAME> --name <worker>. The full list for the app worker is documented in the comment block at the bottom of wrangler.jsonc.
Commands
| Command | What it does |
|---|---|
bun run dev |
Dev server |
bun run check |
typecheck + lint + check, the gate before any commit |
bun run typecheck |
tsc --noEmit, zero errors is enforced |
bun run lint |
Ultracite (Biome preset) in check mode |
bun run test |
Vitest |
bun run build |
Vite build, also regenerates routeTree.gen.ts |
bun run smoke |
Runtime resolution checks (prompts resolve, modules load) |
bun run knip |
Dead-code detection |
| Command | What it does |
|---|---|
bun run db:sql "SELECT ..." |
Run a query. Never use psql with a password on the command line. |
bun run db:migrate |
Apply pending migrations from db/migrations/ |
bun run db:status |
Show which migrations have run |
| Command | What it does |
|---|---|
bun run check:truth |
Asserts registry = database = prompt files |
bun run sync:prompts --diff <slug> |
Show the difference between a prompt file and the DB |
bun run sync:prompts --apply <slug> |
Push the file to the database |
bun run sync:prompts --pull <slug> |
Pull an admin UI edit back into the file |
bun run infra:snapshot |
Regenerate .claude/project/infra.md from Cloudflare |
| Command | Target |
|---|---|
push to staging |
Cloudflare Workers Builds rebuilds adstack-staging |
cd cloudflare-agents && bunx wrangler deploy --config wrangler.jsonc |
Agents worker, manual |
cd workers && bunx wrangler deploy |
Pipeline worker, manual |
Two gaps worth knowing
Neither the linter nor the build type-checks:
- Ultracite is a Biome preset. It does formatting and single-file style rules. It has no cross-file knowledge, so it cannot see a function returning
numberwherestringwas expected. bun run buildis plainvite build. Vite strips TypeScript with esbuild without checking it. A green build means the bundle assembled, nothing more.
bun run typecheck is the only thing that catches type errors, and it is enforced at zero.
Repo layout
- components/ UI, grouped by surface (chat, images, offers, sheets, ui)
- hooks/ React Query hooks and client state
- lib/
- registry.ts single source of truth for names
- agents/registry.ts agent definitions for the chat loop
- chat-v2/ the agentic loop
- db/ Drizzle schema and data access
- api/route-helpers.ts auth, ownership, credits, worker submit
- src/
- routes/ file-based routes, including routes/api/
- pages/ the real screens
- cloudflare-agents/ the LLM worker
- workers/ the media and offer pipeline worker
- db/migrations/ SQL migrations, the only way schema changes
- prompts/ system.md and user.md per module
- scripts/ dev, migrate, check-truth, sync-prompts, infra-snapshot, smoke
- docs/ this documentation site
components/, hooks/, and lib/ live at the repo root, not under src/. src/ holds routes/ (including routes/api/) and pages/. Route files are thin wrappers; the real screens live in src/pages/app/.