Skip to content

Repository files navigation

Wirely

Wirely is an AI web UI ideation and editing workspace built with Next.js App Router.
Create a project, pick enabled models/providers, generate one or many page outputs, then iterate HTML in a canvas-style editor with safe previewing and persisted history.

Features

  • Multi-mode generation:
    • single_page
    • concept_variants
    • information_architecture
  • Multi-output planning for coordinated page sets (up to 3 outputs per run).
  • BYOK provider settings per user (Google, OpenRouter, Z.ai, Unsplash).
  • Encrypted user API key storage (AES-256-GCM, user-bound AAD, key versioning).
  • Quality gating and repair pass for generated HTML.
  • Stock image slot planning plus Unsplash resolution and metadata injection.
  • Canvas editor with persistent project/page/conversation history in Postgres.
  • Safe iframe preview constraints for generated content.
  • Route/runtime guardrails:
    • rate limits
    • request body size limits
    • structured logging with sensitive-field redaction

Tech Stack

  • Next.js 16 (App Router) + React 19 + TypeScript (strict)
  • Bun scripts/test runner
  • Tailwind CSS v4 + Radix/shadcn-style components
  • Zustand editor state store
  • Drizzle ORM + Neon Postgres
  • AI SDK providers:
    • Google (@ai-sdk/google)
    • OpenRouter (@openrouter/ai-sdk-provider)
    • Z.ai (OpenAI-compatible client via @ai-sdk/openai)
  • Clerk authentication (Google OAuth flow)

Architecture (High Level)

  1. Home (/) loads user session, project history, and enabled model settings.
  2. Settings (/setting/*) manages profile, provider keys, and enabled models.
  3. Editor (/wire/[id]) loads pages + conversation history.
  4. Generation requests hit:
    • POST /api/projects/[projectId]/generate (authenticated proxy)
    • POST /api/wire/[id] (planning/generation/repair pipeline)
  5. Outputs and messages persist to Postgres (projects, project_pages, conversation_messages, generation_runs, generation_outputs).
  6. Preview renderer sanitizes output and applies iframe security constraints.

Quick Start

1. Prerequisites

  • Bun (latest stable recommended)
  • Postgres database (Neon or compatible)
  • Clerk app configured for Google OAuth

2. Install

bun install

3. Configure environment

Create .env.local from the example:

cp .env.example .env.local

Generate an encryption secret for user API keys:

openssl rand -base64 32

Use the generated value for USER_API_KEY_MASTER_SECRET_BASE64.

4. Run migrations

bun run db:migrate

db:migrate targets the DATABASE_URL in .env.local — point it at a local Postgres for development. Production (Vercel) runs on the database in PROD_DATABASE_URL; apply schema changes there with:

bun run db:migrate:prod

Deploy a schema change and its migration in the same push — code that selects a column the production database does not have yet breaks every signed-in page.

Migration 0013_mighty_juggernaut (or later) is required for the live generation progress, per-screen device frames, and prototype flow features: it adds project_pages.device_type and the project_prototype_flows table.

5. Start development server

bun run dev

Open http://localhost:3000.

6. First-run flow

  1. Sign in via /login.
  2. Add provider keys at /setting/provider.
  3. Enable models at /setting/model.
  4. Create a project from home and generate outputs.

Environment Variables

From .env.example:

Variable Required Purpose
DATABASE_URL Yes Postgres connection string.
USER_API_KEY_MASTER_SECRET_BASE64 Yes 32-byte base64 secret for encrypting BYOK provider keys.
USER_API_KEY_KEY_VERSION Yes Positive integer key version for new encryptions.
USER_API_KEY_PREVIOUS_MASTER_SECRET_BASE64 Optional Previous 32-byte base64 secret for key rotation compatibility.
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY Yes Clerk frontend key.
CLERK_SECRET_KEY Yes Clerk backend key.
NEXT_PUBLIC_CLERK_SIGN_IN_URL Yes Sign-in route, defaults to /login.
REQUEST_BODY_MAX_BYTES Optional Max JSON body size (default 65536).
DB_POOL_MAX Optional DB pool size (default 5).
DB_POOL_CONNECTION_TIMEOUT_MS Optional DB connection timeout ms (default 3000).
DB_POOL_IDLE_TIMEOUT_MS Optional DB idle timeout ms (default 10000).
GOOGLE_GENERATIVE_AI_API_KEY Optional Server key used for project title generation fallback.
OPENROUTER_API_KEY Optional Present in env example for compatibility; user-level key settings are used for generation.

Notes:

  • Generation requests require user-scoped provider keys saved in settings.
  • Unsplash is configured in-app via BYOK (/setting/provider), not via top-level env var.

Scripts

Command Description
bun run dev Start local Next.js dev server.
bun run build Production build.
bun run start Start production server.
bun run lint Run ESLint.
bun run test Run Bun tests.
bun run db:generate Generate Drizzle migration files.
bun run db:migrate Apply Drizzle migrations to the DATABASE_URL database.
bun run db:migrate:prod Apply pending migrations to production (PROD_DATABASE_URL).
bun run db:backfill:page-html Currently points to a missing script path (see Known Limitations).
bun run agent Start the Wirely local agent (runs opencode on your machine).
bun run agent:doctor Check the local opencode install and print the detected flags.

API Surface (Core Routes)

  • GET /api/projects - list current user projects.
  • POST /api/projects - create a project (and initial page).
  • POST /api/projects/[projectId]/generate - authenticated proxy into wire generation route.
  • POST /api/wire/[id] - main planning/generation/repair pipeline.
  • GET /api/profile/ai-settings - read provider-key/model settings flags.
  • PATCH /api/profile/ai-settings - update encrypted provider keys and enabled models.
  • GET /api/projects/[projectId]/pages - list pages. With ?since=<ISO time> it returns only pages written after that time, every page id, and the next cursor. The open editor polls it every 2.5s while the tab is visible, so MCP edits show without a reload.
  • POST /api/projects/[projectId]/pages - create page.
  • PATCH /api/projects/[projectId]/pages/[pageId] - update page.
  • DELETE /api/projects/[projectId]/pages/[pageId] - delete page.
  • GET /api/projects/[projectId]/pages/[pageId]/png - render the saved page to a PNG with headless Chromium, for the frame's "Copy image" button. Rate limited per user.

Local agent routes (see "Local agent" below):

  • POST /api/projects/[projectId]/concepts - queue a screen-concept run for the user's local agent.
  • GET /api/projects/[projectId]/concepts/[jobId] - poll job status.
  • GET /api/profile/agent-tokens - list agent tokens and whether an agent is connected.
  • POST /api/profile/agent-tokens - mint an agent token (plaintext returned once).
  • DELETE /api/profile/agent-tokens - revoke an agent token.
  • GET /api/agent/jobs/next - long-poll claim endpoint, bearer auth only.
  • POST /api/agent/jobs/[jobId]/result - agent reports back, bearer auth only.
  • POST /api/mcp - MCP server (stateless streamable HTTP), bearer auth only; see "MCP server" below.

MCP server (drive Wirely from an agent)

POST /api/mcp is a stateless MCP endpoint: clients POST JSON-RPC messages and read one JSON body back — no SSE session, nothing stored between requests, so it runs as an ordinary serverless function. It authenticates with the same personal bearer tokens as the local agent (minted in Settings → MCP), and never with a Clerk session.

The client's own model does the designing; Wirely stores the results and shows them on the canvas. Tools: list_projects, create_project, list_pages, add_page, update_page, patch_page, get_page, get_page_png, delete_page. patch_page replaces one exact snippet, so an agent can write a page in chunks while the user watches. Screen HTML passes through the same sandbox sanitizer as generated pages. Every HTML write returns the lib/wireQuality.ts score and violation ids for the saved page. get_page_png renders the stored document with headless Chromium (CHROME_PATH picks the local browser in development, deployments use @sparticuz/chromium). With lint: true it also reports horizontal overflow, clipped text, and low contrast text from the same render.

Local agent (bring your own subscription)

The local agent lets Wirely generate designs through the opencode already installed on the user's machine, using whatever providers they configured with opencode auth login. A GitHub Copilot, OpenRouter, or Z.AI coding plan therefore works without Wirely integrating any of them.

Credentials never reach the server. The agent dials out over ordinary HTTPS, so no relay, tunnel, or inbound port is involved. Wirely stores the prompt going out and the model text coming back, and nothing else.

Setup:

  1. Install opencode (1.18.0 or newer) and sign in with opencode auth login.
  2. Mint a token in Wirely settings, under MCP.
  3. npm install -g wirely-agent.
  4. wirely-agent login <token>, then wirely-agent.

The agent ships as its own npm package (agent/): one bundled file, no dependencies, plain node 18 or newer. It needs neither this repo nor bun. Point it at a local Wirely with WIRELY_URL=http://localhost:3000 npx wirely-agent login <token>; the URL is saved with the token.

For development, bun run agent -- login <token> and bun run agent run it straight from the repo. The agent detects which way it was started and prints matching help.

The agent paces its own polling rather than the server holding the request open, because a serverless request is billed for as long as it stays open: parking for 25s cost a full day of function time per connected agent per day. It asks for work every 2s during a session, every 8s between prompts, and every 30s once idle for half an hour, all inside the 90s window isLocalAgentOnline uses. lib/opencode/pollSchedule.ts holds those rates. An agent that sends no version header is assumed to predate this and gets a short server-side pause so it cannot hot loop.

Publishing the agent: bun run agent:build, then cd agent && npm publish. The build is gitignored, and prepublishOnly rebuilds it. Bump version in agent/package.json and AGENT_VERSION in agent/wirely-agent.ts together; a test asserts they match.

Agent environment variables:

Variable Default Description
WIRELY_URL https://wirely.vercel.app Wirely base URL the agent polls.
WIRELY_TOKEN unset Token, overriding the one saved in ~/.config/wirely/agent.json.
OPENCODE_BIN opencode Path to the opencode binary.
WIRELY_MODEL unset Default provider/model when a job does not name one.

Version handling: the agent probes opencode run --help at startup and builds its argv from the flags that build actually accepts, rather than from a hardcoded version table. This is what keeps the 1.18.x line (which takes --variant and --dir) and the 2.0.x line (which uses provider/model#variant and --standalone) both working. Both lines emit the same --format json event stream, which is what the adapter parses.

Security and Reliability Notes

  • Rate limiting on POST /api/wire/[id]:
    • 5 requests per minute
    • 30 requests per hour
    • keyed by userId + IP + route
  • JSON body guard rejects oversized payloads with 413.
  • Structured logger redacts sensitive fields and avoids stack traces in production.
  • Security headers are applied globally in next.config.ts.
  • Generated HTML is sanitized and constrained before iframe preview.

Repository Structure

app/                    # Next.js routes, pages, API handlers
components/             # Editor UI, canvas, renderers
lib/                    # Core logic (auth, db, generation pipeline, security)
store/                  # Zustand stores
hooks/                  # React hooks
drizzle/                # SQL migrations and metadata snapshots
test/                   # Bun tests

Development and Testing

Run quality checks before opening a PR:

bun run lint
bun run test

If you change schema:

bun run db:generate
bun run db:migrate        # local database
bun run db:migrate:prod   # production database, same push as the code

Known Limitations

  • db:backfill:page-html currently references scripts/backfill-page-html-from-conversation.ts, but that file is not present in this repository snapshot.
  • Rate limiting uses an in-memory store by default (not shared across instances).

Contributing

  1. Fork the repo.
  2. Create a branch for your change.
  3. Keep changes scoped and include tests when behavior changes.
  4. Run lint/tests locally.
  5. Open a pull request with context, rationale, and validation notes.

License

No LICENSE file is currently present in this repository.
Add one before distributing or accepting external contributions under a specific open source license.

About

AI-powered platform for generating and iterating multi-page web UI concepts with real-time preview and export

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages