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)Ein Modul hinzufügen
Abschnitt betitelt „Ein Modul hinzufügen“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 mitlocationfiltert aufctx.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.contactIdstartet Workflows für diesen Kontakt. -
Rechte:
app.guardslaufen nach der Anmeldung; das Agentur-Modul prüft dort die Bereiche eingeschränkter Logins (server/core/permissions.tsordnet 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()ausweb/src/i18n(const t = useT('bereich'), der deutsche Text ist die Nachrichten-ID); die englischen Übersetzungen liegen je Bereich inweb/src/i18n/en/*.ts. Bereiche mitcomplete: trueinweb/src/i18n/registry.tshältnpm run i18n:checkvollständig. Die Sprache je Person speichertmodules/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 neueWHERE/ORDER BYauf großen Tabellen, lesende Listen/Auswertungen übercached()(core/cache.ts) bzw.app.db.memo, Hintergrund-Aufgaben in Scheiben von höchstens ~50 ms (sliceTimer), neue Seiten mitlazyPages– 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.servicesstatt direkter Importe:instrumentEmail/personalizeLinks(Tracking → Versand und Postfach-Versand;instrumentEmailsetzt 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ürPOST /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/blogund 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/buyAfterPaidundstripeBuy(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 – mitprepare, 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-AttrappeoauthFetch),googleBusinessReady/googleBusinessFetch/googleBusinessStatus(Unternehmensprofil → Google-Bewertungen statt Places API).externalBusyist 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:registerTypefü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/refsOfdes 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 inctx.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/updateTicketfü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),socialPostmitmediaUrls+video(Video per signiertem Link an Postiz, dann auch YouTube/TikTok) undthread(weitere Teile als Antworten, z. B. X-Thread),aiRunmittimeoutMsfü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 hinterdeliverEmail/deliverSms– sie finden die App über die nicht aufzählbare Eigenschaftappder Sub-Account-Zeile ausapp.location()), Test-HakenchannelFetch/discordSocket(docs/channels.md). EinRawResultdarf alsbodyeinen 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üraiRun; 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),mcpToolswird 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 Anbietermailboxhängt sich überemailTransportsindeliverEmailein (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):callRouteausservices/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 OptionpayloadvonmetaSend(Nachrichten mit Buttons),creatorPlans,dunning/dunningInvoices/billingPage(Mahnwesen und Abo-Seite),quizKit/assignmentKit/certificates(Quiz, Aufgaben, Zertifikate),igComments. Die KI-Verwendungworkflows(Zuordnung im KI-Anbieter) bedient den KI-Workflow-Builder (aiRunmit dem Zweckworkflow_builder). -
Wissensgedächtnis (Cognee):
modules/cognee.ts(tables, agency + sub-account routes, governance status),cognee-sync.ts(scheduler job,privacyErasehook queues the deletion of a contact’s data items,app.services.cognee),cognee-tools.ts(MCP/agent toolsghl_memory_recall/ghl_memory_remember); logic inservices/cognee/*(client.tsREST client with the sub-account-boundLocationClient,sources.tsoneKindDefper kind of record,sync.tsengine + deletion queue,recall.ts,redact.ts,model.ts). Hooks:app.services.cogneeFetch/cogneeSleep/cogneeNow/cogneeGapMs(test hooks), the optionaluntrusted: trueon an MCPTool(the agent treats its output as untrusted). Dataset isolation, erasure and what is sent: docs/cognee.md. -
Einwilligung & Recht:
modules/consent.ts(Ledgerconsent_events/consent_texts/consent_doi, Double-Opt-in-Tick, Routen, Auslöserconsent.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 inservices/consent-model.ts(rein),consent.ts,consent-proof.ts,cmp.ts(rein),legal-texts.ts(rein),legal-pdf.ts. Haken inapp.services:consentGate(services/messaging.tsund der WhatsApp-Versand fragen vor jeder Werbe-Nachricht;purpose: 'transactional'für Service-Nachrichten),consent.*(Erfassen, Status,allowMarketingManyfür Zielgruppen – Aufrufer mit?.),cmp(attributionConsent,linkCmp),htmlTransforms(Kern: öffentliche HTML-Seiten nachbearbeiten, imtry/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(Tabelleundo_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(MCPghl_undo_list/ghl_undo,/api/mobile/undo/*); Motor inservices/undo-journal.ts,undo-core.ts,undo-table.ts,undo-versioned.ts. Haken:app.services.undomitregister(type, {snapshot, restore, label, …})(was ein Datensatz ist und wie er zurückkommt –tableEntitydeckt 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.undoSinksammelt die Einträge eines Agent-Werkzeugs;Db.txverschachtelt (Savepoints). Neue Module melden ihre Datensätze dort an – Vertrag und Grenzen: docs/undo.md. -
Website-Baukasten (sites.md):
modules/funnels.ts(Seiten, AuslieferungservePage, 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-Bausteinfunnel_overview, MCPghl_funnel_analytics),site-templates.ts(23 Vorlagen),site-media.ts(Bildgrößen,/uploads-opt),site-snapshot.ts(Kopieren); reine Logik inservices/site-*.ts(Blöcke, Layout, Seite, Design-Token, Weiterleitungen, Suche, Prüfung, Bilder, Vorlagen). Haken inapp.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};sharpwird erkannt, nie vorausgesetzt) sowie die bestehendenpageServe,pageVisit,htmlTransforms,buyPageHtml,buyCheckout,boardWidgets,mcpTools,snapshotParts. Oberfläche:web/src/pages/site/*als eigene Chunks (lazyPages), Baum-Operationen rein inmodel.ts. -
Funnel & Website AI (site-ai.md):
modules/site-ai.ts(Briefing → Seitenplan → Block-JSON, Läufe, Vorschau, Anwenden, MCPghl_site_generate, Agent-Hinweis) undsite-ai-drafts.ts(unveröffentlichte KI-Websites über eine Hülle umapp.services.siteRedirect, Wiederherstellungspunkte, KI-Texter für Blog und E-Mail) – eine Registry-Zeile...siteAiModules; reine Logik inservices/site-ai-schema.ts(Block-Schema gegensiteBlocks(app), Prüfen, Kompilieren, Zusammenführen eines Abschnitts),services/site-ai.ts(Prompts, Plan-/Seitenprüfung, Abschnittsbefehle, Diff, Schätzung) undservices/site-ai-kit.ts(Modell, Budget, Schätzung, Frage-Prüfe-Wiederhole-SchleifeaskChecked– nutzt auchmodules/agent-ai.ts, „KI-Agent mit KI erstellen“ → Entwurfs-Agent, MCPghl_agent_generate). Haken:siteAiDrafts(mark/isDraft/restorePoint); BYOK-Verwendungsites. -
Custom Values (custom-values.md):
modules/custom-values.ts(Tabellencustom_values,custom_value_form_targets, Routen,pageServe-Ersetzung auf veröffentlichten Seiten, Snapshot-Teil mit Platzhaltern, Undo-Eintrag, MCP), reine Helfer inservices/custom-values.ts.core/template.tshat dafür ein kleines Namensraum-Register (templateNamespace(name, resolver)):{{custom_values.key}}funktioniert damit in jedemrender(), 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 routerwrappershook that checks every request of a restricted person,app.services.recordAccess),deal-lines.ts,quotes.ts(+quote-model/engine/page, signature viacreateSignedContractincontracts.ts, deposit invoice through the invoice flow),sequences.ts(+sequence-model/engine, sends throughsendMail, reply sweep on the tick),contact-duplicates.ts(+contact-dedupe/merge, undo through the journal entitycontact_merge),quotas.ts(+ board widgets),meeting-notes.ts(app.services.attachCallSummary); interne Genehmigungquote-approvals.ts(+services/quote-approval.ts) über den Hakenapp.services.quoteSendGates–sendQuotefragt 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:aiRunwith 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 inservices/boards-*.ts:boards-layout.tsandboards-consts.tsare 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) withboards-widgets-core.ts(the 13 built-in widgets) andboards-presets.ts,boards-data.ts(widget data throughcallRouteas the viewer),boards-store.ts(state, versions, access),boards-hub.ts(SSE hub with revalidation),boards-embed.ts(host allow-list). Hooks inapp.services:boardWidgets(useboardWidgets(app).register(def)fromservices/boards-widgets.ts; or push a definition ontoboardWidgetDefsbefore the module starts),boards(apply/load/full/data/list/registry/hub),boardsEmbedHosts,boardsStart,boardsHub. Permission areadashboard(/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 + lazyspatial.css, type scale,Workspace.tsx,XrViewer.tsxand the in-house WebGL2 engine inweb/src/xr/engine/), the preferencespatialinappearance-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 überapp.services.accounting.post. -
Workflow-Builder (Canvas, Kopieren/Einfügen, Pausieren, Prüfung): je ein Modul –
modules/workflow-canvas.ts(hältsettings.layoutsauber, überapp.router.wrappers),workflow-clipboard.ts(POST /api/workflows/:id/copy|paste, Formatgohigh-steps),workflow-pause.ts(disabled: true→workflowStepSkips),workflow-validate.ts(issuesin den Antworten vonGET/PATCH /api/workflows/:id,POST …/validate); reine Logik inservices/workflow-validate.tsundservices/workflow-layout.ts, die der Browser mitbenutzt. Oberflächeweb/src/workflow-canvas/*:tools.tsx(Umschalter Liste/Canvas, Menü, Schalter, Hinweise – für beide Editoren),WorkflowCanvas.tsx(eigener Chunk überlazyPages),layout.ts/model.ts/stats.tsrein. Details: workflow-actions.md. -
Version, Demo-Modus, TLS:
modules/version.ts(GET /api/version: Name, Version auspackage.jsonüberserver/version.ts, Demo-Infos),modules/demo.ts(PGHL_DEMO_MODE=1: Seed ausservices/demo-seed.tsübercallRoute, nächtliches Zurücksetzen,app.guardsgegen gefährliche Schreibzugriffe,app.services.demomitinfo/reset/blocked) undservices/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/askfür Caddys On-Demand-Zertifikate, liestapp.services.wantedDomainsaus dem Agentur-Modul). -
Test-Haken:
memberMailerersetzt den Versand der Login-Links (Mitgliederbereich),communityMailerden der Community-Zusammenfassung (communityDigest(at)startet sie),affiliateMailerden des Partnerbereichs,adsFetchdie Meta-/Google-APIs,restHookFetchdie Zustellung der REST-Hooks,hubspotFetch/hubspotSleep/hubspotPauseMsdie HubSpot-API und ihr Warten,openshortsFetchdie OpenShorts-API,feedFetchden Abruf der Autopilot-Feeds – so wie*Factorybei anderen Modulen. -
AI tools & providers:
aiProvider(locationId)(modules/ai-providers.ts: null = Claude, else the OpenAI provider with model, key,callandcost–aiRundispatches to it, so every AI consumer works with either),openaiFetch/githubFetch(test hooks for the OpenAI and GitHub APIs),mcpAuth(list of resolversbearer → {agency, locationId}– further credentials for/mcp, e.g. OAuth access tokens from modules/mcp-oauth.ts),mcpChallenge(theWWW-Authenticateheader of a 401 from/mcp, pointing to the protected resource metadata),mcpOAuth(list/revokeconnected apps for the KI-Werkzeuge page),brandName(white-label name for MCP metadata). Pure OAuth helpers (redirect URI rules, PKCE, metadata) live inservices/mcp-oauth.ts, the Responses API client inservices/openai.ts. -
KI-Studio (media generation):
modules/genmedia.ts(routes, callbacks, files, app API) with one adapter per provider inservices/genmedia/(fal, kie, replicate, openai, gemini, stability, elevenlabs, runway, luma, custom-http) and the job engineservices/genmedia/engine.ts. It reads keys only throughaiConnections(resolve(locationId, capability, connectionId?)/list(locationId)– the BYOK registry of the agent module) and offersgenmedia(create/get/out/process/pollDue/idle) to other modules;genmediaFetchis the test hook for all provider HTTP (URL checks still apply). Workflow actionsgenmedia_image/genmedia_video/genmedia_tts, triggergenmedia.completed, five MCP tools. -
Voice layer (Stimme):
services/voice-engine.ts(synthesize/transcribe/listVoices/liveTts/openLiveStt/phonePlanover the BYOK registry – the speech providers are KI-Studio adapters inservices/voice-adapters.ts, registered withregisterAdapter),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.tshands HTTP upgrades toapp.services.upgradeHandlers),services/voice-audio.ts(G.711, resampling, WAV, VAD).modules/voice.ts(settings, push-to-talk, prefs, MCP, mobile relay) andmodules/voice-phone.ts(Twilio hooks, recorded turns, voicemail). The phone agent’s brain stays inmodules/voice-ai.tsand is offered asapp.services.voiceBrain;voiceTransportlets 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 throughapp.services.companionCheckinHooks) andmodules/companion-live.ts(event triggers viaapp.eventslisteners and its own scan inonTick, the initiative engine overaiRun, the Gemini Live proxy onupgradeHandlersat/ws/companion-live); pure logic inservices/companion-prefs.ts,briefing.ts,companion-triggers.ts(registry, routing, rate limit),companion-initiative.ts(schema, cool-down, levels, learning) andgemini-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) andcall-scorecards.ts; pure logic inservices/ivr.ts,call-groups.ts,call-audio.ts,twilio-numbers.ts,dni.ts,dialer.ts,call-intel.ts. Hooks inapp.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;roomClockdecides waiting/running/late/ended),webinar-model.ts(types, defaults, honesty guardLIVE_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, actionwebinar_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 byumzug-write.ts’sWriterwith 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), oneumzug-<vendor>.tsper 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.tscheck SPF/DKIM/DMARC/MX with an injectableDnsResolver;messaging.tsadds 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 hooksumzugFetch/umzugSleep/umzugPauseMs(fake vendor servers:tests/umzug-fakes.ts),mailDns(fake resolver) andmailCheckFetch(provider APIs).outbound-guard.tsalso blocks the promise API’s DNSResolverin 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 onconversations, views, assign / status / snooze / priority / notes / bulk, settings; hooksconversationFilters,conversationRows,inboxAudience),teaminbox-engine.ts(inbound handling, answer timers in SQL triggers, scheduler,/api/inbox/streampresence),teaminbox-automation.ts(three triggers, three steps, report sources),teaminbox-tools.ts(MCP and/api/mobile/inbox*); servicesteaminbox-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; servicesteamcal-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 thestripeSessionshook ofmodules/stripe.ts),teamcal-video.ts,teamcal-pages.ts. The booking pipeline incalendars.tshas 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(tableappointment_series,series_id/recurrence/occurrence_date/series_exceptionon appointments, rolling generation in the tick),calendar-view.ts(grid data, conflict check, move/resize throughrescheduleAppointmentorPATCH /api/appointments/:id, drawer, invoice draft, tableappointment_invoices),ai-booking.ts(settings, MCP tools,/book/move/:token); logic inservices/calview.ts(viewData,checkTime),services/appt-series.ts,services/appt-recur.ts(pure RRULE subset on top ofpm-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 MCPToolflagbookingand the agent modebook(“Termin buchen”) inservices/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 namespacecalview. -
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/articlesroutes viacallRoute, AI helper throughaiRun),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 inaskmode 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 inservices/:wiki-markdown.ts(escaping renderer + allow-list sanitiser, shared by pages, comments and chat),collab-fts.ts(FTS5 withLIKEfallback, joined by rowid so isolation never depends on the index),collab-cards.ts(live record cards; other modules add kinds withregisterCardKind),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 inapp.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), andcollabTasks.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 areaswikiandteamchat; 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(Tabellenoffice_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-Artoffice),office-collab.ts(Bearbeitungssperre, Kommentare, Personen),office-tools.ts(Lesen, Bearbeiten, PDF, Umwandeln über die Engines; fünf MCP-Werkzeugeghl_office_*); Logik inservices/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 ausPGHL_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 ausPGHL_OFFICE_DIRmit CSP und Nonce, Lade-/Speicher-Brücke, signierte Links). Bereichofficeincore/permissions.ts, SeiteofficeinPRESENCE_ROUTES; Upstream-Programme und Installation:deploy/office/. Details: docs/office.md. -
Live-Zusammenarbeit überall:
modules/presence.ts(Routen/api/presence*, Tabellepresence_prefs, Schalter inlocations.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ächeweb/src/presence/*(eigener Chunk;store.tsim ersten Download für Seitenleiste und obere Leiste). Neue Seiten tragen ihren Bereich inPRESENCE_ROUTESein; eine eigene Fläche für Mauszeiger markiertdata-presence-surface. Haken:presenceHub,presence. Details: collaboration.md. -
Mobile-App:
modules/mobile.ts(Geräte-Tokenghm_…überapp.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 dieapp.guardsgegen eine typische Route des Bereichs laufen lässt (scope/checkArea), und schreibt übercallRoute– die normale Route läuft im selben Prozess mit Guards, Ereignissen undafterWrite(Protokoll mit Wegapp); die äußere Route setzt dafürctx.skipAfterWrite./api/mobilesteht deshalb bewusst nicht inpermissions.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ösertask.*und -Aktionenpm_*, Überfällig-Scan),pm-portal.ts(Kundenportal überportalHeader/portalDashboard/portalMemberCheck),pm-tools.ts(17 MCP-Werkzeuge, Berichtsquellentime_entries/pm_projects),pm-mobile.ts(/api/mobile/pm/*). Die Logik liegt inservices/pm-*.ts;pm-schedule.ts,pm-tree.tsundpm-recur.tssind reine Module ohne Imports (Terminrechnung, Hierarchie-Regeln, Wiederholung) und werden von der Gantt-Ansicht mitbenutzt. Die Tabelletaskswird erweitert statt ersetzt: Trigger haltenstatus_id/done/assigned_tofü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.tsmitservices/pm-costing*.ts/pm-retainers.ts, HakenpmCosting/pmRetainers. Vertrag: docs/projects.md. -
Twilio-Helfer:
verifyTwilio(Signatur-Prüfung) undtwilioRequest(REST mit den Zugangsdaten des Sub-Accounts) ausmodules/telephony.tsnutzen auch KI-Telefonagent (modules/voice-ai.ts, reine Bausteine inservices/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) mitservices/email-design.ts(rendertdesignbeim Speichern von Vorlagen, Kampagnen und Workflow-Schritten);services/community.ts(Zugang, Levels, Wortfilter, Bildprüfung) undservices/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/openIfSealedauscore/secrets.ts(AES-256-GCM, Datenschlüssel + frühere Schlüssel,npm run rotate-secret); Zugangsdaten in den Sub-Account-Einstellungen versiegeltapp.location()selbst (sealSettings). -
Anbieter-Links:
modules/referrals.ts(Katalog,REFERRAL_DEFAULTS,/go/:provider,app.services.signupLink(id)), in der Oberfläche<SignupLink id="…" />ausweb/src/referrals.tsxneben jedem Schlüssel-Feld – docs/referrals.md. -
Nango & MCP-Client:
services/nango.tsspricht als einzige Stelle mit Nango (Test-HakennangoFetch),modules/nango.tsstelltapp.services.nangobereit (requestmit Freigabe-Liste).services/mcp-client.tsist der MCP-Client (Streamable HTTP + SSE, OAuth 2.1-Client, Bereinigung von Werkzeugen/Ergebnissen; Test-HakenmcpClientFetch),modules/mcp-client.tsverwaltet externe Server (app.services.mcpClient). Zwei allgemeine Haken:agentToolSources(Werkzeug-Quellen für den KI-Agenten, Vertrag in mcp-client.md) undmcpToolSources(async (caller) => Tool[]– Werkzeuge von/mcp, die vom Aufrufer abhängen).safeFetchkenntallowPrivatefür Ziele, die die Agentur ausdrücklich als „internes Netz“ erlaubt hat. -
Connectors:
services/composio.tsist die einzige Stelle, die mit Composio spricht (Test-HakencomposioFetch); andere Module nutzenapp.services.composio(Schlüssel, Verbindungen,executemit Freigabe-Liste, Webhook) undcomposioTriggers/composioAgent. Direkt-Verbindungen melden sich mitregisterConnectorausservices/connectors.tsan (Felder, Test, Workflow-Aktionen, Katalog-Eintrag); das Framework (modules/connectors.ts) stelltconnectorConfig/connectorStatusbereit, ausgehende Aufrufe laufen überconnectorFetch(Test-Haken, sonstsafeFetch). 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, Warteschlangebooks_sync_jobs– ein Auftrag je Beleg, Hörer aufinvoice.finalised, Zahlungsabgleich im Tick überrecordPayment, Remote-ID ininvoice_links), Logik inservices/books-sync-model.ts(rein: Belege, Zuordnung, Rate-Limiter, Backoff) undbooks-sync-http.ts(Clients übersafeFetch, Test-HakenbooksSyncFetch/booksSyncSleep);modules/ads-conversions.ts+services/ads-gconv.ts(UploadClickConversions über die Google-Verbindung des Anzeigen-Managers, Einwilligungs-Prüfung überapp.services.consent.status, Hakenapp.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 überapp.services.githubIssues),modules/werkstatt-tools.ts(MCP-Werkzeuge) undmodules/flags.ts(app.services.flags.enabled(name, {locationId, userId})); die reine Logik liegt inservices/werkstatt/*(Manifest, Prüfungen, Brücke, Sandbox-Läufer, deklarative Schritte, Benachrichtigungen), die Namen „Werkstatt“/„Marktplatz“ inservices/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.guardsbegrenzenvia: plugin/werkstattauf freigegebene Routen. Test-HakenaiConnectionsFetchundfeedbackMailerdecken 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.afterReadlä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 deckttests/tenant-isolation.test.tsautomatisch ab – Lookups immer mitlocation_idfiltern. Details: security.md. -
Landing page (
PGHL_LANDING=1):modules/landing.ts(routes/,/de,/vergleich…,/sitemap.xmlonly on the instance’s own host; data, star-count cache) with the pure partsservices/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) andlanding-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 inweb/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 incloud_locations); billing sits on the primary’ssaas_subscriptionsrow, 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 inagency_settings.cloud, plan sync intosaas_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,reconciletick: 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-incloud_waitlisttable, the operator’s list;app.services.cloudWaitlist). Logic inservices/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 inapp.services:saasEventTaps(functions called after every handled Stripe event of the agency webhook),cloudCompleteSignup(SaaS webhook → cloud for checkouts withcloud_signup_id),hideUser(user, locationId)withhiddenUser()incore/permissions.ts(people lists skip operator admins in tenants),hiddenLocations(user)withvisibleLocations()(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/versioncarriescloud. 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 inservices/backup-offsite.ts,backup-crypto.ts,s3-lite.ts,backup-retention.ts, Kommandocli/backup-restore.ts),ops-alerts.ts(umhüllt die/healthz-Route voncore.tsüberrouter.wrappers, kettetapp.services.serverErrorfür die Fehlerrate; Hakenapp.services.ops–health/evaluate/send–,opsAlert.event(alert)für einmalige Alarme anderer Module undopsEveryMinute– Funktionen nach jeder Auswertung),status-page.ts(öffentliche Seite/status, Vorfälle). Im Cloud-Moduscloud-abuse.ts(DSA-Meldungen, Maßnahmen, automatische Versandpause; umhülltusageGate,app.services.cloudAbuse) undcloud-sites.ts(Kunden-Websites nie unter der Login-Adresse, eigene Domains erst nach TXT-Bestätigung; umhülltwantedDomains/domainStatus). Oberfläche über die UI-Slotsagency.systemundcloud.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 ausservices/pay-registry.ts:create/fetch/refundje Anbieter inservices/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öckestore_products/store_product),store-tools.ts; reine Logik inservices/store-model.ts(Versand, Steuerart, Angebot =calcTotalsder Rechnungszeilen) undservices/store-catalog.ts. Haken:registerPayProvider/registerPayPurpose(eigene Anbieter und Zwecke –invoice,product,teamcal_hold,store_order),app.services.payRegistry(start/checkout/refund/refundAll/choiceNeeded… – nutztservices/teamcal-pay.ts),payBuy/buyPayNote(Kaufseite mit anderem Anbieter,modules/stripe.tsübergibt), verkettetesinvoicePayHtml,giftCards(hold/commit/release/payInvoice…),store; UI-Slotsettings.payments; Test-HakenpayFetch(PayPal und Mollie). -
HighLevel-Parität (Schnellgewinne):
modules/tag-meta.ts+services/tag-meta.ts(Farben und Kategorien der Tags, Snapshot-Teiltag_meta; Chips in der Oberfläche überweb/src/tag-chip.tsx),modules/pipeline-board.ts(Reihenfolge der Pipelines; Stufenfarben instages[].color),modules/deal-list.ts+services/deal-query.ts(Listenansicht der Deals: Filter/Sortierung fürGET /api/opportunitiesüber die Filter-Engine ausservices/objects.ts, Massenaktionen, CSV; Bausteinweb/src/pages/ListKit.tsx),modules/inbox-spam.ts(blockierte Absender),modules/call-scripts.ts(Gesprächsleitfäden; in der Oberfläche über den Slotcall.panel–web/src/slots.tsxist die Liste der UI-Slots, eine Zeile je Füllung),modules/notes-plus.ts(Titel, Farbe, Anheften, Markdown und Suche der Notizen; HakennoteExtras– zusätzliche Spalten für den Workflow-Schrittadd_note). Neuer HakeninboundGates(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 keinmessage.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ättag),inbox-filters.ts(Filtertag/pipeline_id/stage_id/ownerüberconversationFilters, VolltextsucheGET /api/conversations/searchüber die FTS5-Tabellemessages_fts, gepflegt von SQL-Triggern aufmessages),contact-bulk-owner.ts,field-cleanup.ts(Undo-Entitätcustom_field),message-triggers.ts(Auslösermessage.outbound/message.team_reply: SQL-Trigger schreiben gesendete Nachrichten inmessage_outbound_queue, geleert nach jeder Anfrage überafterWriteund im Tick),contact-documents.ts(Dateien am Kontakt und an Notizen, Bytes incontact_document_blobs),workflow-folders.ts(Ordner,workflows.folder_id,POST /api/workflows/bulkübercallRoute),field-history.ts(Tabellefield_changes, Akteur ausafterWrite/viaWorkflow),deal-smart-tags.ts,deal-forecast.ts,split-winner.ts(Optionauto_winneram Schrittsplit_test, Prüfung im Tick),company-bulk.ts(Firmen in Massen übercallRoute),smart-lists.ts(Smart Lists 2.0: Spalten, Sortierung, geteilt/privat an der Tabellesmart_lists, Routen/api/smart-lists*, HakencontactOrder, MCPghl_smart_lists/ghl_smart_list_contacts; reine Teile inservices/smart-lists.ts, Oberflächeweb/src/pages/SmartLists.tsx, i18n-Bereichsmartlists). 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-Bereichcrmgaps. -
HighLevel-Parität (Reichweite):
modules/review-sources.ts+services/review-sources.ts(Bewertungen weiterer Plattformen: Adapter-RegistryregisterReviewAdapter– Facebook, Trustpilot, ProvenExpert, Yelp, Tripadvisor, Import per CSV/RSS/Web-Hook –, eine Tabellereview_items, Auslöserreview.receivedmitsource, KI-Entwürfe überapp.services.reviewReplies.suggest, Widget überapp.services.reviewWidgetParts, Test-HakenreviewSourceFetch– reputation.md),modules/social-comments.ts+services/social-comments.ts(Kommentare von Facebook/Instagram übermetaWebhookHooks, YouTube/LinkedIn überapp.services.nango, Kanalcommentim Posteingang, Aktionen übermessageRowsund den UI-Slotinbox.message;app.services.socialComments.ingest– social-comments.md), mehrere Facebook-Seiten/Instagram-Konten inmodules/meta-channels.ts(Tabellemeta_pages, Zuordnung über Seiten-ID,meta_identities.account_id,metaKit.graph(…, accountId)),modules/voice-outbound.ts(Workflow-Aktionvoice_ai_call, Prüfung überapp.services.dialer.verdict+ Tageslimit, Twilio mit Anrufbeantworter-Erkennung,VoiceAgentInput.stage = 'outbound', HakenvoicePromptExtras, vier Ergebnis-Auslöser – voice.md) undmodules/caller-ids.ts(bestätigte eigene Nummern als Absender,app.services.callerIds.pick/allowedfür Browser-Telefon, Klick-Anruf, Power-Dialer und KI-Anrufe – telephony.md). Oberfläche über die UI-Slotsreviews.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 überrouter.wrappersan/api/contracts/:id/send,/s/:tokenund die Einzel-Signatur-Routen voncontracts.ts, Tabellencontract_sign_runs/contract_signers, Auslösercontract.signer_signed),service-menu.ts(Leistungsmenü/book/s/<slug>überbookTeammithostId),calendar-ai.ts(explainSlotauscomputeSlots/slotsOfDayundcheckTime, MCPghl_calendar_create/ghl_calendar_explain_slot),form-pdf.ts(PDF einer Einsendung, MCPghl_forms/ghl_form_submission_pdfs),form-drafts.ts(Fortschritt speichern: Skript per Wrapper an/f/:id, Tabelleform_draftsmit Token-Hash),coupon-codes.ts(Einmal-Rabattcodes alsbuy_couponsmitmax_uses1, Workflow-Schrittcoupon_create,issueCodes),loyalty.ts(Treuepunkte-Ledger ausinvoice.paid, Stufen, Prämien überissueCodes/app.services.giftCards),store-extras.ts(Packzettel-PDF, abgebrochene Warenkörbe),portal-appointments.ts(„Meine Termine“ überportalHeader/portalDashboard/portalMemberCheck),booking-guests.ts(Gäste ausmeta.guestsvonappointment.booked, Einladungen,appointmentRows),event-checkin.ts(Ticket-Seite mit QR überrenderQr, Check-in übercallRouteaufPATCH /api/appointments/:id),booking-multi.ts(mehrere Zeiten in einer Buchung überbookTeam, Rücknahme percancelAppointment),rentals.ts(Vermietung: Mietangebot = Kalender mitsettings.rental, Tabellenrentals/rental_bookings, Verfügbarkeit und Schreiben in einer Transaktion überservices/rentals-book.ts, Zahlung über den Zweckrentalder Zahlungs-Registry, Seiten/book/rent/<slug>und über die KettenteamcalPage/teamcalManagePage/teamcalCancel/teamcalVars). Im Team-Kalender selbst:show_ifan Fragen (checkConditions/questionShowninservices/teamcal-model.ts),guests_max,durationsundmulti_maxin der Konfiguration (guestsAllowed,durationChoices,multiAllowed). UI-Slotscontract.signersundgiftcards.panel. -
HighLevel-Parität (Websites, Marketing, Agentur – Welle 10): eine Registry-Zeile
...gapsSitesModules(modules/gaps-sites.ts) fürsite-seo.ts(SEO-Check, Schema.org je Seite),site-dyntext.ts({{url.…}},{{#if url.…}}),site-fonts.ts(eigene Schriftdateien),site-protect.ts(Passwortschutz – die letztepageServe-Hülle),social-approval.ts+social-queues.ts(überapp.services.socialPost),agency-banners.ts,role-templates.ts(zweiter Wächter für Nur-Lese-Bereiche,afterWritelöst die Rolle bei Handänderung),template-conditions.ts(neuer HakentemplatePassincore/template.ts, Parserservices/template-conditions.ts),video-testimonials.ts(roher Upload überrawBodyLimit) undprompt-lab.ts(überaiRun, Übernehmen percallRoute). Oberfläche über die neuen UI-Slotsshell.banner,agency.clients,settings.team,agents.testsundreviews.panel; Teststests/gaps-sites*.test.ts.
Berichte: eigene Datenquellen
Abschnitt betitelt „Berichte: eigene Datenquellen“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,joinsundcalcssind fester Code – Nutzerwerte kommen nur als Parameter in die Abfrage; die Engine hängt den Sub-Account-Filter (location IN (…)), Zeitraum, Filter,LIMITund eine Zeitgrenze selbst an.- Feldtypen:
string,enum(mitoptions),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);noGroupfür Freitext. - Jede Quelle bekommt automatisch die Gruppe Sub-Account; Quellen mit fehlenden Tabellen (
requires) werden ausgeblendet. Ein Test, der alle Felder kompiliert, steht intests/reports.test.ts.