Contributing to Manifest

May 6, 2026 · View on GitHub

Thanks for your interest in contributing to Manifest! This guide will help you get up and running.

codecov

Tech Stack

LayerTechnology
FrontendSolidJS, uPlot, custom CSS theme
BackendNestJS 11, TypeORM, PostgreSQL 16
AuthBetter Auth (auto-login on localhost)
RoutingOpenAI-compatible proxy (/v1/chat/completions)
BuildTurborepo + npm workspaces

The full NestJS + SolidJS stack runs locally against PostgreSQL, and the same database backend is used in the cloud version. For local development, the simplest option is to run PostgreSQL in Docker and point DATABASE_URL at it.

Prerequisites

  • Node.js 24.x (LTS)
  • npm 10.x

Repository Structure

Manifest is a monorepo managed with Turborepo and npm workspaces.

packages/
├── shared/               # Shared TypeScript types and constants
├── backend/              # NestJS API server (TypeORM, PostgreSQL, Better Auth)
├── frontend/             # SolidJS single-page app (Vite, uPlot)
└── wingman/              # Dev-only gateway tester — never shipped in Docker / cloud bundles

Self-hosting is supported via the Docker image.

Getting Started

  1. Fork and clone the repository:
git clone https://github.com/<your-username>/manifest.git
cd manifest
npm install
  1. Set up environment variables:
cp packages/backend/.env.example packages/backend/.env

Edit packages/backend/.env with at least:

PORT=3001
BIND_ADDRESS=127.0.0.1
NODE_ENV=development
BETTER_AUTH_SECRET=<run: openssl rand -hex 32>
DATABASE_URL=postgresql://myuser:mypassword@localhost:5432/mydatabase
API_KEY=dev-api-key-12345
SEED_DATA=true
  1. Start PostgreSQL locally.

If you already have PostgreSQL running, you can skip this step and reuse it. Otherwise, the quickest option is Docker:

docker run --name manifest-postgres \
  -e POSTGRES_USER=myuser \
  -e POSTGRES_PASSWORD=mypassword \
  -e POSTGRES_DB=mydatabase \
  -p 5432:5432 \
  -d postgres:16

That container matches the sample DATABASE_URL above:

DATABASE_URL=postgresql://myuser:mypassword@localhost:5432/mydatabase

Useful commands:

docker start manifest-postgres   # start again later
docker stop manifest-postgres    # stop without deleting data
docker rm -f manifest-postgres   # remove the container
  1. Start the development servers (in separate terminals):
# Backend (must preload dotenv — needs to start separately because nest's
# CLI watcher doesn't compose with turbo's parallel runner)
cd packages/backend && NODE_OPTIONS='-r dotenv/config' npx nest start --watch

# Frontend + Wingman (turbo runs both in parallel)
npm run dev

The frontend runs on http://localhost:3000 and proxies API requests to the backend on http://localhost:3001. Wingman runs on http://localhost:3002 and is embedded as an iframe in the dashboard's bottom drawer (FAB at bottom-right, or ⌘/Ctrl+Shift+W).

npm run dev is filtered to exactly manifest-frontend + manifest-wingman — adding a new workspace with a dev script will not silently join the dev set. Wingman is excluded from every production path (Dockerfile filter, .dockerignore, changeset config, and the __DEV_MODE__ build constant in packages/frontend/vite.config.ts which dead-code-eliminates the drawer when VITE_MANIFEST_SELFHOSTED=true).

  1. With SEED_DATA=true, you can log in with admin@manifest.build / manifest.

Testing Routing with a Personal AI Agent

Manifest is a smart router for any personal AI agent that speaks OpenAI-compatible HTTP. The list of supported agents lives in packages/shared/src/agent-type.ts — OpenClaw, Hermes, OpenAI SDK, Vercel AI SDK, LangChain, and cURL are all first-class. The dashboard's "Connect Agent" flow generates the right setup snippet for whichever platform you pick.

This section walks through OpenClaw because it's the deepest integration and the easiest to wire up end-to-end. The same backend also handles all other agents — just follow the dashboard instructions after creating the agent, or grab the snippet shown by the setup modal.

To test routing against your local backend, add Manifest as a model provider in your OpenClaw config:

  1. Build and start the backend in self-hosted mode:
npm run build
MANIFEST_MODE=selfhosted PORT=38238 BIND_ADDRESS=127.0.0.1 \
  node -r dotenv/config packages/backend/dist/main.js
  1. Create an agent in the dashboard at http://localhost:38238 and get the API key.

  2. Add Manifest as a provider in OpenClaw:

openclaw config set models.providers.manifest '{"baseUrl":"http://localhost:38238/v1","api":"openai-completions","apiKey":"mnfst_YOUR_KEY","models":[{"id":"auto","name":"Manifest Auto"}]}'
openclaw config set agents.defaults.model.primary manifest/auto
openclaw gateway restart

The backend runs standalone and OpenClaw talks to it as a regular OpenAI-compatible provider — no plugin needed. For other agents (OpenAI SDK, Vercel AI SDK, LangChain, cURL, …) follow the corresponding tab in the dashboard's "Connect Agent" modal — the underlying endpoint and auth are identical.

When to use this:

  • Testing routing, tier assignment, or model resolution
  • Debugging the proxy or message recording
  • Working on the dashboard UI with live data

Wingman — built-in gateway tester (dev only)

Wingman is an in-dashboard playground for sending one-shot requests at the gateway while impersonating any of the agents/SDKs Manifest tracks (OpenClaw, Hermes, OpenAI SDK, Vercel AI SDK, LangChain, cURL, Raw). It's a separate Vite SPA at packages/wingman/ that the dashboard embeds via an iframe drawer when running in dev mode.

Why it exists: reproducing a customer report or verifying routing usually means standing up the full agent. Wingman cuts that to one click — pick a profile, type a message, see the request and response side by side, with the assistant's text, status pill, latency, tokens, and full headers/body dumps in tabs. Each send is saved to a localStorage history sidebar so you can compare runs and replay any of them.

Features:

  • 7 profiles with byte-correct headers + body shapes captured from the real CLIs/SDKs
  • Editable SDK code panel that actually executes via stubbed OpenAI / Vercel AI / LangChain TS runtimes (request goes through the user's typed code, not the form, when edited)
  • Save-to-Gist button that copies a redacted markdown report (API keys masked) for sharing in bug reports
  • Pink GitHub menu (Repository · Open issue · Start discussion · Browse · Contributing) and Discord shortcut

Running it

The dashboard auto-discovers Wingman at backend port + 1. Start two things:

# Terminal 1 — Postgres (skip if already running)
docker start manifest-postgres

# Terminal 2 — backend on :3001
cd packages/backend && NODE_OPTIONS='-r dotenv/config' npx nest start --watch

# Terminal 3 — frontend (:3000) + Wingman (:3002), both started by turbo
npm run dev

Open http://localhost:3000, sign in, then look at the bottom-right corner: a pink 🪶 Wingman floating action button toggles the drawer. The drawer slides up over half the viewport, embeds Wingman in an iframe at http://localhost:3002 with ?baseUrl=http://localhost:3000 pre-filled, and you can drag its top edge to resize. Keyboard: ⌘/Ctrl + Shift + W toggles, Esc closes.

To use Wingman with a single-service backend on a custom port (e.g. you're running node packages/backend/dist/main.js directly with PORT=38238), set the matching env vars on both processes — the backend's frame-src CSP and CORS allowlist read WINGMAN_PORT, and Wingman's Vite reads WINGMAN_PORT + WINGMAN_BACKEND_PORT:

# Backend
WINGMAN_PORT=38239 PORT=38238 node -r dotenv/config packages/backend/dist/main.js

# Wingman
WINGMAN_PORT=38239 WINGMAN_BACKEND_PORT=38238 npm run dev --workspace=manifest-wingman

Where Wingman is excluded

Wingman is intentionally not shipped to production or Docker self-hosted bundles. The exclusion is enforced in four places:

LayerMechanism
packages/frontend/vite.config.ts__DEV_MODE__ is true only when Vite runs in dev mode (vite serve). Any production build sets it to false so esbuild dead-code-eliminates the FAB + drawer module.
docker/DockerfileBuild stage runs turbo build --filter=manifest-backend --filter=manifest-frontend --filter=manifest-shared — Wingman is never built.
.dockerignorepackages/wingman/ is excluded from the build context.
.changeset/config.jsonmanifest-wingman is in the ignored list — it can never trigger a Docker release.

After every change to the Wingman code, verify the production bundle stays clean:

npm run build --workspace=manifest-frontend
grep -lc 'wingman\|Wingman' packages/frontend/dist/assets/*.js | grep -v ':0$'
# ^ no JS chunk should match

Available Scripts

CommandDescription
npm run devStart frontend in watch mode (start backend separately)
npm run buildProduction build (shared, backend, frontend via Turborepo)
npm startStart the production server
npm test --workspace=packages/backendRun backend unit tests (Jest)
npm run test:e2e --workspace=packages/backendRun backend e2e tests (Jest + Supertest)
npm test --workspace=packages/frontendRun frontend tests (Vitest)
npm test --workspace=packages/sharedRun shared tests (Jest)

Working with Individual Packages

Backend (packages/backend)

  • Framework: NestJS 11 with TypeORM 0.3 and PostgreSQL 16
  • Auth: Better Auth (email/password + Google, GitHub, Discord OAuth)
  • Tests: Jest for unit tests (*.spec.ts), Supertest for e2e tests (test/)
  • Key directories: entities/ (data models), analytics/ (dashboard queries), routing/ (proxy, scoring, tier assignment), auth/ (session management)

Frontend (packages/frontend)

  • Framework: SolidJS with Vite
  • Charts: uPlot for time-series visualization
  • Tests: Vitest
  • Key directories: pages/ (route components), components/ (shared UI), services/ (API client, auth client)

Shared (packages/shared)

  • TypeScript types, constants, and helpers used by both the backend and the frontend.
  • Built with tsc — both CJS and ESM outputs are produced so it can be consumed from either package.

Making Changes

Workflow

  1. Create a branch from main for your change
  2. Make your changes in the relevant package(s)
  3. Write or update tests as needed
  4. Add a changeset if your change should appear in the release notes:
npx changeset
# → select "manifest"
# → choose patch / minor / major
# → write a one-line summary

Always target manifest — it's the canonical release version for the whole project, and it's the only package changesets will accept. manifest-backend, manifest-frontend, and manifest-shared are ignored regardless of what you pick. Commit the generated .changeset/*.md alongside your code. Changesets are optional for internal/tooling changes; skip this step if the change doesn't need a CHANGELOG entry.

See packages/manifest/README.md for why this package exists.

  1. Run the test suite to make sure everything passes:
npm test --workspace=packages/shared
npm test --workspace=packages/backend
npm run test:e2e --workspace=packages/backend
npm test --workspace=packages/frontend
  1. Verify the production build works:
npm run build
  1. Open a pull request against main

Cutting a Docker release

Manifest ships as the Docker image at manifestdotbuild/manifest. Releases are manual:

  1. After merging PRs with changesets, a chore: version packages PR will be open on main — merge it to land the version bump in packages/manifest/package.json and update packages/manifest/CHANGELOG.md.
  2. Go to GitHub Actions → Docker → Run workflow, leave the version input blank, click Run.
  3. The workflow reads the version from packages/manifest/package.json and pushes manifestdotbuild/manifest:{version} (plus {major}.{minor}, {major}, and a sha-<short> rollback tag).

If you need to retag an older commit or publish a version that doesn't match the current package.json, pass a semver string in the version input and it overrides the auto-detected value.

Commit Messages

Write clear, concise commit messages that explain why the change was made. Use present tense (e.g., "Add token cost breakdown to overview page").

Pull Requests

  • Keep PRs focused on a single concern
  • Include a short summary of what changed and why
  • Reference any related issues

Architecture Notes

  • Single-service deployment: In production, NestJS serves both the API and the frontend static files from the same port via @nestjs/serve-static.
  • Dev mode: Vite on :3000 proxies /api and /v1 to the backend on :3001. CORS is enabled only in development.
  • Database: PostgreSQL 16 for both local development and production. Schema changes are managed via TypeORM migrations (migrationsRun: true on boot). After modifying an entity, generate a migration with npm run migration:generate -- src/database/migrations/Name.
  • Validation: Global ValidationPipe with whitelist: true and forbidNonWhitelisted: true.
  • TypeScript: Strict mode across all packages.

Reporting Issues

Found a bug or have a feature request? Open an issue with as much detail as possible.

Code of Conduct

This project follows the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code.

License

By contributing, you agree that your contributions will be licensed under the MIT License.