Thanks for your interest in contributing to Thinker! This guide will help you get up and running.
| Layer | Technology |
|---|---|
| Frontend | SolidJS, Vite 8, uPlot, custom CSS theme |
| Backend | NestJS 12, TypeORM 1.1, PostgreSQL 18 |
| Auth | Better Auth (auto-login on localhost) |
| Routing | OpenAI-compatible proxy (/v1/chat/completions) |
| Build | Turborepo + npm workspaces |
The full NestJS + SolidJS stack runs locally against PostgreSQL. For local development, the simplest option is to run PostgreSQL in Docker and point DATABASE_URL at it.
- Node.js 24.x (LTS)
- npm 10.x
Thinker 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)
Self-hosting is supported via the Docker image and Docker Compose (docker/), the only official and CI-tested deploy path. The templates under deploy/ are community-maintained.
- Fork and clone the repository:
git clone https://github.com/<your-username>/llm-gateway.git
cd llm-gateway
npm install- Set up environment variables:
cp packages/backend/.env.example packages/backend/.envEdit 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- 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 thinker-postgres \
-e POSTGRES_USER=myuser \
-e POSTGRES_PASSWORD=mypassword \
-e POSTGRES_DB=mydatabase \
-p 5432:5432 \
-d postgres:18That container matches the sample DATABASE_URL above:
DATABASE_URL=postgresql://myuser:mypassword@localhost:5432/mydatabaseUseful commands:
docker start thinker-postgres # start again later
docker stop thinker-postgres # stop without deleting data
docker rm -f thinker-postgres # remove the container- 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
npm run devThe frontend runs on http://localhost:3000 and proxies API requests to the backend on http://localhost:3001.
npm run dev is filtered to exactly thinker-frontend — adding a new workspace with a dev script will not silently join the dev set.
- With
SEED_DATA=true, you can log in withadmin@thinker.example.com/thinker123.
Thinker is a smart router for any 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 Thinker as a model provider in your OpenClaw config:
- Build and start the backend:
npm run build
PORT=38238 BIND_ADDRESS=127.0.0.1 \
node -r dotenv/config packages/backend/dist/main.js-
Create an agent in the dashboard at
http://localhost:38238and get the API key. -
Add Thinker as a provider in OpenClaw:
openclaw config set models.providers.thinker '{"baseUrl":"http://localhost:38238/v1","api":"openai-completions","apiKey":"thk_YOUR_KEY","models":[{"id":"auto","name":"Thinker Auto"}]}'
openclaw config set agents.defaults.model.primary thinker/auto
openclaw gateway restartThe 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 ingestion
- Working on the dashboard UI with live data
| Command | Description |
|---|---|
npm run dev |
Start frontend in watch mode (start backend separately) |
npm run build |
Production build (shared, backend, frontend via Turborepo) |
npm start |
Start the production server |
npm test --workspace=packages/backend |
Run backend unit tests (Jest) |
npm run test:e2e:docker --workspace=packages/backend |
Run backend e2e tests (Jest + Supertest) against a throwaway Postgres container |
npm test --workspace=packages/frontend |
Run frontend tests (Vitest) |
npm test --workspace=packages/shared |
Run shared tests (Jest) |
- Framework: NestJS 12 with TypeORM 1.1 and PostgreSQL 18
- 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)
- 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)
- 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.
- Create a branch from
mainfor your change - Make your changes in the relevant package(s)
- Write or update tests as needed
- Run the test suite to make sure everything passes:
npm test --workspace=packages/shared
npm test --workspace=packages/backend
npm run test:e2e:docker --workspace=packages/backend # needs Docker
npm test --workspace=packages/frontendThe backend e2e suite drops the schema of the database it connects to, so it refuses to run unless DATABASE_URL names a database with test as a separate word (e.g. thinker_e2e_test or test_db; latest or contest do not count) and THINKER_E2E_ALLOW_DROP=1 is set. test:e2e:docker handles both with a disposable container; never point the suite at your development database.
- Verify the production build works:
npm run build- Open a pull request against
main
Thinker ships as the multi-arch (linux/amd64, linux/arm64) Docker image tmsoftbrasil/thinker. Every release goes out through scripts/ship.sh "<conventional commit message>" [patch|minor|major] (ADR 0005): it bumps packages/thinker/package.json, writes docs/releases/X.Y.Z.md from the commit subjects, lands the change on main, and pushes the vX.Y.Z tag. Then it builds both architectures from the local Docker, using the Docker Hub account it is logged into (tmsoftbrasil), pushes X.Y.Z, X.Y, latest and sha-<short>, and creates the GitHub release. There is no CI publishing workflow.
The full flow is in the developer guide.
Write clear, concise commit messages that explain why the change was made. Use present tense (e.g., "Add token cost breakdown to overview page").
- Keep PRs focused on a single concern
- Include a short summary of what changed and why
- Reference any related issues
- 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
:3000proxies/apiand/v1to the backend on:3001. CORS is enabled only in development. - Database: PostgreSQL 18 for both local development and production. Schema changes are managed via TypeORM migrations (
migrationsRun: trueon boot). After modifying an entity, generate a migration withnpm run migration:generate -- src/database/migrations/Name. - Validation: Global
ValidationPipewithwhitelist: trueandforbidNonWhitelisted: true. - TypeScript: Strict mode across all packages.
Found a bug or have a feature request? Open an issue with as much detail as possible.
This project follows the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code.
By contributing, you agree that your contributions will be licensed under the MIT License.