/** * Pure GTM API v2 resource builders, shared by the MCP server and the desktop app. * * These were the desktop's alone (apps/desktop/src/main/google/gtm-builders.ts), which is why the * two assistants produced different work for the same request: the desktop had typed builders that * guarantee a correct resource shape, while the website chat had only the raw API primitives and * had to re-derive the shape every turn. Asked for a mailto: tag it reached for a Data Layer * Variable that can never populate, where the desktop correctly used a Custom JavaScript one. * * They live HERE, inside the MCP package, because that is the only directory all three surfaces can * reach. The MCP compiles with rootDir "src" and is published to npm, so it cannot import upward * out of src/ without moving dist/index.js and breaking the package's bin path. The desktop and the * orchestrator are both bundled and can import downward into it. * * No I/O and no dependencies beyond types: every function turns simple inputs into a GTM resource, * so OUR code owns the type codes, parameter keys, and the eventSettingsTable list-of-maps shape * rather than the model guessing them. * * The desktop's 206-case suite in apps/desktop/src/main/google/__tests__/gtm-builders.test.ts * exercises this code through its original import path and is the contract for any change here. */ export type Param = Record; export declare const tpl: (key: string, value: string) => Param; /** A template Parameter for a DEDICATED top-level Trigger field (e.g. interval, eventName) — * no `key`, unlike entries in a `parameter[]` array. */ export declare const namedParam: (value: string) => Param; export declare const boolean: (key: string, value: boolean) => Param; export declare const integer: (key: string, value: string) => Param; export declare function sanitizeName(name: string): string; export interface GtmTagResource { name: string; type: string; parameter: Param[]; firingTriggerId?: string[]; } export interface GtmTriggerResource { name: string; type: string; filter?: Param[]; autoEventFilter?: Param[]; customEventFilter?: Param[]; /** Form-trigger options — DEDICATED top-level fields (single Parameter each, no * `key`), per the GTM API v2 Trigger schema; NOT entries in a `parameter[]`. */ waitForTags?: Param; checkValidation?: Param; /** Generic "Additional parameters" array — used by trigger types whose settings * legitimately live here, e.g. the YouTube Video trigger's capture options * (corpus: 69/69 youTubeVideo triggers store them in `parameter`). NOT for * form waitForTags/checkValidation (those are the top-level fields above). */ parameter?: Param[]; /** Timer-trigger options — DEDICATED top-level fields (single Parameter each, no `key`), * per the GTM API v2 Trigger schema; NOT entries in `parameter[]`. */ eventName?: Param; interval?: Param; limit?: Param; } export interface GtmVariableResource { name: string; type: string; parameter: Param[]; } export interface Ga4EventInput { name: string; measurementId: string; eventName: string; eventParameters?: Array<{ name: string; value: string; }>; firingTriggerId?: string[]; /** GA4 "Send Ecommerce data" from the dataLayer — forwards the WHOLE ecommerce object (items, * value, currency, transaction_id, …) with no per-param variables. Corpus shape: * sendEcommerceData=true + getEcommerceDataFrom='dataLayer'. Use for funnel event tags. */ sendEcommerceData?: boolean; /** When no eventParameters are passed, auto-fill DEFAULT_GA4_EVENT_PARAMS (default true). Set false * to create a bare tag with no event parameters. */ autoEventParameters?: boolean; } /** Default GA4 event parameters auto-added to a GA4 event tag when the caller passes none: page_url + * previous_page, bound to the default-enabled {{Page URL}} / {{Referrer}} built-in variables. These are * CUSTOM names (not GA4's auto-collected page_location/page_referrer), so they add reportable data * without clashing with automatic collection, and their variables need no setup. Mirrors the * measurement-plan scan's default page params. GA4 auto-collects session/engagement/geo/device, so * those are intentionally not re-added; context params (click_text, form_id) are added per-event by * the scan / passed explicitly. */ export declare const DEFAULT_GA4_EVENT_PARAMS: Array<{ name: string; value: string; }>; /** GA4 ecommerce events whose value/currency/items ride the `ecommerce` dataLayer object. For these, * a GA4 event tag defaults "Send Ecommerce data" ON (forward the object) when the caller passes no * explicit event parameters — so the tag ships with its ecommerce payload instead of nothing. */ export declare const GA4_ECOMMERCE_EVENTS: Set; export declare function isGa4EcommerceEvent(event: string): boolean; export interface GoogleTagInput { name: string; /** G-XXXX / AW-XXXX / GT-XXXX, or a {{Variable}} reference holding one. */ tagId: string; /** Optional config settings (key/value), e.g. send_page_view=false. */ configSettings?: Array<{ name: string; value: string; }>; firingTriggerId?: string[]; } /** * The "Google tag" (googtag): the modern base tag that loads gtag.js and configures GA4 or Ads. * Config settings use configSettingsTable with parameter/parameterValue maps. * * Moved here from the desktop's builders, which is where this file's own header says pure builders * belong ("so the website chat can use the SAME code"). The website needs it to stand up a GA4 * configuration, and a second copy of a resource shape is how the two surfaces drift into creating * subtly different tags from the same request. */ export declare function buildGoogleTag(o: GoogleTagInput): GtmTagResource; export declare function buildGa4EventTag(o: Ga4EventInput): GtmTagResource; export declare const FILTER_OPS: Set; export declare const OP_TO_CONDITION: Record; export declare function condition(variable: string, op: string, value: string, ignoreCase?: boolean): Param; export type TriggerKind = 'link_click' | 'all_clicks' | 'custom_event' | 'pageview' | 'form_submit' | 'youtube_video' | 'timer' | 'element_visibility' | 'history_change' | 'scroll_depth' | 'dom_ready' | 'window_loaded' | 'js_error'; /** The trigger kinds buildTrigger can construct, as a RUNTIME list (the type above erases at compile * time). Typed `readonly TriggerKind[]` so tsc rejects any entry that is not a real kind - keep it in * step with the union and with buildTrigger's switch. Callers use isTriggerKind() to refuse an * off-enum kind BEFORE writing anything, since buildTrigger THROWS on an unknown kind. */ export declare const TRIGGER_KINDS: readonly TriggerKind[]; /** True when `value` is a trigger kind buildTrigger can build. */ export declare function isTriggerKind(value: string): value is TriggerKind; /** The standard GTM "Video" built-in variables a YouTube Video tag reports. */ export declare const VIDEO_BUILT_IN_VARS: readonly ["videoProvider", "videoUrl", "videoTitle", "videoDuration", "videoCurrentTime", "videoPercent", "videoStatus", "videoVisible"]; export interface TriggerInput { name: string; kind: TriggerKind; /** For link_click/all_clicks: filter on {{Click URL}}. */ clickUrlValue?: string; clickUrlOperator?: string; /** For a matchRegex click-URL condition: GTM's "matches RegEx (ignore case)" — emitted as the * condition-level ignore_case parameter (a web container cannot parse an inline (?i) flag). */ clickUrlIgnoreCase?: boolean; /** For link_click/all_clicks: also filter on {{Click Text}} (e.g. a CTA). */ clickTextValue?: string; clickTextOperator?: string; /** For a matchRegex click-text condition: GTM's "matches RegEx (ignore case)" — emitted as the * condition-level ignore_case parameter (a web container cannot parse an inline (?i) flag). */ clickTextIgnoreCase?: boolean; /** For all_clicks: fire when a companion Lookup Table variable returns "true" — the classic GTM * grouping pattern (ONE tag for several click texts). The trigger condition is {{}} equals * "true"; the variable itself (type smm, input {{Click Text}}, each text → "true", exact-match) * is auto-provisioned by create_gtm_tracking_tag when missing. */ lookupTable?: { name: string; texts: string[]; }; /** For all_clicks: fire on any click matching a CSS selector via {{Click Element}} (operator * cssSelector) — e.g. an FAQ accordion header ".faq__q, .faq__q *" so a click on the question text, * the row padding, OR the arrow icon all fire (they are all inside the matched element). */ clickElementValue?: string; clickElementOperator?: string; /** For clicks: match the element's class ATTRIBUTE via {{Click Classes}}. GTM exposes the whole * attribute as one string, so a single class is matched with `contains`, never `equals` - an * `equals` on one class of several silently never fires. Preferred over a CSS selector because it * keys on the author's own naming rather than on DOM structure. */ clickClassesValue?: string; clickClassesOperator?: string; /** For clicks: match the element's id via {{Click ID}}. The most durable click signal there is, * when the author gave one. */ clickIdValue?: string; clickIdOperator?: string; /** For form_submit: scope to one form via {{Form ID}} / {{Form Classes}}. */ formIdValue?: string; formIdOperator?: string; formClassesValue?: string; formClassesOperator?: string; /** For form_submit with no id/class: scope to the form's page via {{Page Path}}. */ pagePathValue?: string; pagePathOperator?: string; /** For pageview scoped to a results / specific page (e.g. a GET site-search results URL): filter on {{Page URL}}. */ pageUrlValue?: string; pageUrlOperator?: string; /** For custom_event: the dataLayer event name. */ eventName?: string; /** For custom_event: extra ANDed scope conditions that read a pushed dataLayer KEY via an * auto-created {{dlv - }} Data Layer Variable — e.g. scope an AJAX/embed form's custom_event * to ONE form by the `form_id` its listener pushes. GTM's built-in {{Form ID}} does NOT resolve on a * manual dataLayer.push (it is only populated by the native form-submit auto-event), so a pushed-key * variable is the only reliable way to scope a data-layer form trigger. Each key auto-provisions its * `dlv - ` variable. Operator matches the other *Operator fields (default 'equals'). */ dataLayerConditions?: Array<{ key: string; value: string; operator?: string; }>; /** Page-context scope conditions, ANDed into the filter of ANY filter-capable trigger kind. * {{Page Hostname}} and {{Referrer}} are GTM built-ins. */ pageHostnameValue?: string; pageHostnameOperator?: string; referrerValue?: string; referrerOperator?: string; /** Match the URL's QUERY STRING. Web containers have NO built-in query-string variable (the * API's `queryString` built-in is server-container only, confirmed against Google's built-in * variable reference), so this references a {{URL - query}} URL variable with component QUERY, * auto-provisioned by triggerUrlVarNames the same way dataLayerConditions provision dlv vars. */ queryStringValue?: string; queryStringOperator?: string; /** For element_visibility: the element to observe. Give EITHER a CSS selector or an element id; * selectorType follows whichever was supplied (corpus: CSS 295, ID 16 of 311). */ visibilitySelector?: string; visibilityElementId?: string; /** For element_visibility: minimum percent of the element on screen before it fires. * Corpus default 50 (269/311). */ visibilityMinPercent?: number | string; /** For element_visibility: ONCE | ONCE_PER_ELEMENT | MANY_PER_ELEMENT. Corpus default ONCE. */ visibilityFiringFrequency?: string; /** For element_visibility: keep watching for elements added to the DOM after page load. ON by * default (corpus 267/311) - without it an AJAX confirmation message that appears AFTER load is * never observed, which is the single most common use of this trigger type. */ visibilityObserveDomChanges?: boolean; /** For element_visibility: require the element to stay on screen for N ms before firing. */ visibilityMinOnScreenMs?: number | string; /** For scroll_depth: vertical thresholds. Percent by default; PIXELS switches units. */ scrollPercentages?: string; scrollPixels?: string; /** For scroll_depth: horizontal thresholds (rarely used; off unless supplied). */ scrollHorizontalPercentages?: string; /** For timer: fire every N milliseconds (required). */ intervalMs?: number | string; /** For timer: max number of times to fire (omit/empty = unlimited). */ limit?: number | string; } /** GTM defaults "Wait for Tags" + "Check Validation" to ON for linkClick ("Just Links") * and formSubmission triggers — which delays the click/submit and skips some events. * Force them OFF unless they were EXPLICITLY set (so a user asking to enable them, or a * builder that already set them, is respected). Applied at the create funnel so EVERY * trigger path (chat, structured, suggestions) gets the off-by-default behavior. PURE. */ export declare function applyTriggerWaitDefaults(trigger: Record): Record; /** Display name of the user variable a queryString condition references. * Deliberately NOT of the form `URL - `: that convention means "the value of ONE query * parameter" and is auto-provisioned with a queryKey. This one is the WHOLE query string (component * QUERY with no key), and it borrows the name Google itself uses for the equivalent server-container * built-in, which is what a GTM practitioner will look for. */ export declare const URL_QUERY_VAR = "Query String"; /** * Page-context conditions, ANDed into ANY filter-capable trigger. * * GTM puts no restriction on which variables a trigger's filter reads, so the same page scoping is * valid on a click, a form submit, a scroll, a history change or a visibility trigger. Kept in one * place so a new trigger kind cannot silently lose the ability to be scoped. * * Page Path and Page URL stay with their existing per-kind defaults and are NOT emitted here, so * this addition cannot change any trigger the builder already produced. */ export declare function pageScopeConditions(o: TriggerInput): Param[]; export declare function buildTrigger(o: TriggerInput): GtmTriggerResource; /** Normalize a raw Timer trigger so interval/limit/eventName end up where GTM actually reads * them — as DEDICATED top-level Trigger fields (a single template Parameter each, no `key`), * per the GTM API v2 schema. The model often supplies them as a raw string, or wrongly in * parameter[], which leaves the GTM UI's Interval/Limit BLANK. This pulls a value from a * top-level field (raw string OR Parameter object) OR a parameter[] entry, and writes it to * the top-level field. eventName defaults to gtm.timer; interval/limit are kept only when a * value is present (no limit = unlimited). PURE; applied at the create funnel. */ /** Normalize a Custom Event trigger's EVENT NAME (the dataLayer value it matches) to the real * event token: strip our display-name prefixes ("CE - ", "GA4 - Event - ", "Meta - ", …) and * snake_case a display phrase ("Add To Cart" → "add_to_cart"). A clean token (purchase, * add_to_cart, gtm.dom, .*) is left untouched. The dataLayer pushes `purchase`, never * "CE - Purchase" — using the display name as the event name means the trigger never fires. PURE. */ export declare function normalizeCustomEventName(name: string): string; export type VariableKind = 'constant' | 'data_layer' | 'javascript' | 'event_data' | 'request_header'; export interface VariableInput { name: string; kind: VariableKind; value?: string; dataLayerName?: string; javascript?: string; keyPath?: string; defaultValue?: string; headerName?: string; } export declare function buildVariable(o: VariableInput): GtmVariableResource; /** Built-in variables a trigger needs (so we can auto-enable them). */ /** The distinct, non-empty dataLayer KEYS a custom_event trigger scopes on via {{dlv - }} * (from `dataLayerConditions`). Each drives auto-creation of its `dlv - ` Data Layer Variable * so the {{dlv - }} the trigger references actually resolves. [] for any non-custom_event kind * (native {{Form ID}} works on form_submit — no dlv needed there). PURE. */ /** URL variables a trigger references and that must be auto-created for its conditions to resolve. * Currently just {{URL - query}} for a queryString condition: web containers have NO built-in * query-string variable, so it has to be a user-defined URL variable with component QUERY. */ export declare function triggerUrlVarNames(o: TriggerInput): string[]; export declare function triggerDataLayerVarKeys(o: TriggerInput): string[]; export declare function triggerBuiltInVars(o: TriggerInput): string[]; /** Built-in variable type keys referenced by {{Name}} tokens in the given values * (e.g. an event parameter value "{{Click Text}}" → "clickText"). Unknown names * (user-defined variables) are skipped. */ export declare function builtInVarsForTemplates(values: Array): string[]; //# sourceMappingURL=gtm-builders.d.ts.map