@koda-sl/baker-cli 0.269.0 → 0.270.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 +6 -0
- package/dist/cli.js +254 -8
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1232,6 +1232,10 @@ baker analytics tracking # do the ad URLs carry the campaign, a
|
|
|
1232
1232
|
baker analytics tracking --platform meta # the same for one platform, read from their own ad account
|
|
1233
1233
|
baker analytics map --platform google --set kw=keyword # name a parameter, seen or not yet
|
|
1234
1234
|
baker analytics map --platform google --remove kw # take that answer back
|
|
1235
|
+
baker analytics conversions # what counts as a conversion, and what each one produced
|
|
1236
|
+
baker analytics conversions --candidates # everything these pages do, and which of it is counted
|
|
1237
|
+
baker analytics conversions --event page:request_demo --name "Demo requested"
|
|
1238
|
+
baker analytics conversions --remove page:request_demo
|
|
1235
1239
|
baker analytics delivery --page 2 # the next page of a long list
|
|
1236
1240
|
baker analytics submissions --flow contact # every attempt at a Form, delivered or not
|
|
1237
1241
|
```
|
|
@@ -1246,6 +1250,8 @@ Shared flags: `--days <n>` (default 30) or `--start-date` / `--end-date` (`YYYY-
|
|
|
1246
1250
|
|
|
1247
1251
|
**Paging the list reports.** `people`, `submissions` and `delivery` are lists of individual things rather than breakdowns — `delivery` and `submissions` are the ones the CLI exposes, `people` backs the dashboard's People screen over the same wire — and only they take `--page <n>` (1-based) and `--page-size <n>` (up to 200, default 50). The response carries `pageInfo` with `hasMore` — when it is true there **are** more rows, so a total must never be reported from one page. Pass neither flag and a list comes back at the size it always did.
|
|
1248
1252
|
|
|
1253
|
+
**A conversion is a company-level named event, not a property of a Form.** `baker analytics conversions` is the only place "what counts" is decided, and an empty `definitions` list means every conversion number in every other report is zero — not because nobody converted, but because nothing is named as an outcome and Baker never guesses which event is the point of a page. `--candidates` lists every event these pages actually produced — each Form step and trigger, every `data-baker-*` event, every outbound destination — with volume and whether it is already counted; that list is where an event key comes from, so never invent one. `--event <key> --name "Booked a call"` starts counting it **immediately and retroactively**: the whole stored history is rescored, so naming the right event today also fixes last month. Nothing here is staged and publishing is not involved. Two events given the same `--name` become one row and one number, which is how a call booked on three different Forms reads as one outcome. `--count-mode every_time` is for the outcomes people genuinely repeat (a guide downloaded twice is two downloads); the default counts once per visit. One outcome per call — repeating `--event` is refused rather than silently keeping the last one.
|
|
1254
|
+
|
|
1249
1255
|
**Campaign parameters are per platform, and so are the numbers.** `baker analytics ads` returns `adPlatformTraffic` (visits, conversions and *tagged* visits for each of the nine platforms), `adParams` for the platform in `--platform` only, and `adParamsUnattributed` for names arriving on visits with no click id and no recognisable source. Read `adPlatformTraffic` first: a platform with visits and zero tagged visits has untagged ad URLs, and no mapping can fix that from Baker's side. `baker analytics map` needs `--platform` for any change, accepts a parameter name **nobody has sent yet** — which is how a tracking template is configured before its campaign runs — and takes an answer back with `--remove`. `--set <name>=ignore` says a parameter is not campaign information at all, so Baker stops storing it and stops listing it.
|
|
1250
1256
|
|
|
1251
1257
|
**Mapping is staged on the chat, not applied.** Answers appear under **Campaign parameters** in the dashboard and take effect when the chat is published; a discard takes them all back, and a report run in the same turn still reads the old vocabulary. Reading the mapping back inside the chat *does* include what it has staged, so a second turn never re-maps the same parameter.
|
package/dist/cli.js
CHANGED
|
@@ -12291,6 +12291,7 @@ var CAPTURED_WITHOUT_ROLE = [
|
|
|
12291
12291
|
].sort();
|
|
12292
12292
|
|
|
12293
12293
|
// ../api/src/analytics/conversions.ts
|
|
12294
|
+
var CONVERSION_COUNT_MODES = ["once_per_visit", "every_time"];
|
|
12294
12295
|
var PRIMARY_CONVERSION_TRIGGER = {
|
|
12295
12296
|
calendly: "calendlyEvent.eventScheduled",
|
|
12296
12297
|
customForm: "customForm.onFormSubmitted",
|
|
@@ -12306,6 +12307,33 @@ var NON_SUBMIT_NODE_TYPES = ["link", "text"];
|
|
|
12306
12307
|
var STEP_SUBMIT_TRIGGERS = Object.fromEntries(
|
|
12307
12308
|
Object.entries(PRIMARY_CONVERSION_TRIGGER).filter(([nodeType]) => !NON_SUBMIT_NODE_TYPES.includes(nodeType))
|
|
12308
12309
|
);
|
|
12310
|
+
var MAX_CONVERSION_NAME = 80;
|
|
12311
|
+
|
|
12312
|
+
// ../api/src/analytics/eventKey.ts
|
|
12313
|
+
var SEP = ":";
|
|
12314
|
+
function parseEventKey(key) {
|
|
12315
|
+
const cut = key.indexOf(SEP);
|
|
12316
|
+
if (cut <= 0) return null;
|
|
12317
|
+
const family = key.slice(0, cut);
|
|
12318
|
+
const rest = key.slice(cut + 1);
|
|
12319
|
+
if (rest === "") return null;
|
|
12320
|
+
switch (family) {
|
|
12321
|
+
case "form": {
|
|
12322
|
+
const [flowSlug, nodeId, ...triggerParts] = rest.split(SEP);
|
|
12323
|
+
const triggerId = triggerParts.join(SEP);
|
|
12324
|
+
if (!flowSlug || !nodeId || triggerId === "") return null;
|
|
12325
|
+
return { family: "form", flowSlug, nodeId, triggerId };
|
|
12326
|
+
}
|
|
12327
|
+
case "submit":
|
|
12328
|
+
return { family: "submit", flowSlug: rest };
|
|
12329
|
+
case "page":
|
|
12330
|
+
return { family: "page", eventName: rest };
|
|
12331
|
+
case "exit":
|
|
12332
|
+
return { family: "exit", host: rest };
|
|
12333
|
+
default:
|
|
12334
|
+
return null;
|
|
12335
|
+
}
|
|
12336
|
+
}
|
|
12309
12337
|
|
|
12310
12338
|
// ../api/src/analytics/events.ts
|
|
12311
12339
|
import { z as z24 } from "zod";
|
|
@@ -12817,7 +12845,18 @@ var analyticsPresetSchema = z25.enum([
|
|
|
12817
12845
|
* would do is add a choice the agent can never usefully pick to the menu it
|
|
12818
12846
|
* reads before picking one.
|
|
12819
12847
|
*/
|
|
12820
|
-
"realtime"
|
|
12848
|
+
"realtime",
|
|
12849
|
+
/**
|
|
12850
|
+
* What this company counts as a conversion, and everything it could count.
|
|
12851
|
+
*
|
|
12852
|
+
* The two halves of one question, which is why they are one preset: the list
|
|
12853
|
+
* of definitions with what each produced, and the catalogue of every event
|
|
12854
|
+
* these pages actually emitted with whether it is already counted. Reading
|
|
12855
|
+
* the first without the second is how the previous design failed — a
|
|
12856
|
+
* marketer could see that nothing was counted and had no way to find out what
|
|
12857
|
+
* was available to count.
|
|
12858
|
+
*/
|
|
12859
|
+
"conversions"
|
|
12821
12860
|
]);
|
|
12822
12861
|
var ANALYTICS_PRESETS = analyticsPresetSchema.options;
|
|
12823
12862
|
var analyticsQueryRequestSchema = z25.object({
|
|
@@ -13478,21 +13517,40 @@ var analyticsFlowEdgeSchema = z25.object({
|
|
|
13478
13517
|
sessions: z25.number().int().nonnegative()
|
|
13479
13518
|
});
|
|
13480
13519
|
var analyticsConversionRowSchema = z25.object({
|
|
13481
|
-
/** What a person calls this outcome.
|
|
13520
|
+
/** What a person calls this outcome. */
|
|
13482
13521
|
name: z25.string(),
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
13522
|
+
/** Every event counted under this name. One entry unless the name is shared. */
|
|
13523
|
+
eventKeys: z25.array(z25.string()),
|
|
13524
|
+
/**
|
|
13525
|
+
* `once_per_visit` | `every_time` — how repeats inside one visit are counted.
|
|
13526
|
+
*
|
|
13527
|
+
* `mixed` when a shared name spans both, which is a real state and an editing
|
|
13528
|
+
* accident; saying so is what makes it findable.
|
|
13529
|
+
*/
|
|
13488
13530
|
countMode: z25.string(),
|
|
13489
|
-
/** Counted the way
|
|
13531
|
+
/** Counted the way each definition asked to be counted. */
|
|
13490
13532
|
conversions: z25.number(),
|
|
13491
13533
|
/** Distinct visits that produced it, never mode-adjusted. The gap between the two is the repeat behaviour. */
|
|
13492
13534
|
convertingVisits: z25.number(),
|
|
13493
13535
|
/** Null when it has never fired. */
|
|
13494
13536
|
lastSeen: z25.string().nullable()
|
|
13495
13537
|
});
|
|
13538
|
+
var analyticsEventCatalogRowSchema = z25.object({
|
|
13539
|
+
/** `form:…` | `submit:…` | `page:…` | `exit:…` — parse with `parseEventKey`. */
|
|
13540
|
+
eventKey: z25.string(),
|
|
13541
|
+
events: z25.number(),
|
|
13542
|
+
sessions: z25.number(),
|
|
13543
|
+
/** The Form node's kind, for a `form:` key. Empty otherwise. */
|
|
13544
|
+
nodeType: z25.string(),
|
|
13545
|
+
/** The Form this happened in, for a `form:` or `submit:` key. Empty otherwise. */
|
|
13546
|
+
flowSlug: z25.string(),
|
|
13547
|
+
/** Null when it has never fired — only possible for a row we already count. */
|
|
13548
|
+
lastSeen: z25.string().nullable(),
|
|
13549
|
+
/** Already counted as a conversion. Shown, never hidden: a row that vanished on being counted reads as a bug. */
|
|
13550
|
+
isCounted: z25.boolean(),
|
|
13551
|
+
/** The name it is counted under, when it is. */
|
|
13552
|
+
countedAs: z25.string()
|
|
13553
|
+
});
|
|
13496
13554
|
var analyticsAdDimensionRowSchema = z25.object({
|
|
13497
13555
|
/** The dimension's value. Empty means "arrived carrying none of these". */
|
|
13498
13556
|
value: z25.string(),
|
|
@@ -13833,6 +13891,8 @@ var analyticsQueryDataSchema = z25.object({
|
|
|
13833
13891
|
/** Present only on a report that was asked for by the page. */
|
|
13834
13892
|
pageInfo: analyticsPageInfoSchema.optional(),
|
|
13835
13893
|
conversions: z25.array(analyticsConversionRowSchema).optional(),
|
|
13894
|
+
/** Every event these pages produced, counted or not. The "add a conversion" picker. */
|
|
13895
|
+
eventCatalog: z25.array(analyticsEventCatalogRowSchema).optional(),
|
|
13836
13896
|
timeline: z25.array(analyticsTimelineRowSchema).optional(),
|
|
13837
13897
|
visitorSessions: z25.array(analyticsVisitorSessionSchema).optional(),
|
|
13838
13898
|
visitProfile: analyticsVisitProfileSchema.optional(),
|
|
@@ -13855,6 +13915,31 @@ var analyticsQueryResponseSchema = z25.object({
|
|
|
13855
13915
|
warnings: z25.array(analyticsWarningSchema).optional(),
|
|
13856
13916
|
hints: z25.array(z25.string()).optional()
|
|
13857
13917
|
});
|
|
13918
|
+
var analyticsConversionsRequestSchema = z25.object({
|
|
13919
|
+
set: z25.array(
|
|
13920
|
+
z25.object({
|
|
13921
|
+
/** `form:<flow>:<step>:<trigger>` | `submit:<flow>` | `page:<name>` | `exit:<host>`. */
|
|
13922
|
+
eventKey: z25.string().max(320),
|
|
13923
|
+
name: z25.string().max(MAX_CONVERSION_NAME),
|
|
13924
|
+
countMode: z25.enum(CONVERSION_COUNT_MODES).optional()
|
|
13925
|
+
})
|
|
13926
|
+
).max(60).optional(),
|
|
13927
|
+
remove: z25.array(z25.string().max(320)).max(60).optional()
|
|
13928
|
+
});
|
|
13929
|
+
var analyticsConversionsResponseSchema = z25.object({
|
|
13930
|
+
ok: z25.literal(true),
|
|
13931
|
+
data: z25.object({
|
|
13932
|
+
definitions: z25.array(
|
|
13933
|
+
z25.object({
|
|
13934
|
+
eventKey: z25.string(),
|
|
13935
|
+
name: z25.string(),
|
|
13936
|
+
countMode: z25.string(),
|
|
13937
|
+
updatedAt: z25.number()
|
|
13938
|
+
})
|
|
13939
|
+
)
|
|
13940
|
+
}),
|
|
13941
|
+
hints: z25.array(z25.string()).optional()
|
|
13942
|
+
});
|
|
13858
13943
|
var analyticsMappingRequestSchema = z25.object({
|
|
13859
13944
|
/**
|
|
13860
13945
|
* Which platform these operations are about. Required for a change.
|
|
@@ -25004,6 +25089,88 @@ Full guides: __tooling__/docs/tools/baker/ads-<platform>.md (google|meta|linkedi
|
|
|
25004
25089
|
// src/commands/analytics/index.ts
|
|
25005
25090
|
import { defineCommand as defineCommand90 } from "citty";
|
|
25006
25091
|
|
|
25092
|
+
// src/commands/analytics/conversionArgs.ts
|
|
25093
|
+
var ConversionArgsError = class extends Error {
|
|
25094
|
+
};
|
|
25095
|
+
function onlyOnce(value, flag, argv) {
|
|
25096
|
+
if (value === void 0) return void 0;
|
|
25097
|
+
const given = argv.filter((arg) => arg === flag || arg.startsWith(`${flag}=`)).length;
|
|
25098
|
+
if (given > 1) {
|
|
25099
|
+
throw new ConversionArgsError(
|
|
25100
|
+
`${flag} was given ${given} times and only the last would have been used. Count one outcome per call.`
|
|
25101
|
+
);
|
|
25102
|
+
}
|
|
25103
|
+
const trimmed = String(value).trim();
|
|
25104
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
25105
|
+
}
|
|
25106
|
+
function parseConversionsArgs(raw, argv) {
|
|
25107
|
+
const eventKey = onlyOnce(raw.event, "--event", argv);
|
|
25108
|
+
const name = onlyOnce(raw.name, "--name", argv);
|
|
25109
|
+
const countMode = onlyOnce(raw["count-mode"], "--count-mode", argv);
|
|
25110
|
+
const remove = onlyOnce(raw.remove, "--remove", argv);
|
|
25111
|
+
if (eventKey !== void 0 && name === void 0) {
|
|
25112
|
+
throw new ConversionArgsError("--event needs --name: a conversion nobody named is a row labelled with a UUID.");
|
|
25113
|
+
}
|
|
25114
|
+
if (name !== void 0 && eventKey === void 0 && remove === void 0) {
|
|
25115
|
+
throw new ConversionArgsError("--name needs --event, naming which event to count.");
|
|
25116
|
+
}
|
|
25117
|
+
if (eventKey !== void 0 && parseEventKey(eventKey) === null) {
|
|
25118
|
+
throw new ConversionArgsError(
|
|
25119
|
+
`"${eventKey}" is not an event key. Run with --candidates and copy one; they look like form:<form>:<step>:<trigger>, submit:<form>, page:<name> or exit:<host>.`
|
|
25120
|
+
);
|
|
25121
|
+
}
|
|
25122
|
+
if (countMode !== void 0 && !CONVERSION_COUNT_MODES.includes(countMode)) {
|
|
25123
|
+
throw new ConversionArgsError(`--count-mode must be one of ${CONVERSION_COUNT_MODES.join(", ")}.`);
|
|
25124
|
+
}
|
|
25125
|
+
const days = raw.days === void 0 ? void 0 : Number(raw.days);
|
|
25126
|
+
if (days !== void 0 && (!Number.isFinite(days) || days <= 0)) {
|
|
25127
|
+
throw new ConversionArgsError("--days must be a positive number of days.");
|
|
25128
|
+
}
|
|
25129
|
+
return { eventKey, name, countMode, remove, candidates: raw.candidates === true, days };
|
|
25130
|
+
}
|
|
25131
|
+
function conversionsWrite(args) {
|
|
25132
|
+
const set = args.eventKey !== void 0 && args.name !== void 0 ? [{ eventKey: args.eventKey, name: args.name, ...args.countMode ? { countMode: args.countMode } : {} }] : void 0;
|
|
25133
|
+
const remove = args.remove !== void 0 ? [args.remove] : void 0;
|
|
25134
|
+
if (!set && !remove) return null;
|
|
25135
|
+
return { ...set ? { set } : {}, ...remove ? { remove } : {} };
|
|
25136
|
+
}
|
|
25137
|
+
|
|
25138
|
+
// src/commands/analytics/conversionHints.ts
|
|
25139
|
+
var NAMED_CANDIDATES = 3;
|
|
25140
|
+
function conversionHints(definitions, candidates, askedForCandidates) {
|
|
25141
|
+
const hints2 = [];
|
|
25142
|
+
if (definitions.length === 0) {
|
|
25143
|
+
hints2.push(
|
|
25144
|
+
"Nothing is counted as a conversion, so EVERY conversion number in every other report is zero \u2014 whatever visitors actually did. Baker never guesses which event is the point of a page. Run `baker analytics conversions --candidates` to see what these pages produce, then count the ones that are worth money."
|
|
25145
|
+
);
|
|
25146
|
+
if (!askedForCandidates && candidates.length > 0) {
|
|
25147
|
+
const top = candidates.slice(0, NAMED_CANDIDATES).map((row) => row.eventKey).join(", ");
|
|
25148
|
+
hints2.push(`${candidates.length} distinct events are available to count. Busiest: ${top}.`);
|
|
25149
|
+
}
|
|
25150
|
+
return hints2;
|
|
25151
|
+
}
|
|
25152
|
+
const silent = definitions.filter((row) => row.lastSeen === null);
|
|
25153
|
+
if (silent.length > 0) {
|
|
25154
|
+
hints2.push(
|
|
25155
|
+
`${silent.length} counted outcome(s) have never fired in this window: ${silent.slice(0, NAMED_CANDIDATES).map((row) => row.name).join(
|
|
25156
|
+
", "
|
|
25157
|
+
)}. Either the page never reaches them, or the wrong event was named \u2014 check against --candidates before reporting a conversion rate.`
|
|
25158
|
+
);
|
|
25159
|
+
}
|
|
25160
|
+
const uncounted = candidates.filter((row) => !row.isCounted);
|
|
25161
|
+
const busiest = uncounted[0];
|
|
25162
|
+
if (busiest && busiest.sessions > 0) {
|
|
25163
|
+
const counted = candidates.filter((row) => row.isCounted);
|
|
25164
|
+
const busiestCounted = counted.reduce((max, row) => Math.max(max, row.sessions), 0);
|
|
25165
|
+
if (busiest.sessions > busiestCounted) {
|
|
25166
|
+
hints2.push(
|
|
25167
|
+
`\`${busiest.eventKey}\` happened in ${busiest.sessions} visits and is not counted \u2014 more than anything that is. Check it is not the real outcome.`
|
|
25168
|
+
);
|
|
25169
|
+
}
|
|
25170
|
+
}
|
|
25171
|
+
return hints2;
|
|
25172
|
+
}
|
|
25173
|
+
|
|
25007
25174
|
// src/commands/analytics/hints.ts
|
|
25008
25175
|
var SEVERE_STEP_DROP = 0.6;
|
|
25009
25176
|
var MIN_SESSIONS_FOR_RATES = 100;
|
|
@@ -25183,6 +25350,11 @@ var ANALYTICS_PRESET_INFO = [
|
|
|
25183
25350
|
name: "releases",
|
|
25184
25351
|
description: "Traffic and conversions per published build, newest first. Answers whether a change to a page moved the numbers.",
|
|
25185
25352
|
playbook: "landing \u2014 compare a page before and after a change"
|
|
25353
|
+
},
|
|
25354
|
+
{
|
|
25355
|
+
name: "conversions",
|
|
25356
|
+
description: "What this company counts as a conversion, and what each one produced. The report every other conversion number is derived from, so read it FIRST whenever a conversion figure looks wrong or reads zero: an empty list means nothing is counted and every conversion number everywhere is zero, whatever visitors did. `baker analytics conversions --candidates` lists everything these pages actually produce \u2014 every Form step, every data-baker-* event, every outbound destination \u2014 and counting one applies immediately and rescores the whole history, with no publish. Anything can be counted, not just a Form.",
|
|
25357
|
+
playbook: "run `baker analytics conversions` \u2014 this one is fixed with the command, not with a skill"
|
|
25186
25358
|
}
|
|
25187
25359
|
];
|
|
25188
25360
|
|
|
@@ -25855,6 +26027,79 @@ Examples:
|
|
|
25855
26027
|
}
|
|
25856
26028
|
});
|
|
25857
26029
|
})();
|
|
26030
|
+
var conversionsCommand3 = (() => {
|
|
26031
|
+
const args = {
|
|
26032
|
+
event: {
|
|
26033
|
+
type: "string",
|
|
26034
|
+
description: "The event to start counting, from the `candidates` list: form:<form>:<step>:<trigger>, submit:<form>, page:<name>, or exit:<host>. Requires --name. Applies immediately and scores the WHOLE history, not just from now on \u2014 so counting the right event today also corrects last month's reports",
|
|
26035
|
+
required: false
|
|
26036
|
+
},
|
|
26037
|
+
name: {
|
|
26038
|
+
type: "string",
|
|
26039
|
+
description: "What to call this outcome on every report \u2014 'Booked a call', 'Quote requested'. Reuse the SAME name on two events and they become one row and one number, which is how a call booked on three different Forms reads as one outcome rather than three",
|
|
26040
|
+
required: false
|
|
26041
|
+
},
|
|
26042
|
+
"count-mode": {
|
|
26043
|
+
type: "string",
|
|
26044
|
+
description: "once_per_visit (default) or every_time. Ask whether doing it twice in one visit is one outcome or two: a booking clicked twice is one booking, a guide downloaded twice is two downloads. Getting this wrong inflates the rate every budget is set by",
|
|
26045
|
+
required: false
|
|
26046
|
+
},
|
|
26047
|
+
remove: {
|
|
26048
|
+
type: "string",
|
|
26049
|
+
description: "An event key to stop counting. Removes it from the history too, for the same reason adding it fills the history in",
|
|
26050
|
+
required: false
|
|
26051
|
+
},
|
|
26052
|
+
candidates: {
|
|
26053
|
+
type: "boolean",
|
|
26054
|
+
description: "Also list every event these pages actually produced, ranked by volume, with whether it is already counted. This is where an event key comes from \u2014 do not invent one. Off by default because a busy company produces hundreds of rows",
|
|
26055
|
+
required: false
|
|
26056
|
+
},
|
|
26057
|
+
days: {
|
|
26058
|
+
type: "string",
|
|
26059
|
+
description: "Lookback for the counts and for --candidates. Default 30",
|
|
26060
|
+
required: false
|
|
26061
|
+
}
|
|
26062
|
+
};
|
|
26063
|
+
registerSchema({
|
|
26064
|
+
command: "analytics.conversions",
|
|
26065
|
+
description: "Read or change what this company counts as a conversion",
|
|
26066
|
+
args
|
|
26067
|
+
});
|
|
26068
|
+
return defineCommand90({
|
|
26069
|
+
meta: {
|
|
26070
|
+
name: "conversions",
|
|
26071
|
+
description: "What this company counts as a conversion, and what each one produced. Run with no flags first: an empty `definitions` list means every conversion number in every other report is zero \u2014 not because nobody converted, but because nothing is marked as converting, and Baker never guesses. Add `--candidates` to see everything these pages actually do (every Form step, every data-baker-* event, every outbound destination) with counts and whether it is already counted; that list is where an event key comes from, so never invent one. Then `--event <key> --name 'Booked a call'` to count it. It applies IMMEDIATELY and retroactively \u2014 the whole history is rescored, so a company that has been running for a month gets a month of correct numbers the moment you name the right event. Nothing here is staged and publishing is not involved. Two events given the SAME --name are one row and one number. `--remove <key>` stops counting one."
|
|
26072
|
+
},
|
|
26073
|
+
args,
|
|
26074
|
+
run: async ({ args: raw }) => {
|
|
26075
|
+
try {
|
|
26076
|
+
const args2 = parseConversionsArgs(raw, process.argv);
|
|
26077
|
+
const write2 = conversionsWrite(args2);
|
|
26078
|
+
if (write2) {
|
|
26079
|
+
await apiPost("/api/analytics/conversions", write2);
|
|
26080
|
+
}
|
|
26081
|
+
const report = await apiPost("/api/analytics/query", {
|
|
26082
|
+
preset: "conversions",
|
|
26083
|
+
days: args2.days
|
|
26084
|
+
});
|
|
26085
|
+
const definitions = report.data.conversions ?? [];
|
|
26086
|
+
const candidates = report.data.eventCatalog ?? [];
|
|
26087
|
+
const hints2 = conversionHints(definitions, candidates, args2.candidates);
|
|
26088
|
+
writeJsonEnvelope({
|
|
26089
|
+
ok: true,
|
|
26090
|
+
data: { definitions, ...args2.candidates ? { candidates } : {} },
|
|
26091
|
+
...hints2.length > 0 ? { hints: hints2 } : {}
|
|
26092
|
+
});
|
|
26093
|
+
} catch (err) {
|
|
26094
|
+
if (err instanceof ConversionArgsError) {
|
|
26095
|
+
handleError(new ApiError("VALIDATION_ERROR", err.message));
|
|
26096
|
+
return;
|
|
26097
|
+
}
|
|
26098
|
+
handleError(err);
|
|
26099
|
+
}
|
|
26100
|
+
}
|
|
26101
|
+
});
|
|
26102
|
+
})();
|
|
25858
26103
|
function parsePairs(raw) {
|
|
25859
26104
|
if (raw === void 0) return void 0;
|
|
25860
26105
|
const out = {};
|
|
@@ -25929,6 +26174,7 @@ Full guide: __tooling__/docs/tools/baker/analytics.md`
|
|
|
25929
26174
|
events: eventsCommand,
|
|
25930
26175
|
devices: devicesCommand,
|
|
25931
26176
|
ads: adsCommand3,
|
|
26177
|
+
conversions: conversionsCommand3,
|
|
25932
26178
|
map: mapCommand,
|
|
25933
26179
|
tracking: trackingCommand,
|
|
25934
26180
|
presets: presetsCommand
|