@koda-sl/baker-cli 0.426.0-dev.5af7a82af → 0.427.0-dev.ad97d3ceb
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 +123 -33
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -80430,9 +80430,43 @@ function normalize(value2) {
|
|
|
80430
80430
|
function sentencesOf(message) {
|
|
80431
80431
|
return message.split(/[.!?]+(?:\s|$)/).filter((s) => s.trim().length > 0).length;
|
|
80432
80432
|
}
|
|
80433
|
+
var FILLER = /* @__PURE__ */ new Set([
|
|
80434
|
+
"that",
|
|
80435
|
+
"this",
|
|
80436
|
+
"with",
|
|
80437
|
+
"your",
|
|
80438
|
+
"from",
|
|
80439
|
+
"they",
|
|
80440
|
+
"them",
|
|
80441
|
+
"what",
|
|
80442
|
+
"when",
|
|
80443
|
+
"have",
|
|
80444
|
+
"will",
|
|
80445
|
+
"into",
|
|
80446
|
+
"than",
|
|
80447
|
+
"more",
|
|
80448
|
+
"just",
|
|
80449
|
+
"para",
|
|
80450
|
+
"como",
|
|
80451
|
+
"pero",
|
|
80452
|
+
"sobre"
|
|
80453
|
+
]);
|
|
80454
|
+
var SAME_MESSAGE = 0.6;
|
|
80455
|
+
function messageWords(message) {
|
|
80456
|
+
return new Set(
|
|
80457
|
+
message.toLowerCase().split(/[^\p{L}]+/u).filter((w) => w.length >= 4 && !FILLER.has(w)).map((w) => w.replace(/s$/, ""))
|
|
80458
|
+
);
|
|
80459
|
+
}
|
|
80460
|
+
function sameMessage(a, b) {
|
|
80461
|
+
const wa = messageWords(a);
|
|
80462
|
+
const wb = messageWords(b);
|
|
80463
|
+
const shared = [...wa].filter((w) => wb.has(w)).length;
|
|
80464
|
+
const all = (/* @__PURE__ */ new Set([...wa, ...wb])).size;
|
|
80465
|
+
return normalize(a) === normalize(b) || all > 0 && shared / all >= SAME_MESSAGE;
|
|
80466
|
+
}
|
|
80433
80467
|
function repeatedFailure(idea, failed) {
|
|
80434
80468
|
const lost = failed.find(
|
|
80435
|
-
(f) => f.id !== idea.id && (
|
|
80469
|
+
(f) => f.id !== idea.id && (sameMessage(f.message, idea.message) || f.angle === idea.angle && f.format === idea.format)
|
|
80436
80470
|
);
|
|
80437
80471
|
return lost ? [`says what "${lost.id}" said, and that idea failed in the account: change the message, not just the picture`] : [];
|
|
80438
80472
|
}
|
|
@@ -80511,6 +80545,7 @@ function checkOne(idea, ctx) {
|
|
|
80511
80545
|
return {
|
|
80512
80546
|
id: idea.id,
|
|
80513
80547
|
status: idea.status,
|
|
80548
|
+
message: idea.message,
|
|
80514
80549
|
evidence,
|
|
80515
80550
|
...best ? { quote: best.quote } : {},
|
|
80516
80551
|
awareness: idea.awareness,
|
|
@@ -81142,6 +81177,15 @@ function briefHints(parts) {
|
|
|
81142
81177
|
`Competitors already say ${shared.join(", ")}: an idea that says the same needs its own proof or mechanism.`
|
|
81143
81178
|
);
|
|
81144
81179
|
}
|
|
81180
|
+
for (const won of parts.learned.winners) {
|
|
81181
|
+
hints2.push(
|
|
81182
|
+
`${won.idea} won in the account ("${won.message}"): build on it first, the same message in a new format or for a new persona.`
|
|
81183
|
+
);
|
|
81184
|
+
}
|
|
81185
|
+
if (parts.learned.failed.length > 0) {
|
|
81186
|
+
const lost = parts.learned.failed.map((f) => `"${f.message}"`).join(", ");
|
|
81187
|
+
hints2.push(`Never repeat a message that lost in the account: ${lost}.`);
|
|
81188
|
+
}
|
|
81145
81189
|
if (parts.rejected > parts.approved && parts.rejected > 0) {
|
|
81146
81190
|
hints2.push("Reviewers rejected more than they approved: read topRejectionReasons and avoid them in every idea.");
|
|
81147
81191
|
}
|
|
@@ -81195,6 +81239,8 @@ function briefEnvelope(brief2, full) {
|
|
|
81195
81239
|
const summary = isAvailable(brief2.feedback) ? brief2.feedback.data.summary : null;
|
|
81196
81240
|
const next = buildNext(brief2.ideas.ideas);
|
|
81197
81241
|
const toBuild = brief2.ideas.ideas.filter((i) => i.status === "idea").length;
|
|
81242
|
+
const withStatus = (status) => brief2.ideas.ideas.filter((i) => i.status === status).map((i) => ({ idea: i.id, message: i.message }));
|
|
81243
|
+
const learned = { winners: withStatus("winner"), failed: withStatus("failed") };
|
|
81198
81244
|
return {
|
|
81199
81245
|
ok: true,
|
|
81200
81246
|
data: {
|
|
@@ -81209,7 +81255,13 @@ function briefEnvelope(brief2, full) {
|
|
|
81209
81255
|
} : brief2.feedback,
|
|
81210
81256
|
customers: full ? { ...customers, quotes: brief2.insights.map((i) => ({ id: i.id, type: i.type, quote: i.quote })) } : customers,
|
|
81211
81257
|
market: marketPart(brief2.market, full),
|
|
81212
|
-
ideas: {
|
|
81258
|
+
ideas: {
|
|
81259
|
+
to_build: toBuild,
|
|
81260
|
+
built: brief2.ideas.ideas.length - toBuild,
|
|
81261
|
+
build_next: next,
|
|
81262
|
+
...learned.winners.length > 0 ? { winners: learned.winners } : {},
|
|
81263
|
+
...learned.failed.length > 0 ? { failed: learned.failed } : {}
|
|
81264
|
+
}
|
|
81213
81265
|
},
|
|
81214
81266
|
hints: briefHints({
|
|
81215
81267
|
company: brief2.company,
|
|
@@ -81219,7 +81271,8 @@ function briefEnvelope(brief2, full) {
|
|
|
81219
81271
|
buildNext: next,
|
|
81220
81272
|
approved: summary?.approved ?? 0,
|
|
81221
81273
|
rejected: summary?.rejected ?? 0,
|
|
81222
|
-
market: isAvailable(brief2.market) ? brief2.market : null
|
|
81274
|
+
market: isAvailable(brief2.market) ? brief2.market : null,
|
|
81275
|
+
learned
|
|
81223
81276
|
})
|
|
81224
81277
|
};
|
|
81225
81278
|
}
|
|
@@ -81387,6 +81440,7 @@ var WINNER_RATIO = 0.8;
|
|
|
81387
81440
|
var WINNER_MIN_RESULTS = 3;
|
|
81388
81441
|
var FAILED_RATIO = 1.5;
|
|
81389
81442
|
var SILENT_SPEND = 2;
|
|
81443
|
+
var SETTLED = /* @__PURE__ */ new Set(["winner", "failed", "inconclusive"]);
|
|
81390
81444
|
var round4 = (n) => Math.round(n * 100) / 100;
|
|
81391
81445
|
function totals(creatives) {
|
|
81392
81446
|
const spend = creatives.reduce((sum, c) => sum + c.spend, 0);
|
|
@@ -81405,33 +81459,38 @@ function verdict(result, usual) {
|
|
|
81405
81459
|
}
|
|
81406
81460
|
return result.impressions >= SEEN_ENOUGH ? "inconclusive" : "testing";
|
|
81407
81461
|
}
|
|
81462
|
+
function ideaResult(own, usual, asOf) {
|
|
81463
|
+
const mine = totals(own);
|
|
81464
|
+
const costPerResult = mine.results > 0 ? round4(mine.spend / mine.results) : null;
|
|
81465
|
+
return {
|
|
81466
|
+
asOf,
|
|
81467
|
+
creatives: own.length,
|
|
81468
|
+
...own[0]?.currency ? { currency: own[0].currency } : {},
|
|
81469
|
+
spend: round4(mine.spend),
|
|
81470
|
+
impressions: mine.impressions,
|
|
81471
|
+
results: mine.results,
|
|
81472
|
+
costPerResult,
|
|
81473
|
+
ctr: mine.ctr,
|
|
81474
|
+
vsAccount: usual !== null && costPerResult !== null ? round4(costPerResult / usual) : null
|
|
81475
|
+
};
|
|
81476
|
+
}
|
|
81408
81477
|
function learnFromResults(ideas, creatives, asOf) {
|
|
81409
81478
|
const ideaOf = (c) => c.definition?.idea ?? null;
|
|
81410
81479
|
return ideas.flatMap((idea) => {
|
|
81411
81480
|
if (idea.status === "idea" || idea.status === "retired") return [];
|
|
81412
81481
|
const own = creatives.filter((c) => ideaOf(c) === idea.id);
|
|
81413
81482
|
if (own.length === 0) return [];
|
|
81414
|
-
const mine = totals(own);
|
|
81415
81483
|
const rest = totals(creatives.filter((c) => ideaOf(c) !== idea.id));
|
|
81416
81484
|
const usual = rest.results > 0 ? rest.spend / rest.results : null;
|
|
81417
|
-
const
|
|
81418
|
-
const
|
|
81419
|
-
|
|
81420
|
-
|
|
81421
|
-
...own[0]?.currency ? { currency: own[0].currency } : {},
|
|
81422
|
-
spend: round4(mine.spend),
|
|
81423
|
-
impressions: mine.impressions,
|
|
81424
|
-
results: mine.results,
|
|
81425
|
-
costPerResult,
|
|
81426
|
-
ctr: mine.ctr,
|
|
81427
|
-
vsAccount: usual !== null && costPerResult !== null ? round4(costPerResult / usual) : null
|
|
81428
|
-
};
|
|
81429
|
-
return [{ idea: idea.id, from: idea.status, to: verdict(result, usual), result }];
|
|
81485
|
+
const result = ideaResult(own, usual, asOf);
|
|
81486
|
+
const to = verdict(result, usual);
|
|
81487
|
+
if (to === "testing" && SETTLED.has(idea.status)) return [];
|
|
81488
|
+
return [{ idea: idea.id, from: idea.status, to, result }];
|
|
81430
81489
|
});
|
|
81431
81490
|
}
|
|
81432
81491
|
|
|
81433
81492
|
// src/commands/creatives/make-model.ts
|
|
81434
|
-
import { parseDocument as parseDocument3 } from "yaml";
|
|
81493
|
+
import { parse as parse2, parseDocument as parseDocument3, Scalar, visit } from "yaml";
|
|
81435
81494
|
var REFERENCE_CANDIDATES = 6;
|
|
81436
81495
|
function isVideoIdea(idea) {
|
|
81437
81496
|
return IDEA_VIDEO_FORMATS.includes(idea.format);
|
|
@@ -81478,13 +81537,29 @@ function definitionFieldsOf(idea, evidence) {
|
|
|
81478
81537
|
};
|
|
81479
81538
|
}
|
|
81480
81539
|
var FRONTMATTER3 = /^---\r?\n([\s\S]*?\r?\n)?---/;
|
|
81540
|
+
function readsAsText11(text3) {
|
|
81541
|
+
try {
|
|
81542
|
+
return parse2(text3, { version: "1.1" }) === text3;
|
|
81543
|
+
} catch {
|
|
81544
|
+
return false;
|
|
81545
|
+
}
|
|
81546
|
+
}
|
|
81481
81547
|
function setDefinitionFields(raw, fields) {
|
|
81482
81548
|
const match = raw.match(FRONTMATTER3);
|
|
81483
81549
|
if (!match) return raw;
|
|
81484
81550
|
const doc = parseDocument3(match[1] ?? "");
|
|
81485
81551
|
if (doc.errors.length > 0) return raw;
|
|
81486
81552
|
for (const [key, value2] of Object.entries(fields)) {
|
|
81487
|
-
if (value2
|
|
81553
|
+
if (value2 === void 0) continue;
|
|
81554
|
+
const node = doc.createNode(value2);
|
|
81555
|
+
visit(node, {
|
|
81556
|
+
Scalar(_, scalar) {
|
|
81557
|
+
if (typeof scalar.value === "string" && !readsAsText11(scalar.value)) {
|
|
81558
|
+
scalar.type = Scalar.QUOTE_DOUBLE;
|
|
81559
|
+
}
|
|
81560
|
+
}
|
|
81561
|
+
});
|
|
81562
|
+
doc.set(key, node);
|
|
81488
81563
|
}
|
|
81489
81564
|
return `---
|
|
81490
81565
|
${doc.toString()}---${raw.slice(match[0].length)}`;
|
|
@@ -81538,10 +81613,15 @@ var ARGS11 = {
|
|
|
81538
81613
|
"dry-run": { type: "boolean", required: false, default: false, description: "Show the verdicts; write nothing" }
|
|
81539
81614
|
};
|
|
81540
81615
|
registerSchema({ command: "creatives.learn", description: DESCRIPTION9, args: ARGS11 });
|
|
81541
|
-
function learnEnvelope(learnings, written) {
|
|
81616
|
+
function learnEnvelope(learnings, written, unreadable = []) {
|
|
81542
81617
|
const winners = learnings.filter((l) => l.to === "winner").map((l) => l.idea);
|
|
81543
81618
|
const failed = learnings.filter((l) => l.to === "failed").map((l) => l.idea);
|
|
81544
81619
|
const hints2 = [];
|
|
81620
|
+
if (unreadable.length > 0) {
|
|
81621
|
+
hints2.push(
|
|
81622
|
+
`Could not read ${unreadable.join(", ")}, so their results were not written: fix what \`baker creatives ideas\` names, then run this again.`
|
|
81623
|
+
);
|
|
81624
|
+
}
|
|
81545
81625
|
if (learnings.length === 0) {
|
|
81546
81626
|
hints2.push(
|
|
81547
81627
|
"No built idea has ads delivering yet: nothing to learn. Ideas count once a published ad shows their creative."
|
|
@@ -81591,17 +81671,27 @@ var learnCommand = defineCommand158({
|
|
|
81591
81671
|
meta: { name: "learn", description: DESCRIPTION9 },
|
|
81592
81672
|
args: ARGS11,
|
|
81593
81673
|
async run({ args: args8 }) {
|
|
81594
|
-
|
|
81595
|
-
|
|
81596
|
-
|
|
81597
|
-
|
|
81598
|
-
|
|
81599
|
-
|
|
81600
|
-
|
|
81601
|
-
|
|
81602
|
-
|
|
81603
|
-
|
|
81604
|
-
|
|
81674
|
+
try {
|
|
81675
|
+
const root = findWorkspaceRoot(process.cwd()) ?? process.cwd();
|
|
81676
|
+
const days = parseDays(args8.days);
|
|
81677
|
+
const performance = withDefinitions(
|
|
81678
|
+
await apiPost("/api/creatives/performance", days ? { days } : {}),
|
|
81679
|
+
(slug) => readDefinition2(root, slug)
|
|
81680
|
+
);
|
|
81681
|
+
const { ideas, unreadable } = await readIdeas(root);
|
|
81682
|
+
const learnings = learnFromResults(ideas, performance.data.creatives, today());
|
|
81683
|
+
const dryRun = args8["dry-run"] === true;
|
|
81684
|
+
if (!dryRun) await writeBack(root, learnings);
|
|
81685
|
+
writeJson(
|
|
81686
|
+
learnEnvelope(
|
|
81687
|
+
learnings,
|
|
81688
|
+
!dryRun && learnings.length > 0,
|
|
81689
|
+
unreadable.map((u) => u.id)
|
|
81690
|
+
)
|
|
81691
|
+
);
|
|
81692
|
+
} catch (err) {
|
|
81693
|
+
failApi(err);
|
|
81694
|
+
}
|
|
81605
81695
|
}
|
|
81606
81696
|
});
|
|
81607
81697
|
|
|
@@ -83192,7 +83282,7 @@ function suffixLookup(hostname, options, out) {
|
|
|
83192
83282
|
|
|
83193
83283
|
// ../../.pnpm-store/v10/links/@/tldts/7.4.15/b098b1960b5a1104e1fa4520104b6822dcb63a4c9724102fa5412b7e81c108e1/node_modules/tldts/dist/es6/index.js
|
|
83194
83284
|
var RESULT = getEmptyResult();
|
|
83195
|
-
function
|
|
83285
|
+
function parse3(url, options) {
|
|
83196
83286
|
return parseImpl(url, 5, suffixLookup, options, getEmptyResult());
|
|
83197
83287
|
}
|
|
83198
83288
|
function getPublicSuffix(url, options) {
|
|
@@ -83217,7 +83307,7 @@ function validateHostPattern(raw) {
|
|
|
83217
83307
|
if (host === "" || host.includes("*")) {
|
|
83218
83308
|
return { ok: false, reason: "Use a website address like api.example.com or *.example.com." };
|
|
83219
83309
|
}
|
|
83220
|
-
const parsed =
|
|
83310
|
+
const parsed = parse3(host);
|
|
83221
83311
|
if (parsed.isIp || host.startsWith("[")) {
|
|
83222
83312
|
return { ok: false, reason: "Use a website address, not an IP address." };
|
|
83223
83313
|
}
|