@koda-sl/baker-cli 0.313.0-dev.ca75dc08a → 0.313.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/dist/cli.js CHANGED
@@ -67,7 +67,7 @@ import {
67
67
  validateCanvasDeep,
68
68
  withoutCastName,
69
69
  ytDlpBlockSignal
70
- } from "./chunk-PY7R67NE.js";
70
+ } from "./chunk-WONWYE7B.js";
71
71
  import {
72
72
  csvOrJson,
73
73
  daysAgoIso,
@@ -222,6 +222,15 @@ function compactImage(image) {
222
222
  imageUrl: String(image.imageUrl ?? ""),
223
223
  description: String(image.description ?? ""),
224
224
  tags: Array.isArray(image.tags) ? image.tags : [],
225
+ // Two numbers that are not about choosing the image but about placing it.
226
+ // A remote <BakerImage> with no width/height makes Astro DOWNLOAD the file
227
+ // at build time just to measure it — one request per image per build, on a
228
+ // build that is otherwise offline, and a single dropped connection there
229
+ // fails a client's whole verify. They are cheap enough to always carry, and
230
+ // burying them behind --full meant the agent placing the image never had
231
+ // them.
232
+ width: typeof image.width === "number" ? image.width : null,
233
+ height: typeof image.height === "number" ? image.height : null,
225
234
  // The facts that decide a pick, so choosing does not need a second call:
226
235
  // whose brand it is, who is in it, and what text is burned in.
227
236
  subject: typeof analysis.subject === "string" ? analysis.subject : null,
@@ -235,8 +244,6 @@ function fullImage(image) {
235
244
  const compact3 = compactImage(image);
236
245
  return {
237
246
  ...compact3,
238
- width: image.width ?? null,
239
- height: image.height ?? null,
240
247
  aspectRatio: formatAspectRatio(image.aspectRatio),
241
248
  dominantColor: image.dominantColor ?? null,
242
249
  imagePalette: Array.isArray(image.imagePalette) ? image.imagePalette : [],
@@ -10749,14 +10756,14 @@ function collectionLine(collection) {
10749
10756
  }
10750
10757
  function accountsInTree(nodes) {
10751
10758
  const accounts = /* @__PURE__ */ new Set();
10752
- const walk = (node) => {
10759
+ const walk2 = (node) => {
10753
10760
  accounts.add(node.customerId);
10754
10761
  for (const child of node.children) {
10755
- walk(child);
10762
+ walk2(child);
10756
10763
  }
10757
10764
  };
10758
10765
  for (const node of nodes) {
10759
- walk(node);
10766
+ walk2(node);
10760
10767
  }
10761
10768
  return accounts;
10762
10769
  }
@@ -21383,14 +21390,14 @@ function collectionLine2(collection) {
21383
21390
  }
21384
21391
  function accountsInTree2(nodes) {
21385
21392
  const accounts = /* @__PURE__ */ new Set();
21386
- const walk = (node) => {
21393
+ const walk2 = (node) => {
21387
21394
  accounts.add(node.accountId);
21388
21395
  for (const child of node.children) {
21389
- walk(child);
21396
+ walk2(child);
21390
21397
  }
21391
21398
  };
21392
21399
  for (const node of nodes) {
21393
- walk(node);
21400
+ walk2(node);
21394
21401
  }
21395
21402
  return accounts;
21396
21403
  }
@@ -26995,14 +27002,14 @@ function collectionLine3(collection) {
26995
27002
  }
26996
27003
  function accountsInTree3(nodes) {
26997
27004
  const accounts = /* @__PURE__ */ new Set();
26998
- const walk = (node) => {
27005
+ const walk2 = (node) => {
26999
27006
  accounts.add(node.accountId);
27000
27007
  for (const child of node.children) {
27001
- walk(child);
27008
+ walk2(child);
27002
27009
  }
27003
27010
  };
27004
27011
  for (const node of nodes) {
27005
- walk(node);
27012
+ walk2(node);
27006
27013
  }
27007
27014
  return accounts;
27008
27015
  }
@@ -28820,55 +28827,6 @@ Full guides: __tooling__/docs/tools/baker/ads-<platform>.md (google|meta|linkedi
28820
28827
  // src/commands/analytics/index.ts
28821
28828
  import { readFileSync as readFileSync12 } from "fs";
28822
28829
 
28823
- // ../api/src/analytics/flowEventKeys.ts
28824
- function flowNodes(node, acc = []) {
28825
- if (!node || typeof node !== "object") return acc;
28826
- const record = node;
28827
- acc.push(record);
28828
- if (Array.isArray(record.children)) for (const child of record.children) flowNodes(child, acc);
28829
- return acc;
28830
- }
28831
- function flowEventKeys(tree, slug) {
28832
- const primary = [];
28833
- const secondary = [];
28834
- const nodes = flowNodes(tree).filter((node) => typeof node.id === "string" && node.id !== "");
28835
- if (nodes.length === 0) return [];
28836
- for (const node of nodes) {
28837
- const id = node.id;
28838
- const triggers = flowNodeTriggers(node.nodeData);
28839
- for (const triggerId of triggers.available) {
28840
- const candidate = {
28841
- eventKey: formEventKey(slug, id, triggerId),
28842
- nodeId: id,
28843
- nodeName: typeof node.name === "string" ? node.name : id,
28844
- nodeType: triggers.type,
28845
- triggerId,
28846
- isCompletion: triggers.completion === triggerId
28847
- };
28848
- (candidate.isCompletion ? primary : secondary).push(candidate);
28849
- }
28850
- }
28851
- return [
28852
- ...primary,
28853
- ...secondary,
28854
- {
28855
- eventKey: `submit:${slug}`,
28856
- nodeId: null,
28857
- nodeName: "the Form ran out of steps",
28858
- nodeType: null,
28859
- triggerId: null,
28860
- // A Form reaching its last node is as primary as an outcome gets for a
28861
- // Form with no widget in it — but it never fires on one that ends in a
28862
- // booking widget or a redirect, so it does not lead the list.
28863
- isCompletion: false
28864
- }
28865
- ];
28866
- }
28867
- function suggestedEnding(endings) {
28868
- const deepestFirst = [...endings].reverse();
28869
- return deepestFirst.find((row) => row.isCompletion && row.nodeType !== "text") ?? deepestFirst.find((row) => row.isCompletion) ?? endings[endings.length - 1] ?? null;
28870
- }
28871
-
28872
28830
  // ../api/src/event-ingest/arrivals.ts
28873
28831
  import { z as z33 } from "zod";
28874
28832
 
@@ -30048,14 +30006,56 @@ function mergeStoredWithCounts(stored, reported) {
30048
30006
  }
30049
30007
 
30050
30008
  // src/commands/analytics/flowConversionKeys.ts
30009
+ function walk(node, acc = []) {
30010
+ if (!node || typeof node !== "object") return acc;
30011
+ const record = node;
30012
+ acc.push(record);
30013
+ if (Array.isArray(record.children)) for (const child of record.children) walk(child, acc);
30014
+ return acc;
30015
+ }
30016
+ function flowConversionCandidates(tree, slug) {
30017
+ const primary = [];
30018
+ const secondary = [];
30019
+ for (const node of walk(tree)) {
30020
+ const id = typeof node.id === "string" ? node.id : null;
30021
+ if (!id) continue;
30022
+ const triggers = flowNodeTriggers(node.nodeData);
30023
+ for (const triggerId of triggers.available) {
30024
+ const candidate = {
30025
+ eventKey: formEventKey(slug, id, triggerId),
30026
+ nodeId: id,
30027
+ nodeName: typeof node.name === "string" ? node.name : id,
30028
+ nodeType: triggers.type,
30029
+ triggerId,
30030
+ isCompletion: triggers.completion === triggerId
30031
+ };
30032
+ (candidate.isCompletion ? primary : secondary).push(candidate);
30033
+ }
30034
+ }
30035
+ return [
30036
+ ...primary,
30037
+ ...secondary,
30038
+ {
30039
+ eventKey: `submit:${slug}`,
30040
+ nodeId: null,
30041
+ nodeName: "the Form ran out of steps",
30042
+ nodeType: null,
30043
+ triggerId: null,
30044
+ // A Form reaching its last node is as primary as an outcome gets for a
30045
+ // Form with no widget in it — but it never fires on one that ends in a
30046
+ // booking widget or a redirect, so it does not lead the list.
30047
+ isCompletion: false
30048
+ }
30049
+ ];
30050
+ }
30051
30051
  function formKeyRefusal(eventKey, tree, slug) {
30052
30052
  const parsed = parseEventKey(eventKey);
30053
30053
  if (!parsed || parsed.family !== "form" || parsed.flowSlug !== slug) return null;
30054
- const node = flowNodes(tree).find((candidate) => candidate.id === parsed.nodeId);
30054
+ const node = walk(tree).find((candidate) => candidate.id === parsed.nodeId);
30055
30055
  if (!node) {
30056
30056
  return {
30057
30057
  message: `"${slug}" has no step with id "${parsed.nodeId}", so this key would be stored and count zero forever.`,
30058
- fix: `Steps in this Form: ${flowNodes(tree).map((n) => `${String(n.id)} (${String(n.name ?? "unnamed")})`).join(", ")}. \`baker analytics conversions --flow ${slug}\` prints each one already spelled as an event key.`
30058
+ fix: `Steps in this Form: ${walk(tree).map((n) => `${String(n.id)} (${String(n.name ?? "unnamed")})`).join(", ")}. \`baker analytics conversions --flow ${slug}\` prints each one already spelled as an event key.`
30059
30059
  };
30060
30060
  }
30061
30061
  const triggers = flowNodeTriggers(node.nodeData);
@@ -30086,7 +30086,8 @@ function flowConversionHints(slug, rows, noConversionReason) {
30086
30086
  `Nothing here is counted, and that is on purpose: this Form declares no outcome \u2014 "${noConversionReason}". Leave it alone unless that has stopped being true.`
30087
30087
  ];
30088
30088
  }
30089
- const suggestion = suggestedEnding(rows);
30089
+ const deepestFirst = [...rows].reverse();
30090
+ const suggestion = deepestFirst.find((row) => row.isCompletion && row.nodeType !== "text") ?? deepestFirst.find((row) => row.isCompletion) ?? rows[rows.length - 1];
30090
30091
  return [
30091
30092
  `Nothing on "${slug}" is counted, so it converts nobody on every report however many people finish it \u2014 Baker never guesses which ending is the point of a Form.`,
30092
30093
  ...suggestion ? [
@@ -30097,7 +30098,7 @@ function flowConversionHints(slug, rows, noConversionReason) {
30097
30098
  }
30098
30099
  function flowCoverage(forms, definitions) {
30099
30100
  return forms.map(({ slug, tree }) => {
30100
- const rows = joinFlowConversions(flowEventKeys(tree, slug), definitions);
30101
+ const rows = joinFlowConversions(flowConversionCandidates(tree, slug), definitions);
30101
30102
  const declared = tree?.noConversion?.reason;
30102
30103
  const seen = /* @__PURE__ */ new Set();
30103
30104
  return {
@@ -32115,7 +32116,7 @@ function readFlowConversions2(slug, definitions) {
32115
32116
  return {
32116
32117
  slug,
32117
32118
  noConversionReason: reason,
32118
- endings: joinFlowConversions(flowEventKeys(tree, slug), definitions)
32119
+ endings: joinFlowConversions(flowConversionCandidates(tree, slug), definitions)
32119
32120
  };
32120
32121
  }
32121
32122
  function conversionsEnvelope(args4, definitions, candidates) {
@@ -47232,63 +47233,8 @@ Full guide: __tooling__/docs/tools/baker/flows.md`
47232
47233
  });
47233
47234
 
47234
47235
  // src/commands/funnel/index.ts
47235
- import { readFileSync as readFileSync15 } from "fs";
47236
47236
  import { defineCommand as defineCommand129 } from "citty";
47237
47237
 
47238
- // src/commands/funnel/formMoments.ts
47239
- var MAX_NAMED_FORMS = 8;
47240
- function formMoments(events, forms) {
47241
- const outcomes = /* @__PURE__ */ new Map();
47242
- for (const form of forms) {
47243
- const outcome = suggestedEnding(flowEventKeys(form.tree, form.slug));
47244
- if (outcome !== null) outcomes.set(outcome.eventKey, { slug: form.slug, outcome });
47245
- }
47246
- const marked = events.map((event) => {
47247
- const found = outcomes.get(event.eventKey);
47248
- if (found === void 0) return event;
47249
- outcomes.delete(event.eventKey);
47250
- return { ...event, step: found.outcome.nodeName, outcome: true };
47251
- });
47252
- const unfired = [...outcomes.values()].map(
47253
- ({ slug, outcome }) => ({
47254
- eventKey: outcome.eventKey,
47255
- origin: "site",
47256
- flowSlug: slug,
47257
- ...outcome.nodeType === null ? {} : { nodeType: outcome.nodeType },
47258
- events: 0,
47259
- sessions: 0,
47260
- step: outcome.nodeName,
47261
- seen: false,
47262
- outcome: true
47263
- })
47264
- );
47265
- return [...marked, ...unfired];
47266
- }
47267
- function withFormMoments(moments, forms) {
47268
- return { ...moments, events: formMoments(moments.events, forms) };
47269
- }
47270
- function formsNamedHere(forms) {
47271
- return forms.filter((form) => suggestedEnding(flowEventKeys(form.tree, form.slug)) !== null).map((form) => form.slug);
47272
- }
47273
- function unplacedFormHints(steps, forms) {
47274
- const onAStep = new Set(
47275
- steps.flatMap((step) => (step.triggers ?? []).flatMap((trigger) => trigger.eventKey ? [trigger.eventKey] : []))
47276
- );
47277
- const unplaced = [];
47278
- for (const form of forms) {
47279
- const endings = flowEventKeys(form.tree, form.slug);
47280
- if (endings.some((ending) => onAStep.has(ending.eventKey))) continue;
47281
- const outcome = suggestedEnding(endings);
47282
- if (outcome !== null) unplaced.push(`${form.slug} \u2192 \`${outcome.eventKey}\` ("${outcome.nodeName}")`);
47283
- }
47284
- if (unplaced.length === 0) return [];
47285
- const shown = unplaced.slice(0, MAX_NAMED_FORMS);
47286
- const rest = unplaced.length - shown.length;
47287
- return [
47288
- `${unplaced.length} Form(s) in this workspace reach no funnel step, so they move nobody along it however many people finish them: ${shown.join("; ")}${rest > 0 ? `; and ${rest} more` : ""}. Place one with \`baker funnel --add-trigger <step> --event <key>\`, which adds that one way in and leaves the rest of the step alone (\`baker funnel\` with no flags lists the step keys). If you also count a Form as a page conversion, count THAT SAME key, or the two numbers disagree for ever. A Form's other endings \u2014 a booking widget mid-flow, an embed's own signals \u2014 are listed by \`baker analytics conversions --flow <slug>\`.`
47289
- ];
47290
- }
47291
-
47292
47238
  // ../api/src/funnel/vocabulary.ts
47293
47239
  var FUNNEL_STEP_CLASSES = [
47294
47240
  /** Saw the offer: a landing opened, an ad clicked through. */
@@ -47581,18 +47527,6 @@ var funnelRequestSchema = z44.object({
47581
47527
  step: funnelStepSchema.optional(),
47582
47528
  /** One step taken out by key; whatever came after it moves up to whatever came before it. */
47583
47529
  removeStep: z44.string().min(1).max(64).optional(),
47584
- /**
47585
- * ONE way in added to a step, leaving the rest of that step alone.
47586
- *
47587
- * `step` takes a whole step, so adding a way in through it means restating
47588
- * the step's name, stage, `after[]` and every trigger it already had — and a
47589
- * trigger left out of that restatement is silently removed. This is the door
47590
- * for the thing that is actually being asked ("this Form's booking should
47591
- * count here"), and it cannot drop anything.
47592
- */
47593
- addTrigger: z44.object({ stepKey: z44.string().min(1).max(64), eventKey: z44.string().min(1).max(320) }).optional(),
47594
- /** ONE way in taken off a step, by the event key it listens for. */
47595
- removeTrigger: z44.object({ stepKey: z44.string().min(1).max(64), eventKey: z44.string().min(1).max(320) }).optional(),
47596
47530
  /** One of the ready-made graphs, instead of stating steps. */
47597
47531
  preset: z44.enum(FUNNEL_VERTICALS).optional(),
47598
47532
  /** Also return the suggestion library and the presets. */
@@ -47604,14 +47538,7 @@ var funnelRequestSchema = z44.object({
47604
47538
  */
47605
47539
  moments: z44.boolean().optional(),
47606
47540
  /** How many days the counts beside each step cover. Default 30. */
47607
- days: z44.number().int().min(1).max(400).optional(),
47608
- /**
47609
- * The Forms, by slug, the caller names itself when no step counts them —
47610
- * `baker funnel` reads its own workspace and names each with the key that
47611
- * places it. The backend leaves them out of its own hint, so no Form is
47612
- * named twice.
47613
- */
47614
- namedByCaller: z44.array(z44.string().min(1).max(200)).max(1e3).optional()
47541
+ days: z44.number().int().min(1).max(400).optional()
47615
47542
  });
47616
47543
  var funnelResponseSchema = z44.object({
47617
47544
  ok: z44.literal(true),
@@ -47677,30 +47604,7 @@ var funnelResponseSchema = z44.object({
47677
47604
  events: z44.number(),
47678
47605
  sessions: z44.number(),
47679
47606
  /** The name it is counted under as a page conversion, when the company counts it. Informational. */
47680
- countedAs: z44.string().optional(),
47681
- /**
47682
- * The step of the Form this happens on, in the Form's own words —
47683
- * never a node id. Present on a Form's ending read from the Form
47684
- * itself, which is the only place the name is known.
47685
- */
47686
- step: z44.string().optional(),
47687
- /**
47688
- * `true` on the ONE ending of a Form that is its outcome — the
47689
- * booking on a Form that books, the submit on a Form that submits.
47690
- * A Form raises several events and only one of them is the point;
47691
- * without this the agent is handed five equal-looking keys and the
47692
- * lifecycle noise a widget raises about itself sits among them.
47693
- * Same rule as `baker analytics conversions` recommends, so the
47694
- * conversion and the funnel step land on the same event.
47695
- */
47696
- outcome: z44.boolean().optional(),
47697
- /**
47698
- * `false` on an ending a Form raises natively but nobody has fired
47699
- * yet — derived from the Form, offered at zero, exactly as a
47700
- * Source is offered before its first send. Absent means it came
47701
- * from the event store and therefore happened.
47702
- */
47703
- seen: z44.boolean().optional()
47607
+ countedAs: z44.string().optional()
47704
47608
  })
47705
47609
  ),
47706
47610
  /** The company's sources: a system of theirs that posts events to Baker, by the event name it raises. */
@@ -47756,15 +47660,9 @@ function parseFunnelArgs(raw) {
47756
47660
  const step = parseStep(raw.step);
47757
47661
  const removeStep = parseRemoveStep(raw["remove-step"] ?? raw.removeStep);
47758
47662
  const preset = parsePreset(raw.preset);
47759
- const addTrigger = parseWayIn(raw["add-trigger"] ?? raw.addTrigger, raw.event, "--add-trigger");
47760
- const removeTrigger = parseWayIn(raw["remove-trigger"] ?? raw.removeTrigger, raw.event, "--remove-trigger");
47761
- const doors = [steps, step, removeStep, preset, addTrigger, removeTrigger].filter(
47762
- (door) => door !== void 0
47763
- ).length;
47663
+ const doors = [steps, step, removeStep, preset].filter((door) => door !== void 0).length;
47764
47664
  if (doors > 1) {
47765
- throw new FunnelArgsError(
47766
- "Give one of --steps, --step, --remove-step, --preset, --add-trigger or --remove-trigger, not two."
47767
- );
47665
+ throw new FunnelArgsError("Give one of --steps, --step, --remove-step or --preset, not two.");
47768
47666
  }
47769
47667
  const days = raw.days === void 0 ? void 0 : Number(raw.days);
47770
47668
  if (days !== void 0 && (!Number.isInteger(days) || days <= 0 || days > 400)) {
@@ -47775,8 +47673,6 @@ function parseFunnelArgs(raw) {
47775
47673
  ...step === void 0 ? {} : { step },
47776
47674
  ...removeStep === void 0 ? {} : { removeStep },
47777
47675
  ...preset === void 0 ? {} : { preset },
47778
- ...addTrigger === void 0 ? {} : { addTrigger },
47779
- ...removeTrigger === void 0 ? {} : { removeTrigger },
47780
47676
  templates: raw.templates === true,
47781
47677
  moments: raw.moments === true,
47782
47678
  ...days === void 0 ? {} : { days }
@@ -47788,8 +47684,6 @@ function funnelWrite(args4) {
47788
47684
  ...args4.step === void 0 ? {} : { step: args4.step },
47789
47685
  ...args4.removeStep === void 0 ? {} : { removeStep: args4.removeStep },
47790
47686
  ...args4.preset === void 0 ? {} : { preset: args4.preset },
47791
- ...args4.addTrigger === void 0 ? {} : { addTrigger: args4.addTrigger },
47792
- ...args4.removeTrigger === void 0 ? {} : { removeTrigger: args4.removeTrigger },
47793
47687
  ...args4.templates ? { templates: true } : {},
47794
47688
  ...args4.moments ? { moments: true } : {},
47795
47689
  ...args4.days === void 0 ? {} : { days: args4.days }
@@ -47899,26 +47793,6 @@ function parsePreset(raw) {
47899
47793
  }
47900
47794
  return raw;
47901
47795
  }
47902
- function parseWayIn(stepKey, eventKey, flag) {
47903
- if (stepKey === void 0) return void 0;
47904
- if (typeof stepKey !== "string" || stepKey.trim() === "") {
47905
- throw new FunnelArgsError(`${flag} takes the key of the step to change: \`${flag} lead\`.`);
47906
- }
47907
- if (typeof eventKey !== "string" || eventKey.trim() === "") {
47908
- throw new FunnelArgsError(`${flag} needs --event, naming what gets somebody there.`, {
47909
- action: "read_the_moments",
47910
- explanation: `Run \`baker funnel --moments\` and copy an \`events[].eventKey\` \u2014 a Form's own outcome is listed there at zero before anybody has filled it in. Then: \`${flag} ${stepKey.trim()} --event "<key>"\`.`
47911
- });
47912
- }
47913
- const key = eventKey.trim();
47914
- if (flag === "--add-trigger" && parseEventKey(key) === null) {
47915
- throw new FunnelArgsError(`"${key}" is not an event key, so nothing would ever reach that step through it.`, {
47916
- action: "read_the_moments",
47917
- explanation: "Run `baker funnel --moments` and copy an `events[].eventKey`. They look like form:<form>:<step>:<trigger>, submit:<form>, page:<name> or view:<landing>."
47918
- });
47919
- }
47920
- return { stepKey: stepKey.trim(), eventKey: key };
47921
- }
47922
47796
 
47923
47797
  // src/commands/funnel/index.ts
47924
47798
  var args3 = {
@@ -47932,21 +47806,6 @@ var args3 = {
47932
47806
  description: 'ONE step as a JSON object, in the same shape as an entry of --steps. It is added to the funnel as it now stands, or replaces the step with the same `key`, and every other step is left exactly as it is \u2014 the door for adding or changing a stage without restating the rest. Example: --step \'{"key":"demo","displayName":"Demo held","stepClass":"engaged","after":["lead"],"triggers":[{"id":"t1","kind":"event","eventKey":"page:demo_held"}]}\'',
47933
47807
  required: false
47934
47808
  },
47935
- "add-trigger": {
47936
- type: "string",
47937
- description: 'The key of ONE step to add a way in to, with `--event` naming what gets somebody there: `--add-trigger lead --event "submit:contact"`. Start here for "this Form should count as a lead" \u2014 it adds that one way in and leaves the step\'s name, stage, `after[]` and every other trigger exactly as they are. Prefer it to `--step`, which takes a WHOLE step and therefore silently removes any trigger the restatement forgets. Adding a key the step already listens for changes nothing. Take the key from `--moments`, never from memory',
47938
- required: false
47939
- },
47940
- "remove-trigger": {
47941
- type: "string",
47942
- description: 'The key of ONE step to take a way in OFF, with `--event` naming which: `--remove-trigger lead --event "submit:contact"`. By event key rather than by trigger id, because the id is not something you ever saw. Everything else about the step is untouched',
47943
- required: false
47944
- },
47945
- event: {
47946
- type: "string",
47947
- description: "The event key `--add-trigger` or `--remove-trigger` acts on \u2014 an `events[].eventKey` from `--moments`, never invented. A Form's own outcome is listed there at zero, marked `outcome: true`, before anybody has filled the Form in",
47948
- required: false
47949
- },
47950
47809
  "remove-step": {
47951
47810
  type: "string",
47952
47811
  description: "The key of one step to take off the funnel. Whatever came after it moves up to whatever came before it, so the chain stays joined. The response lists it under `removed`",
@@ -47959,7 +47818,7 @@ var args3 = {
47959
47818
  },
47960
47819
  moments: {
47961
47820
  type: "boolean",
47962
- description: "Also return everything a trigger can name, so a trigger is PICKED rather than invented: `events` (every event this company's Forms, pages and systems produced in the window, with volume, the Form it belongs to, `origin` \u2014 `site` for a page or Form, `systems` for the client's own server, `both` \u2014 and `countedAs` when the company also counts it as a page conversion, which a trigger does not need; with **each Form's OUTCOME in this workspace marked** `outcome: true` and the `step` it happens on \u2014 a Form nobody has filled in yet gets that row at zero, `seen: false`, so a Form built minutes ago is pickable with nothing set up first. It is the booking on a Form that books, the submit on a Form that submits, and it is the same ending `baker analytics conversions` recommends counting, so the step and the conversion land on the same event. One marked row and not all seven a Form raises: the rest are rarely what a step wants, and `baker analytics conversions --flow <slug>` lists every ending of one Form, fired or not), `sources` (the client's own systems that post events to Baker, by the `page:<name>` key each raises, pickable before its first send arrives) and `landings` (its pages, by the id a `landing` trigger takes). Use this before writing any `triggers`: a key typed from memory saves, publishes and counts nobody forever. A Form's events need NOTHING set up first \u2014 no Source, no conversion: a Baker page raises them by itself",
47821
+ description: "Also return everything a trigger can name, so a trigger is PICKED rather than invented: `events` (every event this company's Forms, pages and systems actually produced in the window, with volume, the Form it belongs to, `origin` \u2014 `site` for a page or Form, `systems` for the client's own server, `both` \u2014 and `countedAs` when the company also counts it as a page conversion, which a trigger does not need), `sources` (the client's own systems that post events to Baker, by the `page:<name>` key each raises, pickable before its first send arrives) and `landings` (its pages, by the id a `landing` trigger takes). Use this before writing any `triggers`: a key typed from memory saves, publishes and counts nobody forever",
47963
47822
  required: false
47964
47823
  },
47965
47824
  days: {
@@ -47981,25 +47840,17 @@ registerSchema({
47981
47840
  var funnelCommand2 = defineCommand129({
47982
47841
  meta: {
47983
47842
  name: "funnel",
47984
- description: 'Start here: run with no flags to see the funnel this company has, with the last 30 days of PEOPLE counted beside every step \u2014 `people` reached it, `atLeast` got at least that far, `before` got as far as the step before, `share` is the ratio, and `worstStep` names where the most people are lost. Every company has one from the day it exists: Visited (any landing) \u2192 Became a lead \u2192 Became a customer, the last two with no way in until somebody gives them one. Then `--moments` for everything a trigger can name (every event the company\'s Forms, pages and systems produced, its sources, its landings, plus each Form\'s own outcome at zero). **To make an event count on a step \u2014 the usual job \u2014 use `--add-trigger <step> --event <key>`:** it adds that one way in and touches nothing else about the step. `--step \'<json>\'` is for adding or renaming a STEP; it replaces the step with that key, so it means restating its `after` and every trigger it keeps, and one left out is gone. A step is reached by three kinds of thing and nothing else: an event by its own key (`{"id":"t1","kind":"event","eventKey":"<from moments.events or moments.sources>"}`), one landing being viewed (`{"id":"t2","kind":"landing","landingId":"<from moments.landings>"}`) or any landing (`{"id":"t3","kind":"any_landing"}`). Nothing has to be named first. Pick every trigger from `--moments`, never from memory. A step with no `triggers` advances NOBODY and reports zero forever without warning \u2014 that is what the hints keep pointing at. There is no default door: a Form is only counted where a trigger names it. Keep it true: a chat that adds a Form, a Source or an event that moves people forward places it on its step in the same chat. Each step comes back with `sends[]`, read-only: where the people who reach it are sent and what happened, read off the system events behind its ways in. A send is set on the event with `baker analytics sends`, never on a step, and once published it spends ad budget \u2014 a failed one is named in the hints. Everything here is STAGED: it becomes the company\'s funnel in one piece when the chat is published \u2014 the client sees it on the canvas in this chat and can edit it there too \u2014 and the previous version stays on file so what was already measured under it is not lost.'
47843
+ description: 'Start here: run with no flags to see the funnel this company has, with the last 30 days of PEOPLE counted beside every step \u2014 `people` reached it, `atLeast` got at least that far, `before` got as far as the step before, `share` is the ratio, and `worstStep` names where the most people are lost. Every company has one from the day it exists: Visited (any landing) \u2192 Became a lead \u2192 Became a customer, the last two with no way in until somebody gives them one. Then `--moments` for everything a trigger can name (every event the company\'s Forms, pages and systems actually produced, its sources, its landings), and `--step \'<json>\'` to add or change ONE step in the client\'s own words \u2014 it replaces the step with that key, so restate its `after` and the triggers it keeps. A step is reached by three kinds of thing and nothing else: an event by its own key (`{"id":"t1","kind":"event","eventKey":"<from moments.events or moments.sources>"}`), one landing being viewed (`{"id":"t2","kind":"landing","landingId":"<from moments.landings>"}`) or any landing (`{"id":"t3","kind":"any_landing"}`). Nothing has to be named first. Pick every trigger from `--moments`, never from memory. A step with no `triggers` advances NOBODY and reports zero forever without warning \u2014 that is what the hints keep pointing at. There is no default door: a Form is only counted where a trigger names it. Keep it true: a chat that adds a Form, a Source or an event that moves people forward places it on its step in the same chat. Each step comes back with `sends[]`, read-only: where the people who reach it are sent and what happened, read off the system events behind its ways in. A send is set on the event with `baker analytics sends`, never on a step, and once published it spends ad budget \u2014 a failed one is named in the hints. Everything here is STAGED: it becomes the company\'s funnel in one piece when the chat is published \u2014 the client sees it on the canvas in this chat and can edit it there too \u2014 and the previous version stays on file so what was already measured under it is not lost.'
47985
47844
  },
47986
47845
  args: args3,
47987
47846
  run: async ({ args: raw }) => {
47988
47847
  try {
47989
47848
  const parsed = parseFunnelArgs(raw);
47990
- const forms = workspaceForms();
47991
- const namedByCaller = formsNamedHere(forms);
47992
- const request = {
47993
- ...funnelWrite(parsed),
47994
- ...namedByCaller.length === 0 ? {} : { namedByCaller }
47995
- };
47996
- const response = await apiPost("/api/funnel", request);
47997
- const data = response.data.moments === void 0 ? response.data : { ...response.data, moments: withFormMoments(response.data.moments, forms) };
47998
- const hints2 = [...response.hints ?? [], ...unplacedFormHints(response.data.steps ?? [], forms)];
47849
+ const response = await apiPost("/api/funnel", funnelWrite(parsed));
47999
47850
  writeJsonEnvelope({
48000
47851
  ok: true,
48001
- data,
48002
- ...hints2.length > 0 ? { hints: hints2 } : {}
47852
+ data: response.data,
47853
+ ...response.hints && response.hints.length > 0 ? { hints: response.hints } : {}
48003
47854
  });
48004
47855
  } catch (err) {
48005
47856
  handleError4(err);
@@ -48024,16 +47875,6 @@ function handleError4(err) {
48024
47875
  });
48025
47876
  process.exit(1);
48026
47877
  }
48027
- function workspaceForms() {
48028
- const forms = [];
48029
- for (const slug of listFlowSlugs()) {
48030
- try {
48031
- forms.push({ slug, tree: JSON.parse(readFileSync15(flowDataPath(slug), "utf-8")) });
48032
- } catch {
48033
- }
48034
- }
48035
- return forms;
48036
- }
48037
47878
 
48038
47879
  // src/commands/ga4/index.ts
48039
47880
  import { defineCommand as defineCommand136 } from "citty";
@@ -48163,7 +48004,7 @@ Examples:
48163
48004
  import { defineCommand as defineCommand131 } from "citty";
48164
48005
 
48165
48006
  // src/commands/ga4/shared.ts
48166
- import { readFileSync as readFileSync16 } from "fs";
48007
+ import { readFileSync as readFileSync15 } from "fs";
48167
48008
  function failValidation3(message) {
48168
48009
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
48169
48010
  process.exit(1);
@@ -48179,7 +48020,7 @@ function requireTarget5(args4, entity) {
48179
48020
  function readJsonSource(args4, flag) {
48180
48021
  const inline = args4[flag];
48181
48022
  const file = args4.file;
48182
- const raw = typeof file === "string" && file.length > 0 ? readFileSync16(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
48023
+ const raw = typeof file === "string" && file.length > 0 ? readFileSync15(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
48183
48024
  if (raw === void 0) {
48184
48025
  failValidation3(`pass --${flag} with inline JSON or --file with a path to a JSON file`);
48185
48026
  }
@@ -48530,7 +48371,7 @@ Examples:
48530
48371
  });
48531
48372
 
48532
48373
  // src/commands/ga4/query.ts
48533
- import { appendFileSync as appendFileSync2, existsSync as existsSync7, readFileSync as readFileSync17, writeFileSync as writeFileSync4 } from "fs";
48374
+ import { appendFileSync as appendFileSync2, existsSync as existsSync7, readFileSync as readFileSync16, writeFileSync as writeFileSync4 } from "fs";
48534
48375
  import { resolve as resolve2 } from "path";
48535
48376
  import { defineCommand as defineCommand134 } from "citty";
48536
48377
 
@@ -48621,7 +48462,7 @@ function writeRowsToFile2(filePath, rows, append) {
48621
48462
  writeFileSync4(filePath, content, "utf-8");
48622
48463
  }
48623
48464
  } else if (append && existsSync7(filePath)) {
48624
- const existing = JSON.parse(readFileSync17(filePath, "utf-8"));
48465
+ const existing = JSON.parse(readFileSync16(filePath, "utf-8"));
48625
48466
  writeFileSync4(filePath, JSON.stringify([...existing, ...rows], null, 2), "utf-8");
48626
48467
  } else {
48627
48468
  writeFileSync4(filePath, JSON.stringify(rows, null, 2), "utf-8");
@@ -49473,7 +49314,7 @@ Full guide: __tooling__/docs/tools/baker/ga4.md`
49473
49314
  import { defineCommand as defineCommand140 } from "citty";
49474
49315
 
49475
49316
  // src/commands/gsc/query.ts
49476
- import { appendFileSync as appendFileSync3, existsSync as existsSync8, readFileSync as readFileSync18, writeFileSync as writeFileSync5 } from "fs";
49317
+ import { appendFileSync as appendFileSync3, existsSync as existsSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync5 } from "fs";
49477
49318
  import { resolve as resolve3 } from "path";
49478
49319
  import { defineCommand as defineCommand137 } from "citty";
49479
49320
 
@@ -49621,7 +49462,7 @@ function writeRowsToFile3(filePath, rows, append) {
49621
49462
  writeFileSync5(filePath, content, "utf-8");
49622
49463
  }
49623
49464
  } else if (append && existsSync8(filePath)) {
49624
- const existing = JSON.parse(readFileSync18(filePath, "utf-8"));
49465
+ const existing = JSON.parse(readFileSync17(filePath, "utf-8"));
49625
49466
  writeFileSync5(filePath, JSON.stringify([...existing, ...rows], null, 2), "utf-8");
49626
49467
  } else {
49627
49468
  writeFileSync5(filePath, JSON.stringify(rows, null, 2), "utf-8");
@@ -56499,7 +56340,7 @@ var inPageCollectUsedCss = (selector) => {
56499
56340
  }
56500
56341
  return false;
56501
56342
  };
56502
- const walk = (rules, sink) => {
56343
+ const walk2 = (rules, sink) => {
56503
56344
  for (const rule of Array.from(rules)) {
56504
56345
  totalRules++;
56505
56346
  if (rule instanceof CSSStyleRule) {
@@ -56520,7 +56361,7 @@ var inPageCollectUsedCss = (selector) => {
56520
56361
  const grouping = rule instanceof CSSMediaRule || rule instanceof CSSSupportsRule || typeof CSSLayerBlockRule !== "undefined" && rule instanceof CSSLayerBlockRule || typeof CSSContainerRule !== "undefined" && rule instanceof CSSContainerRule;
56521
56362
  if (grouping) {
56522
56363
  const inner = [];
56523
- walk(rule.cssRules, inner);
56364
+ walk2(rule.cssRules, inner);
56524
56365
  if (inner.length === 0) continue;
56525
56366
  const condition = rule.conditionText ?? "";
56526
56367
  const prelude = rule instanceof CSSMediaRule ? `@media ${condition}` : rule.cssText.split("{")[0]?.trim();
@@ -56534,7 +56375,7 @@ ${inner.join("\n")}
56534
56375
  const owner = sheet.ownerNode;
56535
56376
  if (owner?.hasAttribute?.("data-baker-freeze")) continue;
56536
56377
  try {
56537
- walk(sheet.cssRules, kept);
56378
+ walk2(sheet.cssRules, kept);
56538
56379
  } catch {
56539
56380
  if (sheet.href) unreadableHrefs.push(sheet.href);
56540
56381
  }
@@ -56985,7 +56826,7 @@ var inPageCollectMotion = (selector) => {
56985
56826
  else if (infinite) loop.push(effect);
56986
56827
  else entrance.push(effect);
56987
56828
  };
56988
- const walk = (rules, insideScrollTimeline) => {
56829
+ const walk2 = (rules, insideScrollTimeline) => {
56989
56830
  for (const rule of Array.from(rules)) {
56990
56831
  if (rule instanceof CSSKeyframesRule) {
56991
56832
  keyframesByName.set(rule.name, rule.cssText);
@@ -57003,17 +56844,17 @@ var inPageCollectMotion = (selector) => {
57003
56844
  respectsReducedMotion = true;
57004
56845
  continue;
57005
56846
  }
57006
- walk(rule.cssRules, insideScrollTimeline);
56847
+ walk2(rule.cssRules, insideScrollTimeline);
57007
56848
  continue;
57008
56849
  }
57009
56850
  const grouping = rule;
57010
- if (grouping.cssRules) walk(grouping.cssRules, insideScrollTimeline);
56851
+ if (grouping.cssRules) walk2(grouping.cssRules, insideScrollTimeline);
57011
56852
  }
57012
56853
  };
57013
56854
  for (const sheet of Array.from(document.styleSheets)) {
57014
56855
  if (sheet.ownerNode?.hasAttribute?.("data-baker-freeze")) continue;
57015
56856
  try {
57016
- walk(sheet.cssRules, false);
56857
+ walk2(sheet.cssRules, false);
57017
56858
  } catch {
57018
56859
  }
57019
56860
  }
@@ -62852,7 +62693,7 @@ import { defineCommand as defineCommand217 } from "citty";
62852
62693
  import { defineCommand as defineCommand214 } from "citty";
62853
62694
 
62854
62695
  // src/commands/tag-manager/shared.ts
62855
- import { readFileSync as readFileSync19 } from "fs";
62696
+ import { readFileSync as readFileSync18 } from "fs";
62856
62697
  function failValidation5(message) {
62857
62698
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
62858
62699
  process.exit(1);
@@ -62868,7 +62709,7 @@ function requireTarget6(args4, entity) {
62868
62709
  function loadJsonArg2(args4, flag = "json") {
62869
62710
  const inline = args4[flag];
62870
62711
  const file = args4.file;
62871
- const raw = typeof file === "string" && file.length > 0 ? readFileSync19(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
62712
+ const raw = typeof file === "string" && file.length > 0 ? readFileSync18(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
62872
62713
  if (raw === void 0) {
62873
62714
  failValidation5(`pass --${flag} with inline JSON or --file with a path to a JSON file`);
62874
62715
  }
@@ -66147,7 +65988,7 @@ function refuseUnknownFlags(root, argv) {
66147
65988
  }
66148
65989
 
66149
65990
  // src/version.ts
66150
- import { readFileSync as readFileSync20 } from "fs";
65991
+ import { readFileSync as readFileSync19 } from "fs";
66151
65992
  function packageJsonUrl() {
66152
65993
  return new URL("../package.json", import.meta.url);
66153
65994
  }
@@ -66159,7 +66000,7 @@ function parsePackageVersion(raw) {
66159
66000
  throw new Error("Invalid CLI package.json: missing version");
66160
66001
  }
66161
66002
  function getCliVersion() {
66162
- return parsePackageVersion(readFileSync20(packageJsonUrl(), "utf8"));
66003
+ return parsePackageVersion(readFileSync19(packageJsonUrl(), "utf8"));
66163
66004
  }
66164
66005
 
66165
66006
  // src/cli.ts