Zum Inhalt springen

Aufbau

server/
core/ HTTP-Router, Auth, DB (node:sqlite), Event-Bus, Templates, Zeitzonen, öffentliche Seiten
services/ Fachlogik, die mehrere Module teilen: Kontakte, Versand (E-Mail/SMS), Opportunities,
Workflow-Engine, Bewertungen, GHL-Import
modules/ Ein Modul je Bereich: eigene Tabellen (migrations) + Routen (register)
modules/index.ts Registry – neue Module hier eintragen
assets/forms/ Browser-Skripte und -Styles der öffentlichen Formulare (Teile, die `npm run build` minimiert; docs/forms.md)
web/src/ React-Oberfläche (eine Datei je Bereich unter pages/)
tests/ Unit- und End-to-End-API-Tests (vitest)
server/modules/memberships.ts
import type { Module } from '../core/app.js';
export const membershipsModule: Module = {
name: 'memberships',
migrations: [['001-init', `CREATE TABLE courses (id TEXT PRIMARY KEY, location_id TEXT NOT NULL REFERENCES locations(id) ON DELETE CASCADE, …)`]],
register(app) {
app.router.get('/api/courses', 'location', (ctx) => app.db.all('SELECT * FROM courses WHERE location_id = ?', ctx.locationId));
app.events.on('invoice.paid', (e) => { /* Zugang freischalten */ });
},
};

…und in server/modules/index.ts eintragen. Migrationsnamen nie ändern, nur neue anhängen.

  • Auth je Route: public, user (angemeldet), admin, location (angemeldet oder API-Schlüssel + Sub-Account). Alles mit location filtert auf ctx.locationId.

  • CSRF: Schreibende Anfragen mit Cookie brauchen den Header x-pghl: 1 (macht die Oberfläche automatisch).

  • Ereignisse (app.events.emit(locationId, type, data)) laufen nach der Antwort; Workflows und Webhooks hängen daran. data.contactId startet Workflows für diesen Kontakt.

  • Rechte: app.guards laufen nach der Anmeldung; das Agentur-Modul prüft dort die Bereiche eingeschränkter Logins (server/core/permissions.ts ordnet Routen Bereichen zu). Neue Module tragen ihre Routen dort ein – dieselbe Zuordnung nutzt der SaaS-Guard (saas-gate.ts), um Bereiche außerhalb des Tarifs zu sperren und Limits beim Anlegen zu prüfen.

  • Sprache der Oberfläche: Texte bleiben deutsch im Code und laufen über t() aus web/src/i18n (const t = useT('bereich'), der deutsche Text ist die Nachrichten-ID); die englischen Übersetzungen liegen je Bereich in web/src/i18n/en/*.ts. Bereiche mit complete: true in web/src/i18n/registry.ts hält npm run i18n:check vollständig. Die Sprache je Person speichert modules/language.ts (users.lang, /api/me/language) – Anleitung: translating.md (Englisch).

  • Performance: keine Abfrage pro Sub-Account/Zeile in einer Anfrage (gruppieren, siehe services/location-stats.ts), Index in der eigenen Migration für jedes neue WHERE/ORDER BY auf großen Tabellen, lesende Listen/Auswertungen über cached() (core/cache.ts) bzw. app.db.memo, Hintergrund-Aufgaben in Scheiben von höchstens ~50 ms (sliceTimer), neue Seiten mit lazyPages – die Regeln mit Beispielen: docs/performance.md. app.perf (Ereignisschleife, langsame Anfragen/Abfragen, Job-Zeiten) → GET /api/admin/metrics.

  • Hintergrund-Aufgaben über app.onTick(fn) (Workflow-Wartezeiten, Erinnerungen, geplante Kampagnen, Abo-Rechnungen, iCal-Sync, Datums-Auslöser, Backups).

  • Haken zwischen Modulen über app.services statt direkter Importe: instrumentEmail/personalizeLinks (Tracking → Versand und Postfach-Versand; instrumentEmail setzt Klick-Links und Zählpixel, trackingCommit übernimmt wartende Klicks – docs/email-tracking.md), externalBusy (iCal → Kalender), secondFactor (2FA → Login), workflowActions (eigene Workflow-Aktionen, z. B. social_post, ai_text, whatsapp_send), workflowTriggers/workflowRecipes (eigene Auslöser und Vorlagen, z. B. call.missed), messageChannels (weitere Kanäle für POST /api/messages, z. B. WhatsApp), mcpTools (eigene MCP-Werkzeuge), aiRun/aiFetch (KI-Aufruf mit Limit und Protokoll / Test-Haken für die Claude-API), reviewWidgetGoogle (Google-Sterne im Bewertungs-Widget), twilioFetch/googleFetch (Test-Attrappen für externe APIs), publicBaseUrl (Agentur → Links), sitePaths/siteSitemap (eigene Pfade wie /blog und Sitemap-Einträge auf Websites, z. B. Blog → Funnels), pageServe (andere Fassung einer Funnel-Seite je Besucher oder HTML nachbearbeiten, z. B. A/B-Tests → Funnels), buyPageHtml/buyCheckout/buyInvoiceItems/buyAfterPaid und stripeBuy (Kaufseite, Checkout-Session, Rechnungszeilen und Schritt nach dem Bezahlen erweitern, z. B. Order-Bump, Rabattcodes, Upsell → Stripe), renderLessonPreview/sendMemberLoginLink (Mitgliederbereich → Kurse), portalKit/portalHead/portalHeader/portalDashboard/portalMemberCheck (weitere Bereiche im Mitgliederbereich, z. B. die Community: Seitenrahmen, Links oben, Karten auf der Startseite, wer sich anmelden darf), snapshotParts (eigene Teile in Snapshots und beim Kopieren, z. B. Blog-Beiträge, Kurse, Community-Gruppen – mit prepare, damit Verweise auf die Kopien zeigen), invoicePayHtml (Stripe → „Jetzt bezahlen“ auf der Rechnung), pushNotify (Push-Benachrichtigung an Team/Nutzer), usageGate (SaaS-Tarif: zählt E-Mail/SMS/KI-Aufrufe direkt vor dem Versand bzw. Claude-Aufruf und gibt einen Grund zurück, wenn das Limit erreicht oder das Konto pausiert ist), saasCompleteSignup/saasPriceId (SaaS-Anmeldung ↔ Agentur-Stripe), oauth (OAuth-Verbindungen: token(id) liefert ein gültiges Access-Token, Refresh-Tokens sieht kein anderes Modul; Test-Attrappe oauthFetch), googleBusinessReady/googleBusinessFetch/googleBusinessStatus (Unternehmensprofil → Google-Bewertungen statt Places API). externalBusy ist eine Kette: iCal (calendar-sync) und Google/Outlook (calendar-connect) hängen ihre belegten Zeiten an, twilioFetch/googleFetch/todoistFetch (Test-Attrappen für externe APIs), todoistSync (Todoist-Abgleich anstoßen), aiReady (ob ein KI-Aufruf gerade ginge – KI-Assistent an, Schlüssel, Limit), voiceAgent (KI-Telefonagent übernimmt einen eingehenden Anruf → TwiML), voiceDialTargets/voiceDialHeld (Browser-Telefon klingelt bei eingehenden Anrufen mit bzw. hält ein Gespräch), cspExtra ({connect: [...]} – zusätzliche https/wss-Ursprünge in der Content-Security-Policy der App, z, pageVisit (Funnels → Attribution: Token und Hinweis je Seitenaufruf), contactFilters (eigene Filter für Kontaktlisten und Smart Lists, z. B. Quelle/Kampagne, Firmen-Eigenschaften, Lead-Score), contactSorts (eigene Sortierungen der Kontaktliste, z. B. score), contactOrder (Sortierung nach jeder Spalte samt eigenen Feldern und Richtung – modules/smart-lists.ts, feste SQL-Liste, Feldpfad als Parameter), associations (Verknüpfungen zwischen beliebigen Datensätzen: registerType für eigene Typen wie Tickets, link/unlink/list/setPrimary, Löschen räumt per Trigger auf), objectSchema (Eigenschaften je Objekt mit Typprüfung, Aktivität und Notizen je Datensatz), companies/customObjects (Firmen und eigene Objekte anlegen/ändern, auch für Importe), supabaseTables (eigene Tabellen für den Supabase-Spiegel samt SQL), workflowTriggerFilters ({auslöser: (config, data) => boolean} – Filter für eigene Workflow-Auslöser), workflowActionMeta/workflowTriggerMeta (Beschreibung für Aktionsauswahl und generiertes Formular), workflowStepCleaners (Schritte beim Speichern prüfen/begrenzen), workflowStepSkips ((step) => Grund | undefined – die Engine überspringt den Schritt mit dieser Protokollzeile, z. B. pausierte Schritte), workflowImportKit (reId/warningsFor/stripSecrets/refsOf des Workflow-Imports – Kopieren und Einfügen von Schritten nutzt sie), channelDnd (DND je Kanal beim Senden), connectorFetch/sheetsApi/stripeKey (Außenaufrufe und Zugänge der App-Schritte), wfxNow/wfxRandom/wfxSleep/wfxRunScans (Test-Haken: Uhr, Zufall, Pausen, Zeit-Auslöser) – Details in docs/workflow-actions.md, attribution (visit/capture/checkout: Formulare, Termine, Chat und Kauf-Links melden ihre Kontakte), attributionHooks (onTouch/onLink – Affiliates zählen Klicks und Leads, Werbung meldet Conversions), attributionReport (Bericht für die Werbe-Auswertung), rawBodyLimit ((method, path, req, cookies) → Bytes: Route nimmt einen rohen Binär-Body an, steht dann in ctx.rawBuffer – z. B. der APK-Upload), mobileAppInfo (aktuelle Android-App für /api/mobile/me), mobileFeed/mobileScanDue (Meldungen für die App eintragen / fällige Aufgaben prüfen). companies/associations/ticketsImport (Firmen- und Helpdesk-Module, die der HubSpot-Import nutzt, falls vorhanden – Vertrag in docs/integrations.md), hubspotStatus (HubSpot-Verbindung → Integrations-Katalog), aiKnowledge (weitere Wissens-Abschnitte für den KI-Assistenten, z. B. veröffentlichte Hilfe-Artikel), portalNextPrefixes (weitere Mitgliederseiten, zu denen der Login-Link zurückführt, z. B. tickets), helpdesk (createTicket/updateTicket für andere Module), csatLink/sendCsat/npsStats/surveysOfContact (Umfragen → Tickets, Dashboard, Kontaktseite), healthCompute (Health-Score neu berechnen). catalogExtra (eigene Einträge im Integrations-Katalog samt Status-Probe), videoStudio (Video-Studio: Jobs anlegen/abrufen, Clips einplanen (scheduleClip), Hintergrund-Aufgaben – nutzen das YouTube-/KI-Shorts- und das Recycling-Modul), videoStudioReady (Liste von Haken, die nach einem fertigen Shorts-Job laufen, z. B. Recycling/Autopilot), videoClipThumb (Vorschaubild-URL eines Clips), videoRecycle (Content-Recycling erzeugen/übernehmen, Autopilot), socialPost mit mediaUrls + video (Video per signiertem Link an Postiz, dann auch YouTube/TikTok) und thread (weitere Teile als Antworten, z. B. X-Thread), aiRun mit timeoutMs für lange Texte. Weitere Kanäle (modules/channels.ts + services/channel-kit.ts): channelStatus/channelMedia (Composer und Dateien je Kanal), channelSend (auf jedem Kanal senden, z. B. Antworten aus Slack), emailTransports/smsTransports (Anbieter hinter deliverEmail/deliverSms – sie finden die App über die nicht aufzählbare Eigenschaft app der Sub-Account-Zeile aus app.location()), Test-Haken channelFetch/discordSocket (docs/channels.md). Ein RawResult darf als body einen Readable-Stream haben (wird gestreamt, z. B. der Clip-Proxy). aiConnections (KI-Anbieter/BYOK: list/resolve(locationId, capability|usage, id?)/models/textProvider/fetchFor – jede Text- und Medien-Verbindung, docs/ai-providers.md), aiProvider(locationId, purpose?) (Anbieter für aiRun; eine Zuordnung in den KI-Anbietern geht vor), agent (KI-Agent: run/confirm/runTool), agentDefs (eigene Agenten: Konfiguration, Sichtbarkeit, Besitzer), agentBridge (lokale Bridge), agentFetch/aiConnectionsFetch (Test-Haken für Modell-Aufrufe), mcpTools wird vom KI-Agenten in-process genutzt – neue Werkzeuge stehen ihm sofort zur Verfügung (docs/agent.md). Postfach-Sync (Gmail/Outlook): conversationFilters ((ctx, where, params) – Filter der Konversationsliste, z. B. ?mailbox=), conversationRows ((ctx, rows) → rows – Zusatzfelder wie Ordner-Marken), messageRows ((ctx, conversationId, rows) → rows – Anhänge, Labels, Flags je Nachricht), oauthPurposes ({ mail: { callback } } – ein Modul beendet den gemeinsamen OAuth-Rückruf für seinen eigenen Zweck), mailbox (get/sync/flush), mailboxPush (register/stop/renewDue); der Anbieter mailbox hängt sich über emailTransports in deliverEmail ein (docs/mailbox-sync.md). Fehlt das Modul, läuft der Kern ohne die Funktion weiter. Andere Routen in-process aufrufen (gleiche Anmeldung, Guards und Protokoll): callRoute aus services/route-call.ts – nutzen die REST-Hook-Aktionen und der Integrations-Katalog. Creator & Commerce (payment-plans, dunning, quizzes-certificates, comment-to-dm, workflow-ai): stripeTaps ([(locationId, event) => {handled} | undefined] – jedes geprüfte, neue Stripe-Ereignis geht zuerst an diese Abgriffe, z. B. Ratenzahlung und Mahnwesen; ein Ergebnis beendet die Verarbeitung), lessonGates/lessonExtras/lessonCompleteBlock (Mitgliederbereich: Lektion sperren, HTML nach dem Lektionstext, Abhaken von Hand sperren – Quiz und Aufgaben), metaKit (accountFor/ids/graph/windowState – Meta-Zugang für Module, ohne Token herauszugeben), metaWebhookHooks (bekommt {payload, secret, locationId}) und die Option payload von metaSend (Nachrichten mit Buttons), creatorPlans, dunning/dunningInvoices/billingPage (Mahnwesen und Abo-Seite), quizKit/assignmentKit/certificates (Quiz, Aufgaben, Zertifikate), igComments. Die KI-Verwendung workflows (Zuordnung im KI-Anbieter) bedient den KI-Workflow-Builder (aiRun mit dem Zweck workflow_builder).

  • Wissensgedächtnis (Cognee): modules/cognee.ts (tables, agency + sub-account routes, governance status), cognee-sync.ts (scheduler job, privacyErase hook queues the deletion of a contact’s data items, app.services.cognee), cognee-tools.ts (MCP/agent tools ghl_memory_recall/ghl_memory_remember); logic in services/cognee/* (client.ts REST client with the sub-account-bound LocationClient, sources.ts one KindDef per kind of record, sync.ts engine + deletion queue, recall.ts, redact.ts, model.ts). Hooks: app.services.cogneeFetch/cogneeSleep/cogneeNow/cogneeGapMs (test hooks), the optional untrusted: true on an MCP Tool (the agent treats its output as untrusted). Dataset isolation, erasure and what is sent: docs/cognee.md.

  • Einwilligung & Recht: modules/consent.ts (Ledger consent_events/consent_texts/consent_doi, Double-Opt-in-Tick, Routen, Auslöser consent.confirmed/consent.revoked), consent-capture.ts (Formulare, Webinar, Community, Import, API), consent-pages.ts (Bestätigungsseite, Einstellungsseite), consent-banner.ts (Cookie-Banner/CMP), legal-pages.ts (Rechtstexte), checkout-law.ts (Kaufseite nach deutschem Recht); Logik in services/consent-model.ts (rein), consent.ts, consent-proof.ts, cmp.ts (rein), legal-texts.ts (rein), legal-pdf.ts. Haken in app.services: consentGate (services/messaging.ts und der WhatsApp-Versand fragen vor jeder Werbe-Nachricht; purpose: 'transactional' für Service-Nachrichten), consent.* (Erfassen, Status, allowMarketingMany für Zielgruppen – Aufrufer mit ?.), cmp (attributionConsent, linkCmp), htmlTransforms (Kern: öffentliche HTML-Seiten nachbearbeiten, im try/catch), buyButtonLabel, legalTexts (profile, facts, generate, render, hostedUrl, prefill, providers – für andere Module wie die gehostete Anmeldung). Vertrag, Grenzen und offene Rechtsfragen: docs/consent.md.

  • Rückgängig, Verlauf, Papierkorb: modules/undo.ts (Tabelle undo_entries, Wächter + After-Write-Haken, Routen /api/undo/*, Aufbewahrung, DSGVO-Schritt), modules/undo-entities.ts (die Datensätze und ihre Routen), modules/undo-tools.ts (MCP ghl_undo_list/ghl_undo, /api/mobile/undo/*); Motor in services/undo-journal.ts, undo-core.ts, undo-table.ts, undo-versioned.ts. Haken: app.services.undo mit register(type, {snapshot, restore, label, …}) (was ein Datensatz ist und wie er zurückkommt – tableEntity deckt die Tabelle samt Fremdschlüsseln ab), track({method, pattern, entity, targets, action, when, notice, coalesceMs, quiet}) (welche Route welche Datensätze ändert), enabled, cleanup(at); ctx.undoSink sammelt die Einträge eines Agent-Werkzeugs; Db.tx verschachtelt (Savepoints). Neue Module melden ihre Datensätze dort an – Vertrag und Grenzen: docs/undo.md.

  • Website-Baukasten (sites.md): modules/funnels.ts (Seiten, Auslieferung servePage, Vorschau) mit je einem Modul pro Thema – site-routing.ts (Weiterleitungen, Suche, Übersetzungen, tiefe Pfade), site-design.ts (Komponenten), site-cms.ts (Sammlungen), site-product.ts (Produkt-Block), site-a11y.ts (BFSG-Prüfung), site-analytics.ts (Statistik je Schritt, Board-Baustein funnel_overview, MCP ghl_funnel_analytics), site-templates.ts (23 Vorlagen), site-media.ts (Bildgrößen, /uploads-opt), site-snapshot.ts (Kopieren); reine Logik in services/site-*.ts (Blöcke, Layout, Seite, Design-Token, Weiterleitungen, Suche, Prüfung, Bilder, Vorlagen). Haken in app.services: siteBlocks (Map der Block-Renderer – eigene Blöcke tragen sich dort ein), siteRedirect, siteResolve (dynamische Pfade, z. B. Sammlungs-Einträge), siteSearchDocs, siteSitemap, siteServe, siteNotFound, siteMissed, siteImages (dims, srcset), imageEngine ({name, formats, resize}; sharp wird erkannt, nie vorausgesetzt) sowie die bestehenden pageServe, pageVisit, htmlTransforms, buyPageHtml, buyCheckout, boardWidgets, mcpTools, snapshotParts. Oberfläche: web/src/pages/site/* als eigene Chunks (lazyPages), Baum-Operationen rein in model.ts.

  • Funnel & Website AI (site-ai.md): modules/site-ai.ts (Briefing → Seitenplan → Block-JSON, Läufe, Vorschau, Anwenden, MCP ghl_site_generate, Agent-Hinweis) und site-ai-drafts.ts (unveröffentlichte KI-Websites über eine Hülle um app.services.siteRedirect, Wiederherstellungspunkte, KI-Texter für Blog und E-Mail) – eine Registry-Zeile ...siteAiModules; reine Logik in services/site-ai-schema.ts (Block-Schema gegen siteBlocks(app), Prüfen, Kompilieren, Zusammenführen eines Abschnitts), services/site-ai.ts (Prompts, Plan-/Seitenprüfung, Abschnittsbefehle, Diff, Schätzung) und services/site-ai-kit.ts (Modell, Budget, Schätzung, Frage-Prüfe-Wiederhole-Schleife askChecked – nutzt auch modules/agent-ai.ts, „KI-Agent mit KI erstellen“ → Entwurfs-Agent, MCP ghl_agent_generate). Haken: siteAiDrafts (mark/isDraft/restorePoint); BYOK-Verwendung sites.

  • Custom Values (custom-values.md): modules/custom-values.ts (Tabellen custom_values, custom_value_form_targets, Routen, pageServe-Ersetzung auf veröffentlichten Seiten, Snapshot-Teil mit Platzhaltern, Undo-Eintrag, MCP), reine Helfer in services/custom-values.ts. core/template.ts hat dafür ein kleines Namensraum-Register (templateNamespace(name, resolver)): {{custom_values.key}} funktioniert damit in jedem render(), der Sub-Account kommt aus den Template-Daten (location.id, contact.location_id …).

  • Vertrieb: modules/sales.ts (one registry line for seven modules) – record-access.ts (teams, rules, the router wrappers hook that checks every request of a restricted person, app.services.recordAccess), deal-lines.ts, quotes.ts (+ quote-model/engine/page, signature via createSignedContract in contracts.ts, deposit invoice through the invoice flow), sequences.ts (+ sequence-model/engine, sends through sendMail, reply sweep on the tick), contact-duplicates.ts (+ contact-dedupe/merge, undo through the journal entity contact_merge), quotas.ts (+ board widgets), meeting-notes.ts (app.services.attachCallSummary); interne Genehmigung quote-approvals.ts (+ services/quote-approval.ts) über den Haken app.services.quoteSendGates – sendQuote fragt jede Sperre vor dem Versand. Details: sales.md.

  • Living Boards: modules/boards.ts (boards, versions with undo/redo/restore, proposals, preferences, SSE stream, widget catalogue and option sources), boards-compose.ts (the prompt bar: aiRun with a strict patch schema, proposals bound to the asker), boards-templates.ts (starters, saved and Marktplatz templates, review/consent, export/import), boards-tools.ts (MCP tools, /api/mobile/boards*, Agent hint). Logic in services/boards-*.ts: boards-layout.ts and boards-consts.ts are pure, import-free modules shared with the browser (grid maths: settle, placeNew, derive), boards-patch.ts (the one patch engine for editor, agent, MCP and API), boards-widgets.ts (registry) with boards-widgets-core.ts (the 13 built-in widgets) and boards-presets.ts, boards-data.ts (widget data through callRoute as the viewer), boards-store.ts (state, versions, access), boards-hub.ts (SSE hub with revalidation), boards-embed.ts (host allow-list). Hooks in app.services: boardWidgets (use boardWidgets(app).register(def) from services/boards-widgets.ts; or push a definition onto boardWidgetDefs before the module starts), boards (apply/load/full/data/list/registry/hub), boardsEmbedHosts, boardsStart, boardsHub. Permission area dashboard (/api/boards, /api/board-templates); callRoute(..., {quiet: true}) skips the audit line of read-like POSTs. Contract, widget API and self-hosting notes: docs/living-boards.md.

  • XR (Raum-Modus, Arbeitsfläche, In XR öffnen): modules/xr.ts + services/xr-model.ts (per-person layouts: board panels, canvas plane, workspace; validation shared with the browser), web/src/xr/* (detection, glass tokens + lazy spatial.css, type scale, Workspace.tsx, XrViewer.tsx and the in-house WebGL2 engine in web/src/xr/engine/), the preference spatial in appearance-model.ts. See docs/xr.md.

  • Back-Office-Haken (backoffice.md): stripeEvents (Liste von (app, event) => void, vom Stripe-Modul für Ereignisse aufgerufen, die es nicht selbst behandelt – die Buchhaltung bucht damit Auszahlungen, Gebühren und Rückerstattungen), accounting (settings/enabled/post/storno), absences (forUser/targetHours – Abwesenheiten und Soll-Stunden je Login, für die Zeiterfassung), workingTimeProvider (hoursByUser(locationId, from, to) – das Gegenstück, das die Zeiterfassung dem Personal-Modul liefert), hrBalance/hrHolidays, accountingPending/accountingFlush (Hintergrundarbeit abwarten). Buchungen, Belege, Lagerbewegungen und Personaldaten sind durch Trigger bzw. eigene Regeln geschützt (Unveränderbarkeit, Festschreibung, versiegelte Felder) – neue Module schreiben nur über app.services.accounting.post.

  • Workflow-Builder (Canvas, Kopieren/Einfügen, Pausieren, Prüfung): je ein Modul – modules/workflow-canvas.ts (hält settings.layout sauber, über app.router.wrappers), workflow-clipboard.ts (POST /api/workflows/:id/copy|paste, Format gohigh-steps), workflow-pause.ts (disabled: true → workflowStepSkips), workflow-validate.ts (issues in den Antworten von GET/PATCH /api/workflows/:id, POST …/validate); reine Logik in services/workflow-validate.ts und services/workflow-layout.ts, die der Browser mitbenutzt. Oberfläche web/src/workflow-canvas/*: tools.tsx (Umschalter Liste/Canvas, Menü, Schalter, Hinweise – für beide Editoren), WorkflowCanvas.tsx (eigener Chunk über lazyPages), layout.ts/model.ts/stats.ts rein. Details: workflow-actions.md.

  • Version, Demo-Modus, TLS: modules/version.ts (GET /api/version: Name, Version aus package.json über server/version.ts, Demo-Infos), modules/demo.ts (PGHL_DEMO_MODE=1: Seed aus services/demo-seed.ts über callRoute, nächtliches Zurücksetzen, app.guards gegen gefährliche Schreibzugriffe, app.services.demo mit info/reset/blocked) und services/outbound-guard.ts (sperrt prozessweit jede ausgehende Verbindung und DNS-Abfrage außer Loopback – auch für Tests nutzbar), modules/tls-ask.ts (GET /api/tls/ask für Caddys On-Demand-Zertifikate, liest app.services.wantedDomains aus dem Agentur-Modul).

  • Test-Haken: memberMailer ersetzt den Versand der Login-Links (Mitgliederbereich), communityMailer den der Community-Zusammenfassung (communityDigest(at) startet sie), affiliateMailer den des Partnerbereichs, adsFetch die Meta-/Google-APIs, restHookFetch die Zustellung der REST-Hooks, hubspotFetch/hubspotSleep/hubspotPauseMs die HubSpot-API und ihr Warten, openshortsFetch die OpenShorts-API, feedFetch den Abruf der Autopilot-Feeds – so wie *Factory bei anderen Modulen.

  • AI tools & providers: aiProvider(locationId) (modules/ai-providers.ts: null = Claude, else the OpenAI provider with model, key, call and cost – aiRun dispatches to it, so every AI consumer works with either), openaiFetch/githubFetch (test hooks for the OpenAI and GitHub APIs), mcpAuth (list of resolvers bearer → {agency, locationId} – further credentials for /mcp, e.g. OAuth access tokens from modules/mcp-oauth.ts), mcpChallenge (the WWW-Authenticate header of a 401 from /mcp, pointing to the protected resource metadata), mcpOAuth (list/revoke connected apps for the KI-Werkzeuge page), brandName (white-label name for MCP metadata). Pure OAuth helpers (redirect URI rules, PKCE, metadata) live in services/mcp-oauth.ts, the Responses API client in services/openai.ts.

  • KI-Studio (media generation): modules/genmedia.ts (routes, callbacks, files, app API) with one adapter per provider in services/genmedia/ (fal, kie, replicate, openai, gemini, stability, elevenlabs, runway, luma, custom-http) and the job engine services/genmedia/engine.ts. It reads keys only through aiConnections (resolve(locationId, capability, connectionId?)/list(locationId) – the BYOK registry of the agent module) and offers genmedia (create/get/out/process/pollDue/idle) to other modules; genmediaFetch is the test hook for all provider HTTP (URL checks still apply). Workflow actions genmedia_image/genmedia_video/genmedia_tts, trigger genmedia.completed, five MCP tools.

  • Voice layer (Stimme): services/voice-engine.ts (synthesize/transcribe/listVoices/liveTts/openLiveStt/phonePlan over the BYOK registry – the speech providers are KI-Studio adapters in services/voice-adapters.ts, registered with registerAdapter), services/voice-phone.ts (Twilio Media Stream session: protocol, turn loop, barge-in, fallback to Twilio’s voices), services/voice-ws.ts (WebSocket server endpoint; core/app.ts hands HTTP upgrades to app.services.upgradeHandlers), services/voice-audio.ts (G.711, resampling, WAV, VAD). modules/voice.ts (settings, push-to-talk, prefs, MCP, mobile relay) and modules/voice-phone.ts (Twilio hooks, recorded turns, voicemail). The phone agent’s brain stays in modules/voice-ai.ts and is offered as app.services.voiceBrain; voiceTransport lets the voice layer carry a call. Test hooks: voiceFetch, voiceWs. Details: docs/voice.md.

  • Fokus-Modus mit Companion / Jarvis live: modules/companion.ts (prefs, briefing, check-ins and their scheduler; other modules add to a check-in through app.services.companionCheckinHooks) and modules/companion-live.ts (event triggers via app.events listeners and its own scan in onTick, the initiative engine over aiRun, the Gemini Live proxy on upgradeHandlers at /ws/companion-live); pure logic in services/companion-prefs.ts, briefing.ts, companion-triggers.ts (registry, routing, rate limit), companion-initiative.ts (schema, cool-down, levels, learning) and gemini-live.ts (Live API framing). Test hooks: companionLiveNow, companionLiveWs, companionLiveBase, companionLiveTimeoutMs. Details: living-boards.md.

  • Telefonie & Gesprächs-Intelligenz: modules/telephony-audio.ts (audio files, hold melody), telephony-numbers.ts (number registry, search/buy/release over the Twilio API, route kinds), telephony-ivr.ts (menus and schedules; the position in a menu travels in the signed webhook URL), telephony-queues.ts (ring groups and poll-based queues), telephony-dni.ts (pools, the page snippet, attribution, report), telephony-calls.ts (notes, cold and announced transfer, retention sweeper), dialer.ts (lists, the compliance gate, outcomes), call-intel.ts (stereo split → per-channel STT → counted metrics + AI analysis → timeline/tasks/events) and call-scorecards.ts; pure logic in services/ivr.ts, call-groups.ts, call-audio.ts, twilio-numbers.ts, dni.ts, dialer.ts, call-intel.ts. Hooks in app.services: callRouters/callRouteKinds (take over an inbound call by dialled number), callInbound (run when an inbound call row is created), telephonyKit (hookRoute, voicemail, unanswered, settleDial …), callGroups.enter, voiceUserClient, audioUsage, dialer.authorize, callIntel, callScorecards.score, callSummaries (list/get/subscribe – summaries for deal timelines), telephonyRetention.sweep; VoiceAgentInput.force. Details and the contract: telephony.md.

  • Webinare (evergreen webinars): services/webinar-schedule.ts (pure schedule engine: sessions are computed from rules, never stored; roomClock decides waiting/running/late/ended), webinar-model.ts (types, defaults, honesty guard LIVE_CLAIM, signed join/preview tokens), webinar-video.ts (sources, Range parsing), webinar-store.ts/webinar-register.ts (registration, confirmation with .ics, reminders), webinar-track.ts (heartbeat on the server clock, questions into Konversationen, polls, offer, purchase attribution, end-of-session bookkeeping), webinar-stats.ts, webinar-pages.ts + webinar-room-client.ts (server-rendered registration page and room with a nonce-CSP). Modules: modules/webinars.ts (admin API, tables, upload), webinar-public.ts (/w/:slug…), webinar-automation.ts (six triggers, action webinar_register, form and purchase bridges, tick), webinar-tools.ts (MCP and app endpoints). Test hook: app.services.webinarNow (virtual clock). Details: docs/webinars.md.

  • Umzug (switch-over importers) and deliverability: modules/umzug.ts (tables, routes, one background job per sub-account, housekeeping), services/umzug-engine.ts (runJob: sources are async generators of {records, cursor, total} batches over normalised records, applied by umzug-write.ts’s Writer with a savepoint per record; buildPreview, undoJob), umzug-core.ts (record types, SourceDef, auto-mapping), umzug-http.ts (VendorHttp: retries, Retry-After, German errors; hosts are fixed per source, links in responses are never followed), one umzug-<vendor>.ts per source, umzug-files.ts/umzug-zip.ts (CSV/JSON/HTML/ZIP exports), umzug-automation.ts (automations → workflow drafts), modules/umzug-tools.ts (MCP). modules/umzug-mailcheck.ts + services/umzug-mailcheck.ts check SPF/DKIM/DMARC/MX with an injectable DnsResolver; messaging.ts adds the RFC 8058 headers (unsubscribeHeaders, OutMail.headers, SendInput.bulk). Details: umzug.md, deliverability.md. Hooks: app.services.umzug.idle() (resolves when no job runs), privacyErase (an erased contact leaves the id map and undo list); test hooks umzugFetch/umzugSleep/umzugPauseMs (fake vendor servers: tests/umzug-fakes.ts), mailDns (fake resolver) and mailCheckFetch (provider APIs). outbound-guard.ts also blocks the promise API’s DNS Resolver in demo mode.

  • Team-Posteingang and Team-Kalender: modules/team-people.ts (availability and out-of-office, one source for assignment and round robin), teaminbox.ts (columns on conversations, views, assign / status / snooze / priority / notes / bulk, settings; hooks conversationFilters, conversationRows, inboxAudience), teaminbox-engine.ts (inbound handling, answer timers in SQL triggers, scheduler, /api/inbox/stream presence), teaminbox-automation.ts (three triggers, three steps, report sources), teaminbox-tools.ts (MCP and /api/mobile/inbox*); services teaminbox-model.ts (pure: settings, deadlines on the helpdesk’s business-hours maths, mentions, view SQL), teaminbox-store.ts, teaminbox-assign.ts (round robin / least busy / rules). Calendars: teamcal.ts (config, members, holds, waiting list, admin routes), teamcal-public.ts (booking, manage, pay-return, routing and waiting-list pages), teamcal-tools.ts; services teamcal-model.ts (pure slot maths for several people), teamcal-engine.ts (loads, busy times incl. OAuth calendars), teamcal-book.ts, teamcal-pay.ts (Stripe Checkout holds via the stripeSessions hook of modules/stripe.ts), teamcal-video.ts, teamcal-pages.ts. The booking pipeline in calendars.ts has small hooks (teamcalPrepare, teamcalSlots, teamcalPage, teamcalManagePage, teamcalCancel, teamcalVars, teamcalPayload). Docs: team-inbox.md, team-calendars.md.

  • Kalenderansicht, Terminserien, KI-Buchung (booking.md): modules/calview.ts (one registry line for three modules) – appointment-series.ts (table appointment_series, series_id/recurrence/occurrence_date/series_exception on appointments, rolling generation in the tick), calendar-view.ts (grid data, conflict check, move/resize through rescheduleAppointment or PATCH /api/appointments/:id, drawer, invoice draft, table appointment_invoices), ai-booking.ts (settings, MCP tools, /book/move/:token); logic in services/calview.ts (viewData, checkTime), services/appt-series.ts, services/appt-recur.ts (pure RRULE subset on top of pm-recur.ts), services/ai-booking.ts (free times and booking shared with the KI-Telefonagent). Hooks: chatBotTurn (the KI-Assistent’s chat bot asks it every turn), aiBooking, appointmentCancel (the calendar page’s cancel with refund rules), calendarView; the MCP Tool flag booking and the agent mode book (“Termin buchen”) in services/agent-safety.ts. UI: web/src/pages/CalendarView.tsx + CalendarGrid.tsx, CalendarDrawer.tsx, calview-model.ts (pure wall-clock maths), calview.css – one lazy chunk, i18n namespace calview.

  • Wiki and Team-Chat: modules/wiki.ts (spaces, page tree, versions, comments, favourites, shares, import/export, uploads), wiki-extras.ts (help-center publishing through the /api/kb/articles routes via callRoute, AI helper through aiRun), wiki-public.ts (read-only share pages under /wiki/s/<token>), wiki-mcp.ts; modules/teamchat.ts (channels, DMs, messages, threads, reactions, pins, files, search, SSE stream, retention), teamchat-commands.ts (/aufgabe, /kontakt, /agent – the agent runs in ask mode with the asker’s rights, confirmation is bound to the asker), teamchat-mcp.ts, collab-mobile.ts (/api/mobile/chat/*, /api/mobile/wiki/*). Shared pieces in services/: wiki-markdown.ts (escaping renderer + allow-list sanitiser, shared by pages, comments and chat), collab-fts.ts (FTS5 with LIKE fallback, joined by rowid so isolation never depends on the index), collab-cards.ts (live record cards; other modules add kinds with registerCardKind), collab-actor.ts, wiki-store.ts, teamchat-store.ts, teamchat-hub.ts (in-memory SSE hub: presence, typing, revalidation of open streams), teamchat-notify.ts. Hooks in app.services: wikiFts, chatFts, chatHub, chatRate (rate-limit overrides for tests), wikiPdf, wikiPageOut, collabCards, teamchat (post/notify/channelOut), teamchatCommands, teamchatRetention, collabIdle/collabPending (wait for background notifications), and collabTasks.create – the seam the project-management module can use to take over task creation for /aufgabe (default: POST /api/tasks). Both areas map to the permission areas wiki and teamchat; the audit log gets one line per sensitive write and a throttled line for autosaves and chat posts. Docs: wiki.md, team-chat.md.

  • Office (Dokumente, Tabellen, Präsentationen, PDF): modules/office.ts (Tabellen office_files/office_versions – ein Trigger verbietet Änderungen an Versionen –, office_locks, office_comments; Dateien, Versionen, Inhalt, Editor-Routen /office/app/<art>/, /office/bridge.js, /office/blob/<token>/<name>, contactFiles, Push-Art office), office-collab.ts (Bearbeitungssperre, Kommentare, Personen), office-tools.ts (Lesen, Bearbeiten, PDF, Umwandeln über die Engines; fünf MCP-Werkzeuge ghl_office_*); Logik in services/office-model.ts (rein: Arten, Namen, Befehlsprüfung, Sperre), office-store.ts (Bytes unter <data>/office-files/, PGHL_OFFICE_MAX_MB/PGHL_OFFICE_QUOTA_MB), office-engine.ts (Adapter zu den optionalen Kommandozeilen-Engines aus PGHL_OFFICE_BIN_DIR: eigener Temp-Ordner, keine Shell, Zeit- und Größenlimits), office-blank.ts (leere Dateien, Text ohne Engine), office-bridge.ts/office-bridge-js.ts (Editor-Seite aus PGHL_OFFICE_DIR mit CSP und Nonce, Lade-/Speicher-Brücke, signierte Links). Bereich office in core/permissions.ts, Seite office in PRESENCE_ROUTES; Upstream-Programme und Installation: deploy/office/. Details: docs/office.md.

  • Live-Zusammenarbeit überall: modules/presence.ts (Routen /api/presence*, Tabelle presence_prefs, Schalter in locations.settings.presence), services/presence-hub.ts (Zustand nur im Speicher: Streams und Tabs, Sichtbarkeit je Empfänger, TTL), services/presence-model.ts (rein, auch im Browser: PRESENCE_ROUTES – Seite → Bereich, eigene Echtzeit, Mauszeiger-Art –, Prüfung der Seitenadresse, Schalter), Oberfläche web/src/presence/* (eigener Chunk; store.ts im ersten Download für Seitenleiste und obere Leiste). Neue Seiten tragen ihren Bereich in PRESENCE_ROUTES ein; eine eigene Fläche für Mauszeiger markiert data-presence-surface. Haken: presenceHub, presence. Details: collaboration.md.

  • Mobile-App: modules/mobile.ts (Geräte-Token ghm_… über app.bearerAuth, /api/mobile/*, Meldungs-Feed), modules/mobile-app.ts (APK, /android, App Links), services/mobile.ts + services/mobile-read.ts. Sie prüft Rechte, indem sie die app.guards gegen eine typische Route des Bereichs laufen lässt (scope/checkArea), und schreibt über callRoute – die normale Route läuft im selben Prozess mit Guards, Ereignissen und afterWrite (Protokoll mit Weg app); die äußere Route setzt dafür ctx.skipAfterWrite. /api/mobile steht deshalb bewusst nicht in permissions.ts.

  • Projektplanung & Zeiterfassung: modules/pm.ts (Projekte, Aufgaben, Ansichten, /api/pm/*), pm-time.ts (Timer, Einträge, Wochenzettel, Abrechnung), pm-automation.ts (Regeln, Workflow-Auslöser task.* und -Aktionen pm_*, Überfällig-Scan), pm-portal.ts (Kundenportal über portalHeader/portalDashboard/portalMemberCheck), pm-tools.ts (17 MCP-Werkzeuge, Berichtsquellen time_entries/pm_projects), pm-mobile.ts (/api/mobile/pm/*). Die Logik liegt in services/pm-*.ts; pm-schedule.ts, pm-tree.ts und pm-recur.ts sind reine Module ohne Imports (Terminrechnung, Hierarchie-Regeln, Wiederholung) und werden von der Gantt-Ansicht mitbenutzt. Die Tabelle tasks wird erweitert statt ersetzt: Trigger halten status_id/done/assigned_to für alle alten Schreiber (Aufgabenliste, Workflows, Todoist, App) synchron, neue Aufgaben landen im Projekt „Allgemein“. Haken: app.services.pm.getTask, pmTime.autoStopTimers, pmScan.overdue/dueToday; Aufgaben und Projekte sind Verknüpfungs-Typen (task, pm_project) – so hängen sich Dokumente und Chat der Collab-Module an. Kalkulation (Budgets mit Schwellen, Kostensätze, Fremdkosten, Rentabilität, Retainer): pm-costing.ts, pm-retainers.ts mit services/pm-costing*.ts/pm-retainers.ts, Haken pmCosting/pmRetainers. Vertrag: docs/projects.md.

  • Twilio-Helfer: verifyTwilio (Signatur-Prüfung) und twilioRequest (REST mit den Zugangsdaten des Sub-Accounts) aus modules/telephony.ts nutzen auch KI-Telefonagent (modules/voice-ai.ts, reine Bausteine in services/voice-ai.ts) und Browser-Telefon (modules/browser-phone.ts, services/browser-phone.ts).

  • Gemeinsame Helfer: services/markdown.ts (sicheres Markdown, Video-Einbettung, Auszüge) für Blog und Kurse; services/courses.ts (Zugang, Drip, Fortschritt) für Kurse, Mitgliederbereich und Workflows; services/email-render.ts (E-Mail-Designer: Design-JSON → HTML + Text, ohne Imports, auch von der Oberfläche für die Vorschau genutzt) mit services/email-design.ts (rendert design beim Speichern von Vorlagen, Kampagnen und Workflow-Schritten); services/community.ts (Zugang, Levels, Wortfilter, Bildprüfung) und services/community-actions.ts (Beiträge, Punkte, Benachrichtigungen, Moderation, Zusammenfassung) für Community-Admin (modules/community.ts) und -Mitgliederseiten (modules/community-portal.ts).

  • Geheimnisse in den Einstellungen (Passwörter, Tokens) gehen nie zurück an den Browser (maskSettings). Gespeichert werden sie versiegelt: sealSecret/openSecret/openIfSealed aus core/secrets.ts (AES-256-GCM, Datenschlüssel + frühere Schlüssel, npm run rotate-secret); Zugangsdaten in den Sub-Account-Einstellungen versiegelt app.location() selbst (sealSettings).

  • Anbieter-Links: modules/referrals.ts (Katalog, REFERRAL_DEFAULTS, /go/:provider, app.services.signupLink(id)), in der Oberfläche <SignupLink id="…" /> aus web/src/referrals.tsx neben jedem Schlüssel-Feld – docs/referrals.md.

  • Nango & MCP-Client: services/nango.ts spricht als einzige Stelle mit Nango (Test-Haken nangoFetch), modules/nango.ts stellt app.services.nango bereit (request mit Freigabe-Liste). services/mcp-client.ts ist der MCP-Client (Streamable HTTP + SSE, OAuth 2.1-Client, Bereinigung von Werkzeugen/Ergebnissen; Test-Haken mcpClientFetch), modules/mcp-client.ts verwaltet externe Server (app.services.mcpClient). Zwei allgemeine Haken: agentToolSources (Werkzeug-Quellen für den KI-Agenten, Vertrag in mcp-client.md) und mcpToolSources (async (caller) => Tool[] – Werkzeuge von /mcp, die vom Aufrufer abhängen). safeFetch kennt allowPrivate für Ziele, die die Agentur ausdrücklich als „internes Netz“ erlaubt hat.

  • Connectors: services/composio.ts ist die einzige Stelle, die mit Composio spricht (Test-Haken composioFetch); andere Module nutzen app.services.composio (Schlüssel, Verbindungen, execute mit Freigabe-Liste, Webhook) und composioTriggers/composioAgent. Direkt-Verbindungen melden sich mit registerConnector aus services/connectors.ts an (Felder, Test, Workflow-Aktionen, Katalog-Eintrag); das Framework (modules/connectors.ts) stellt connectorConfig/connectorStatus bereit, ausgehende Aufrufe laufen über connectorFetch (Test-Haken, sonst safeFetch). Anleitung: docs/connectors.md.

  • Buchhaltungs-Live-Sync & Google-Offline-Conversions: modules/books-sync.ts (Lexware Office/sevDesk: Verbindung je Sub-Account mit versiegeltem Schlüssel, Warteschlange books_sync_jobs – ein Auftrag je Beleg, Hörer auf invoice.finalised, Zahlungsabgleich im Tick über recordPayment, Remote-ID in invoice_links), Logik in services/books-sync-model.ts (rein: Belege, Zuordnung, Rate-Limiter, Backoff) und books-sync-http.ts (Clients über safeFetch, Test-Haken booksSyncFetch/booksSyncSleep); modules/ads-conversions.ts + services/ads-gconv.ts (UploadClickConversions über die Google-Verbindung des Anzeigen-Managers, Einwilligungs-Prüfung über app.services.consent.status, Haken app.services.adsGoogleConversions). Doku: integrations.md, ads-manager.md.

  • Werkstatt, Plugins, Marktplatz: modules/werkstatt.ts (Aufträge: Plan → Zustimmung → Freigabe → Bau, Fortschritt, Kosten, Richtlinie), modules/plugins.ts (Plugin-Speicher, Brücke, isolierte Oberfläche, Jobs, Export/Import), modules/marktplatz.ts (Katalog, Installieren mit Zustimmung, Prüfung, Veröffentlichen), modules/werkstatt-core.ts (Kernfeature-Vorschläge über app.services.githubIssues), modules/werkstatt-tools.ts (MCP-Werkzeuge) und modules/flags.ts (app.services.flags.enabled(name, {locationId, userId})); die reine Logik liegt in services/werkstatt/* (Manifest, Prüfungen, Brücke, Sandbox-Läufer, deklarative Schritte, Benachrichtigungen), die Namen „Werkstatt“/„Marktplatz“ in services/werkstatt/names.ts. Haken: app.services.plugins, werkstatt (idle() für Tests), pluginPreviewUrl, pluginScreenshot (Test-Haken für Vorschaubilder), agentPromptExtras (Zusätze zum Agent-Prompt), githubIssues (create/status/hasToken/defaultRepo), workflowActions.plugin_action; app.guards begrenzen via: plugin/werkstatt auf freigegebene Routen. Test-Haken aiConnectionsFetch und feedbackMailer decken KI-Anbieter und E-Mails ab. Sicherheitsmodell und Grenzen: werkstatt.md.

  • Sicherheits-Bausteine: safeFetch (services/net.ts) für jede vom Nutzer kommende URL, contentMatches/extFor (core/files.ts) für Uploads, safeEqual/verifyPasswordAsync (core/util.ts), clampLimit (core/http.ts) für Seitengrößen, csvSafe (core/csv.ts) für Exporte, oneLine (services/messaging.ts) für Mail-Header. app.services.bodyLimits ({match(method, path, req), bytes}) erlaubt öffentlichen Routen mehr als 1 MB; app.afterRead läuft nach jedem angemeldeten GET (das Protokoll erfasst so Exporte). DSGVO: app.services.privacyExport/privacyErase ((app, locationId, contact)) für eigene Daten eines Moduls (modules/privacy.ts; läuft in der Lösch-Transaktion), contactFiles ((app, locationId, contactId) → Pfade – Dateien eines Kontakts, die beim Löschen und bei der DSGVO-Löschung mitgehen, services/contacts.ts). Neue Routen deckt tests/tenant-isolation.test.ts automatisch ab – Lookups immer mit location_id filtern. Details: security.md.

  • Landing page (PGHL_LANDING=1): modules/landing.ts (routes /, /de, /vergleich…, /sitemap.xml only on the instance’s own host; data, star-count cache) with the pure parts services/landing-calc.ts (HighLevel prices with date, calculator, share links), landing-compare.ts (research matrix), landing-stack.ts (“Replaces your stack”), landing-switch.ts, landing-copy.ts (DE/EN, one interface), landing-waitlist-copy.ts (the texts in cloud waitlist mode) and landing-page.ts (HTML, CSS, the hash-allowed inline script). Hooks: robotsExtra (lines for /robots.txt), landingFetch (test hook for GitHub), landing (refreshStars/stats/matrix). Data and images in web/public/landing/ – docs/landing.md.

  • Hosted cloud (PGHL_CLOUD_MODE=1): every customer is a tenant (cloud_tenants) around one primary sub-account (Agentur plan: plus client sub-accounts in cloud_locations); billing sits on the primary’s saas_subscriptions row, so the SaaS statuses, usage gate, read-only mode and banner are reused. Modules (all return early when the mode is off): modules/cloud.ts (tables, settings in agency_settings.cloud, plan sync into saas_plans, guards, hideUser, /api/cloud/me|plans), cloud-pages.ts (/cloud, /start, legal pages – server-rendered, cookie-free, script-free CSP), cloud-signup.ts (form, verification, Google, Checkout, provisioning, housekeeping), cloud-billing.ts (plan changes, portal, cancel, invoices, Stripe event taps, reconcile tick: trial, dunning, read-only, deletion schedule), cloud-account.ts (team, clients, branding, domain, export, delete, support consent, AVV, onboarding), cloud-console.ts (operator, admin-only), cloud-waitlist.ts (waitlist mode – with cloud mode or the landing page: a router wrapper refuses the signup routes, the waitlist form’s endpoint into the landing hook’s form or the built-in cloud_waitlist table, the operator’s list; app.services.cloudWaitlist). Logic in services/cloud.ts (plans, entitlements, settings validation, pure timeline maths), cloud-legal.ts, cloud-mail.ts, cloud-export.ts (ZIP export and the erase that finds every row), cloud-pages.ts, cloud-waitlist.ts (texts and form of the waitlist mode). Hooks in app.services: saasEventTaps (functions called after every handled Stripe event of the agency webhook), cloudCompleteSignup (SaaS webhook → cloud for checkouts with cloud_signup_id), hideUser(user, locationId) with hiddenUser() in core/permissions.ts (people lists skip operator admins in tenants), hiddenLocations(user) with visibleLocations() (the operator’s /api/me, /api/locations, agency overview and projects skip customers not open for support), cloudMailer/cloudFormMinMs/ssoFetch (test hooks), cloud (info/reconcile/tenantOfLocation/tenantOfUser/resetLimits/cloudGoogleReady). GET /api/version carries cloud. Decision record, setup and limits: docs/cloud.md.

  • Betrieb (Backups, Alarme, Statusseite): modules/offsite-backup.ts (stündliche, verschlüsselte Off-site-Backups in S3-kompatiblen Speicher, monatlicher Restore-Test; Logik in services/backup-offsite.ts, backup-crypto.ts, s3-lite.ts, backup-retention.ts, Kommando cli/backup-restore.ts), ops-alerts.ts (umhüllt die /healthz-Route von core.ts über router.wrappers, kettet app.services.serverError für die Fehlerrate; Haken app.services.ops – health/evaluate/send –, opsAlert.event(alert) für einmalige Alarme anderer Module und opsEveryMinute – Funktionen nach jeder Auswertung), status-page.ts (öffentliche Seite /status, Vorfälle). Im Cloud-Modus cloud-abuse.ts (DSA-Meldungen, Maßnahmen, automatische Versandpause; umhüllt usageGate, app.services.cloudAbuse) und cloud-sites.ts (Kunden-Websites nie unter der Login-Adresse, eigene Domains erst nach TXT-Bestätigung; umhüllt wantedDomains/domainStatus). Oberfläche über die UI-Slots agency.system und cloud.abuse. Details: backups.md, cloud.md.

  • Zahlungsanbieter, Online-Shop, Gutscheine (payment-providers.md, store.md): eine Registry-Zeile ...storeModules – modules/pay-registry.ts (Stripe, PayPal, Mollie hinter einer kleinen Schnittstelle aus services/pay-registry.ts: create/fetch/refund je Anbieter in services/pay-stripe.ts, pay-paypal.ts, pay-mollie.ts; Bezahlseite /pay/<id>, Webhooks, Anbindung an Rechnungen, Kauf-Links und Termine), giftcards.ts (Ledger mit Triggern, Reservierungen), store.ts, store-public.ts (Warenkorb, Kasse, Blöcke store_products/store_product), store-tools.ts; reine Logik in services/store-model.ts (Versand, Steuerart, Angebot = calcTotals der Rechnungszeilen) und services/store-catalog.ts. Haken: registerPayProvider/registerPayPurpose (eigene Anbieter und Zwecke – invoice, product, teamcal_hold, store_order), app.services.payRegistry (start/checkout/refund/refundAll/choiceNeeded … – nutzt services/teamcal-pay.ts), payBuy/buyPayNote (Kaufseite mit anderem Anbieter, modules/stripe.ts übergibt), verkettetes invoicePayHtml, giftCards (hold/commit/release/payInvoice …), store; UI-Slot settings.payments; Test-Haken payFetch (PayPal und Mollie).

  • HighLevel-Parität (Schnellgewinne): modules/tag-meta.ts + services/tag-meta.ts (Farben und Kategorien der Tags, Snapshot-Teil tag_meta; Chips in der Oberfläche über web/src/tag-chip.tsx), modules/pipeline-board.ts (Reihenfolge der Pipelines; Stufenfarben in stages[].color), modules/deal-list.ts + services/deal-query.ts (Listenansicht der Deals: Filter/Sortierung für GET /api/opportunities über die Filter-Engine aus services/objects.ts, Massenaktionen, CSV; Baustein web/src/pages/ListKit.tsx), modules/inbox-spam.ts (blockierte Absender), modules/call-scripts.ts (Gesprächsleitfäden; in der Oberfläche über den Slot call.panel – web/src/slots.tsx ist die Liste der UI-Slots, eine Zeile je Füllung), modules/notes-plus.ts (Titel, Farbe, Anheften, Markdown und Suche der Notizen; Haken noteExtras – zusätzliche Spalten für den Workflow-Schritt add_note). Neuer Haken inboundGates (services/inbound-gate.ts): jeder Eingangsweg (E-Mail/SMS-Webhooks, Meta-Kanäle, channel-kit, Postfach-Sync) fragt ihn, bevor eine Nachricht zählt – ein Tor, das sie beansprucht, archiviert sie, und es gibt kein message.inbound. Details: team-inbox.md, api.md.

  • HighLevel-Parität (CRM, Posteingang, Automationen): eine Registry-Zeile ...crmGapModules (modules/crm-gaps.ts) – tag-bulk.ts (Nutzung, Umbenennen/Zusammenführen/Löschen/Färben in SQL, Undo über die Entität tag), inbox-filters.ts (Filter tag/pipeline_id/stage_id/owner über conversationFilters, Volltextsuche GET /api/conversations/search über die FTS5-Tabelle messages_fts, gepflegt von SQL-Triggern auf messages), contact-bulk-owner.ts, field-cleanup.ts (Undo-Entität custom_field), message-triggers.ts (Auslöser message.outbound/message.team_reply: SQL-Trigger schreiben gesendete Nachrichten in message_outbound_queue, geleert nach jeder Anfrage über afterWrite und im Tick), contact-documents.ts (Dateien am Kontakt und an Notizen, Bytes in contact_document_blobs), workflow-folders.ts (Ordner, workflows.folder_id, POST /api/workflows/bulk über callRoute), field-history.ts (Tabelle field_changes, Akteur aus afterWrite/viaWorkflow), deal-smart-tags.ts, deal-forecast.ts, split-winner.ts (Option auto_winner am Schritt split_test, Prüfung im Tick), company-bulk.ts (Firmen in Massen über callRoute), smart-lists.ts (Smart Lists 2.0: Spalten, Sortierung, geteilt/privat an der Tabelle smart_lists, Routen /api/smart-lists*, Haken contactOrder, MCP ghl_smart_lists/ghl_smart_list_contacts; reine Teile in services/smart-lists.ts, Oberfläche web/src/pages/SmartLists.tsx, i18n-Bereich smartlists). Oberfläche: web/src/pages/TagBulk.tsx, InboxFilters.tsx, ContactBulkOwner.tsx, FieldCleanup.tsx, ContactDocuments.tsx, WorkflowFolders.tsx, DealSmartTags.tsx, DealForecast.tsx (je eine Zeile in der Seite, die sie zeigt), i18n-Bereich crmgaps.

  • HighLevel-Parität (Reichweite): modules/review-sources.ts + services/review-sources.ts (Bewertungen weiterer Plattformen: Adapter-Registry registerReviewAdapter – Facebook, Trustpilot, ProvenExpert, Yelp, Tripadvisor, Import per CSV/RSS/Web-Hook –, eine Tabelle review_items, Auslöser review.received mit source, KI-Entwürfe über app.services.reviewReplies.suggest, Widget über app.services.reviewWidgetParts, Test-Haken reviewSourceFetch – reputation.md), modules/social-comments.ts + services/social-comments.ts (Kommentare von Facebook/Instagram über metaWebhookHooks, YouTube/LinkedIn über app.services.nango, Kanal comment im Posteingang, Aktionen über messageRows und den UI-Slot inbox.message; app.services.socialComments.ingest – social-comments.md), mehrere Facebook-Seiten/Instagram-Konten in modules/meta-channels.ts (Tabelle meta_pages, Zuordnung über Seiten-ID, meta_identities.account_id, metaKit.graph(…, accountId)), modules/voice-outbound.ts (Workflow-Aktion voice_ai_call, Prüfung über app.services.dialer.verdict + Tageslimit, Twilio mit Anrufbeantworter-Erkennung, VoiceAgentInput.stage = 'outbound', Haken voicePromptExtras, vier Ergebnis-Auslöser – voice.md) und modules/caller-ids.ts (bestätigte eigene Nummern als Absender, app.services.callerIds.pick/allowed für Browser-Telefon, Klick-Anruf, Power-Dialer und KI-Anrufe – telephony.md). Oberfläche über die UI-Slots reviews.panel, channels.meta, voiceai.panel, telephony.settings (web/src/slots.tsx).

  • HighLevel-Parität (Buchung, Formulare, Zahlung, Shop, Verträge): eine Registry-Zeile ...parityBookingModules (modules/parity-booking.ts) – contract-signers.ts (mehrere Unterzeichner mit Rollen und Reihenfolge; hängt sich über router.wrappers an /api/contracts/:id/send, /s/:token und die Einzel-Signatur-Routen von contracts.ts, Tabellen contract_sign_runs/contract_signers, Auslöser contract.signer_signed), service-menu.ts (Leistungsmenü /book/s/<slug> über bookTeam mit hostId), calendar-ai.ts (explainSlot aus computeSlots/slotsOfDay und checkTime, MCP ghl_calendar_create/ghl_calendar_explain_slot), form-pdf.ts (PDF einer Einsendung, MCP ghl_forms/ghl_form_submission_pdfs), form-drafts.ts (Fortschritt speichern: Skript per Wrapper an /f/:id, Tabelle form_drafts mit Token-Hash), coupon-codes.ts (Einmal-Rabattcodes als buy_coupons mit max_uses 1, Workflow-Schritt coupon_create, issueCodes), loyalty.ts (Treuepunkte-Ledger aus invoice.paid, Stufen, Prämien über issueCodes/app.services.giftCards), store-extras.ts (Packzettel-PDF, abgebrochene Warenkörbe), portal-appointments.ts („Meine Termine“ über portalHeader/portalDashboard/portalMemberCheck), booking-guests.ts (Gäste aus meta.guests von appointment.booked, Einladungen, appointmentRows), event-checkin.ts (Ticket-Seite mit QR über renderQr, Check-in über callRoute auf PATCH /api/appointments/:id), booking-multi.ts (mehrere Zeiten in einer Buchung über bookTeam, Rücknahme per cancelAppointment), rentals.ts (Vermietung: Mietangebot = Kalender mit settings.rental, Tabellen rentals/rental_bookings, Verfügbarkeit und Schreiben in einer Transaktion über services/rentals-book.ts, Zahlung über den Zweck rental der Zahlungs-Registry, Seiten /book/rent/<slug> und über die Ketten teamcalPage/teamcalManagePage/teamcalCancel/teamcalVars). Im Team-Kalender selbst: show_if an Fragen (checkConditions/questionShown in services/teamcal-model.ts), guests_max, durations und multi_max in der Konfiguration (guestsAllowed, durationChoices, multiAllowed). UI-Slots contract.signers und giftcards.panel.

  • HighLevel-Parität (Websites, Marketing, Agentur – Welle 10): eine Registry-Zeile ...gapsSitesModules (modules/gaps-sites.ts) für site-seo.ts (SEO-Check, Schema.org je Seite), site-dyntext.ts ({{url.…}}, {{#if url.…}}), site-fonts.ts (eigene Schriftdateien), site-protect.ts (Passwortschutz – die letzte pageServe-Hülle), social-approval.ts + social-queues.ts (über app.services.socialPost), agency-banners.ts, role-templates.ts (zweiter Wächter für Nur-Lese-Bereiche, afterWrite löst die Rolle bei Handänderung), template-conditions.ts (neuer Haken templatePass in core/template.ts, Parser services/template-conditions.ts), video-testimonials.ts (roher Upload über rawBodyLimit) und prompt-lab.ts (über aiRun, Übernehmen per callRoute). Oberfläche über die neuen UI-Slots shell.banner, agency.clients, settings.team, agents.tests und reviews.panel; Tests tests/gaps-sites*.test.ts.

Berichte & Dashboards (modules/reports.ts, Engine in services/report-engine.ts, eingebaute Quellen in services/report-sources.ts) lesen alles über die Liste app.services.reportSources. Ein Modul meldet seine Tabelle mit wenigen Zeilen an – egal, ob es vor oder nach dem Berichte-Modul registriert wird:

import type { ReportSource } from '../services/report-engine.js';
import { contactJoin, lookupTable } from '../services/report-sources.js';
const ticketContact = contactJoin('t.contact_id'); // optional: Kontaktfelder als „Kontakt: …“
const TICKETS: ReportSource = {
key: 'tickets', label: 'Tickets', area: 'conversations', // Recht, das Kunden-Logins brauchen (+ SaaS-Tarif)
from: 'tickets t', location: 't.location_id', dateField: 'created_at',
requires: ['tickets'], countLabel: 'Anzahl Tickets', joins: ticketContact.joins,
fields: [
{ key: 'created_at', label: 'Erstellt am', type: 'date', sql: 't.created_at' },
{ key: 'status', label: 'Status', type: 'enum', sql: 't.status', options: { open: 'Offen', closed: 'Geschlossen' } },
{ key: 'agent', label: 'Bearbeiter', type: 'string', sql: 't.assigned_to', lookup: lookupTable('users', 'name'), empty: 'Nicht zugewiesen' },
{ key: 'hours', label: 'Lösungszeit', type: 'number', sql: '(julianday(t.closed_at) - julianday(t.created_at)) * 24', format: 'number' },
...ticketContact.fields,
],
calcs: [{ key: 'close_rate', label: 'Lösungsquote', sql: `100.0 * SUM(t.status = 'closed') / NULLIF(COUNT(*), 0)`, format: 'percent' }],
};
app.services.reportSources = [...(app.services.reportSources ?? []), TICKETS];
  • sql, from, joins und calcs sind fester Code – Nutzerwerte kommen nur als Parameter in die Abfrage; die Engine hängt den Sub-Account-Filter (location IN (…)), Zeitraum, Filter, LIMIT und eine Zeitgrenze selbst an.
  • Feldtypen: string, enum (mit options), number (format: number, money – in Euro –, percent, days, minutes), date (ISO-Zeitstempel; Gruppierung nach Tag … Stunde in der Zeitzone des Sub-Accounts), boolean (SQL liefert 1/0), tags (JSON-Array). lookup übersetzt IDs in Namen (und Reihenfolge, z. B. Pipeline-Phasen); noGroup für Freitext.
  • Jede Quelle bekommt automatisch die Gruppe Sub-Account; Quellen mit fehlenden Tabellen (requires) werden ausgeblendet. Ein Test, der alle Felder kompiliert, steht in tests/reports.test.ts.