Project Architecture
OpenQuok's architecture — project layout and key files for OpenQuok.
Connect your agent today
Draft from chat, review in your calendar, and publish only what you approve.
Overview
The repository is a pnpm monorepo: the root holds the workspace manifest, shared tooling, and top-level packages.
The customer-facing SvelteKit app lives in web/. It serves the marketing site, and authenticated product UI. The docs runtime (content loading, navigation, search) lives under web/src/lib/docs/.
Tech stack
- PNPM workspace
- Supabase DB & Auth
- Express.js
- Svelte 5
- Tailwind CSS
- DaisyUI
- Redis
- Stripe
- Resend
- Sentry
- Vercel
Runtime architecture
OpenQuok has three main services (web, API, and workers), a browser extension for some networks, CLI and SDK tools, and four external systems (database, queues, files, and email). The web app calls the API over the network. The extension works with the dashboard when you connect a channel. Workers run jobs from a queue — for example they publish scheduled posts, including posts for channels that use the extension.
- Web — SvelteKit UI; talks to the backend API.
- Backend API — Express API, Supabase assets, and workflow enqueue.
- Orchestrator — BullMQ workers for posts, email, and token refresh.
- Programmatic clients — CLI, SDK, and MCP against the public API.
- Browser extension — Chrome add-on for networks like Skool; keeps your site sign-in in sync with OpenQuok.
- Supabase — Postgres, Auth, and optional Storage buckets.
- Redis / BullMQ — Job queues and shared cache between API and workers.
- Storage — User media (Supabase Storage, R2, or local disk in self-host).
- Resend — Transactional email when EMAIL_ENABLED is enabled.
Maintenance mode — When MAINTENANCE_MODE=freeze_writes is set, mutations are blocked while public SEO pages stay live. See Maintenance mode.
Web
The web app is what users see in the browser — workspace, admin, and in-app docs.
Backend API
The backend coordinates product logic: REST API for the web and public clients, Supabase migrations and RLS, and uploads.
Orchestrator
Orchestrator workers run Flowcraft graphs backed by BullMQ. They handle:
- Publishing scheduled content to social platforms
- Refreshing OAuth tokens for connected integrations
- Sending notification and digest email
- Reconciling missing scheduled posts
Programmatic clients
The CLI, Node SDK, and hosted MCP server call /api/v1/public/*. The CLI may use the device-flow auth server in agent/server/ for login — see Auth server architecture.
Browser extension
The OpenQuok Chrome extension (extension/) does not schedule posts on your computer. When you add a channel in the dashboard, the extension reads your sign-in on the network site and sends it to OpenQuok. OpenQuok stores it safely and uses it when a post is due. The extension updates that sign-in on a regular schedule.
See Chrome extension to install, build, or self-host. See Project layout below for where the extension package lives in the repo.
Project Layout
Repository layout at the root:
- LICENSE
- README.md
- .backups/
- README.md
- migration/
- .cursor/
- rules/
- agent/
- server/
- skills/
- src/
- backend/
- common/
- extension/
- infra/
- self-host/
- docker-compose.yml
- orchestrator/
- web/
- sdk/
- scripts/
- package.json
- pnpm-workspace.yaml
- railway.toml
- vercel.backend.json
- vercel.web.json
- agent/ — Published as @openquok/auto-cli: the programmatic CLI, agent skills under skills/, and the OAuth2 device-flow auth server in server/. See Getting Started - CLI and Configuration - Agent.
- .backups/ See Supabase backup.
- backend/ — Supabase project assets (migrations, RLS, modules) and the Express API that talks to Supabase (database + auth, and Storage).
- common/ — Shared workspace package (`openquok-common`): types and small utilities imported by backend/, orchestrator/, web/, and extension/ (including browser-extension/ message protocol and cookie-session provider registry).
- extension/ — Published as @openquok/browser-extension: Chrome MV3 service worker, cookie harvest, and refresh alarms. Build output in extension/dist/. See Chrome extension.
- .github/ — CI workflows (for example release automation under workflows/).
- infra/ — Docker Compose and self-host env templates. Dev dependencies live in infra/docker-compose.yml; the full operator stack is under infra/self-host/. See Docker Compose.
- orchestrator/ — Workspace package: Flowcraft blueprints, BullMQ adapters, and worker entrypoints. See Orchestrator workflows, Configuration - Worker, and Railway (workers).
- .railway/ — Railway infrastructure-as-code (railway.ts).
- sdk/ — Published as @openquok/node-sdk: a typed Node.js client for the programmatic API.
- scripts/ — Monorepo automation: Vercel env sync/deploy helpers (vercelSync*.mjs, vercelDeploy*.mjs), Railway worker service setup (railwaySetupWorkerService.mjs), and prod-backup/ for Supabase export/restore scripts.
- web/ — SvelteKit frontend; public static files live under web/static/.
- .cursor/ — Cursor rules that encode repository conventions. Contributors and agents should follow the matching .cursor/rules/*.mdc files when editing code or using them as chat context in each area.
Key Directories
backend/
- backend/
- api/
- handler/
- app.ts
- config/
- connections/
- controllers/
- data/
- emails/
- errors/
- guards/
- integrations/
- mcp/
- middlewares/
- public/
- repositories/
- routes/
- publicApi/
- scripts/
- services/
- supabase/
- swagger/
- tests/
- types/
- utils/
- api/ and handler/ — HTTP entrypoints shaped or types for Vercel. Use them as the deployment shell.
- services/ — Domain orchestration and use-cases; this layer is also where caching belongs when you need to reuse or shorten expensive work across requests (in-memory, keyed stores, or upstream cache). Services may also call `openquok-orchestrator` to enqueue Flowcraft runs.
- supabase/ — Database source of truth: modular SQL under db/ tables, RLS, functions, seeds, and migration files.
- integrations/ — Social provider adapters (OAuth, credentials-in-app, cookie-session via extension, publish, analytics) consumed by services and orchestrator activities. Extension refresh and connect paths are handled in services/IntegrationConnectionService.ts.
- routes/publicApi/ — Programmatic API surface mounted at /api/v1/public/* (SDK, CLI, MCP clients).
- mcp/ — Hosted MCP server tools and auth wired into the API process.
- swagger/ — OpenAPI JSDoc sources merged into /api/v1/openapi.json for docs and SDK alignment.
- repositories/, controllers/, routes/ — Persistence adapters, request/response handling, and route tables; prefer Supabase clients and SQL in migrations over ad hoc SQL in the web app.
- middlewares/, errors/, connections/, config/, types/, utils/, data/, guards/ — Cross-cutting behavior (including maintenanceMode.ts write-freeze), Supabase/client wiring, shared types, helpers, and supporting data fixtures or reference payloads.
- emails/ — Transactional templates and send flows.
- scripts/ and tests/ — Migration aggregation (aggregate_migrations_all.mjs), one-off backend scripts, and automated tests.
- Storage — User or system files go through Supabase Storage (buckets and policies live with the rest of the backend).
orchestrator/
- orchestrator/
- adapters/
- activities/
- blueprints/
- flows/
- nodes/
- stores/
- worker/
- scripts/
- index.ts
- blueprints/ and nodes/ — Flowcraft graph definitions (scheduled social posts, notification email, integration token refresh).
- flows/ — Workflow implementations and reconciliation helpers (for example missing scheduled post rescans).
- adapters/ — BullMQ transport: enqueue helpers used from the API and worker bootstrap code.
- worker/ — Process entrypoints plus health checks and Sentry init.
- activities/ — Side-effecting steps invoked from flows (publish, email send, OAuth refresh).
- stores/ — Redis-backed state for rate limits and notification digests.
- scripts/ — Operator utilities (env validation, queue cleanup, Railway env setup).
When config/orchestratorFlows.ts keeps transport on in_process, the API runs flows inline; with bullmq, these workers execute jobs from Redis queues shared with the API.
agent/
- agent/
- src/
- commands/
- server/
- app.ts
- skills/
- openquok-core/
- tests/
- package.json
- tsup.config.ts
- src/
- src/ — CLI implementation published as @openquok/auto-cli (`openquok` binary): auth, posts, integrations, analytics, uploads, and config commands.
- server/ — Standalone OAuth2 device-flow auth server (Postgres-backed device codes, token polling). Deployed separately from the main API; the web app proxies browser routes under web/src/routes/(public)/cli/device.
- skills/ — Agent skill packs (for example openquok-core) with channel recipes, provider settings, and command references for MCP clients.
- tests/ — Vitest unit tests and CLI e2e helpers.
See Auth server architecture for the device-login sequence and endpoint map.
extension/
Chrome Manifest V3 package for cookie-session social channels:
- extension/
- manifest.json
- src/
- background.ts
- messageHandler.ts
- cookies/
- refresh/
- providers/
- storage/
- dist/
- package.json
- vite.config.ts
- src/background.ts and messageHandler.ts — Entrypoints for `chrome.runtime` messages from the dashboard (externally_connectable in manifest.json).
- src/cookies/ — Read provider cookies from the user’s browser profile (requires matching host_permissions per platform).
- src/refresh/ — Alarms and POST /integrations/extension-refresh calls using the signed extensionToken from channel connect.
- src/providers/ — Extension-side provider registry aligned with common/src/browser-extension/cookieSessionProviders.ts.
infra/
Docker and self-host operator assets:
- infra/
- docker-compose.yml
- self-host/
- docker-compose.yml
- .env.example
- infra/docker-compose.yml — Contributor development environment only.
- infra/self-host/ — full stack: Redis, API, web, BullMQ workers, uploads volume; optional `cli` profile for Postgres + agent server. Default UI: http://localhost:4007.
- infra/self-host/.env.example — Template for self-host env vars; operators copy to `.env` beside the Compose file.
User-facing bring-up steps live under Installation (especially Docker Compose — open the UI at http://localhost:4007 after up —build).
web/
The SvelteKit app root:
- web/
- package.json
- web-config.json
- static/
- src/
- content/
- docs/
- data/
- docs.ts
- icons.ts
- lib/
- area-admin/
- area-protected/
- area-public/
- core/
- ui/
- …
- params/
- routes/
- (auth)/
- (docs)/
- (legal)/
- (protected)/
- (public)/
- maintenance/
- …
- styles/
- tests/
- content/
- src/routes/ — File-based routing. Route groups (public), (auth), (protected), (docs), (legal) share layouts and auth boundaries without affecting the URL prefix. maintenance/ is the write-freeze landing page when MAINTENANCE_MODE=freeze_writes (see src/lib/maintenance/ and hooks.server.ts).
- src/data/ — Small typed registries and config imported from $data/…(e.g. `docs.ts`, `icons.ts`).
- src/lib/core/ — HttpGateway, cookies, shared presenters that sit next to I/O. DTOs from the API are parsed here and in repositories, not in `.svelte` files.
- src/lib/integrations/browser-extension/ — Dashboard bridge to @openquok/browser-extension (connect flow, cookie encode, refresh registration after social-connect).
- src/lib/area-admin/, src/lib/area-protected/, src/lib/area-public/ — Page-level presenters, including admin console, signed-in app, public/marketing and etc. Routes import singletons from these indexes.
- src/lib/ui/ — Reusable UI components (buttons, dialogs, docs chrome, DaisyUI-styled patterns). Feature routes pass view models and callbacks into these components.
- Theming (DaisyUI) — Styling favors semantic DaisyUI + Tailwind tokens (bg-base-100, text-base-content, border-base-300, primary, and etc.) so theme presets swap via CSS variables instead of hand-maintained color pairs per component.
- src/content/ — In-app docs Markdown.
- static/ — Public assets (favicon, PWA icons, doc images under docs/_assets/).
Marketing copy (programmatic SEO)
Routes under src/routes/(public)/ stay generic (one +page per surface). Hero, FAQ, meta strings, and hub blurbs live under web/src/lib/content/constants/. Import from the canonical module for each public URL (e.g. agents/index.ts, channels/index.ts) — not legacy root public*Config.ts shims. Tool-specific marketing config may also live under feature libs (e.g. web/src/lib/best-time-to-post/constants/).
| Kind | Edit when | Examples |
|---|---|---|
| Generic | Hub or shared builders for every slug on a surface | hubs/channels.ts, channels/api/_shared/publicApiCapabilityAudienceConfig.ts, channels/api/posting/general.ts |
| Tailored | One URL needs its own hero, meta, or feature copy | channels/catalog/platforms/{slug}.ts, channels/api/posting/platforms/{slug}.ts, agents/hosts/{slug}.ts |
Rule: match the public URL. Example: /social-media-posting-api/x → channels/api/posting/platforms/x.ts; the posting API hub → generic capability configs under channels/api/_shared/ and channels/api/index.ts.
Paths below are repo-relative from web/src/lib/content/constants/ unless noted.
| Public surface | Canonical import | Tailored per slug |
|---|---|---|
| /channels, /channels/{slug} | channels/index.ts, hubs/channels.ts | channels/catalog/platforms/{slug}.ts + channels/catalog/seeds.ts |
| /agents, host /agents/{slug} | agents/index.ts, hubs/agents.ts | agents/hosts/{slug}.ts + agents/seeds.ts |
| MCP client on /agents/{slug} | mcps/index.ts | mcps/hosts/{slug}.ts + mcps/seeds.ts |
| /agents/{host}/{channelSlug} (skill host) | agents/channels/index.ts | agents/channels/{host}.ts + channels/catalog/platforms/{channelSlug}.ts |
| /agents/{host}/{channelSlug} (MCP client) | mcps/channels/index.ts | mcps/hosts/{slug}.ts + channels/catalog/platforms/{channelSlug}.ts + buildMcpChannelLandingVm.ts (no mcps/channels/{channelSlug}.ts files) |
| /social-media-posting-api, /social-media-scheduling-api (+ /{slug}) | channels/api/index.ts, channels/api/_shared/* | channels/api/posting/platforms/{slug}.ts; scheduling hub in channels/api/scheduling/ |
| /tools/* | hubs/tools.ts | channels/tools/{tool}/general.ts, channels/tools/{tool}/faq.ts; optional Tier 3 channels/tool-surfaces/{slug}.ts |
| /compare, /alternatives | competitors/index.ts, hubs/compare.ts | competitors/{product}.ts |
| /self-hosting | self-hosting/landing.ts, self-hosting/whoIsFor.ts | — |
| / and shared hub chrome | landing/index.ts (hero, breadcrumbs, who-is-for, setup-steps-footer) — not channel or self-host landings | — |
| Shared FAQ pool | faq/index.ts (PUBLIC_FAQ_ITEMS, appendPublicGeneralFaqItems, PUBLIC_*_FAQ_ITEM_IDS) | Tailored arrays in catalogs and hubs/*.ts |
Channels: per-slug VMs in channels/catalog/platforms/; register in channels/catalog/seeds.ts (PUBLIC_CHANNEL_LANDING_PAGES). Maintainer map: channels/catalog/index.ts. Shared: types.ts, shared.ts, feature-bento.ts in catalog/. Optional fourth WhoIsFor: audienceTailoredCard on the platform file; channels/index.ts merges for agent/API pages. Do not confuse landing/ (home/hub chrome) or self-hosting/landing.ts (/self-hosting only) with channel catalog files.
FAQs: tailored Q&A in each catalog or hub file; append shared rows with appendPublicGeneralFaqItems and the matching PUBLIC_*_FAQ_ITEM_IDS from faq/index.ts. Link helpers: web/src/lib/content/utils/publicFaqLinks.ts.
Agents: CLI install snippets → agents/cli-command-reference.ts; skill example JSON → agents/core-example-json.ts.
Route path helpers (not copy): web/src/lib/area-public/constants/getRootPathPublic*.ts.
Maintainer-only (do not import from app code, routes, or presenters): _dev/programmatic-landing/publicProgrammaticLandingRegistry.ts — surface list, page counts, estimateNewProviderMarketingPages(slug). Verify after catalog changes:
pnpm --filter ./web run test:pseo-registry Route-shaped catalogs also live under channels/api/, channels/catalog/, channels/tools/, hubs/, faq/, landing/, and self-hosting/.
Related Cursor rules: programmatic SEO (constants, templates, FAQ funnel, footer) — .cursor/rules/web-seo-pseo.mdc; FAQ ids and append sets — web-landing-faqs.mdc; adding channels / agents — add-social-provider-integration.mdc, add-agent.mdc.
Presenters, repositories, and tests
We keep Svelte focused on layout and inputs, and push behavior into layers you can test without the DOM or a real API:
| Layer | Role | Typical files |
|---|---|---|
| UI (Svelte) | Render view models; forward user actions via callbacks. Parent routes own the page presenter. | *.svelte under routes/ and $lib/ui/ |
| Presenter | View-specific state (status, toasts and etc), actions that call repositories. | *.presenter.svelte.ts |
| Repository | Domain state as programmer models; maps DTOs ↔ domain; calls the gateway. | *.repository.svelte.ts |
| Gateway / infrastructure | HTTP and other I/O; DTOs at the boundary. | $lib/core/ (e.g. HttpGateway) |
Why separate repository and presenter?
Repositories encode business rules and data shape (what the app believes is true). Presenters encode how a screen behaves (loading, errors, which *Vm the template sees).
Splitting them means you can unit test presenters with a stubbed repository and unit test repositories with a stubbed gateway—asserting on state and return values instead of rendering components or running end-to-end tests for every branch. That matches the goal: test view-model and domain behavior, not the DOM.
Convention reference: The full rules (page vs child components, toast wiring, *Pm / *Vm naming, Get* presenters) live in the repo at .cursor/rules/web-repository-presenter-architecture.mdc for contributors.
Document Directories
src/content/docs/
This is where in-app documentation markdown lives. Each .md file becomes a page; URLs follow the folder path. Locale mirrors live beside docs/ (e.g. docs-es/ → /docs/es/…).
Sidebar tabs and section order are declared in src/lib/docs/constants/config.ts as docsTabs: General, Cloud, Self-hosting, CLI, MCP, Public API, Add OpenQuok to your app, Contributing.
- src/content/docs/
- getting-started/
- channels/
- creating-posts/
- posts-management/
- saved/
- settings/
- platforms/
- automations/
- insights/
- media/
- troubleshooting/
- help/
- cloud/
- billing/
- getting-started-for-dev/
- installation/
- configuration-backend/
- configuration-web/
- configuration-worker/
- configuration-agent/
- admin/
- social-integration/
- getting-started-for-cli/
- cli-usages/
- cli-examples/
- agent-setup-guides/
- other-skills/
- getting-started-for-mcp/
- mcp-examples/
- mcp-references/
- mcp-setup-guides/
- getting-started-for-public-api/
- public-api-providers/
- oauth2-for-apps/
- apis-integrations/
- apis-posts/
- apis-analytics/
- apis-notifications/
- apis-uploads/
- developer-guidelines/
- contribution-opportunities/
- publish-listings/
- documentation-contribution/
- General tab — Product usage: getting-started/ through help/ (including saved/, insights/, media/, troubleshooting/).
- Cloud tab — cloud/ and billing/.
- Self-hosting tab — Operator install and config: installation/ (including maintenance-mode.md), configuration-*, admin/, and social-integration/.
- Public API tab — getting-started-for-public-api/, public-api-providers/, apis-*, and oauth2-for-apps/.
- Contributing tab — developer-guidelines/, contribution-opportunities/, publish-listings/, and documentation-contribution/.
src/lib/docs/
The documentation engine:
- constants/config.ts — Defines docs site metadata,
docsTabs(General, Cloud, Self-hosting, CLI, MCP, Public API, Add OpenQuok to your app, Contributing), i18n, and assembles `docsConfig` - content.ts — Content loader that discovers and parses markdown files
- navigation.ts — Generates sidebar navigation and maps paths/slugs to the active tab
- types.ts — TypeScript types for docs, navigation, and config
- utils/ui/tocState.svelte.ts — Table of contents state management
src/lib/ui/components/docs/
Documentation UI (layouts, MDX helpers, search, nav):
- layout/ — Header, footers, sidebars
- nav/ — Breadcrumbs, keyboard nav, social links
- search/ — Command palette search
- mdx/ — Callout, tabs, cards, and other markdown components
Configuration Files
Root (monorepo)
| File | Purpose |
|---|---|
| package.json | Workspace scripts (pnpm filters), shared devDependencies, packageManager pin |
| pnpm-workspace.yaml | Workspace package globs |
| railway.toml | Railway deploy config for workers and related services |
| vercel.backend.json / vercel.web.json | Vercel project wiring templates for API and web packages |
| .dockerignore | Excludes local artifacts from Docker build context |
Backend
| File | Purpose |
|---|---|
| package.json | Scripts, dependencies, and workspace metadata for the API package |
| vercel.json | Vercel deployment: routes, builds, and serverless/function wiring |
| tsconfig.json / tsconfig.build.json / tsconfig.tsup.json | TypeScript: editor vs production compile targets |
| tsup.config.ts | Bundles the deployable API surface |
| eslint.config.js | ESLint rules for the backend source |
| jest.config.js | Jest entry; see also jest.*.cjs / jest.*.js env files and babel.config.jest.cjs for test transforms |
Orchestrator
| File | Purpose |
|---|---|
| package.json | Worker scripts and openquok-orchestrator package metadata |
| tsconfig.json / tsconfig.build.json | TypeScript compile targets for workers |
| Dockerfile | Production worker image (one image; Compose overrides command per queue) |
| railpack.*.json | Railway buildpack configs per worker flavor |
| jest.config.js / jest.bullmq.config.js | Unit and BullMQ integration test entrypoints |
| babel.config.jest.cjs | Jest transform config |
Agent
| File | Purpose |
|---|---|
| package.json | CLI package (@openquok/auto-cli) scripts and dependencies |
| tsconfig.json | TypeScript for CLI sources |
| tsup.config.ts | Bundles the openquok CLI binary |
| vitest.config.mjs / run-vitest.mjs | Vitest runner for CLI tests |
| server/package.json | Auth server dependencies and start scripts |
| server/tsconfig.json | TypeScript for the device-flow server |
| server/vercel.json | Vercel deployment for the hosted auth server |
| server/Dockerfile | Optional self-host cli profile image |
Common
| File | Purpose |
|---|---|
| package.json | openquok-common workspace package metadata |
| tsconfig.json | TypeScript compile settings for shared types and utilities |
SDK
| File | Purpose |
|---|---|
| package.json | @openquok/node-sdk publish metadata and scripts |
| tsconfig.json | TypeScript compile settings |
| tsup.config.ts | Bundles the published SDK entrypoint |
Infra
| File | Purpose |
|---|---|
| infra/docker-compose.yml | Contributor Redis (+ optional CLI Postgres) for local dev |
| infra/self-host/docker-compose.yml | Full self-host stack (API, web, workers, Redis; optional cli profile) |
| infra/self-host/.env.example | Operator env template (Supabase keys, Redis, OAuth apps, self-host defaults) |
Web
| File | Purpose |
|---|---|
| svelte.config.js | SvelteKit + MDSvex configuration |
| vite.config.ts | Vite + Tailwind CSS setup |
| package.json | Scripts and dependencies for the SvelteKit app |
| web-config.json | Web package metadata consumed by tooling |
| vercel.json | Vercel adapter and deployment settings |
| tsconfig.json | TypeScript for the SvelteKit app |
| eslint.config.js | ESLint rules for web sources |
| src/lib/docs/constants/config.ts | Docs site title, sidebar tabs/sections, social links, locales; assembles docsConfig |
| src/data/docs.ts | Docs site social URLs and shared docs data |