@koda-sl/baker-cli 0.505.0 → 0.507.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/{ads-OLOEMAID.js → ads-T5RPISTB.js} +3 -3
- package/dist/{analytics-4CCQYY7Z.js → analytics-P5UFA6I4.js} +315 -18
- package/dist/analytics-P5UFA6I4.js.map +1 -0
- package/dist/{chunk-DI44SZVP.js → chunk-7J3NZBPV.js} +31 -1
- package/dist/chunk-7J3NZBPV.js.map +1 -0
- package/dist/{chunk-VU5Z2V4S.js → chunk-D3KLDIPQ.js} +2 -2
- package/dist/{chunk-GJCB46L6.js → chunk-HWDRW36T.js} +2 -2
- package/dist/{chunk-S3FFLLYF.js → chunk-LIRX2Q66.js} +2 -2
- package/dist/{chunk-DX46OE4U.js → chunk-OJGYYH6K.js} +2 -2
- package/dist/{chunk-ZEGNIP3Y.js → chunk-TVTSA4EN.js} +3 -3
- package/dist/cli.js +4 -4
- package/dist/{critique-DU7K2KVR.js → critique-WOSMZPHW.js} +7 -17
- package/dist/critique-WOSMZPHW.js.map +1 -0
- package/dist/{funnel-727XATM5.js → funnel-S6JTLUK6.js} +3 -3
- package/dist/{google-VPL4KZDP.js → google-3GSINSOV.js} +2 -2
- package/dist/{landing-X4PG6XWU.js → landing-GEWSOHGZ.js} +3 -3
- package/dist/landing-GEWSOHGZ.js.map +1 -0
- package/dist/{lookup-LWJMDEEL.js → lookup-ZJIP2M4X.js} +4 -4
- package/dist/{performance-LMO73TUB.js → performance-WPFGEJTE.js} +2 -2
- package/dist/{plan-O2TTEUIW.js → plan-TVQSEKYL.js} +6 -6
- package/dist/{questionnaire-VE42SW77.js → questionnaire-LEZJLMAV.js} +3 -3
- package/dist/{sends-OLVQGOAE.js → sends-6FI7WELX.js} +3 -3
- package/dist/{sources-PSM2A5QD.js → sources-L3KNVLIB.js} +4 -4
- package/dist/{unstage-QFVL7AZ4.js → unstage-MDSSPUKV.js} +3 -3
- package/dist/{url-options-LA6BL3GS.js → url-options-IHL4OU4C.js} +2 -2
- package/package.json +1 -1
- package/dist/analytics-4CCQYY7Z.js.map +0 -1
- package/dist/chunk-DI44SZVP.js.map +0 -1
- package/dist/critique-DU7K2KVR.js.map +0 -1
- package/dist/landing-X4PG6XWU.js.map +0 -1
- /package/dist/{ads-OLOEMAID.js.map → ads-T5RPISTB.js.map} +0 -0
- /package/dist/{chunk-VU5Z2V4S.js.map → chunk-D3KLDIPQ.js.map} +0 -0
- /package/dist/{chunk-GJCB46L6.js.map → chunk-HWDRW36T.js.map} +0 -0
- /package/dist/{chunk-S3FFLLYF.js.map → chunk-LIRX2Q66.js.map} +0 -0
- /package/dist/{chunk-DX46OE4U.js.map → chunk-OJGYYH6K.js.map} +0 -0
- /package/dist/{chunk-ZEGNIP3Y.js.map → chunk-TVTSA4EN.js.map} +0 -0
- /package/dist/{funnel-727XATM5.js.map → funnel-S6JTLUK6.js.map} +0 -0
- /package/dist/{google-VPL4KZDP.js.map → google-3GSINSOV.js.map} +0 -0
- /package/dist/{lookup-LWJMDEEL.js.map → lookup-ZJIP2M4X.js.map} +0 -0
- /package/dist/{performance-LMO73TUB.js.map → performance-WPFGEJTE.js.map} +0 -0
- /package/dist/{plan-O2TTEUIW.js.map → plan-TVQSEKYL.js.map} +0 -0
- /package/dist/{questionnaire-VE42SW77.js.map → questionnaire-LEZJLMAV.js.map} +0 -0
- /package/dist/{sends-OLVQGOAE.js.map → sends-6FI7WELX.js.map} +0 -0
- /package/dist/{sources-PSM2A5QD.js.map → sources-L3KNVLIB.js.map} +0 -0
- /package/dist/{unstage-QFVL7AZ4.js.map → unstage-MDSSPUKV.js.map} +0 -0
- /package/dist/{url-options-LA6BL3GS.js.map → url-options-IHL4OU4C.js.map} +0 -0
package/README.md
CHANGED
|
@@ -1818,6 +1818,9 @@ baker analytics delivery --page 2 # the next page of a long list
|
|
|
1818
1818
|
baker analytics submissions --flow contact # every attempt at a Form, delivered or not
|
|
1819
1819
|
baker analytics feed --source Zapier # the raw event feed, newest first, narrowed to what one source sent
|
|
1820
1820
|
baker analytics feed --sends failed # only the uploads a conversion's sends made and the platform refused
|
|
1821
|
+
baker analytics feed --bots # only the events taken for machine traffic (a headless browser such as agent-browser), hidden everywhere else
|
|
1822
|
+
baker analytics website-tag --check # every event the website's Tag Manager sends Baker, and which no real visit has brought
|
|
1823
|
+
baker analytics debug --start --url https://client.com/ # a link that records what the person's own browser heard; read it with `baker analytics debug <code>`
|
|
1821
1824
|
baker analytics feed --person 'ana@empresa.com, +34600111222, deal:40001234567' --days 90 # everything these people did; emails and phones are hashed by Baker
|
|
1822
1825
|
baker analytics lookup --person 'ana@empresa.com, 90001234567' # what Baker saw of each person in a list, all time — found or not, pages, CRM stages, uploads
|
|
1823
1826
|
baker analytics lookup --people-file leads.txt # one per line: emails, phones, HubSpot contact or deal:<id>, gclid/fbclid, browser ids
|
|
@@ -7590,7 +7593,7 @@ baker landing critique spring-offer summer-offer claude # score three landings
|
|
|
7590
7593
|
|
|
7591
7594
|
- **Takes several slugs at once.** `data.landings` is always an array with one entry per slug (`{ slug, overall, counts, dimensions, findings }`). For a single slug those fields are also spread at the top of `data`, so existing one-landing readers are unaffected. A wrong slug anywhere in the batch fails the whole call before any landing is scored.
|
|
7592
7595
|
- **Advisory by design — findings never fail it.** A finding is guidance, not a gate; the command errors (exit 2) only on a wrong slug (`NOT_FOUND` / `INVALID_SLUG`, with `error.fix.availableSlugs`).
|
|
7593
|
-
- **The copy is never judged on a guess.** When the decision model cannot answer (no key, a timeout), no copy rule reports anything
|
|
7596
|
+
- **The copy is never judged on a guess.** When the decision model cannot answer (no key, a timeout), no copy rule reports anything, and a hint says the copy went unchecked and to re-run.
|
|
7594
7597
|
- **The brand is the allowlist.** A font, hue, or ground the client's brand commits is a decision, not a tell — matched values downgrade to advisory `brandDrift` so the critic never fights a real brand. The allowlist reads `src/brand/tokens.json` (the canonical machine-readable brand token file) when present; workspaces that predate it fall back to scanning the `@theme` block in `src/styles/global.css` (hex or oklch) plus the faces and colours `src/brand/BRAND.md` says the brand uses, read in any language by the brand reader — a face or colour BRAND.md bans ("nunca uses Inter") is never allowlisted. Gradient text is allowlisted the same way: it stays a block unless every coloured stop is a committed brand colour or a stop of a gradient `BRAND.md` writes out as CSS (`linear-gradient(90deg, #5061FF, #AA46CE, #FF9B63)`), so a brand's signature headline gradient publishes and an invented one does not.
|
|
7595
7598
|
- **Records a freshness snapshot** to `.cache/landing-critique/<slug>.json` (the source hash it scored). The publish quality gate reads it to enforce that every changed landing was critiqued and ships no block-tier tell. Re-run after edits so the snapshot stays fresh.
|
|
7596
7599
|
|
|
@@ -7598,7 +7601,7 @@ baker landing critique spring-offer summer-offer claude # score three landings
|
|
|
7598
7601
|
|
|
7599
7602
|
- **Agent washing is a copy tell.** A page with a sentence claiming autonomy ("fully autonomous", "while you sleep", "no human intervention", in any language) that never says anywhere what the buyer approves, overrides, or audits scores an `agent-washing` warn. Oversight of the agent's actions — approval, review, override, undo/rollback, audit log, escalation — said anywhere on the page clears it; the rule is page-scope precisely because the hero can defer the trust story to a section below it. Generic privacy boilerplate ("GDPR", "encrypted", "your data") deliberately does **not** clear it, since it appears in most footers and says nothing about who is in charge of the agent.
|
|
7600
7603
|
|
|
7601
|
-
- **
|
|
7604
|
+
- **A rival named in a comparison is a warning.** `competitor-named-in-comparison` fires when a name from `src/content/competitors/` appears in a sentence outside a `<table>` that compares the two (naming it as an integration is not a comparison) — a comparison table is a section the landing skill teaches, but naming a rival anywhere else on paid traffic is the client's call, not the build's. Any language.
|
|
7602
7605
|
|
|
7603
7606
|
- **Comments are not markup, and no rule reads one.** Astro expression comments (`{/* … */}`) and HTML comments (`<!-- … -->`) are blanked before detection, offsets preserved so `file:line` stays exact. A shared scaffold component explaining its `preconnect` in prose that spelled `<img>` twice was read as two broken images — block-tier — so every landing rendering a video failed the publish gate on a file the client cannot edit. Comment prose is also where a component's reasoning lives, em-dashes and buzzwords included, so reading it inflated the count-threshold tells too.
|
|
7604
7607
|
|
|
@@ -33,16 +33,16 @@ Examples:
|
|
|
33
33
|
Full guides: __tooling__/docs/tools/baker/ads-<platform>.md (google|meta|linkedin|x|chatgpt|performance|terms)`
|
|
34
34
|
},
|
|
35
35
|
subCommands: {
|
|
36
|
-
google: () => import("./google-
|
|
36
|
+
google: () => import("./google-3GSINSOV.js").then((m) => m.googleCommand),
|
|
37
37
|
meta: () => import("./meta-KS3SQSYZ.js").then((m) => m.metaCommand),
|
|
38
38
|
linkedin: () => import("./linkedin-4MGRSWXZ.js").then((m) => m.linkedinCommand),
|
|
39
39
|
x: () => import("./x-7VY6K7JS.js").then((m) => m.xCommand),
|
|
40
40
|
chatgpt: () => import("./chatgpt-SVGWMEQD.js").then((m) => m.chatgptCommand),
|
|
41
|
-
performance: () => import("./performance-
|
|
41
|
+
performance: () => import("./performance-WPFGEJTE.js").then((m) => m.performanceCommand),
|
|
42
42
|
terms: () => import("./terms-O6ODXEOK.js").then((m) => m.termsCommand)
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
45
|
export {
|
|
46
46
|
adsCommand
|
|
47
47
|
};
|
|
48
|
-
//# sourceMappingURL=ads-
|
|
48
|
+
//# sourceMappingURL=ads-T5RPISTB.js.map
|
|
@@ -5,7 +5,7 @@ import "./chunk-MGYC2IRC.js";
|
|
|
5
5
|
import {
|
|
6
6
|
flowEventKeys,
|
|
7
7
|
flowNodes
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-LIRX2Q66.js";
|
|
9
9
|
import {
|
|
10
10
|
flowDataPath,
|
|
11
11
|
flowExists,
|
|
@@ -16,20 +16,23 @@ import {
|
|
|
16
16
|
INGEST_ARRIVALS_MAX,
|
|
17
17
|
eventIngestUrl,
|
|
18
18
|
ingestBrief
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-HWDRW36T.js";
|
|
20
20
|
import "./chunk-GRQ2AXDX.js";
|
|
21
|
+
import {
|
|
22
|
+
TAG_MANAGER_TIMEOUT_MS
|
|
23
|
+
} from "./chunk-3GAR4AZK.js";
|
|
21
24
|
import {
|
|
22
25
|
peopleArgs,
|
|
23
26
|
peopleLimitError
|
|
24
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-TVTSA4EN.js";
|
|
25
28
|
import {
|
|
26
29
|
ConversionArgsError,
|
|
27
30
|
conversionsWrite,
|
|
28
31
|
parseConversionsArgs
|
|
29
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-OJGYYH6K.js";
|
|
30
33
|
import {
|
|
31
34
|
analyticsErrorEnvelope
|
|
32
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-D3KLDIPQ.js";
|
|
33
36
|
import {
|
|
34
37
|
AD_PARAM_ROLE_SPECS,
|
|
35
38
|
ANALYTICS_EVENT_DOORS,
|
|
@@ -42,11 +45,12 @@ import {
|
|
|
42
45
|
isBookkeepingRow,
|
|
43
46
|
parseEventKey,
|
|
44
47
|
rowMatchKeys
|
|
45
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-7J3NZBPV.js";
|
|
46
49
|
import "./chunk-EDLZGTGN.js";
|
|
47
50
|
import {
|
|
48
51
|
repeatedValues
|
|
49
52
|
} from "./chunk-ERSX3MKH.js";
|
|
53
|
+
import "./chunk-D5FY4FAK.js";
|
|
50
54
|
import {
|
|
51
55
|
writeJsonEnvelope
|
|
52
56
|
} from "./chunk-ZRP7Y2DM.js";
|
|
@@ -507,6 +511,84 @@ function withFormatHint(envelope, format) {
|
|
|
507
511
|
return { ...envelope, ...hints.length > 0 ? { hints } : {} };
|
|
508
512
|
}
|
|
509
513
|
|
|
514
|
+
// src/commands/analytics/debugLink.ts
|
|
515
|
+
function debugStartHints(started) {
|
|
516
|
+
if (started.link === null) {
|
|
517
|
+
return [
|
|
518
|
+
`Code ${started.code} records for two hours. Run it again with --url <the page to test> for a link to send, or add ?baker_debug=${started.code} to that page's address yourself.`
|
|
519
|
+
];
|
|
520
|
+
}
|
|
521
|
+
return [
|
|
522
|
+
`Send the person this link: ${started.link} \u2014 they open it in the browser they normally use, accept the cookie banner, do exactly what did not work (open the widget, start the call, submit the form), then tell you they are done. Then run \`baker analytics debug ${started.code}\`. It records for two hours, nothing it records is counted anywhere, and emails and phone numbers are masked.`
|
|
523
|
+
];
|
|
524
|
+
}
|
|
525
|
+
function inner(row) {
|
|
526
|
+
if (!row.data) return "";
|
|
527
|
+
try {
|
|
528
|
+
const parsed = JSON.parse(row.data);
|
|
529
|
+
const name = parsed.event ?? parsed.action ?? parsed.name;
|
|
530
|
+
return typeof name === "string" ? name : "";
|
|
531
|
+
} catch {
|
|
532
|
+
return "";
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function isTagManagerNoise(label) {
|
|
536
|
+
return label.startsWith("gtm.") || label.startsWith("cookie_consent") || label === "consent" || label === "set";
|
|
537
|
+
}
|
|
538
|
+
var FOLLOWS_WITHIN_MS = 2e3;
|
|
539
|
+
function debugReadHints(read) {
|
|
540
|
+
if (read.records.length === 0) {
|
|
541
|
+
return [
|
|
542
|
+
`Nothing recorded under ${read.code}: the Baker tag never loaded on a visit with this link. Either the link was not opened yet, the cookie banner was not accepted (on a site that loads Tag Manager only after consent, the tag never starts), or the site dropped ?baker_debug from the address before the tag ran. ${read.active ? "The link still records \u2014 ask them to try again." : "The link has expired; start a new one."}`
|
|
543
|
+
];
|
|
544
|
+
}
|
|
545
|
+
const hints = [];
|
|
546
|
+
const boot = read.records.find((row) => row.kind === "pixel" && row.label === "boot");
|
|
547
|
+
if (boot?.data?.includes('"measuring":false')) {
|
|
548
|
+
hints.push(
|
|
549
|
+
"The tag did not measure this visit (its scope or the visitor's consent ruled it out), so its `track` calls sent nothing to Baker. The records below are still what the page heard."
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
const messages = read.records.filter((row) => row.kind === "message" && row.from && row.from !== "null");
|
|
553
|
+
const byOrigin = /* @__PURE__ */ new Map();
|
|
554
|
+
for (const row of messages) {
|
|
555
|
+
const said = byOrigin.get(row.from ?? "") ?? /* @__PURE__ */ new Map();
|
|
556
|
+
const what = inner(row) ? `${row.label} (${inner(row)})` : row.label;
|
|
557
|
+
said.set(what, (said.get(what) ?? 0) + 1);
|
|
558
|
+
byOrigin.set(row.from ?? "", said);
|
|
559
|
+
}
|
|
560
|
+
for (const [origin, said] of byOrigin) {
|
|
561
|
+
hints.push(
|
|
562
|
+
`${origin} sent: ${[...said].map(([what, count]) => count > 1 ? `${what} \xD7${count}` : what).join(", ")}. An event that is not on this list was never sent by that frame on this visit \u2014 the tag manager could not have passed it on.`
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
const unanswered = messages.filter(
|
|
566
|
+
(message) => !read.records.some(
|
|
567
|
+
(row) => row.kind === "dataLayer" && !isTagManagerNoise(row.label) && // Either side of it: a listener the site registered before the tag
|
|
568
|
+
// ran first, so its push is stamped before the message is.
|
|
569
|
+
Math.abs(row.t - message.t) <= FOLLOWS_WITHIN_MS
|
|
570
|
+
)
|
|
571
|
+
);
|
|
572
|
+
if (unanswered.length > 0) {
|
|
573
|
+
hints.push(
|
|
574
|
+
`${[...new Set(unanswered.map((row) => row.label))].join(", ")}: arrived as a message and no dataLayer event followed, so no tag listening for one could fire. Compare the message (--full) with the listener in the container (\`baker tag-manager read --entities tag --full\`) \u2014 a different \`type\` or event name is the usual cause.`
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
const tracked = read.records.filter((row) => row.kind === "track").map((row) => row.label);
|
|
578
|
+
if (tracked.length > 0) {
|
|
579
|
+
hints.push(
|
|
580
|
+
`The tag was asked to send: ${[...new Set(tracked)].join(", ")}. These reach Baker's feed within a minute or two (\`baker analytics feed --origin website --days 1\`).`
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
if (read.dropped > 0)
|
|
584
|
+
hints.push(`${read.dropped} records arrived after this link had kept its maximum and were not kept.`);
|
|
585
|
+
return hints;
|
|
586
|
+
}
|
|
587
|
+
function compactDebugRead(read, full) {
|
|
588
|
+
if (full) return read;
|
|
589
|
+
return { ...read, records: read.records.map(({ data: _data, ...row }) => row) };
|
|
590
|
+
}
|
|
591
|
+
|
|
510
592
|
// src/commands/analytics/feedRows.ts
|
|
511
593
|
function isStreamRow(value) {
|
|
512
594
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -540,6 +622,14 @@ function feedHints(shape) {
|
|
|
540
622
|
}
|
|
541
623
|
return hints;
|
|
542
624
|
}
|
|
625
|
+
function hiddenMachinesHints(hidden) {
|
|
626
|
+
if (hidden === void 0 || hidden.events === 0) return [];
|
|
627
|
+
const headless = hidden.reasons.some((share) => share.reason === "ua");
|
|
628
|
+
const why = headless ? "a headless browser, which is what agent-browser is, so a check you ran on the site lands here" : "a datacenter or crawler network, or headers no real browser sends";
|
|
629
|
+
return [
|
|
630
|
+
`${hidden.events} events in this window came from machines and are not listed \u2014 stored, but left out of the feed and every report (${why}). They arrived; this is not lost data. \`baker analytics feed --bots\` lists them, each with its \`botReason\`. Never present them to the client as visits.`
|
|
631
|
+
];
|
|
632
|
+
}
|
|
543
633
|
function peopleSearchHints(search, rows) {
|
|
544
634
|
const found = new Set(rows.flatMap((row) => Array.isArray(row.personTerms) ? row.personTerms : []));
|
|
545
635
|
return search.filter((term) => !found.has(term.term)).map(
|
|
@@ -938,6 +1028,61 @@ function refineCompaniesReport(data, companies, targets) {
|
|
|
938
1028
|
return [...visitorCompanyHints(matched.companies.length > 0 ? matched.companies : companies), ...skipped, ...cut];
|
|
939
1029
|
}
|
|
940
1030
|
|
|
1031
|
+
// src/commands/analytics/websiteTagCheck.ts
|
|
1032
|
+
var BAKER_CALL = /baker\s*\.\s*(?:track|conversion)\s*\(\s*(["'])([^"'\n]{1,120})\1/g;
|
|
1033
|
+
function tagHtml(raw) {
|
|
1034
|
+
if (typeof raw !== "object" || raw === null) return "";
|
|
1035
|
+
const parameters = raw.parameter;
|
|
1036
|
+
if (!Array.isArray(parameters)) return "";
|
|
1037
|
+
const html = parameters.find(
|
|
1038
|
+
(parameter) => typeof parameter === "object" && parameter !== null && parameter.key === "html" && typeof parameter.value === "string"
|
|
1039
|
+
);
|
|
1040
|
+
return html?.value ?? "";
|
|
1041
|
+
}
|
|
1042
|
+
function checkWebsiteEvents(tags, catalog) {
|
|
1043
|
+
const expected = /* @__PURE__ */ new Map();
|
|
1044
|
+
for (const tag of tags) {
|
|
1045
|
+
for (const match of tagHtml(tag.raw).matchAll(BAKER_CALL)) {
|
|
1046
|
+
const name = match[2];
|
|
1047
|
+
if (name === void 0) continue;
|
|
1048
|
+
const entry = expected.get(name) ?? { tags: [], paused: true };
|
|
1049
|
+
entry.tags.push(tag.name);
|
|
1050
|
+
entry.paused = entry.paused && tag.paused === true;
|
|
1051
|
+
expected.set(name, entry);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
const seen = new Map((catalog ?? []).map((row) => [row.eventKey, row]));
|
|
1055
|
+
const events = [...expected].map(([name, { tags: sentBy, paused }]) => {
|
|
1056
|
+
const arrived = seen.get(`page:${name}`);
|
|
1057
|
+
const events2 = arrived?.events ?? 0;
|
|
1058
|
+
const status = paused ? "paused" : catalog === null ? "not_checked" : events2 > 0 ? "arriving" : "never_seen";
|
|
1059
|
+
return { name, tags: sentBy, paused, status, events: events2, lastSeen: arrived?.lastSeen ?? null };
|
|
1060
|
+
});
|
|
1061
|
+
return { events };
|
|
1062
|
+
}
|
|
1063
|
+
function websiteEventCheckHints(events, days) {
|
|
1064
|
+
const hints = [];
|
|
1065
|
+
const missing = events.filter((event) => event.status === "never_seen");
|
|
1066
|
+
if (missing.length > 0) {
|
|
1067
|
+
hints.push(
|
|
1068
|
+
`${missing.map((event) => `\`${event.name}\``).join(", ")}: a tag sends ${missing.length === 1 ? "it" : "them"} and no real visit brought one in ${days} days. Tag Manager only fires the tag when something on the page pushes its trigger \u2014 find out whether the page ever does: \`baker analytics debug --start\` gives the person a link that records what their browser hears, and the agent-browser recipe "what a live site tells its tag manager" reads a widget's code for when it sends each event.`
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
const paused = events.filter((event) => event.status === "paused");
|
|
1072
|
+
if (paused.length > 0) {
|
|
1073
|
+
hints.push(
|
|
1074
|
+
`${paused.map((event) => `\`${event.name}\``).join(", ")}: every tag sending ${paused.length === 1 ? "it is" : "them is"} paused in Tag Manager, so nothing is sent. That is a setting in the container, not a fault on the site.`
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
const doubled = events.filter((event) => event.tags.length > 1);
|
|
1078
|
+
if (doubled.length > 0) {
|
|
1079
|
+
hints.push(
|
|
1080
|
+
`${doubled.map((event) => `\`${event.name}\` (${event.tags.join(", ")})`).join("; ")}: sent by more than one tag, so one moment can arrive twice. Give each call the same \`eventId\` or keep one tag.`
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
return hints;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
941
1086
|
// src/commands/analytics/websiteTagHints.ts
|
|
942
1087
|
function asTyped(origin) {
|
|
943
1088
|
return origin.replace(/^https?:\/\//, "");
|
|
@@ -1136,6 +1281,7 @@ function requestBody(args, options) {
|
|
|
1136
1281
|
sentTo: options.sentTo,
|
|
1137
1282
|
eventUid: options.eventUid,
|
|
1138
1283
|
people: options.people,
|
|
1284
|
+
machines: options.machines,
|
|
1139
1285
|
...pageOf(args)
|
|
1140
1286
|
};
|
|
1141
1287
|
return Object.fromEntries(
|
|
@@ -1143,9 +1289,13 @@ function requestBody(args, options) {
|
|
|
1143
1289
|
);
|
|
1144
1290
|
}
|
|
1145
1291
|
var EVENT_ORIGINS = ANALYTICS_EVENT_DOORS;
|
|
1146
|
-
function feedReportHints(feed,
|
|
1292
|
+
function feedReportHints(feed, data) {
|
|
1147
1293
|
if (feed === void 0) return [];
|
|
1148
|
-
return [
|
|
1294
|
+
return [
|
|
1295
|
+
...feedHints(feed),
|
|
1296
|
+
...hiddenMachinesHints(data.hiddenMachines),
|
|
1297
|
+
...peopleSearchHints(data.peopleSearch ?? [], feed.rows)
|
|
1298
|
+
];
|
|
1149
1299
|
}
|
|
1150
1300
|
async function runPreset(args, options, refine) {
|
|
1151
1301
|
const origin = args.origin === void 0 ? "" : String(args.origin);
|
|
@@ -1174,7 +1324,7 @@ async function runPreset(args, options, refine) {
|
|
|
1174
1324
|
if (feed !== void 0) data.stream = feed.rows;
|
|
1175
1325
|
const refinedHints = refine ? refine(data, response.data) : [];
|
|
1176
1326
|
const hints = [
|
|
1177
|
-
...feedReportHints(feed, response.data
|
|
1327
|
+
...feedReportHints(feed, response.data),
|
|
1178
1328
|
// The platform the ad blocks were scoped to, so the tagging hint can
|
|
1179
1329
|
// only speak about the inventory it was actually given. `adParams` is one
|
|
1180
1330
|
// platform's list; a hint drawn from it without knowing which platform
|
|
@@ -1459,6 +1609,7 @@ function feedNarrowing(args) {
|
|
|
1459
1609
|
...args.send ? { sendId: String(args.send) } : {},
|
|
1460
1610
|
...args["sent-to"] ? { sentTo: String(args["sent-to"]) } : {},
|
|
1461
1611
|
...args.uid ? { eventUid: String(args.uid) } : {},
|
|
1612
|
+
...args.bots === true ? { machines: true } : {},
|
|
1462
1613
|
...peopleArgs(args)
|
|
1463
1614
|
};
|
|
1464
1615
|
}
|
|
@@ -1512,6 +1663,11 @@ var feedCommand = presetCommand({
|
|
|
1512
1663
|
description: "A file with one person per line, read like --person \u2014 a column pasted from a CRM export",
|
|
1513
1664
|
required: false
|
|
1514
1665
|
},
|
|
1666
|
+
bots: {
|
|
1667
|
+
type: "boolean",
|
|
1668
|
+
description: "List ONLY the events taken for machine traffic \u2014 a headless browser (agent-browser is one: every check you run on a site from it lands here), a datacenter network \u2014 each with its `botReason`. Stored, but left out of the feed and every report. Reach for it to confirm a test visit you just made arrived; never quote these rows to the client as visitors",
|
|
1669
|
+
required: false
|
|
1670
|
+
},
|
|
1515
1671
|
everything: {
|
|
1516
1672
|
type: "boolean",
|
|
1517
1673
|
description: "Include the rows held back by default: the ones recorded so the reports can be worked out (identifications, step views, session starts, a destination's answer already folded onto its dispatch) and embedded forms and calendars announcing that they drew. Reach for it when debugging the collector rather than an integration \u2014 an embed that draws and never fires again is only visible here",
|
|
@@ -1946,7 +2102,13 @@ var websiteTagCommand = (() => {
|
|
|
1946
2102
|
type: "string",
|
|
1947
2103
|
required: false,
|
|
1948
2104
|
description: 'Whose activity to measure: "campaigns" (default), "outcomes" or "all"'
|
|
1949
|
-
}
|
|
2105
|
+
},
|
|
2106
|
+
check: {
|
|
2107
|
+
type: "boolean",
|
|
2108
|
+
required: false,
|
|
2109
|
+
description: "Every event the Tag Manager container sends Baker, and whether a real visit has brought it"
|
|
2110
|
+
},
|
|
2111
|
+
days: { type: "string", required: false, description: "With --check: how far back to look (default 30)" }
|
|
1950
2112
|
}
|
|
1951
2113
|
});
|
|
1952
2114
|
return defineCommand({
|
|
@@ -1969,15 +2131,25 @@ Three things worth knowing before you install it:
|
|
|
1969
2131
|
Examples:
|
|
1970
2132
|
baker analytics website-tag \u2014 the tag, the origins and the install brief
|
|
1971
2133
|
baker analytics website-tag --measure all \u2014 measure their whole website, because they asked for that
|
|
2134
|
+
baker analytics website-tag --check \u2014 every event Tag Manager sends Baker, and which never arrived
|
|
1972
2135
|
baker analytics events --origin website \u2014 after it is live, what that website is reporting`
|
|
1973
2136
|
},
|
|
1974
2137
|
args: {
|
|
1975
2138
|
measure: {
|
|
1976
2139
|
type: "string",
|
|
1977
2140
|
description: 'Whose activity to measure: "campaigns" (default), "outcomes" or "all". Widening is a decision \u2014 see above.'
|
|
1978
|
-
}
|
|
2141
|
+
},
|
|
2142
|
+
check: {
|
|
2143
|
+
type: "boolean",
|
|
2144
|
+
description: 'Read the connected Tag Manager container for every window.baker.track call its tags make, and say which events a real visit has brought in the window and which never arrived. Start here when "an event is missing": an event the container sends and nobody has seen is invisible on every other report'
|
|
2145
|
+
},
|
|
2146
|
+
days: { type: "string", description: "With --check: how many days back to look for each event (default 30)" }
|
|
1979
2147
|
},
|
|
1980
2148
|
run: async (ctx) => {
|
|
2149
|
+
if (ctx.args.check === true) {
|
|
2150
|
+
await runWebsiteEventCheck(Number(ctx.args.days ?? 30) || 30);
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
1981
2153
|
try {
|
|
1982
2154
|
const measure = typeof ctx.args.measure === "string" ? ctx.args.measure : void 0;
|
|
1983
2155
|
const envelope = await apiPost(
|
|
@@ -1991,6 +2163,130 @@ Examples:
|
|
|
1991
2163
|
}
|
|
1992
2164
|
});
|
|
1993
2165
|
})();
|
|
2166
|
+
async function runWebsiteEventCheck(days) {
|
|
2167
|
+
let tags;
|
|
2168
|
+
try {
|
|
2169
|
+
tags = (await apiPost(
|
|
2170
|
+
"/api/tag-manager/read",
|
|
2171
|
+
{ entities: ["tag"], full: true },
|
|
2172
|
+
{ timeoutMs: TAG_MANAGER_TIMEOUT_MS }
|
|
2173
|
+
)).tags;
|
|
2174
|
+
} catch (err) {
|
|
2175
|
+
writeJsonEnvelope({
|
|
2176
|
+
ok: false,
|
|
2177
|
+
error: {
|
|
2178
|
+
code: "TAG_MANAGER_UNAVAILABLE",
|
|
2179
|
+
message: `The check reads the website's Tag Manager container, and it could not: ${err instanceof Error ? err.message : String(err)}`,
|
|
2180
|
+
fix: {
|
|
2181
|
+
action: "continue_without",
|
|
2182
|
+
explanation: "Without a connected container there is no list of what the site is wired to send. Read what did arrive with `baker analytics feed --origin website`, and ask the person to connect Tag Manager if the site uses it."
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
});
|
|
2186
|
+
process.exit(1);
|
|
2187
|
+
}
|
|
2188
|
+
try {
|
|
2189
|
+
let catalog;
|
|
2190
|
+
let unread = "";
|
|
2191
|
+
try {
|
|
2192
|
+
const response = await apiPost("/api/analytics/query", {
|
|
2193
|
+
preset: "conversions",
|
|
2194
|
+
eventOrigin: "website",
|
|
2195
|
+
days
|
|
2196
|
+
});
|
|
2197
|
+
catalog = response.data.eventCatalog ?? [];
|
|
2198
|
+
} catch (err) {
|
|
2199
|
+
catalog = null;
|
|
2200
|
+
unread = err instanceof Error ? err.message : String(err);
|
|
2201
|
+
}
|
|
2202
|
+
const { events } = checkWebsiteEvents(tags, catalog);
|
|
2203
|
+
const hints = websiteEventCheckHints(events, days);
|
|
2204
|
+
if (catalog === null) {
|
|
2205
|
+
hints.push(
|
|
2206
|
+
`What arrived could not be read (${unread}), so this lists what the container sends, unchecked. \`baker analytics feed --origin website\` is the other half once reading works.`
|
|
2207
|
+
);
|
|
2208
|
+
}
|
|
2209
|
+
if (events.length === 0) {
|
|
2210
|
+
hints.push(
|
|
2211
|
+
"No tag in the container calls window.baker.track, so the website sends Baker page views and nothing else. Outcomes come from tags you stage with `baker tag-manager`."
|
|
2212
|
+
);
|
|
2213
|
+
}
|
|
2214
|
+
writeJsonEnvelope({ ok: true, data: { days, events }, hints });
|
|
2215
|
+
} catch (err) {
|
|
2216
|
+
handleError(err);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
var DEBUG_ARGS = {
|
|
2220
|
+
code: {
|
|
2221
|
+
type: "positional",
|
|
2222
|
+
required: false,
|
|
2223
|
+
description: "The code `--start` returned: read what was recorded under it"
|
|
2224
|
+
},
|
|
2225
|
+
start: {
|
|
2226
|
+
type: "boolean",
|
|
2227
|
+
required: false,
|
|
2228
|
+
description: "Mint a debug link for the person to open in their own browser"
|
|
2229
|
+
},
|
|
2230
|
+
url: {
|
|
2231
|
+
type: "string",
|
|
2232
|
+
required: false,
|
|
2233
|
+
description: "With --start: the page the person should open (the link is built on it)"
|
|
2234
|
+
},
|
|
2235
|
+
full: { type: "boolean", required: false, description: "Every record's payload, masked, not just what it was" }
|
|
2236
|
+
};
|
|
2237
|
+
registerSchema({
|
|
2238
|
+
command: "analytics.debug",
|
|
2239
|
+
description: "A link that records what a person's own browser heard on their website, and the read of it",
|
|
2240
|
+
args: DEBUG_ARGS
|
|
2241
|
+
});
|
|
2242
|
+
var debugCommand = defineCommand({
|
|
2243
|
+
meta: {
|
|
2244
|
+
name: "debug",
|
|
2245
|
+
description: `Watch a real visit from the person's own browser: what the page heard \u2014 messages from its iframes, custom events, dataLayer pushes \u2014 and what Baker's website tag did with it.
|
|
2246
|
+
|
|
2247
|
+
Start here when an event from the client's own website never arrives and a test from your browser cannot settle it: a voice, video or chat widget your headless browser cannot run, a login that is theirs, a cookie banner only they answer. Your own browser is flagged as a bot and in no report, so their visit is the test that counts.
|
|
2248
|
+
|
|
2249
|
+
baker analytics debug --start --url https://client.com/ \u2014 a link to send them; it records for two hours
|
|
2250
|
+
baker analytics debug <code> \u2014 what their visit recorded, in order, with what to read first
|
|
2251
|
+
baker analytics debug <code> --full \u2014 every record's payload (emails and phones masked)
|
|
2252
|
+
|
|
2253
|
+
Nothing recorded is counted or shown in Baker; it is purged a day later.`
|
|
2254
|
+
},
|
|
2255
|
+
args: DEBUG_ARGS,
|
|
2256
|
+
run: async ({ args }) => {
|
|
2257
|
+
try {
|
|
2258
|
+
if (args.start === true) {
|
|
2259
|
+
const response2 = await apiPost("/api/analytics/debug", {
|
|
2260
|
+
start: true,
|
|
2261
|
+
...typeof args.url === "string" && args.url !== "" ? { url: args.url } : {}
|
|
2262
|
+
});
|
|
2263
|
+
writeJsonEnvelope({ ok: true, data: response2.data, hints: debugStartHints(response2.data) });
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
if (typeof args.code !== "string" || args.code === "") {
|
|
2267
|
+
writeJsonEnvelope({
|
|
2268
|
+
ok: false,
|
|
2269
|
+
error: {
|
|
2270
|
+
code: "VALIDATION_ERROR",
|
|
2271
|
+
message: "Pass --start to mint a debug link, or the code it returned to read it."
|
|
2272
|
+
}
|
|
2273
|
+
});
|
|
2274
|
+
process.exit(1);
|
|
2275
|
+
}
|
|
2276
|
+
const response = await apiPost("/api/analytics/debug", {
|
|
2277
|
+
code: args.code,
|
|
2278
|
+
full: true
|
|
2279
|
+
});
|
|
2280
|
+
writeJsonEnvelope({
|
|
2281
|
+
ok: true,
|
|
2282
|
+
data: compactDebugRead(response.data, args.full === true),
|
|
2283
|
+
hints: debugReadHints(response.data)
|
|
2284
|
+
});
|
|
2285
|
+
} catch (err) {
|
|
2286
|
+
handleError(err);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
1994
2290
|
var sitesCommand = (() => {
|
|
1995
2291
|
registerSchema({
|
|
1996
2292
|
command: "analytics.sites",
|
|
@@ -2108,21 +2404,22 @@ Full guide: __tooling__/docs/tools/baker/analytics.md`
|
|
|
2108
2404
|
tags: tagsCommand,
|
|
2109
2405
|
events: eventsCommand,
|
|
2110
2406
|
feed: feedCommand,
|
|
2111
|
-
lookup: () => import("./lookup-
|
|
2407
|
+
lookup: () => import("./lookup-ZJIP2M4X.js").then((m) => m.lookupCommand),
|
|
2112
2408
|
devices: devicesCommand,
|
|
2113
2409
|
ads: adsCommand,
|
|
2114
2410
|
conversions: conversionsCommand,
|
|
2115
|
-
sends: () => import("./sends-
|
|
2411
|
+
sends: () => import("./sends-6FI7WELX.js").then((m) => m.sendsCommand),
|
|
2116
2412
|
map: mapCommand,
|
|
2117
|
-
unstage: () => import("./unstage-
|
|
2413
|
+
unstage: () => import("./unstage-MDSSPUKV.js").then((m) => m.unstageCommand),
|
|
2118
2414
|
tracking: trackingCommand,
|
|
2119
2415
|
arrivals: arrivalsCommand,
|
|
2120
2416
|
"sending-events": sendingEventsCommand,
|
|
2121
|
-
sources: () => import("./sources-
|
|
2417
|
+
sources: () => import("./sources-L3KNVLIB.js").then((m) => m.sourcesCommand),
|
|
2122
2418
|
"website-tag": websiteTagCommand,
|
|
2419
|
+
debug: debugCommand,
|
|
2123
2420
|
sites: sitesCommand,
|
|
2124
|
-
plan: () => import("./plan-
|
|
2125
|
-
questionnaire: () => import("./questionnaire-
|
|
2421
|
+
plan: () => import("./plan-TVQSEKYL.js").then((m) => m.planCommand),
|
|
2422
|
+
questionnaire: () => import("./questionnaire-LEZJLMAV.js").then((m) => m.questionnaireCommand),
|
|
2126
2423
|
presets: presetsCommand
|
|
2127
2424
|
}
|
|
2128
2425
|
});
|
|
@@ -2135,4 +2432,4 @@ export {
|
|
|
2135
2432
|
requestBody,
|
|
2136
2433
|
sendingEventsAddress
|
|
2137
2434
|
};
|
|
2138
|
-
//# sourceMappingURL=analytics-
|
|
2435
|
+
//# sourceMappingURL=analytics-P5UFA6I4.js.map
|