@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.
Files changed (47) hide show
  1. package/README.md +5 -2
  2. package/dist/{ads-OLOEMAID.js → ads-T5RPISTB.js} +3 -3
  3. package/dist/{analytics-4CCQYY7Z.js → analytics-P5UFA6I4.js} +315 -18
  4. package/dist/analytics-P5UFA6I4.js.map +1 -0
  5. package/dist/{chunk-DI44SZVP.js → chunk-7J3NZBPV.js} +31 -1
  6. package/dist/chunk-7J3NZBPV.js.map +1 -0
  7. package/dist/{chunk-VU5Z2V4S.js → chunk-D3KLDIPQ.js} +2 -2
  8. package/dist/{chunk-GJCB46L6.js → chunk-HWDRW36T.js} +2 -2
  9. package/dist/{chunk-S3FFLLYF.js → chunk-LIRX2Q66.js} +2 -2
  10. package/dist/{chunk-DX46OE4U.js → chunk-OJGYYH6K.js} +2 -2
  11. package/dist/{chunk-ZEGNIP3Y.js → chunk-TVTSA4EN.js} +3 -3
  12. package/dist/cli.js +4 -4
  13. package/dist/{critique-DU7K2KVR.js → critique-WOSMZPHW.js} +7 -17
  14. package/dist/critique-WOSMZPHW.js.map +1 -0
  15. package/dist/{funnel-727XATM5.js → funnel-S6JTLUK6.js} +3 -3
  16. package/dist/{google-VPL4KZDP.js → google-3GSINSOV.js} +2 -2
  17. package/dist/{landing-X4PG6XWU.js → landing-GEWSOHGZ.js} +3 -3
  18. package/dist/landing-GEWSOHGZ.js.map +1 -0
  19. package/dist/{lookup-LWJMDEEL.js → lookup-ZJIP2M4X.js} +4 -4
  20. package/dist/{performance-LMO73TUB.js → performance-WPFGEJTE.js} +2 -2
  21. package/dist/{plan-O2TTEUIW.js → plan-TVQSEKYL.js} +6 -6
  22. package/dist/{questionnaire-VE42SW77.js → questionnaire-LEZJLMAV.js} +3 -3
  23. package/dist/{sends-OLVQGOAE.js → sends-6FI7WELX.js} +3 -3
  24. package/dist/{sources-PSM2A5QD.js → sources-L3KNVLIB.js} +4 -4
  25. package/dist/{unstage-QFVL7AZ4.js → unstage-MDSSPUKV.js} +3 -3
  26. package/dist/{url-options-LA6BL3GS.js → url-options-IHL4OU4C.js} +2 -2
  27. package/package.json +1 -1
  28. package/dist/analytics-4CCQYY7Z.js.map +0 -1
  29. package/dist/chunk-DI44SZVP.js.map +0 -1
  30. package/dist/critique-DU7K2KVR.js.map +0 -1
  31. package/dist/landing-X4PG6XWU.js.map +0 -1
  32. /package/dist/{ads-OLOEMAID.js.map → ads-T5RPISTB.js.map} +0 -0
  33. /package/dist/{chunk-VU5Z2V4S.js.map → chunk-D3KLDIPQ.js.map} +0 -0
  34. /package/dist/{chunk-GJCB46L6.js.map → chunk-HWDRW36T.js.map} +0 -0
  35. /package/dist/{chunk-S3FFLLYF.js.map → chunk-LIRX2Q66.js.map} +0 -0
  36. /package/dist/{chunk-DX46OE4U.js.map → chunk-OJGYYH6K.js.map} +0 -0
  37. /package/dist/{chunk-ZEGNIP3Y.js.map → chunk-TVTSA4EN.js.map} +0 -0
  38. /package/dist/{funnel-727XATM5.js.map → funnel-S6JTLUK6.js.map} +0 -0
  39. /package/dist/{google-VPL4KZDP.js.map → google-3GSINSOV.js.map} +0 -0
  40. /package/dist/{lookup-LWJMDEEL.js.map → lookup-ZJIP2M4X.js.map} +0 -0
  41. /package/dist/{performance-LMO73TUB.js.map → performance-WPFGEJTE.js.map} +0 -0
  42. /package/dist/{plan-O2TTEUIW.js.map → plan-TVQSEKYL.js.map} +0 -0
  43. /package/dist/{questionnaire-VE42SW77.js.map → questionnaire-LEZJLMAV.js.map} +0 -0
  44. /package/dist/{sends-OLVQGOAE.js.map → sends-6FI7WELX.js.map} +0 -0
  45. /package/dist/{sources-PSM2A5QD.js.map → sources-L3KNVLIB.js.map} +0 -0
  46. /package/dist/{unstage-QFVL7AZ4.js.map → unstage-MDSSPUKV.js.map} +0 -0
  47. /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 — a concession included — and a hint says the copy went unchecked and to re-run.
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
- - **Positioning integrity is scored too, and it is the one copy rule that blocks on a single sentence.** `competitor-concession` fires when the page ranks somebody else above the client, or says the client does not lead at what the page sells ("su constructor es mejor que el nuestro", "we're not the best"). Every other prose rule waits for a cluster because one AI tell proves nothing; this one is the whole defect on its own — the page is paid traffic, and it is arguing the visitor should leave. Two shapes that read identically are exempt by design: a concession about **price**, in either direction ("we are not the cheapest / the most expensive, and here is why" is premium positioning), and a superlative flipped by a negation ("ningún equipo trabaja más rápido que el nuestro"), which is the strongest claim *for* the client. Its warn-tier sibling `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.
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-VPL4KZDP.js").then((m) => m.googleCommand),
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-LMO73TUB.js").then((m) => m.performanceCommand),
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-OLOEMAID.js.map
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-S3FFLLYF.js";
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-GJCB46L6.js";
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-ZEGNIP3Y.js";
27
+ } from "./chunk-TVTSA4EN.js";
25
28
  import {
26
29
  ConversionArgsError,
27
30
  conversionsWrite,
28
31
  parseConversionsArgs
29
- } from "./chunk-DX46OE4U.js";
32
+ } from "./chunk-OJGYYH6K.js";
30
33
  import {
31
34
  analyticsErrorEnvelope
32
- } from "./chunk-VU5Z2V4S.js";
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-DI44SZVP.js";
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, peopleSearch) {
1292
+ function feedReportHints(feed, data) {
1147
1293
  if (feed === void 0) return [];
1148
- return [...feedHints(feed), ...peopleSearchHints(peopleSearch ?? [], feed.rows)];
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.peopleSearch),
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-LWJMDEEL.js").then((m) => m.lookupCommand),
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-OLVQGOAE.js").then((m) => m.sendsCommand),
2411
+ sends: () => import("./sends-6FI7WELX.js").then((m) => m.sendsCommand),
2116
2412
  map: mapCommand,
2117
- unstage: () => import("./unstage-QFVL7AZ4.js").then((m) => m.unstageCommand),
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-PSM2A5QD.js").then((m) => m.sourcesCommand),
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-O2TTEUIW.js").then((m) => m.planCommand),
2125
- questionnaire: () => import("./questionnaire-VE42SW77.js").then((m) => m.questionnaireCommand),
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-4CCQYY7Z.js.map
2435
+ //# sourceMappingURL=analytics-P5UFA6I4.js.map