@koda-sl/baker-cli 0.307.0-dev.5c40193fb → 0.307.0-dev.ce3b0e7c2
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 +102 -19
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4816,6 +4816,8 @@ var chatChangeTypeSchema = z13.enum([
|
|
|
4816
4816
|
"conversion-sends",
|
|
4817
4817
|
// The company's sources: a HubSpot event made or changed. Staged, applied at Publish.
|
|
4818
4818
|
"event-sources",
|
|
4819
|
+
// What the company counts as a conversion, when an agent changed it. Staged, applied at Publish.
|
|
4820
|
+
"conversions",
|
|
4819
4821
|
// An A/B test between a landing and its alternative. Staged, not immediate.
|
|
4820
4822
|
"experiment",
|
|
4821
4823
|
"briefs"
|
|
@@ -14950,7 +14952,9 @@ var analyticsConversionRowSchema = z30.object({
|
|
|
14950
14952
|
* The doors its events came in by in the window, in
|
|
14951
14953
|
* {@link ANALYTICS_EVENT_DOORS} order. Empty when it has not fired.
|
|
14952
14954
|
*/
|
|
14953
|
-
doors: z30.array(z30.enum(ANALYTICS_EVENT_DOORS)).default([])
|
|
14955
|
+
doors: z30.array(z30.enum(ANALYTICS_EVENT_DOORS)).default([]),
|
|
14956
|
+
/** Proposed in this chat: counted from when it is published, so every report reads it as nothing until then. */
|
|
14957
|
+
staged: z30.boolean().optional()
|
|
14954
14958
|
});
|
|
14955
14959
|
var analyticsEventCatalogRowSchema = z30.object({
|
|
14956
14960
|
/** `form:…` | `submit:…` | `page:…` | `exit:…` — parse with `parseEventKey`. */
|
|
@@ -15558,14 +15562,24 @@ var analyticsConversionsRequestSchema = z30.object({
|
|
|
15558
15562
|
var analyticsConversionsResponseSchema = z30.object({
|
|
15559
15563
|
ok: z30.literal(true),
|
|
15560
15564
|
data: z30.object({
|
|
15565
|
+
/**
|
|
15566
|
+
* What the company counts — inside a chat, with this chat's proposal on
|
|
15567
|
+
* top: `staged` marks a definition that applies when the chat is published.
|
|
15568
|
+
*/
|
|
15561
15569
|
definitions: z30.array(
|
|
15562
15570
|
z30.object({
|
|
15563
15571
|
eventKey: z30.string(),
|
|
15564
15572
|
name: z30.string(),
|
|
15565
15573
|
countMode: z30.string(),
|
|
15566
|
-
updatedAt: z30.number()
|
|
15574
|
+
updatedAt: z30.number(),
|
|
15575
|
+
staged: z30.boolean().optional()
|
|
15567
15576
|
})
|
|
15568
|
-
)
|
|
15577
|
+
),
|
|
15578
|
+
/** This chat's proposal, when it has one: counted from Publish, and gone if the chat is discarded. */
|
|
15579
|
+
staged: z30.object({
|
|
15580
|
+
set: z30.array(z30.object({ eventKey: z30.string(), name: z30.string() })),
|
|
15581
|
+
remove: z30.array(z30.string())
|
|
15582
|
+
}).optional()
|
|
15569
15583
|
}),
|
|
15570
15584
|
hints: z30.array(z30.string()).optional()
|
|
15571
15585
|
});
|
|
@@ -23163,6 +23177,20 @@ var lookalikeSpecSchema = z31.object({
|
|
|
23163
23177
|
ratio: z31.number().min(0.01).max(0.2).optional(),
|
|
23164
23178
|
country: z31.string().length(2).optional()
|
|
23165
23179
|
});
|
|
23180
|
+
function valuesOfKey(node, key) {
|
|
23181
|
+
if (Array.isArray(node)) return node.flatMap((entry) => valuesOfKey(entry, key));
|
|
23182
|
+
if (!node || typeof node !== "object") return [];
|
|
23183
|
+
const record = node;
|
|
23184
|
+
return [
|
|
23185
|
+
...key in record ? [record[key]] : [],
|
|
23186
|
+
...Object.values(record).flatMap((value) => valuesOfKey(value, key))
|
|
23187
|
+
];
|
|
23188
|
+
}
|
|
23189
|
+
function mixedEventSourceTypes(rule) {
|
|
23190
|
+
const types = valuesOfKey(rule, "event_sources").flatMap((sources) => Array.isArray(sources) ? sources : []).map((source) => source?.type).filter((type) => typeof type === "string");
|
|
23191
|
+
const distinct = [...new Set(types)].sort();
|
|
23192
|
+
return distinct.length > 1 ? distinct : [];
|
|
23193
|
+
}
|
|
23166
23194
|
var customAudienceCreateSchema = z31.object({
|
|
23167
23195
|
name: z31.string().min(1).max(META_LIMITS.audience.nameMax),
|
|
23168
23196
|
subtype: z31.enum(CUSTOM_AUDIENCE_SUBTYPES),
|
|
@@ -23179,6 +23207,21 @@ var customAudienceCreateSchema = z31.object({
|
|
|
23179
23207
|
if ((p.subtype === "WEBSITE" || p.subtype === "ENGAGEMENT") && !p.rule) {
|
|
23180
23208
|
ctx.addIssue({ code: "custom", path: ["rule"], message: `${p.subtype} audiences need a rule (use --file)` });
|
|
23181
23209
|
}
|
|
23210
|
+
if (p.rule && p.retention_days !== void 0) {
|
|
23211
|
+
ctx.addIssue({
|
|
23212
|
+
code: "custom",
|
|
23213
|
+
path: ["retention_days"],
|
|
23214
|
+
message: "a rule carries its own window in retention_seconds, so drop retention_days \u2014 Meta takes it only on a customer-file audience, and sending both leaves the review showing a window the audience will not have"
|
|
23215
|
+
});
|
|
23216
|
+
}
|
|
23217
|
+
const mixed = mixedEventSourceTypes(p.rule);
|
|
23218
|
+
if (mixed.length > 0) {
|
|
23219
|
+
ctx.addIssue({
|
|
23220
|
+
code: "custom",
|
|
23221
|
+
path: ["rule"],
|
|
23222
|
+
message: `one audience covers one kind of source, but this rule mixes ${mixed.join(" and ")} \u2014 split it into one audience per source`
|
|
23223
|
+
});
|
|
23224
|
+
}
|
|
23182
23225
|
});
|
|
23183
23226
|
var customAudienceUpdateSchema = z31.object({
|
|
23184
23227
|
name: z31.string().min(1).max(META_LIMITS.audience.nameMax).optional(),
|
|
@@ -23919,6 +23962,7 @@ var audiencesCreateCommand2 = defineCommand57({
|
|
|
23919
23962
|
name: "create",
|
|
23920
23963
|
description: `Stage a new custom audience shell. ${STAGED_NOTE2}
|
|
23921
23964
|
LOOKALIKE/WEBSITE/ENGAGEMENT specifics go via --file (lookalike_spec / rule). Member-list upload is not staged in v1.
|
|
23965
|
+
A WEBSITE/ENGAGEMENT rule carries its own window in retention_seconds, so --retention-days is refused next to one, and one audience covers one kind of event source (a Page and an Instagram profile are two audiences).
|
|
23922
23966
|
Example: baker ads meta audiences create --name "Site visitors 30d" --subtype WEBSITE --file rule.json`
|
|
23923
23967
|
},
|
|
23924
23968
|
args: {
|
|
@@ -23926,7 +23970,10 @@ Example: baker ads meta audiences create --name "Site visitors 30d" --subtype WE
|
|
|
23926
23970
|
name: { type: "string", description: "Audience name" },
|
|
23927
23971
|
subtype: { type: "string", description: "CUSTOM|WEBSITE|ENGAGEMENT|LOOKALIKE" },
|
|
23928
23972
|
description: { type: "string", description: "Description" },
|
|
23929
|
-
"retention-days": {
|
|
23973
|
+
"retention-days": {
|
|
23974
|
+
type: "string",
|
|
23975
|
+
description: "Retention window in days \u2014 customer-file audiences only; a rule carries its own retention_seconds"
|
|
23976
|
+
},
|
|
23930
23977
|
file: { type: "string", description: "JSON file with lookalike_spec / rule / full payload" }
|
|
23931
23978
|
},
|
|
23932
23979
|
run: async ({ args: args4 }) => {
|
|
@@ -29245,6 +29292,14 @@ function namingHints(definitions) {
|
|
|
29245
29292
|
`Names in use here: ${names.join(", ")}. Reuse one EXACTLY when another Form produces the same outcome \u2014 that is what makes them one number \u2014 and use \`--rename "<old>" --to "<existing>"\` to merge two that should always have been one.`
|
|
29246
29293
|
];
|
|
29247
29294
|
}
|
|
29295
|
+
function stagedHints(definitions) {
|
|
29296
|
+
const staged = definitions.filter((row) => row.staged === true);
|
|
29297
|
+
if (staged.length === 0) return [];
|
|
29298
|
+
return [
|
|
29299
|
+
`Staged on this chat, not counted yet: ${staged.map((row) => `"${row.name}"`).join(", ")}. The person sees ${staged.length === 1 ? "it" : "them"} under Conversions in the chat's changes, and ${staged.length === 1 ? "it counts" : "they count"} \u2014 history included \u2014 from when they publish the chat; discarding the chat drops ${staged.length === 1 ? "it" : "them"}. Tell them that, not that it is already counted.`,
|
|
29300
|
+
'When you talk to the person, call each event by its name ("Appointment scheduled"), never its key (`page:Appointment scheduled`): the key is how Baker spells it internally, and the `page:` part means nothing to them.'
|
|
29301
|
+
];
|
|
29302
|
+
}
|
|
29248
29303
|
|
|
29249
29304
|
// src/commands/analytics/conversionList.ts
|
|
29250
29305
|
function mergeStoredWithCounts(stored, reported) {
|
|
@@ -29270,7 +29325,8 @@ function mergeStoredWithCounts(stored, reported) {
|
|
|
29270
29325
|
conversions: counted?.conversions ?? 0,
|
|
29271
29326
|
convertingVisits: counted?.convertingVisits ?? 0,
|
|
29272
29327
|
lastSeen: counted?.lastSeen ?? null,
|
|
29273
|
-
doors: counted?.doors ?? []
|
|
29328
|
+
doors: counted?.doors ?? [],
|
|
29329
|
+
...rows.some((row) => row.staged === true) ? { staged: true } : {}
|
|
29274
29330
|
};
|
|
29275
29331
|
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
29276
29332
|
}
|
|
@@ -31397,6 +31453,7 @@ function conversionsEnvelope(args4, definitions, candidates) {
|
|
|
31397
31453
|
const hints2 = [
|
|
31398
31454
|
...flow ? flowConversionHints(flow.slug, flow.endings, flow.noConversionReason) : [],
|
|
31399
31455
|
...flowCoverageHints(coverage),
|
|
31456
|
+
...stagedHints(definitions),
|
|
31400
31457
|
...page.hints,
|
|
31401
31458
|
...namingHints(definitions)
|
|
31402
31459
|
];
|
|
@@ -31415,7 +31472,7 @@ var conversionsCommand3 = (() => {
|
|
|
31415
31472
|
const args4 = {
|
|
31416
31473
|
event: {
|
|
31417
31474
|
type: "string",
|
|
31418
|
-
description: "The event to start counting, from the `candidates` or `--flow` list: form:<form>:<step>:<trigger>, submit:<form>, page:<name>, or exit:<host>. Requires --name. REPEATABLE \u2014 pass it several times with one --name to count all of them as that single outcome, which is the whole point of the name.
|
|
31475
|
+
description: "The event to start counting, from the `candidates` or `--flow` list: form:<form>:<step>:<trigger>, submit:<form>, page:<name>, or exit:<host>. Requires --name. REPEATABLE \u2014 pass it several times with one --name to count all of them as that single outcome, which is the whole point of the name. Inside a chat it is STAGED: the chat lists it under Conversions and it counts from when the chat is published \u2014 then it scores the WHOLE history, not just from then on, so counting the right event also corrects last month's reports. An event a client's system sends (the API, HubSpot) counts here too: it is credited to the LAST LANDING its person viewed, up to 90 days before, so a deal won weeks later is that landing's conversion. A person who never saw a landing is not counted at all. Such an event is usually also a funnel step (`baker funnel`) and a send (`baker analytics sends`)",
|
|
31419
31476
|
required: false
|
|
31420
31477
|
},
|
|
31421
31478
|
name: {
|
|
@@ -31472,7 +31529,7 @@ var conversionsCommand3 = (() => {
|
|
|
31472
31529
|
return defineCommand97({
|
|
31473
31530
|
meta: {
|
|
31474
31531
|
name: "conversions",
|
|
31475
|
-
description: "What this company counts as a conversion
|
|
31532
|
+
description: "What this company counts as a conversion \u2014 the numerator of every conversion rate on Measure. A page event (a Form ending, a data-baker-* event) and an event one of the client's systems sends (a HubSpot event made with `baker analytics sources --create-hubspot`, an order the server posts) both count; a system's event is credited to the last landing its person viewed. A HubSpot event is `page:<its name>` here \u2014 `--event 'page:Deal won' --name 'Deal won'`. 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. Inside a chat a change is STAGED: the person sees it under Conversions in the chat's changes and it applies when they publish, retroactively \u2014 the whole history is rescored, so a company that has been running for a month gets a month of correct numbers. A read shows a proposal marked `staged`. Call an event by its name when you talk to the person, never by its key. Two events given the SAME --name are one row and one number. `--remove <key>` stops counting one. `--flow <slug>` answers the other direction \u2014 what one Form counts, with every ending it has already spelled as an event key, read from the Form itself so it works before a single visitor has seen it. Run it whenever you create or edit a Form: a Form nobody named an outcome for is published converting nobody, and no report says so."
|
|
31476
31533
|
},
|
|
31477
31534
|
args: args4,
|
|
31478
31535
|
run: async ({ args: raw }) => {
|
|
@@ -31503,12 +31560,8 @@ var conversionsCommand3 = (() => {
|
|
|
31503
31560
|
}
|
|
31504
31561
|
});
|
|
31505
31562
|
})();
|
|
31506
|
-
var SYSTEM_EVENTS_HINT = "Events your systems send are not listed: a system's event is a funnel step (`baker funnel`) and is sent to an ad platform with `baker analytics sends`, never counted as a page conversion.";
|
|
31507
31563
|
function pageCandidates(definitions, catalogue, listing) {
|
|
31508
|
-
|
|
31509
|
-
const hints2 = conversionHints(definitions, candidates, listing);
|
|
31510
|
-
if (listing && candidates.length < catalogue.length) hints2.push(SYSTEM_EVENTS_HINT);
|
|
31511
|
-
return { candidates, hints: hints2 };
|
|
31564
|
+
return { candidates: [...catalogue], hints: conversionHints(definitions, catalogue, listing) };
|
|
31512
31565
|
}
|
|
31513
31566
|
function parsePairs(raw) {
|
|
31514
31567
|
if (raw === void 0) return void 0;
|
|
@@ -45255,12 +45308,37 @@ function splitParts(raw) {
|
|
|
45255
45308
|
}
|
|
45256
45309
|
return parts.map((part) => part.trim()).filter((part) => part.length > 0);
|
|
45257
45310
|
}
|
|
45311
|
+
var TRANSFORM_REFUSAL = "A value cannot run code. Map the field as it is (firstname=full_name), or split it in the step with a codeExecution that exposes first_name and last_name and map code:first_name.";
|
|
45312
|
+
var METHOD_CALL = /\.\s*[A-Za-z_$][\w$]*\s*\(/;
|
|
45313
|
+
var TRAILING_INDEX = /\[[^\]]*\]$/;
|
|
45314
|
+
function mentionPart(inner) {
|
|
45315
|
+
if (inner.startsWith("tracking.")) return { kind: "tracking", name: inner.slice("tracking.".length) };
|
|
45316
|
+
if (inner.startsWith("global.")) return { kind: "global", name: inner.slice("global.".length) };
|
|
45317
|
+
return referencePart(inner);
|
|
45318
|
+
}
|
|
45319
|
+
function referencePart(ref) {
|
|
45320
|
+
return /\.codeExecution\.[^.]+$/.test(ref) ? { kind: "code", ref } : { kind: "field", ref };
|
|
45321
|
+
}
|
|
45258
45322
|
function parsePart(part) {
|
|
45259
45323
|
if (part.startsWith('"') && part.endsWith('"') && part.length >= 2) {
|
|
45260
45324
|
return { kind: "text", text: part.slice(1, -1) };
|
|
45261
45325
|
}
|
|
45326
|
+
if (METHOD_CALL.test(part) || TRAILING_INDEX.test(part)) {
|
|
45327
|
+
throw new MappingError(TRANSFORM_REFUSAL);
|
|
45328
|
+
}
|
|
45329
|
+
if (part.startsWith("{{")) {
|
|
45330
|
+
const close = part.indexOf("}}");
|
|
45331
|
+
if (close < 0) {
|
|
45332
|
+
throw new MappingError(
|
|
45333
|
+
`Unclosed \`{{\` in value \`${part}\`.`,
|
|
45334
|
+
"A mention is written {{<stepId>.customForm.<field>}}."
|
|
45335
|
+
);
|
|
45336
|
+
}
|
|
45337
|
+
if (part.slice(close + 2).trim()) throw new MappingError(TRANSFORM_REFUSAL);
|
|
45338
|
+
return mentionPart(part.slice(2, close).trim());
|
|
45339
|
+
}
|
|
45262
45340
|
const colon = part.indexOf(":");
|
|
45263
|
-
if (colon <= 0) return
|
|
45341
|
+
if (colon <= 0) return referencePart(part);
|
|
45264
45342
|
const prefix = part.slice(0, colon);
|
|
45265
45343
|
const rest = part.slice(colon + 1).trim();
|
|
45266
45344
|
if (!PREFIXES.includes(prefix)) {
|
|
@@ -45318,7 +45396,12 @@ function fieldOwners(tree) {
|
|
|
45318
45396
|
];
|
|
45319
45397
|
});
|
|
45320
45398
|
}
|
|
45321
|
-
function splitRef(ref) {
|
|
45399
|
+
function splitRef(ref, segment) {
|
|
45400
|
+
const marker = `.${segment}.`;
|
|
45401
|
+
const at = ref.lastIndexOf(marker);
|
|
45402
|
+
if (at > 0 && !ref.slice(at + marker.length).includes(".")) {
|
|
45403
|
+
return { step: ref.slice(0, at), name: ref.slice(at + marker.length) };
|
|
45404
|
+
}
|
|
45322
45405
|
const dot = ref.lastIndexOf(".");
|
|
45323
45406
|
if (dot <= 0) return { name: ref };
|
|
45324
45407
|
return { step: ref.slice(0, dot), name: ref.slice(dot + 1) };
|
|
@@ -45330,7 +45413,7 @@ function titleize(value) {
|
|
|
45330
45413
|
return value.split(/[_-]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
45331
45414
|
}
|
|
45332
45415
|
function resolveField(tree, ref) {
|
|
45333
|
-
const { step, name } = splitRef(ref);
|
|
45416
|
+
const { step, name } = splitRef(ref, "customForm");
|
|
45334
45417
|
const owners = fieldOwners(tree);
|
|
45335
45418
|
const inventory = owners.map((owner2) => ` ${owner2.name} (${owner2.id}): ${owner2.fields.join(", ") || "(no fields)"}`).join("\n");
|
|
45336
45419
|
const candidates = owners.filter(
|
|
@@ -45373,7 +45456,7 @@ function resolveGlobal(tree, name) {
|
|
|
45373
45456
|
return mention(id, id);
|
|
45374
45457
|
}
|
|
45375
45458
|
function resolveCode(tree, ref) {
|
|
45376
|
-
const { step, name } = splitRef(ref);
|
|
45459
|
+
const { step, name } = splitRef(ref, "codeExecution");
|
|
45377
45460
|
const owners = walkNodes(tree).flatMap((node) => {
|
|
45378
45461
|
const exposed = externalOf(node).codeExecution?.exposedValues ?? [];
|
|
45379
45462
|
const names = exposed.map((value) => value?.name).filter((value) => typeof value === "string");
|
|
@@ -45917,7 +46000,7 @@ function describeDestination(chosen) {
|
|
|
45917
46000
|
// two different questions with two different answers, and the type is the
|
|
45918
46001
|
// one an agent does not know it is being asked until a mapping is refused.
|
|
45919
46002
|
...slots.flatMap((slot) => slot.fieldTypes ? [`${slot.slot} field types \u2014 ${slot.fieldTypes}`] : []),
|
|
45920
|
-
'Values (right of the `=`) are the same for every slot: a form field, tracking:<var>, global:<name>, code:<name>, a "literal", or those joined with +.'
|
|
46003
|
+
'Values (right of the `=`) are the same for every slot: a form field, tracking:<var>, global:<name>, code:<name>, a "literal", or those joined with +. A field may also be written as the file stores it, {{<stepId>.customForm.<field>}}. A value cannot run code (no .split(), .slice(), [0]).'
|
|
45921
46004
|
]
|
|
45922
46005
|
});
|
|
45923
46006
|
}
|
|
@@ -59508,7 +59591,7 @@ registerSchema({
|
|
|
59508
59591
|
},
|
|
59509
59592
|
sort: {
|
|
59510
59593
|
type: "string",
|
|
59511
|
-
description: "Sort:
|
|
59594
|
+
description: "Sort: search_volume, competition, cpc (default: search_volume)",
|
|
59512
59595
|
required: false
|
|
59513
59596
|
},
|
|
59514
59597
|
type: { type: "string", description: "Filter: paid, organic, all (default: all)", required: false },
|
|
@@ -59537,7 +59620,7 @@ Examples:
|
|
|
59537
59620
|
target: { type: "positional", description: "Domain or URL to analyze", required: true },
|
|
59538
59621
|
location: { type: "string", description: "Country code or location code (default: us)", required: false },
|
|
59539
59622
|
language: { type: "string", description: "Language code or name (default: en)", required: false },
|
|
59540
|
-
sort: { type: "string", description: "Sort:
|
|
59623
|
+
sort: { type: "string", description: "Sort: search_volume, competition, cpc", required: false },
|
|
59541
59624
|
type: { type: "string", description: "Filter: paid, organic, all (default: all)", required: false },
|
|
59542
59625
|
limit: { type: "string", description: "Max results (default: 50, max: 1000)", required: false },
|
|
59543
59626
|
output: { type: "string", description: "Format: json|csv|md|jsonl", required: false, default: "json" },
|