@koda-sl/baker-cli 0.430.0-dev.8c14009f3 → 0.434.0-dev.42b55fa66
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 +3 -3
- package/dist/{chunk-GTHLEQHU.js → chunk-MMTPMFFH.js} +6 -6
- package/dist/chunk-MMTPMFFH.js.map +1 -0
- package/dist/cli.js +716 -689
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-GTHLEQHU.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -198,7 +198,7 @@ import {
|
|
|
198
198
|
withoutCastName,
|
|
199
199
|
wordCount,
|
|
200
200
|
ytDlpBlockSignal
|
|
201
|
-
} from "./chunk-
|
|
201
|
+
} from "./chunk-MMTPMFFH.js";
|
|
202
202
|
import {
|
|
203
203
|
NAME_CONTAINS_ARG,
|
|
204
204
|
csvOrJson,
|
|
@@ -65294,7 +65294,7 @@ var catalogCommand = defineCommand134({
|
|
|
65294
65294
|
});
|
|
65295
65295
|
|
|
65296
65296
|
// src/commands/canvas/critique.ts
|
|
65297
|
-
import
|
|
65297
|
+
import path15 from "path";
|
|
65298
65298
|
import { defineCommand as defineCommand135 } from "citty";
|
|
65299
65299
|
|
|
65300
65300
|
// src/engine/scaffold/lib/shoot-modes.ts
|
|
@@ -66050,8 +66050,8 @@ function critiqueCanvas(canvas) {
|
|
|
66050
66050
|
}
|
|
66051
66051
|
|
|
66052
66052
|
// src/commands/canvas/prepare-canvas.ts
|
|
66053
|
-
import { readdir as
|
|
66054
|
-
import
|
|
66053
|
+
import { readdir as readdir7, readFile as readFile12, writeFile as writeFile6 } from "fs/promises";
|
|
66054
|
+
import path14 from "path";
|
|
66055
66055
|
|
|
66056
66056
|
// src/commands/canvas/normalize-paths.ts
|
|
66057
66057
|
import { existsSync as existsSync4, realpathSync } from "fs";
|
|
@@ -66845,6 +66845,10 @@ function projectAdaptCopy(nodes, spec) {
|
|
|
66845
66845
|
}
|
|
66846
66846
|
return changed;
|
|
66847
66847
|
}
|
|
66848
|
+
function variantChecks(nodes, spec) {
|
|
66849
|
+
const checks = staticChecksOf(spec);
|
|
66850
|
+
return checks ? { ...checks, logo: checks.logo && nodes.some((n) => n.tags?.role === BRAND_LOGO_ROLE) } : null;
|
|
66851
|
+
}
|
|
66848
66852
|
function projectJudges(nodes, spec, brief2) {
|
|
66849
66853
|
const criteria = judgeBrief(brief2, {
|
|
66850
66854
|
...spec.brand?.name ? { brand: spec.brand.name } : {},
|
|
@@ -66852,7 +66856,7 @@ function projectJudges(nodes, spec, brief2) {
|
|
|
66852
66856
|
mechanisms: spec.mechanism.map((m) => m.device)
|
|
66853
66857
|
});
|
|
66854
66858
|
let changed = 0;
|
|
66855
|
-
const checks =
|
|
66859
|
+
const checks = variantChecks(nodes, spec);
|
|
66856
66860
|
for (const prefilter of nodes.filter((n) => n.tags?.role === VARIANT_PREFILTER_ROLE)) {
|
|
66857
66861
|
const before = JSON.stringify([prefilter.params, prefilter.inputs]);
|
|
66858
66862
|
const params = { ...prefilter.params, ...checks ? { checks } : {} };
|
|
@@ -67619,8 +67623,15 @@ function figureSourced(match, knowledge2) {
|
|
|
67619
67623
|
const [, currency, price, number = "", unit = ""] = match;
|
|
67620
67624
|
if (currency && price) return figureForms(price.replace(/[.,]$/, "")).some((form) => knowledge2.includes(form));
|
|
67621
67625
|
const forms = figureForms(number.replace(/[.,]$/, "")).map((f) => f.replace(/[.,]/g, "[.,]?"));
|
|
67622
|
-
|
|
67623
|
-
|
|
67626
|
+
if (unit === "%" || /^[x×]$/i.test(unit)) {
|
|
67627
|
+
const sign = unit === "%" ? "%" : "[x\xD7]";
|
|
67628
|
+
return forms.some((form) => new RegExp(`(^|[^\\d])${form}\\s?${sign}`, "iu").test(knowledge2));
|
|
67629
|
+
}
|
|
67630
|
+
const stem = unit.slice(0, 3).toLowerCase();
|
|
67631
|
+
return forms.some((form) => {
|
|
67632
|
+
const number2 = `(?:^|[^\\d.,])${form}(?![\\d])`;
|
|
67633
|
+
return new RegExp(`${number2}[^.!?\\n]{0,60}?${stem}|${stem}[^.!?\\n]{0,60}?${number2}`, "iu").test(knowledge2);
|
|
67634
|
+
});
|
|
67624
67635
|
}
|
|
67625
67636
|
function unsourcedFigureLines(spec, knowledge2) {
|
|
67626
67637
|
return spec.copy.flatMap(
|
|
@@ -70972,6 +70983,414 @@ function toPosix(p) {
|
|
|
70972
70983
|
return p.split(path12.sep).join("/");
|
|
70973
70984
|
}
|
|
70974
70985
|
|
|
70986
|
+
// src/commands/creatives/ideas-model.ts
|
|
70987
|
+
import { parse as parseYaml2 } from "yaml";
|
|
70988
|
+
var IDEA_ANGLES = [
|
|
70989
|
+
"pain_relief",
|
|
70990
|
+
"aspiration",
|
|
70991
|
+
"status",
|
|
70992
|
+
"fear",
|
|
70993
|
+
"FOMO",
|
|
70994
|
+
"identity",
|
|
70995
|
+
"contrarian",
|
|
70996
|
+
"education",
|
|
70997
|
+
"novelty",
|
|
70998
|
+
"authority",
|
|
70999
|
+
"community",
|
|
71000
|
+
"transformation",
|
|
71001
|
+
"convenience",
|
|
71002
|
+
"save_money",
|
|
71003
|
+
"save_time",
|
|
71004
|
+
"save_effort",
|
|
71005
|
+
"named_enemy",
|
|
71006
|
+
"objection_crusher",
|
|
71007
|
+
"villain_democratization",
|
|
71008
|
+
"commiseration",
|
|
71009
|
+
"recognition_mirror",
|
|
71010
|
+
"flipped_idiom"
|
|
71011
|
+
];
|
|
71012
|
+
var IDEA_VIDEO_FORMATS = [
|
|
71013
|
+
"video_ugc",
|
|
71014
|
+
"video_founder",
|
|
71015
|
+
"video_testimonial",
|
|
71016
|
+
"video_demo",
|
|
71017
|
+
"video_screen_recording",
|
|
71018
|
+
"video_explainer",
|
|
71019
|
+
"video_motion_graphics"
|
|
71020
|
+
];
|
|
71021
|
+
var IDEA_FORMATS = [...staticFormatPatternSchema.options, ...IDEA_VIDEO_FORMATS];
|
|
71022
|
+
var AWARENESS_STAGES = ["unaware", "problem_aware", "solution_aware", "product_aware", "most_aware"];
|
|
71023
|
+
var EVIDENCE_LEVELS = ["observed", "interpreted", "hypothesized", "generated"];
|
|
71024
|
+
var IDEA_STATUSES = ["idea", "built", "testing", "winner", "failed", "inconclusive", "retired"];
|
|
71025
|
+
var FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
71026
|
+
function frontmatterOf(markdown) {
|
|
71027
|
+
const block2 = FRONTMATTER.exec(markdown)?.[1];
|
|
71028
|
+
if (block2 === void 0) return null;
|
|
71029
|
+
try {
|
|
71030
|
+
const parsed = parseYaml2(block2);
|
|
71031
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
71032
|
+
} catch {
|
|
71033
|
+
return null;
|
|
71034
|
+
}
|
|
71035
|
+
}
|
|
71036
|
+
function text2(value2) {
|
|
71037
|
+
if (typeof value2 === "string") return value2.trim() || void 0;
|
|
71038
|
+
if (typeof value2 === "number") return String(value2);
|
|
71039
|
+
return void 0;
|
|
71040
|
+
}
|
|
71041
|
+
function oneOf(values, value2) {
|
|
71042
|
+
return typeof value2 === "string" && values.includes(value2) ? value2 : void 0;
|
|
71043
|
+
}
|
|
71044
|
+
var STATISTIC = /(?:\d+(?:[.,]\d+)?\s?%|\b(?:a|one)\s+(?:quarter|third|half|fifth)|\bhalf|\b\d+\s+(?:in|out of)\s+\d+)\s+(?:of\s+)?(?:[\p{L}-]+\s+){0,2}(?:people|homeowners|customers|users|owners|respondents|consumers|households|adults|buyers|shoppers|businesses|companies)\b/iu;
|
|
71045
|
+
var CHECKABLE_SOURCE = /^(?:https?:\/\/\S+|[a-z_]+:[\w-]+)$/i;
|
|
71046
|
+
function evidenceStatus(stated, quote, source) {
|
|
71047
|
+
if (stated !== "observed") return stated;
|
|
71048
|
+
return STATISTIC.test(quote) || !CHECKABLE_SOURCE.test(source) ? "interpreted" : "observed";
|
|
71049
|
+
}
|
|
71050
|
+
function parseInsight(id, markdown) {
|
|
71051
|
+
const fm = frontmatterOf(markdown);
|
|
71052
|
+
const quote = text2(fm?.quote);
|
|
71053
|
+
const source = text2(fm?.source);
|
|
71054
|
+
if (!fm || !quote || !source) return null;
|
|
71055
|
+
const force = text2(fm.force);
|
|
71056
|
+
const persona = text2(fm.persona);
|
|
71057
|
+
const sourceKind = text2(fm.sourceKind);
|
|
71058
|
+
return {
|
|
71059
|
+
id,
|
|
71060
|
+
type: text2(fm.type) ?? "other",
|
|
71061
|
+
quote,
|
|
71062
|
+
source,
|
|
71063
|
+
status: evidenceStatus(oneOf(EVIDENCE_LEVELS, fm.status) ?? "observed", quote, source),
|
|
71064
|
+
...sourceKind ? { sourceKind } : {},
|
|
71065
|
+
...force ? { force } : {},
|
|
71066
|
+
...persona ? { persona } : {}
|
|
71067
|
+
};
|
|
71068
|
+
}
|
|
71069
|
+
function evidenceList(value2) {
|
|
71070
|
+
if (Array.isArray(value2)) return value2.flatMap((e) => text2(e) ? [String(text2(e))] : []);
|
|
71071
|
+
const single = text2(value2);
|
|
71072
|
+
return single ? [single] : [];
|
|
71073
|
+
}
|
|
71074
|
+
function fieldProblems(f) {
|
|
71075
|
+
const problems = [];
|
|
71076
|
+
if (!f.who) problems.push("who is missing: the persona it is for");
|
|
71077
|
+
if (!f.message)
|
|
71078
|
+
problems.push(
|
|
71079
|
+
"message is missing: the one promise a stranger should get, what is offered and what changes for them"
|
|
71080
|
+
);
|
|
71081
|
+
if (!f.why) problems.push("why is missing: the brief finding it answers");
|
|
71082
|
+
if (!f.awareness) problems.push(`awareness must be one of ${AWARENESS_STAGES.join(", ")}`);
|
|
71083
|
+
if (!(f.angle && IDEA_ANGLES.includes(f.angle)))
|
|
71084
|
+
problems.push(`angle ${f.angle ? `"${f.angle}" ` : ""}must be one of ${IDEA_ANGLES.join(", ")}`);
|
|
71085
|
+
if (!(f.format && IDEA_FORMATS.includes(f.format)))
|
|
71086
|
+
problems.push(`format ${f.format ? `"${f.format}" ` : ""}must be one of ${IDEA_FORMATS.join(", ")}`);
|
|
71087
|
+
return problems;
|
|
71088
|
+
}
|
|
71089
|
+
function parseIdea(id, markdown) {
|
|
71090
|
+
const fm = frontmatterOf(markdown) ?? {};
|
|
71091
|
+
const f = {
|
|
71092
|
+
who: text2(fm.who),
|
|
71093
|
+
message: text2(fm.message),
|
|
71094
|
+
why: text2(fm.why),
|
|
71095
|
+
angle: text2(fm.angle),
|
|
71096
|
+
format: text2(fm.format),
|
|
71097
|
+
awareness: oneOf(AWARENESS_STAGES, fm.awareness)
|
|
71098
|
+
};
|
|
71099
|
+
const problems = fieldProblems(f);
|
|
71100
|
+
const { who, message, why, angle, format, awareness } = f;
|
|
71101
|
+
if (problems.length > 0 || !who || !message || !why || !awareness || !angle || !format) return { problems };
|
|
71102
|
+
return {
|
|
71103
|
+
idea: {
|
|
71104
|
+
id,
|
|
71105
|
+
who,
|
|
71106
|
+
message,
|
|
71107
|
+
evidence: evidenceList(fm.evidence),
|
|
71108
|
+
awareness,
|
|
71109
|
+
angle,
|
|
71110
|
+
format,
|
|
71111
|
+
job: fm.job === "closer" ? "closer" : "opener",
|
|
71112
|
+
why,
|
|
71113
|
+
status: oneOf(IDEA_STATUSES, fm.status) ?? "idea"
|
|
71114
|
+
},
|
|
71115
|
+
problems
|
|
71116
|
+
};
|
|
71117
|
+
}
|
|
71118
|
+
var EVIDENCE_WEIGHT = {
|
|
71119
|
+
observed: 3,
|
|
71120
|
+
interpreted: 2,
|
|
71121
|
+
hypothesized: 1,
|
|
71122
|
+
generated: 0,
|
|
71123
|
+
explore: 0
|
|
71124
|
+
};
|
|
71125
|
+
var LIVE_STATUSES = ["built", "testing", "winner"];
|
|
71126
|
+
function claimsOf(message) {
|
|
71127
|
+
return [...message.matchAll(/\d[\d.,]*\s?(?:%|€|\$|£|x\b|k\b)?/gi)].map((m) => m[0].trim());
|
|
71128
|
+
}
|
|
71129
|
+
function figureOf(claim) {
|
|
71130
|
+
return claim.replace(/[^\d]/g, "");
|
|
71131
|
+
}
|
|
71132
|
+
function figuresIn(text3) {
|
|
71133
|
+
return new Set([...text3.matchAll(/\d(?:[\d.,]*\d)?/g)].map((m) => figureOf(m[0])));
|
|
71134
|
+
}
|
|
71135
|
+
function normalize(value2) {
|
|
71136
|
+
return value2.toLowerCase().replace(/\s+/g, " ").trim();
|
|
71137
|
+
}
|
|
71138
|
+
function sentencesOf(message) {
|
|
71139
|
+
return message.split(/[.!?]+(?:\s|$)/).filter((s) => s.trim().length > 0).length;
|
|
71140
|
+
}
|
|
71141
|
+
var FILLER = /* @__PURE__ */ new Set([
|
|
71142
|
+
"that",
|
|
71143
|
+
"this",
|
|
71144
|
+
"with",
|
|
71145
|
+
"your",
|
|
71146
|
+
"from",
|
|
71147
|
+
"they",
|
|
71148
|
+
"them",
|
|
71149
|
+
"what",
|
|
71150
|
+
"when",
|
|
71151
|
+
"have",
|
|
71152
|
+
"will",
|
|
71153
|
+
"into",
|
|
71154
|
+
"than",
|
|
71155
|
+
"more",
|
|
71156
|
+
"just",
|
|
71157
|
+
"para",
|
|
71158
|
+
"como",
|
|
71159
|
+
"pero",
|
|
71160
|
+
"sobre"
|
|
71161
|
+
]);
|
|
71162
|
+
var SAME_MESSAGE = 0.6;
|
|
71163
|
+
function messageWords(message) {
|
|
71164
|
+
return new Set(
|
|
71165
|
+
message.toLowerCase().split(/[^\p{L}]+/u).filter((w) => w.length >= 4 && !FILLER.has(w)).map((w) => w.replace(/s$/, ""))
|
|
71166
|
+
);
|
|
71167
|
+
}
|
|
71168
|
+
function sameMessage(a, b) {
|
|
71169
|
+
const wa = messageWords(a);
|
|
71170
|
+
const wb = messageWords(b);
|
|
71171
|
+
const shared = [...wa].filter((w) => wb.has(w)).length;
|
|
71172
|
+
const all = (/* @__PURE__ */ new Set([...wa, ...wb])).size;
|
|
71173
|
+
return normalize(a) === normalize(b) || all > 0 && shared / all >= SAME_MESSAGE;
|
|
71174
|
+
}
|
|
71175
|
+
function repeatedFailure(idea, failed) {
|
|
71176
|
+
const lost = failed.find(
|
|
71177
|
+
(f) => f.id !== idea.id && (sameMessage(f.message, idea.message) || f.angle === idea.angle && f.format === idea.format)
|
|
71178
|
+
);
|
|
71179
|
+
return lost ? [`says what "${lost.id}" said, and that idea failed in the account: change the message, not just the picture`] : [];
|
|
71180
|
+
}
|
|
71181
|
+
function backingOf(idea, insights, problems) {
|
|
71182
|
+
const backing = idea.evidence.flatMap((id) => {
|
|
71183
|
+
const insight = insights.get(id);
|
|
71184
|
+
if (!insight)
|
|
71185
|
+
problems.push(`evidence "${id}" is not in src/content/insights/: name an insight on file or leave it out`);
|
|
71186
|
+
return insight ? [insight] : [];
|
|
71187
|
+
});
|
|
71188
|
+
return [...backing].sort((a, b) => EVIDENCE_WEIGHT[b.status] - EVIDENCE_WEIGHT[a.status])[0];
|
|
71189
|
+
}
|
|
71190
|
+
function unsourcedClaims(text3, corpusFigures) {
|
|
71191
|
+
return claimsOf(text3).filter((claim) => {
|
|
71192
|
+
const figure = figureOf(claim);
|
|
71193
|
+
return figure !== "" && !corpusFigures.has(figure);
|
|
71194
|
+
});
|
|
71195
|
+
}
|
|
71196
|
+
function claimProblems(message, corpusFigures) {
|
|
71197
|
+
return unsourcedClaims(message, corpusFigures).map(
|
|
71198
|
+
(claim) => `"${claim}" is in no customer quote, COMPANY.md or Knowledge entry: source it or drop it`
|
|
71199
|
+
);
|
|
71200
|
+
}
|
|
71201
|
+
var MARKET_SOURCES = ["reddit", "forum", "web"];
|
|
71202
|
+
var TESTIMONIAL_FORMATS = ["review_screenshot", "testimonial_quote", "video_testimonial"];
|
|
71203
|
+
function fromMarket(insight) {
|
|
71204
|
+
return insight?.sourceKind !== void 0 && MARKET_SOURCES.includes(insight.sourceKind);
|
|
71205
|
+
}
|
|
71206
|
+
function sourcesClaims(insight) {
|
|
71207
|
+
return insight.status === "observed" && !fromMarket(insight);
|
|
71208
|
+
}
|
|
71209
|
+
function borrowedProof(idea, insights) {
|
|
71210
|
+
return idea.evidence.find((id) => {
|
|
71211
|
+
const insight = insights.get(id);
|
|
71212
|
+
return fromMarket(insight) && insight?.type === "proof";
|
|
71213
|
+
});
|
|
71214
|
+
}
|
|
71215
|
+
function fakeReviewProblem(idea, insights) {
|
|
71216
|
+
if (!TESTIMONIAL_FORMATS.includes(idea.format)) return [];
|
|
71217
|
+
const cited = idea.evidence.flatMap((id) => insights.get(id) ?? []);
|
|
71218
|
+
if (cited.length === 0 || !cited.every(fromMarket)) return [];
|
|
71219
|
+
return [
|
|
71220
|
+
`${idea.format} needs a quote from our own customer (a review, testimonial, call or survey); every one cited was said by someone in the market. Pick another format, or cite our own customer`
|
|
71221
|
+
];
|
|
71222
|
+
}
|
|
71223
|
+
function warningsOf(idea, evidence, personas, insights) {
|
|
71224
|
+
const warnings = [];
|
|
71225
|
+
const borrowed = borrowedProof(idea, insights);
|
|
71226
|
+
if (borrowed) {
|
|
71227
|
+
warnings.push(
|
|
71228
|
+
`"${borrowed}" was said by someone in the market, not a customer of ours: use their words for the problem, never show it as our review, testimonial or result`
|
|
71229
|
+
);
|
|
71230
|
+
}
|
|
71231
|
+
if (sentencesOf(idea.message) > 1 || idea.message.split(/\s+/).length > 25) {
|
|
71232
|
+
warnings.push("the message says more than one thing: keep one promise, the rest goes to the ad text");
|
|
71233
|
+
}
|
|
71234
|
+
if (personas.length > 0 && !personas.includes(idea.who)) {
|
|
71235
|
+
warnings.push(`who "${idea.who}" is not a persona in the Knowledge (${personas.join(", ")})`);
|
|
71236
|
+
}
|
|
71237
|
+
if (evidence === "explore") {
|
|
71238
|
+
warnings.push("no customer evidence: an explore bet. `baker research customers` may find some");
|
|
71239
|
+
}
|
|
71240
|
+
return warnings;
|
|
71241
|
+
}
|
|
71242
|
+
function checkOne(idea, ctx) {
|
|
71243
|
+
const problems = [];
|
|
71244
|
+
const best = backingOf(idea, ctx.insights, problems);
|
|
71245
|
+
const evidence = best?.status ?? "explore";
|
|
71246
|
+
problems.push(...claimProblems(idea.message, ctx.corpusFigures), ...fakeReviewProblem(idea, ctx.insights));
|
|
71247
|
+
const duplicate = ctx.duplicateOf.get(idea.id);
|
|
71248
|
+
if (duplicate) {
|
|
71249
|
+
problems.push(
|
|
71250
|
+
`same angle and format, or the same message, as "${duplicate}": to the platform it is the same ad. Change the angle or the format`
|
|
71251
|
+
);
|
|
71252
|
+
}
|
|
71253
|
+
const gaps = [];
|
|
71254
|
+
if (!ctx.covered.has(idea.awareness)) gaps.push(`no live ad speaks to ${idea.awareness} people`);
|
|
71255
|
+
if (idea.job === "closer" && ctx.closersLive === 0) gaps.push("no live ad closes warm people");
|
|
71256
|
+
return {
|
|
71257
|
+
id: idea.id,
|
|
71258
|
+
status: idea.status,
|
|
71259
|
+
message: idea.message,
|
|
71260
|
+
evidence,
|
|
71261
|
+
...best ? { quote: best.quote } : {},
|
|
71262
|
+
awareness: idea.awareness,
|
|
71263
|
+
angle: idea.angle,
|
|
71264
|
+
format: idea.format,
|
|
71265
|
+
problems,
|
|
71266
|
+
warnings: [...warningsOf(idea, evidence, ctx.personas, ctx.insights), ...repeatedFailure(idea, ctx.failed)],
|
|
71267
|
+
gaps,
|
|
71268
|
+
score: EVIDENCE_WEIGHT[evidence] + gaps.length - (problems.length > 0 ? 10 : 0)
|
|
71269
|
+
};
|
|
71270
|
+
}
|
|
71271
|
+
function duplicatesOf(ideas, insights) {
|
|
71272
|
+
const active = ideas.filter((i) => i.status !== "retired" && i.status !== "failed");
|
|
71273
|
+
const strength = (idea) => Math.max(0, ...idea.evidence.map((id) => EVIDENCE_WEIGHT[insights.get(id)?.status ?? "explore"]));
|
|
71274
|
+
const position = new Map(ideas.map((idea, index) => [idea.id, index]));
|
|
71275
|
+
const order = [...active].sort(
|
|
71276
|
+
(a, b) => Number(LIVE_STATUSES.includes(b.status)) - Number(LIVE_STATUSES.includes(a.status)) || strength(b) - strength(a) || (position.get(a.id) ?? 0) - (position.get(b.id) ?? 0)
|
|
71277
|
+
);
|
|
71278
|
+
const seenPairs = /* @__PURE__ */ new Map();
|
|
71279
|
+
const seenMessages = /* @__PURE__ */ new Map();
|
|
71280
|
+
const duplicateOf = /* @__PURE__ */ new Map();
|
|
71281
|
+
for (const idea of order) {
|
|
71282
|
+
const pair = `${idea.angle}\xD7${idea.format}`;
|
|
71283
|
+
const message = normalize(idea.message);
|
|
71284
|
+
const earlier = seenPairs.get(pair) ?? seenMessages.get(message);
|
|
71285
|
+
if (earlier) duplicateOf.set(idea.id, earlier);
|
|
71286
|
+
if (!seenPairs.has(pair)) seenPairs.set(pair, idea.id);
|
|
71287
|
+
if (!seenMessages.has(message)) seenMessages.set(message, idea.id);
|
|
71288
|
+
}
|
|
71289
|
+
return duplicateOf;
|
|
71290
|
+
}
|
|
71291
|
+
function checkIdeas(input) {
|
|
71292
|
+
const insights = new Map(input.insights.map((insight) => [insight.id, insight]));
|
|
71293
|
+
const ownQuotes = input.insights.filter(sourcesClaims).map((i) => i.quote);
|
|
71294
|
+
const corpusFigures = figuresIn(`${input.knowledge}
|
|
71295
|
+
${ownQuotes.join("\n")}`);
|
|
71296
|
+
const covered = new Set(input.coveredAwareness);
|
|
71297
|
+
const duplicateOf = duplicatesOf(input.ideas, insights);
|
|
71298
|
+
const checks = input.ideas.map(
|
|
71299
|
+
(idea) => checkOne(idea, {
|
|
71300
|
+
insights,
|
|
71301
|
+
corpusFigures,
|
|
71302
|
+
covered,
|
|
71303
|
+
duplicateOf,
|
|
71304
|
+
personas: input.personas,
|
|
71305
|
+
closersLive: input.closersLive,
|
|
71306
|
+
failed: input.ideas.filter((i) => i.status === "failed")
|
|
71307
|
+
})
|
|
71308
|
+
);
|
|
71309
|
+
const toBuild = (c) => c.status === "idea";
|
|
71310
|
+
return checks.sort(
|
|
71311
|
+
(a, b) => Number(toBuild(b)) - Number(toBuild(a)) || b.score - a.score || a.id.localeCompare(b.id)
|
|
71312
|
+
);
|
|
71313
|
+
}
|
|
71314
|
+
|
|
71315
|
+
// src/commands/creatives/ideas-workspace.ts
|
|
71316
|
+
import { readdir as readdir6, readFile as readFile10 } from "fs/promises";
|
|
71317
|
+
import path13 from "path";
|
|
71318
|
+
var IDEAS_DIR = path13.join("src", "creatives", "_ideas");
|
|
71319
|
+
var INSIGHTS_DIR = path13.join("src", "content", "insights");
|
|
71320
|
+
async function markdownFiles(dir, recursive = false) {
|
|
71321
|
+
const entries = await readdir6(dir, { recursive }).catch(() => []);
|
|
71322
|
+
return entries.filter((f) => f.endsWith(".md")).sort();
|
|
71323
|
+
}
|
|
71324
|
+
var idOf2 = (file) => file.replace(/\\/g, "/").replace(/\.md$/, "");
|
|
71325
|
+
async function readInsights(root) {
|
|
71326
|
+
const dir = path13.join(root, INSIGHTS_DIR);
|
|
71327
|
+
const insights = [];
|
|
71328
|
+
for (const file of await markdownFiles(dir, true)) {
|
|
71329
|
+
const insight = parseInsight(idOf2(file), await readFile10(path13.join(dir, file), "utf8").catch(() => ""));
|
|
71330
|
+
if (insight) insights.push(insight);
|
|
71331
|
+
}
|
|
71332
|
+
return insights;
|
|
71333
|
+
}
|
|
71334
|
+
async function readIdeas(root) {
|
|
71335
|
+
const dir = path13.join(root, IDEAS_DIR);
|
|
71336
|
+
const ideas = [];
|
|
71337
|
+
const unreadable = [];
|
|
71338
|
+
for (const file of await markdownFiles(dir)) {
|
|
71339
|
+
const id = idOf2(file);
|
|
71340
|
+
const parsed = parseIdea(id, await readFile10(path13.join(dir, file), "utf8").catch(() => ""));
|
|
71341
|
+
if (parsed.idea) ideas.push(parsed.idea);
|
|
71342
|
+
else unreadable.push({ id, problems: parsed.problems });
|
|
71343
|
+
}
|
|
71344
|
+
return { ideas, unreadable };
|
|
71345
|
+
}
|
|
71346
|
+
async function readPersonaIds(root) {
|
|
71347
|
+
return (await markdownFiles(path13.join(root, "src", "content", "personas"), true)).map(idOf2);
|
|
71348
|
+
}
|
|
71349
|
+
var MAX_KNOWLEDGE_CHARS = 2e5;
|
|
71350
|
+
async function readKnowledgeCorpus(root) {
|
|
71351
|
+
const parts = [];
|
|
71352
|
+
const company = await readFile10(path13.join(root, "COMPANY.md"), "utf8").catch(() => "");
|
|
71353
|
+
if (company) parts.push(company);
|
|
71354
|
+
const content = path13.join(root, "src", "content");
|
|
71355
|
+
const insights = path13.relative(content, path13.join(root, INSIGHTS_DIR));
|
|
71356
|
+
const files = (await markdownFiles(content, true)).filter((f) => !f.replace(/\\/g, "/").startsWith(`${insights}/`));
|
|
71357
|
+
for (const file of files) {
|
|
71358
|
+
parts.push(await readFile10(path13.join(content, file), "utf8").catch(() => ""));
|
|
71359
|
+
if (parts.join("").length > MAX_KNOWLEDGE_CHARS) break;
|
|
71360
|
+
}
|
|
71361
|
+
return parts.join("\n").slice(0, MAX_KNOWLEDGE_CHARS);
|
|
71362
|
+
}
|
|
71363
|
+
async function readCreativeRecords(root) {
|
|
71364
|
+
const dir = path13.join(root, "src", "creatives");
|
|
71365
|
+
const entries = await readdir6(dir, { withFileTypes: true }).catch(() => []);
|
|
71366
|
+
const records = [];
|
|
71367
|
+
for (const entry of entries) {
|
|
71368
|
+
if (!entry.isDirectory() || entry.name.startsWith("_")) continue;
|
|
71369
|
+
const raw = await readFile10(path13.join(dir, entry.name, "_definition.md"), "utf8").catch(() => null);
|
|
71370
|
+
if (raw === null) continue;
|
|
71371
|
+
const fm = frontmatterOf(raw) ?? {};
|
|
71372
|
+
const pick2 = (key) => typeof fm[key] === "string" && fm[key] ? { [key]: fm[key] } : {};
|
|
71373
|
+
records.push({
|
|
71374
|
+
slug: entry.name,
|
|
71375
|
+
...pick2("status"),
|
|
71376
|
+
...pick2("idea"),
|
|
71377
|
+
...pick2("awareness"),
|
|
71378
|
+
...pick2("angle"),
|
|
71379
|
+
...pick2("kind")
|
|
71380
|
+
});
|
|
71381
|
+
}
|
|
71382
|
+
return records;
|
|
71383
|
+
}
|
|
71384
|
+
var SHOWN_STATUSES = /* @__PURE__ */ new Set(["rendered", "published"]);
|
|
71385
|
+
function coverageOf(ideas, creatives) {
|
|
71386
|
+
const live = ideas.filter((i) => i.status === "built" || i.status === "testing" || i.status === "winner");
|
|
71387
|
+
const awareness = new Set(live.map((i) => i.awareness));
|
|
71388
|
+
for (const creative of creatives) {
|
|
71389
|
+
if (creative.awareness && SHOWN_STATUSES.has(creative.status ?? "")) awareness.add(creative.awareness);
|
|
71390
|
+
}
|
|
71391
|
+
return { coveredAwareness: [...awareness].sort(), closersLive: live.filter((i) => i.job === "closer").length };
|
|
71392
|
+
}
|
|
71393
|
+
|
|
70975
71394
|
// src/engine/scaffold/static-upgrade.ts
|
|
70976
71395
|
var TAKE_ID = /^ad(?:_[a-c])?$/;
|
|
70977
71396
|
var RELAY_ID = /^ad_(\d+)x(\d+)$/;
|
|
@@ -71185,7 +71604,7 @@ function upgradedDescription(text3, old, plan) {
|
|
|
71185
71604
|
}
|
|
71186
71605
|
|
|
71187
71606
|
// src/commands/canvas/style-projection.ts
|
|
71188
|
-
import { readFile as
|
|
71607
|
+
import { readFile as readFile11, writeFile as writeFile5 } from "fs/promises";
|
|
71189
71608
|
function findBlueprintProjection(canvas) {
|
|
71190
71609
|
if (!canvas || typeof canvas !== "object") return null;
|
|
71191
71610
|
const nodes = canvas.nodes;
|
|
@@ -71213,8 +71632,8 @@ function renderStyleProjectionFromValue(blueprint) {
|
|
|
71213
71632
|
async function syncStyleProjection(canvas, log) {
|
|
71214
71633
|
const pair = findBlueprintProjection(canvas);
|
|
71215
71634
|
if (!pair) return "not_applicable";
|
|
71216
|
-
const rendered = renderStyleProjection(await
|
|
71217
|
-
const current = await
|
|
71635
|
+
const rendered = renderStyleProjection(await readFile11(pair.promptPath, "utf8"));
|
|
71636
|
+
const current = await readFile11(pair.stylePath, "utf8").catch(() => null);
|
|
71218
71637
|
if (current === rendered) return "up_to_date";
|
|
71219
71638
|
await writeFile5(pair.stylePath, rendered, "utf8");
|
|
71220
71639
|
log(
|
|
@@ -71262,10 +71681,13 @@ async function staticSpecGate(canvas, canvasDir) {
|
|
|
71262
71681
|
return null;
|
|
71263
71682
|
}
|
|
71264
71683
|
async function readRecords(root) {
|
|
71265
|
-
const parts = [await
|
|
71266
|
-
const content =
|
|
71267
|
-
const files = (await
|
|
71268
|
-
|
|
71684
|
+
const parts = [await readFile12(path14.join(root, "COMPANY.md"), "utf8").catch(() => "")];
|
|
71685
|
+
const content = path14.join(root, "src", "content");
|
|
71686
|
+
const files = (await readdir7(content, { recursive: true }).catch(() => [])).filter(
|
|
71687
|
+
(f) => /\.(md|mdx|json|ya?ml)$/.test(f) && !f.replace(/\\/g, "/").startsWith("insights/")
|
|
71688
|
+
);
|
|
71689
|
+
for (const file of files.slice(0, 400)) parts.push(await readFile12(path14.join(content, file), "utf8").catch(() => ""));
|
|
71690
|
+
parts.push(...(await readInsights(root)).filter(sourcesClaims).map((insight) => insight.quote));
|
|
71269
71691
|
return parts.join("\n");
|
|
71270
71692
|
}
|
|
71271
71693
|
var CanvasPrepareError = class extends Error {
|
|
@@ -71288,14 +71710,14 @@ var CanvasPrepareError = class extends Error {
|
|
|
71288
71710
|
}
|
|
71289
71711
|
};
|
|
71290
71712
|
async function prepareCanvas(canvasPath, opts = {}) {
|
|
71291
|
-
const filePath =
|
|
71292
|
-
const canvasDir =
|
|
71713
|
+
const filePath = path14.resolve(canvasPath);
|
|
71714
|
+
const canvasDir = path14.dirname(filePath);
|
|
71293
71715
|
const notes = [];
|
|
71294
71716
|
const log = (line) => {
|
|
71295
71717
|
notes.push(line);
|
|
71296
71718
|
opts.log?.(line);
|
|
71297
71719
|
};
|
|
71298
|
-
const raw = await
|
|
71720
|
+
const raw = await readFile12(filePath, "utf8");
|
|
71299
71721
|
let parsed;
|
|
71300
71722
|
try {
|
|
71301
71723
|
parsed = JSON.parse(raw);
|
|
@@ -71334,7 +71756,7 @@ async function prepareCanvas(canvasPath, opts = {}) {
|
|
|
71334
71756
|
if (logo === "regenerated" || pictures === "regenerated") staticCopy = "regenerated";
|
|
71335
71757
|
} catch (e) {
|
|
71336
71758
|
throw new CanvasPrepareError("style_projection", `prompt.json is not readable or not a valid ad: ${errorMessage(e)}`, {
|
|
71337
|
-
fix: `Fix ${
|
|
71759
|
+
fix: `Fix ${path14.join(canvasDir, "prompt.json")} so it parses as JSON (and, for a static ad, matches the ad spec the message names), then re-run.`
|
|
71338
71760
|
});
|
|
71339
71761
|
}
|
|
71340
71762
|
const gate = opts.gate && isStaticBriefCanvas(canvas) ? await staticSpecGate(canvas, canvasDir) : null;
|
|
@@ -71346,7 +71768,7 @@ async function prepareCanvas(canvasPath, opts = {}) {
|
|
|
71346
71768
|
throw new CanvasPrepareError(
|
|
71347
71769
|
"scene_projection",
|
|
71348
71770
|
`a scenes/*.json file is not readable/valid or its rebuild failed: ${errorMessage(e)}`,
|
|
71349
|
-
{ fix: `Fix the scene files in ${
|
|
71771
|
+
{ fix: `Fix the scene files in ${path14.join(canvasDir, "scenes")} so each parses as JSON, then re-run.` }
|
|
71350
71772
|
);
|
|
71351
71773
|
}
|
|
71352
71774
|
const projected = withFilePaths(canvas, onDisk, canvasDir);
|
|
@@ -71382,7 +71804,7 @@ async function prepareCanvas(canvasPath, opts = {}) {
|
|
|
71382
71804
|
};
|
|
71383
71805
|
}
|
|
71384
71806
|
function healInMemory(filePath, canvas) {
|
|
71385
|
-
const canvasDir =
|
|
71807
|
+
const canvasDir = path14.dirname(filePath);
|
|
71386
71808
|
const workspaceRoot = findWorkspaceRoot(canvasDir);
|
|
71387
71809
|
if (!workspaceRoot) return { canvas, rewrites: [], text: null };
|
|
71388
71810
|
return { ...normalizeCanvasPaths(canvas, canvasDir, workspaceRoot), text: null };
|
|
@@ -71403,10 +71825,10 @@ function withFilePaths(projected, onDisk, canvasDir) {
|
|
|
71403
71825
|
if (!key || !node.params || typeof node.params[key] !== "string") continue;
|
|
71404
71826
|
const value2 = node.params[key];
|
|
71405
71827
|
const original = byId.get(node.id)?.params?.[key];
|
|
71406
|
-
if (typeof original === "string" &&
|
|
71828
|
+
if (typeof original === "string" && path14.resolve(canvasDir, original) === path14.resolve(canvasDir, value2)) {
|
|
71407
71829
|
node.params[key] = original;
|
|
71408
|
-
} else if (
|
|
71409
|
-
const rel =
|
|
71830
|
+
} else if (path14.isAbsolute(value2)) {
|
|
71831
|
+
const rel = path14.relative(canvasDir, value2).split(path14.sep).join("/");
|
|
71410
71832
|
node.params[key] = rel.startsWith(".") ? rel : `./${rel}`;
|
|
71411
71833
|
}
|
|
71412
71834
|
}
|
|
@@ -71430,7 +71852,7 @@ var critiqueCommand = defineCommand135({
|
|
|
71430
71852
|
},
|
|
71431
71853
|
args: { file: { type: "positional", required: true, description: "Path to canvas JSON" } },
|
|
71432
71854
|
async run({ args: args8 }) {
|
|
71433
|
-
const filePath =
|
|
71855
|
+
const filePath = path15.resolve(String(args8.file));
|
|
71434
71856
|
let prepared;
|
|
71435
71857
|
try {
|
|
71436
71858
|
prepared = await prepareCanvas(filePath, { log: (line) => process.stderr.write(`${line}
|
|
@@ -71458,8 +71880,8 @@ var critiqueCommand = defineCommand135({
|
|
|
71458
71880
|
|
|
71459
71881
|
// src/commands/canvas/inspect.ts
|
|
71460
71882
|
import { execFile } from "child_process";
|
|
71461
|
-
import { readdir as
|
|
71462
|
-
import
|
|
71883
|
+
import { readdir as readdir8, readFile as readFile13, stat as stat2 } from "fs/promises";
|
|
71884
|
+
import path16 from "path";
|
|
71463
71885
|
import { promisify } from "util";
|
|
71464
71886
|
import { defineCommand as defineCommand136 } from "citty";
|
|
71465
71887
|
|
|
@@ -71491,12 +71913,12 @@ var inspectCommand = defineCommand136({
|
|
|
71491
71913
|
},
|
|
71492
71914
|
async run({ args: args8 }) {
|
|
71493
71915
|
const cwd = process.cwd();
|
|
71494
|
-
const outputsDir = args8["outputs-dir"] ?
|
|
71916
|
+
const outputsDir = args8["outputs-dir"] ? path16.resolve(String(args8["outputs-dir"])) : path16.join(findWorkspaceRoot(cwd) ?? cwd, "canvas");
|
|
71495
71917
|
const runArg = String(args8.run);
|
|
71496
71918
|
const runDir = await localRunDir(runArg, outputsDir);
|
|
71497
71919
|
if (!runDir) {
|
|
71498
|
-
if (
|
|
71499
|
-
await inspectFromHistory(runArg,
|
|
71920
|
+
if (path16.isAbsolute(runArg)) fail3("not_found", `${runArg} is not a run directory`);
|
|
71921
|
+
await inspectFromHistory(runArg, path16.join(outputsDir, runArg));
|
|
71500
71922
|
return;
|
|
71501
71923
|
}
|
|
71502
71924
|
const manifest = await loadManifest(runDir);
|
|
@@ -71509,7 +71931,7 @@ var inspectCommand = defineCommand136({
|
|
|
71509
71931
|
const summary = {
|
|
71510
71932
|
ok: true,
|
|
71511
71933
|
source: "local",
|
|
71512
|
-
run_id: manifest.run_id ??
|
|
71934
|
+
run_id: manifest.run_id ?? path16.basename(runDir),
|
|
71513
71935
|
run_dir: runDir,
|
|
71514
71936
|
stats: manifest.stats ?? null,
|
|
71515
71937
|
output: manifest.output ?? null,
|
|
@@ -71522,7 +71944,7 @@ var inspectCommand = defineCommand136({
|
|
|
71522
71944
|
}
|
|
71523
71945
|
});
|
|
71524
71946
|
async function localRunDir(run3, outputsDir) {
|
|
71525
|
-
const candidate =
|
|
71947
|
+
const candidate = path16.isAbsolute(run3) ? run3 : path16.join(outputsDir, run3);
|
|
71526
71948
|
const s = await stat2(candidate).catch(() => null);
|
|
71527
71949
|
return s?.isDirectory() ? candidate : null;
|
|
71528
71950
|
}
|
|
@@ -71577,9 +71999,9 @@ function fail3(code, message) {
|
|
|
71577
71999
|
process.exit(2);
|
|
71578
72000
|
}
|
|
71579
72001
|
async function loadManifest(runDir) {
|
|
71580
|
-
const manifestPath =
|
|
72002
|
+
const manifestPath = path16.join(runDir, "manifest.json");
|
|
71581
72003
|
try {
|
|
71582
|
-
const raw = await
|
|
72004
|
+
const raw = await readFile13(manifestPath, "utf-8");
|
|
71583
72005
|
return JSON.parse(raw);
|
|
71584
72006
|
} catch {
|
|
71585
72007
|
return {};
|
|
@@ -71587,9 +72009,9 @@ async function loadManifest(runDir) {
|
|
|
71587
72009
|
}
|
|
71588
72010
|
async function listRunFiles(runDir) {
|
|
71589
72011
|
const out = [];
|
|
71590
|
-
const names = await
|
|
72012
|
+
const names = await readdir8(runDir);
|
|
71591
72013
|
for (const name of names) {
|
|
71592
|
-
const abs =
|
|
72014
|
+
const abs = path16.join(runDir, name);
|
|
71593
72015
|
const s = await stat2(abs).catch(() => null);
|
|
71594
72016
|
if (!s?.isFile()) continue;
|
|
71595
72017
|
out.push({ name, path: abs, size: s.size });
|
|
@@ -71637,18 +72059,18 @@ async function probeDuration(filePath) {
|
|
|
71637
72059
|
import { defineCommand as defineCommand140 } from "citty";
|
|
71638
72060
|
|
|
71639
72061
|
// src/commands/canvas/run.ts
|
|
71640
|
-
import
|
|
72062
|
+
import path29 from "path";
|
|
71641
72063
|
import { defineCommand as defineCommand139 } from "citty";
|
|
71642
72064
|
|
|
71643
72065
|
// src/commands/canvas/definition-status.ts
|
|
71644
|
-
import { readFile as
|
|
71645
|
-
import
|
|
72066
|
+
import { readFile as readFile14, writeFile as writeFile7 } from "fs/promises";
|
|
72067
|
+
import path17 from "path";
|
|
71646
72068
|
import { parseDocument } from "yaml";
|
|
71647
72069
|
var ORDER = { draft: 0, rendered: 1, published: 2 };
|
|
71648
72070
|
async function advanceDefinitionStatus(definitionPath, to) {
|
|
71649
72071
|
let raw;
|
|
71650
72072
|
try {
|
|
71651
|
-
raw = await
|
|
72073
|
+
raw = await readFile14(definitionPath, "utf8");
|
|
71652
72074
|
} catch {
|
|
71653
72075
|
return "missing";
|
|
71654
72076
|
}
|
|
@@ -71671,12 +72093,12 @@ ${nextFrontmatter}---${raw.slice(match[0].length)}`, "utf8");
|
|
|
71671
72093
|
return "updated";
|
|
71672
72094
|
}
|
|
71673
72095
|
function definitionPathFor(workspaceRoot, slug) {
|
|
71674
|
-
return
|
|
72096
|
+
return path17.join(workspaceRoot, "src", "creatives", slug, "_definition.md");
|
|
71675
72097
|
}
|
|
71676
72098
|
|
|
71677
72099
|
// src/commands/canvas/output-ratio.ts
|
|
71678
|
-
import { readFile as
|
|
71679
|
-
import
|
|
72100
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
72101
|
+
import path18 from "path";
|
|
71680
72102
|
function definitionFormats2(markdown) {
|
|
71681
72103
|
const front = markdown.match(/^---\n([\s\S]*?)\n---/)?.[1];
|
|
71682
72104
|
if (!front) return [];
|
|
@@ -71751,21 +72173,21 @@ function outputRatioWarnings(declared, produced) {
|
|
|
71751
72173
|
});
|
|
71752
72174
|
}
|
|
71753
72175
|
async function outputRatioWarningsFor(canvas, canvasPath) {
|
|
71754
|
-
const definition = await
|
|
72176
|
+
const definition = await readFile15(path18.join(path18.dirname(canvasPath), "_definition.md"), "utf8").catch(() => null);
|
|
71755
72177
|
if (!definition) return [];
|
|
71756
72178
|
return outputRatioWarnings(definitionFormats2(definition), await producedRatios(canvas));
|
|
71757
72179
|
}
|
|
71758
72180
|
|
|
71759
72181
|
// src/commands/canvas/render-state.ts
|
|
71760
|
-
import { mkdir as mkdir3, readdir as
|
|
71761
|
-
import
|
|
72182
|
+
import { mkdir as mkdir3, readdir as readdir9, readFile as readFile16, rm as rm2, writeFile as writeFile8 } from "fs/promises";
|
|
72183
|
+
import path19 from "path";
|
|
71762
72184
|
var RENDER_STATE_DIR = ".rendered";
|
|
71763
72185
|
function statePath(canvasRoot, slug) {
|
|
71764
|
-
return
|
|
72186
|
+
return path19.join(canvasRoot, RENDER_STATE_DIR, `${slug}.json`);
|
|
71765
72187
|
}
|
|
71766
72188
|
async function readRenderState(canvasRoot, slug) {
|
|
71767
72189
|
try {
|
|
71768
|
-
const parsed = JSON.parse(await
|
|
72190
|
+
const parsed = JSON.parse(await readFile16(statePath(canvasRoot, slug), "utf8"));
|
|
71769
72191
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
71770
72192
|
} catch {
|
|
71771
72193
|
return {};
|
|
@@ -71774,7 +72196,7 @@ async function readRenderState(canvasRoot, slug) {
|
|
|
71774
72196
|
async function updateRenderState(canvasRoot, slug, patch) {
|
|
71775
72197
|
try {
|
|
71776
72198
|
const next = { ...await readRenderState(canvasRoot, slug), ...patch };
|
|
71777
|
-
await mkdir3(
|
|
72199
|
+
await mkdir3(path19.join(canvasRoot, RENDER_STATE_DIR), { recursive: true });
|
|
71778
72200
|
await writeFile8(statePath(canvasRoot, slug), `${JSON.stringify(next, null, 2)}
|
|
71779
72201
|
`, "utf8");
|
|
71780
72202
|
} catch {
|
|
@@ -71805,11 +72227,11 @@ function isRenderStale(state, currentSourceSha) {
|
|
|
71805
72227
|
return state.attempted?.sourceSha !== currentSourceSha;
|
|
71806
72228
|
}
|
|
71807
72229
|
async function staleCreatives(workspaceRoot) {
|
|
71808
|
-
const creativesDir =
|
|
71809
|
-
const canvasRoot =
|
|
72230
|
+
const creativesDir = path19.join(workspaceRoot, "src", "creatives");
|
|
72231
|
+
const canvasRoot = path19.join(workspaceRoot, "canvas");
|
|
71810
72232
|
let slugs;
|
|
71811
72233
|
try {
|
|
71812
|
-
slugs = (await
|
|
72234
|
+
slugs = (await readdir9(creativesDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
71813
72235
|
} catch {
|
|
71814
72236
|
return [];
|
|
71815
72237
|
}
|
|
@@ -71817,7 +72239,7 @@ async function staleCreatives(workspaceRoot) {
|
|
|
71817
72239
|
for (const slug of slugs.sort()) {
|
|
71818
72240
|
const state = await readRenderState(canvasRoot, slug);
|
|
71819
72241
|
if (!state.rendered) continue;
|
|
71820
|
-
const canvasPath =
|
|
72242
|
+
const canvasPath = path19.join(creativesDir, slug, `${slug}.canvas.json`);
|
|
71821
72243
|
let prepared;
|
|
71822
72244
|
try {
|
|
71823
72245
|
prepared = await prepareCanvas(canvasPath, { write: false });
|
|
@@ -71829,7 +72251,7 @@ async function staleCreatives(workspaceRoot) {
|
|
|
71829
72251
|
if (!current || !isRenderStale(state, current)) continue;
|
|
71830
72252
|
stale.push({
|
|
71831
72253
|
slug,
|
|
71832
|
-
canvasPath:
|
|
72254
|
+
canvasPath: path19.relative(workspaceRoot, canvasPath).split(path19.sep).join("/"),
|
|
71833
72255
|
renderedRunId: state.rendered.runId,
|
|
71834
72256
|
sourceSha: current
|
|
71835
72257
|
});
|
|
@@ -71837,7 +72259,7 @@ async function staleCreatives(workspaceRoot) {
|
|
|
71837
72259
|
return stale;
|
|
71838
72260
|
}
|
|
71839
72261
|
async function clearLegacyDirtyMarker(canvasRoot, slug) {
|
|
71840
|
-
await rm2(
|
|
72262
|
+
await rm2(path19.join(canvasRoot, ".dirty", `${slug}.json`), { force: true }).catch(() => void 0);
|
|
71841
72263
|
}
|
|
71842
72264
|
|
|
71843
72265
|
// src/commands/canvas/node-preview.ts
|
|
@@ -71893,13 +72315,13 @@ ${body}` : header || body || compactJson(record);
|
|
|
71893
72315
|
}
|
|
71894
72316
|
|
|
71895
72317
|
// src/commands/canvas/run-record.ts
|
|
71896
|
-
import
|
|
72318
|
+
import path20 from "path";
|
|
71897
72319
|
var MAX_RUN_NODES = 200;
|
|
71898
72320
|
var MAX_OUTPUTS_PER_NODE = 10;
|
|
71899
72321
|
var MAX_FINAL_OUTPUTS = 10;
|
|
71900
72322
|
var MAX_CREATIVE_SLUG_LENGTH = 100;
|
|
71901
72323
|
function creativeSlugFromCanvasPath(filePath) {
|
|
71902
|
-
const normalized2 = filePath.split(
|
|
72324
|
+
const normalized2 = filePath.split(path20.sep).join("/");
|
|
71903
72325
|
const match = normalized2.match(/(?:^|\/)src\/creatives\/([a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\//);
|
|
71904
72326
|
const slug = match?.[1] ?? null;
|
|
71905
72327
|
return slug && slug.length <= MAX_CREATIVE_SLUG_LENGTH ? slug : null;
|
|
@@ -72207,7 +72629,7 @@ var RunRecordPoster = class {
|
|
|
72207
72629
|
|
|
72208
72630
|
// src/commands/canvas/run-retention.ts
|
|
72209
72631
|
import { rm as rm3 } from "fs/promises";
|
|
72210
|
-
import
|
|
72632
|
+
import path21 from "path";
|
|
72211
72633
|
function runDirsToPrune(entries, keep, currentRunId) {
|
|
72212
72634
|
const runs = entries.filter((e) => /^r_[0-9A-Za-z]+$/.test(e) && e !== currentRunId).sort();
|
|
72213
72635
|
if (keep <= 0) return runs;
|
|
@@ -72224,7 +72646,7 @@ async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
|
|
|
72224
72646
|
const toPrune = runDirsToPrune(entries, keep, currentRunId);
|
|
72225
72647
|
if (toPrune.length === 0) return;
|
|
72226
72648
|
for (const dir of toPrune) {
|
|
72227
|
-
await rm3(
|
|
72649
|
+
await rm3(path21.join(outputsDir, dir), { recursive: true, force: true }).catch(
|
|
72228
72650
|
(e) => log(`[prune ] could not remove ${dir}: ${e.message}`)
|
|
72229
72651
|
);
|
|
72230
72652
|
}
|
|
@@ -72233,15 +72655,15 @@ async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
|
|
|
72233
72655
|
|
|
72234
72656
|
// src/commands/canvas/run-resume.ts
|
|
72235
72657
|
import { randomUUID } from "crypto";
|
|
72236
|
-
import { mkdir as mkdir4, readFile as
|
|
72237
|
-
import
|
|
72658
|
+
import { mkdir as mkdir4, readFile as readFile17, rm as rm4, writeFile as writeFile9 } from "fs/promises";
|
|
72659
|
+
import path22 from "path";
|
|
72238
72660
|
function markerKey(canvasPath) {
|
|
72239
72661
|
const slug = creativeSlugFromCanvasPath(canvasPath);
|
|
72240
|
-
const identity = slug ??
|
|
72662
|
+
const identity = slug ?? path22.relative(process.cwd(), path22.resolve(canvasPath));
|
|
72241
72663
|
return sha256Hex(Buffer.from(identity)).slice(0, 32);
|
|
72242
72664
|
}
|
|
72243
72665
|
function markerFile(outputsDir, canvasPath) {
|
|
72244
|
-
return
|
|
72666
|
+
return path22.join(outputsDir, ".inflight", `${markerKey(canvasPath)}.json`);
|
|
72245
72667
|
}
|
|
72246
72668
|
function classifyRemoteRun(run3) {
|
|
72247
72669
|
if (run3.interrupted === true || run3.status === "awaiting_approval") return "adopt";
|
|
@@ -72294,7 +72716,7 @@ async function resolveRunId(opts) {
|
|
|
72294
72716
|
}
|
|
72295
72717
|
async function readMarker(outputsDir, canvasPath) {
|
|
72296
72718
|
try {
|
|
72297
|
-
const parsed = JSON.parse(await
|
|
72719
|
+
const parsed = JSON.parse(await readFile17(markerFile(outputsDir, canvasPath), "utf8"));
|
|
72298
72720
|
if (typeof parsed.runId !== "string" || parsed.runId.length === 0) return null;
|
|
72299
72721
|
const num4 = (value2) => typeof value2 === "number" && Number.isFinite(value2) ? value2 : void 0;
|
|
72300
72722
|
return {
|
|
@@ -72319,14 +72741,14 @@ async function markRunInFlight(outputsDir, canvasPath, runId, sourceSha) {
|
|
|
72319
72741
|
runId,
|
|
72320
72742
|
pid: process.pid,
|
|
72321
72743
|
token,
|
|
72322
|
-
canvasPath:
|
|
72744
|
+
canvasPath: path22.resolve(canvasPath),
|
|
72323
72745
|
startedAt,
|
|
72324
72746
|
heartbeatAt,
|
|
72325
72747
|
...sourceSha ? { sourceSha } : {}
|
|
72326
72748
|
})
|
|
72327
72749
|
);
|
|
72328
72750
|
try {
|
|
72329
|
-
await mkdir4(
|
|
72751
|
+
await mkdir4(path22.dirname(file), { recursive: true });
|
|
72330
72752
|
await write(startedAt);
|
|
72331
72753
|
} catch {
|
|
72332
72754
|
}
|
|
@@ -72349,8 +72771,8 @@ async function clearRunMarker(outputsDir, canvasPath, token) {
|
|
|
72349
72771
|
|
|
72350
72772
|
// src/commands/canvas/run-snapshot.ts
|
|
72351
72773
|
import { existsSync as existsSync5 } from "fs";
|
|
72352
|
-
import { mkdir as mkdir5, readdir as
|
|
72353
|
-
import
|
|
72774
|
+
import { mkdir as mkdir5, readdir as readdir10, readFile as readFile18, stat as stat3, writeFile as writeFile10 } from "fs/promises";
|
|
72775
|
+
import path23 from "path";
|
|
72354
72776
|
var SNAPSHOT_SCHEMA = "baker-canvas-snapshot/1";
|
|
72355
72777
|
var MAX_SNAPSHOT_FILE_BYTES = 32 * 1024 * 1024;
|
|
72356
72778
|
var EXT_TO_MIME = {
|
|
@@ -72377,15 +72799,15 @@ var EXT_TO_MIME = {
|
|
|
72377
72799
|
woff2: "font/woff2"
|
|
72378
72800
|
};
|
|
72379
72801
|
function mimeForFile(filePath) {
|
|
72380
|
-
const ext =
|
|
72802
|
+
const ext = path23.extname(filePath).slice(1).toLowerCase();
|
|
72381
72803
|
return EXT_TO_MIME[ext] ?? "application/octet-stream";
|
|
72382
72804
|
}
|
|
72383
72805
|
function toPosix2(p) {
|
|
72384
|
-
return p.split(
|
|
72806
|
+
return p.split(path23.sep).join("/");
|
|
72385
72807
|
}
|
|
72386
72808
|
function isInside2(dir, target2) {
|
|
72387
|
-
const rel =
|
|
72388
|
-
return !rel.startsWith("..") && !
|
|
72809
|
+
const rel = path23.relative(dir, target2);
|
|
72810
|
+
return !rel.startsWith("..") && !path23.isAbsolute(rel);
|
|
72389
72811
|
}
|
|
72390
72812
|
function localPathRefsFromCanvas(parsed) {
|
|
72391
72813
|
const nodes = parsed?.nodes;
|
|
@@ -72408,20 +72830,20 @@ var FINGERPRINTED_FILES = ["prompt.json", "_definition.md"];
|
|
|
72408
72830
|
async function sourceRefsToSnapshot(canvasDir, parsed) {
|
|
72409
72831
|
const refs = new Set(localPathRefsFromCanvas(parsed));
|
|
72410
72832
|
for (const sceneFile of await listSceneFiles(canvasDir)) {
|
|
72411
|
-
refs.add(toPosix2(
|
|
72833
|
+
refs.add(toPosix2(path23.relative(canvasDir, sceneFile)));
|
|
72412
72834
|
}
|
|
72413
72835
|
refs.add(REBUILD_FILE);
|
|
72414
72836
|
for (const fingerprinted of FINGERPRINTED_FILES) {
|
|
72415
|
-
if (existsSync5(
|
|
72837
|
+
if (existsSync5(path23.join(canvasDir, fingerprinted))) refs.add(fingerprinted);
|
|
72416
72838
|
}
|
|
72417
72839
|
return [...refs];
|
|
72418
72840
|
}
|
|
72419
72841
|
async function uploadRunSnapshot(client, opts) {
|
|
72420
72842
|
try {
|
|
72421
|
-
const canvasDir =
|
|
72843
|
+
const canvasDir = path23.dirname(opts.canvasPath);
|
|
72422
72844
|
const canvasBytes = Buffer.from(opts.raw);
|
|
72423
72845
|
const canvasEntry = {
|
|
72424
|
-
path:
|
|
72846
|
+
path: path23.basename(opts.canvasPath),
|
|
72425
72847
|
bytes: canvasBytes,
|
|
72426
72848
|
sha256: sha256Hex(canvasBytes),
|
|
72427
72849
|
mime: "application/json"
|
|
@@ -72429,10 +72851,10 @@ async function uploadRunSnapshot(client, opts) {
|
|
|
72429
72851
|
const files = [];
|
|
72430
72852
|
const skipped = [];
|
|
72431
72853
|
for (const refPath of await sourceRefsToSnapshot(canvasDir, opts.parsed)) {
|
|
72432
|
-
const abs =
|
|
72854
|
+
const abs = path23.isAbsolute(refPath) ? refPath : path23.resolve(canvasDir, refPath);
|
|
72433
72855
|
if (!isInside2(canvasDir, abs)) {
|
|
72434
72856
|
skipped.push({
|
|
72435
|
-
path: toPosix2(
|
|
72857
|
+
path: toPosix2(path23.relative(canvasDir, abs)),
|
|
72436
72858
|
reason: "outside the creative folder \u2014 read from the workspace on rerun"
|
|
72437
72859
|
});
|
|
72438
72860
|
continue;
|
|
@@ -72441,18 +72863,18 @@ async function uploadRunSnapshot(client, opts) {
|
|
|
72441
72863
|
try {
|
|
72442
72864
|
st = await stat3(abs);
|
|
72443
72865
|
} catch {
|
|
72444
|
-
skipped.push({ path: toPosix2(
|
|
72866
|
+
skipped.push({ path: toPosix2(path23.relative(canvasDir, abs)), reason: "missing" });
|
|
72445
72867
|
continue;
|
|
72446
72868
|
}
|
|
72447
72869
|
const fileList = st.isDirectory() ? await listFilesRecursive(abs) : [abs];
|
|
72448
72870
|
for (const file of fileList) {
|
|
72449
|
-
const rel = toPosix2(
|
|
72871
|
+
const rel = toPosix2(path23.relative(canvasDir, file));
|
|
72450
72872
|
const size = (await stat3(file)).size;
|
|
72451
72873
|
if (size > MAX_SNAPSHOT_FILE_BYTES) {
|
|
72452
72874
|
skipped.push({ path: rel, reason: `too large (${size} bytes)` });
|
|
72453
72875
|
continue;
|
|
72454
72876
|
}
|
|
72455
|
-
const bytes = await
|
|
72877
|
+
const bytes = await readFile18(file);
|
|
72456
72878
|
files.push({ path: rel, bytes, sha256: sha256Hex(bytes), mime: mimeForFile(file) });
|
|
72457
72879
|
}
|
|
72458
72880
|
}
|
|
@@ -72508,8 +72930,8 @@ async function putContentAddressed(client, bytes, mime, signal) {
|
|
|
72508
72930
|
return { sha256: sha2562, url: publicUrl };
|
|
72509
72931
|
}
|
|
72510
72932
|
async function listFilesRecursive(dir) {
|
|
72511
|
-
const entries = await
|
|
72512
|
-
return entries.filter((d) => d.isFile()).map((d) =>
|
|
72933
|
+
const entries = await readdir10(dir, { recursive: true, withFileTypes: true });
|
|
72934
|
+
return entries.filter((d) => d.isFile()).map((d) => path23.join(d.parentPath, d.name));
|
|
72513
72935
|
}
|
|
72514
72936
|
var SnapshotConflictError = class extends Error {
|
|
72515
72937
|
conflicts;
|
|
@@ -72521,19 +72943,19 @@ var SnapshotConflictError = class extends Error {
|
|
|
72521
72943
|
};
|
|
72522
72944
|
async function restoreRunSnapshot(manifest, targetDir, opts = {}) {
|
|
72523
72945
|
const entries = [manifest.canvas, ...manifest.files];
|
|
72524
|
-
const resolvedTarget =
|
|
72946
|
+
const resolvedTarget = path23.resolve(targetDir);
|
|
72525
72947
|
const planned = [];
|
|
72526
72948
|
const conflicts = [];
|
|
72527
72949
|
const upToDate = [];
|
|
72528
72950
|
for (const entry of entries) {
|
|
72529
|
-
if (
|
|
72951
|
+
if (path23.isAbsolute(entry.path) || entry.path.split("/").includes("..")) {
|
|
72530
72952
|
throw new Error(`snapshot entry escapes the creative directory: ${entry.path}`);
|
|
72531
72953
|
}
|
|
72532
|
-
const target2 =
|
|
72533
|
-
if (target2 !== resolvedTarget && !target2.startsWith(resolvedTarget +
|
|
72954
|
+
const target2 = path23.resolve(resolvedTarget, entry.path);
|
|
72955
|
+
if (target2 !== resolvedTarget && !target2.startsWith(resolvedTarget + path23.sep)) {
|
|
72534
72956
|
throw new Error(`snapshot entry escapes the creative directory: ${entry.path}`);
|
|
72535
72957
|
}
|
|
72536
|
-
const existing = await
|
|
72958
|
+
const existing = await readFile18(target2).catch(() => null);
|
|
72537
72959
|
if (existing) {
|
|
72538
72960
|
if (sha256Hex(existing) === entry.sha256) {
|
|
72539
72961
|
upToDate.push(entry.path);
|
|
@@ -72555,11 +72977,11 @@ async function restoreRunSnapshot(manifest, targetDir, opts = {}) {
|
|
|
72555
72977
|
if (sha256Hex(bytes) !== entry.sha256) {
|
|
72556
72978
|
throw new Error(`snapshot download for ${entry.path} does not match its recorded sha256`);
|
|
72557
72979
|
}
|
|
72558
|
-
await mkdir5(
|
|
72980
|
+
await mkdir5(path23.dirname(target2), { recursive: true });
|
|
72559
72981
|
await writeFile10(target2, bytes);
|
|
72560
72982
|
restored.push(entry.path);
|
|
72561
72983
|
}
|
|
72562
|
-
return { canvasPath:
|
|
72984
|
+
return { canvasPath: path23.resolve(resolvedTarget, manifest.canvas.path), restored, upToDate };
|
|
72563
72985
|
}
|
|
72564
72986
|
|
|
72565
72987
|
// src/commands/canvas/static-ad-canvas.ts
|
|
@@ -72886,7 +73308,7 @@ async function fetchBrandLogo(domain) {
|
|
|
72886
73308
|
|
|
72887
73309
|
// src/commands/canvas/slug-restore.ts
|
|
72888
73310
|
import { existsSync as existsSync6 } from "fs";
|
|
72889
|
-
import
|
|
73311
|
+
import path24 from "path";
|
|
72890
73312
|
var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
72891
73313
|
var SlugRestoreError = class extends Error {
|
|
72892
73314
|
code;
|
|
@@ -72902,8 +73324,8 @@ async function canvasForSlug(slug, opts) {
|
|
|
72902
73324
|
if (!SLUG_PATTERN.test(slug)) {
|
|
72903
73325
|
throw new SlugRestoreError("invalid_slug", `"${slug}" is not a valid creative slug`);
|
|
72904
73326
|
}
|
|
72905
|
-
const targetDir =
|
|
72906
|
-
const local =
|
|
73327
|
+
const targetDir = path24.join(opts.workspaceRoot, "src", "creatives", slug);
|
|
73328
|
+
const local = path24.join(targetDir, `${slug}.canvas.json`);
|
|
72907
73329
|
if (!opts.forceRemote && existsSync6(local)) return { canvasPath: local };
|
|
72908
73330
|
const client = opts.client();
|
|
72909
73331
|
if (!client) {
|
|
@@ -73348,11 +73770,11 @@ function withDeliveredSizes(canvas, last) {
|
|
|
73348
73770
|
}
|
|
73349
73771
|
|
|
73350
73772
|
// src/commands/canvas/static-fix.ts
|
|
73351
|
-
import { readFile as
|
|
73352
|
-
import
|
|
73773
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
73774
|
+
import path25 from "path";
|
|
73353
73775
|
async function previousAdImage(canvasRoot, runId, fetchRecord = fetchRunRecord, roles = {}) {
|
|
73354
73776
|
try {
|
|
73355
|
-
const manifest = JSON.parse(await
|
|
73777
|
+
const manifest = JSON.parse(await readFile19(path25.join(canvasRoot, runId, "manifest.json"), "utf8"));
|
|
73356
73778
|
const byNode = Object.fromEntries(
|
|
73357
73779
|
Object.entries(manifest.outputs_by_node ?? {}).map(([id, out]) => [id, { ...out, ...roles[id] ? { role: roles[id] } : {} }])
|
|
73358
73780
|
);
|
|
@@ -73368,7 +73790,7 @@ async function previousAdImage(canvasRoot, runId, fetchRecord = fetchRunRecord,
|
|
|
73368
73790
|
}
|
|
73369
73791
|
async function previousDelivered(canvasRoot, runId, finalNode, labels, opts = {}) {
|
|
73370
73792
|
try {
|
|
73371
|
-
const manifest = JSON.parse(await
|
|
73793
|
+
const manifest = JSON.parse(await readFile19(path25.join(canvasRoot, runId, "manifest.json"), "utf8"));
|
|
73372
73794
|
const images = manifest.outputs_by_node?.[finalNode]?.images ?? [];
|
|
73373
73795
|
return images.flatMap((image, i) => {
|
|
73374
73796
|
const local = typeof image.path === "string" ? image.path : null;
|
|
@@ -73414,7 +73836,7 @@ function correctedUrl(byNode) {
|
|
|
73414
73836
|
}
|
|
73415
73837
|
async function correctedPicture(canvasRoot, runId, fetchRecord = fetchRunRecord) {
|
|
73416
73838
|
try {
|
|
73417
|
-
const manifest = JSON.parse(await
|
|
73839
|
+
const manifest = JSON.parse(await readFile19(path25.join(canvasRoot, runId, "manifest.json"), "utf8"));
|
|
73418
73840
|
if (manifest.outputs_by_node) return correctedUrl(manifest.outputs_by_node);
|
|
73419
73841
|
} catch {
|
|
73420
73842
|
}
|
|
@@ -73446,7 +73868,7 @@ async function variantImage(canvasRoot, runId, sha2562, fetchRecord = fetchRunRe
|
|
|
73446
73868
|
const sha = sha2562.trim().toLowerCase();
|
|
73447
73869
|
if (!SHA_PREFIX.test(sha)) return null;
|
|
73448
73870
|
try {
|
|
73449
|
-
const manifest = JSON.parse(await
|
|
73871
|
+
const manifest = JSON.parse(await readFile19(path25.join(canvasRoot, runId, "manifest.json"), "utf8"));
|
|
73450
73872
|
for (const [id, out] of Object.entries(manifest.outputs_by_node ?? {})) {
|
|
73451
73873
|
if (roles[id] !== STATIC_VARIANTS_ROLE) continue;
|
|
73452
73874
|
const url = matchVariant(out.images ?? [], sha);
|
|
@@ -73460,7 +73882,7 @@ async function variantImage(canvasRoot, runId, sha2562, fetchRecord = fetchRunRe
|
|
|
73460
73882
|
}
|
|
73461
73883
|
|
|
73462
73884
|
// src/commands/canvas/static-run-review.ts
|
|
73463
|
-
import { readFile as
|
|
73885
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
73464
73886
|
|
|
73465
73887
|
// src/commands/canvas/glance-check.ts
|
|
73466
73888
|
import sharp2 from "sharp";
|
|
@@ -73468,16 +73890,16 @@ import { z as z65 } from "zod";
|
|
|
73468
73890
|
|
|
73469
73891
|
// src/commands/canvas/viewer-check.ts
|
|
73470
73892
|
import { createHash as createHash4 } from "crypto";
|
|
73471
|
-
import { mkdir as mkdir8, readdir as
|
|
73472
|
-
import
|
|
73893
|
+
import { mkdir as mkdir8, readdir as readdir12, readFile as readFile22, stat as stat4, writeFile as writeFile13 } from "fs/promises";
|
|
73894
|
+
import path28 from "path";
|
|
73473
73895
|
import { defineCommand as defineCommand138 } from "citty";
|
|
73474
|
-
import { parse as
|
|
73896
|
+
import { parse as parseYaml3 } from "yaml";
|
|
73475
73897
|
import { z as z64 } from "zod";
|
|
73476
73898
|
|
|
73477
73899
|
// src/commands/canvas/unbilled-spend.ts
|
|
73478
73900
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
73479
|
-
import { mkdir as mkdir6, readdir as
|
|
73480
|
-
import
|
|
73901
|
+
import { mkdir as mkdir6, readdir as readdir11, readFile as readFile20, rename, unlink, writeFile as writeFile11 } from "fs/promises";
|
|
73902
|
+
import path26 from "path";
|
|
73481
73903
|
import { z as z62 } from "zod";
|
|
73482
73904
|
var DIR = "unbilled-spend";
|
|
73483
73905
|
var Held = z62.array(
|
|
@@ -73485,22 +73907,22 @@ var Held = z62.array(
|
|
|
73485
73907
|
);
|
|
73486
73908
|
async function holdUnbilledSpend(cacheDir, usage) {
|
|
73487
73909
|
if (usage.length === 0) return;
|
|
73488
|
-
const dir =
|
|
73910
|
+
const dir = path26.join(cacheDir, DIR);
|
|
73489
73911
|
await mkdir6(dir, { recursive: true });
|
|
73490
|
-
await writeFile11(
|
|
73912
|
+
await writeFile11(path26.join(dir, `${Date.now()}-${randomUUID2()}.json`), JSON.stringify(usage), "utf8");
|
|
73491
73913
|
}
|
|
73492
73914
|
async function takeUnbilledSpend(cacheDir) {
|
|
73493
|
-
const dir =
|
|
73494
|
-
const files = await
|
|
73915
|
+
const dir = path26.join(cacheDir, DIR);
|
|
73916
|
+
const files = await readdir11(dir).catch(() => []);
|
|
73495
73917
|
const byModel = /* @__PURE__ */ new Map();
|
|
73496
73918
|
for (const file of files.filter((f) => f.endsWith(".json"))) {
|
|
73497
|
-
const claimed =
|
|
73919
|
+
const claimed = path26.join(dir, `${file}.${randomUUID2()}.taken`);
|
|
73498
73920
|
try {
|
|
73499
|
-
await rename(
|
|
73921
|
+
await rename(path26.join(dir, file), claimed);
|
|
73500
73922
|
} catch {
|
|
73501
73923
|
continue;
|
|
73502
73924
|
}
|
|
73503
|
-
const parsed = Held.safeParse(await
|
|
73925
|
+
const parsed = Held.safeParse(await readFile20(claimed, "utf8").then(JSON.parse, () => null).catch(() => null));
|
|
73504
73926
|
await unlink(claimed).catch(() => {
|
|
73505
73927
|
});
|
|
73506
73928
|
for (const call of parsed.success ? parsed.data : []) {
|
|
@@ -73543,8 +73965,8 @@ function holdWhenSettled(cacheDir, spendOf) {
|
|
|
73543
73965
|
|
|
73544
73966
|
// src/commands/canvas/casting-check.ts
|
|
73545
73967
|
import { createHash as createHash3 } from "crypto";
|
|
73546
|
-
import { mkdir as mkdir7, readFile as
|
|
73547
|
-
import
|
|
73968
|
+
import { mkdir as mkdir7, readFile as readFile21, writeFile as writeFile12 } from "fs/promises";
|
|
73969
|
+
import path27 from "path";
|
|
73548
73970
|
import { z as z63 } from "zod";
|
|
73549
73971
|
var CastingAnswerSchema = z63.object({
|
|
73550
73972
|
passes: z63.boolean(),
|
|
@@ -73576,15 +73998,15 @@ function parseOrNull(raw) {
|
|
|
73576
73998
|
}
|
|
73577
73999
|
async function castingCheck(input, opts) {
|
|
73578
74000
|
if (input.people.length === 0) return { status: "skipped" };
|
|
73579
|
-
const file =
|
|
73580
|
-
const cachedRaw = await
|
|
74001
|
+
const file = path27.join(opts.cacheDir, "casting-check", `${cacheKey(input, opts.model)}.json`);
|
|
74002
|
+
const cachedRaw = await readFile21(file, "utf8").catch(() => null);
|
|
73581
74003
|
const cachedParse = cachedRaw ? CastingAnswerSchema.safeParse(parseOrNull(cachedRaw)) : null;
|
|
73582
74004
|
if (cachedParse?.success) return { status: cachedParse.data.passes ? "passed" : "failed", answer: cachedParse.data, cached: true };
|
|
73583
74005
|
const prompt = buildCastingPrompt(input);
|
|
73584
74006
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
73585
74007
|
const parsed = CastingAnswerSchema.safeParse(await opts.ask(prompt));
|
|
73586
74008
|
if (!parsed.success) continue;
|
|
73587
|
-
await mkdir7(
|
|
74009
|
+
await mkdir7(path27.dirname(file), { recursive: true });
|
|
73588
74010
|
await writeFile12(file, `${JSON.stringify(parsed.data, null, 2)}
|
|
73589
74011
|
`, "utf8");
|
|
73590
74012
|
return { status: parsed.data.passes ? "passed" : "failed", answer: parsed.data, cached: false };
|
|
@@ -73723,14 +74145,14 @@ function viewerCheckCacheKey(input, model) {
|
|
|
73723
74145
|
}
|
|
73724
74146
|
async function cachedAnswer(file) {
|
|
73725
74147
|
try {
|
|
73726
|
-
const parsed = ViewerAnswerSchema.safeParse(JSON.parse(await
|
|
74148
|
+
const parsed = ViewerAnswerSchema.safeParse(JSON.parse(await readFile22(file, "utf8")));
|
|
73727
74149
|
return parsed.success ? parsed.data : null;
|
|
73728
74150
|
} catch {
|
|
73729
74151
|
return null;
|
|
73730
74152
|
}
|
|
73731
74153
|
}
|
|
73732
74154
|
async function checkViewer(input, opts) {
|
|
73733
|
-
const file =
|
|
74155
|
+
const file = path28.join(opts.cacheDir, "viewer-check", `${viewerCheckCacheKey(input, opts.model)}.json`);
|
|
73734
74156
|
let answer = await cachedAnswer(file);
|
|
73735
74157
|
const cached = answer !== null;
|
|
73736
74158
|
const prompt = buildViewerPrompt(input);
|
|
@@ -73740,7 +74162,7 @@ async function checkViewer(input, opts) {
|
|
|
73740
74162
|
}
|
|
73741
74163
|
if (answer === null) return { status: "unavailable", input };
|
|
73742
74164
|
if (!cached) {
|
|
73743
|
-
await mkdir8(
|
|
74165
|
+
await mkdir8(path28.dirname(file), { recursive: true });
|
|
73744
74166
|
await writeFile13(file, `${JSON.stringify(answer, null, 2)}
|
|
73745
74167
|
`, "utf8");
|
|
73746
74168
|
}
|
|
@@ -73867,19 +74289,19 @@ var Frontmatter = z64.object({
|
|
|
73867
74289
|
awareness: z64.enum(AWARENESS_VALUES).optional().catch(void 0),
|
|
73868
74290
|
angle: z64.string().optional()
|
|
73869
74291
|
});
|
|
73870
|
-
function
|
|
74292
|
+
function frontmatterOf2(raw) {
|
|
73871
74293
|
const block2 = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1];
|
|
73872
74294
|
if (!block2) return {};
|
|
73873
74295
|
try {
|
|
73874
|
-
const parsed = Frontmatter.safeParse(
|
|
74296
|
+
const parsed = Frontmatter.safeParse(parseYaml3(block2));
|
|
73875
74297
|
return parsed.success ? parsed.data : {};
|
|
73876
74298
|
} catch {
|
|
73877
74299
|
return {};
|
|
73878
74300
|
}
|
|
73879
74301
|
}
|
|
73880
74302
|
async function readCreativeForViewerCheck(creativeDir) {
|
|
73881
|
-
const spec = readAdSpec(JSON.parse(await
|
|
73882
|
-
const definition =
|
|
74303
|
+
const spec = readAdSpec(JSON.parse(await readFile22(path28.join(creativeDir, "prompt.json"), "utf8")));
|
|
74304
|
+
const definition = frontmatterOf2(await readFile22(path28.join(creativeDir, "_definition.md"), "utf8").catch(() => ""));
|
|
73883
74305
|
const offer = spec.message?.trim() || definition.angle?.trim();
|
|
73884
74306
|
const persona = spec.viewer?.who.trim() || definition.persona?.trim();
|
|
73885
74307
|
const brand = spec.brand?.name?.trim();
|
|
@@ -73908,26 +74330,26 @@ function castPeople(spec) {
|
|
|
73908
74330
|
return people.length > 0 ? { people } : {};
|
|
73909
74331
|
}
|
|
73910
74332
|
async function readPersonas(workspaceRoot) {
|
|
73911
|
-
const dir =
|
|
74333
|
+
const dir = path28.join(workspaceRoot, "src", "content", "personas");
|
|
73912
74334
|
let files;
|
|
73913
74335
|
try {
|
|
73914
|
-
files = (await
|
|
74336
|
+
files = (await readdir12(dir, { recursive: true })).filter((f) => f.endsWith(".md"));
|
|
73915
74337
|
} catch {
|
|
73916
74338
|
return [];
|
|
73917
74339
|
}
|
|
73918
74340
|
return await Promise.all(
|
|
73919
74341
|
files.map(async (file) => {
|
|
73920
|
-
const raw = await
|
|
74342
|
+
const raw = await readFile22(path28.join(dir, file), "utf8");
|
|
73921
74343
|
const block2 = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1];
|
|
73922
74344
|
let segment;
|
|
73923
74345
|
try {
|
|
73924
|
-
segment = block2 ?
|
|
74346
|
+
segment = block2 ? parseYaml3(block2)?.segment : void 0;
|
|
73925
74347
|
} catch {
|
|
73926
74348
|
segment = void 0;
|
|
73927
74349
|
}
|
|
73928
74350
|
const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim();
|
|
73929
74351
|
return {
|
|
73930
|
-
slug:
|
|
74352
|
+
slug: path28.basename(file, ".md"),
|
|
73931
74353
|
...title ? { title } : {},
|
|
73932
74354
|
...typeof segment === "string" && segment.trim() ? { segment: segment.trim() } : {}
|
|
73933
74355
|
};
|
|
@@ -73966,8 +74388,8 @@ function liveViewerLookups(workspaceRoot) {
|
|
|
73966
74388
|
return {
|
|
73967
74389
|
adSetViewer: metaAdSetViewer,
|
|
73968
74390
|
personas: () => readPersonas(workspaceRoot),
|
|
73969
|
-
companyMarket: async () => marketFromCompanyDoc(await
|
|
73970
|
-
company: async () => (await
|
|
74391
|
+
companyMarket: async () => marketFromCompanyDoc(await readFile22(path28.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => null)),
|
|
74392
|
+
company: async () => (await readFile22(path28.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => "")).slice(0, 800)
|
|
73971
74393
|
};
|
|
73972
74394
|
}
|
|
73973
74395
|
function gatewayViewerModel(model, onSpend) {
|
|
@@ -74038,11 +74460,11 @@ var ARGS5 = {
|
|
|
74038
74460
|
};
|
|
74039
74461
|
registerSchema({ command: "canvas.viewer-check", description: DESCRIPTION2, args: ARGS5 });
|
|
74040
74462
|
async function creativeDirOf(arg, workspaceRoot) {
|
|
74041
|
-
const direct =
|
|
74463
|
+
const direct = path28.resolve(arg);
|
|
74042
74464
|
try {
|
|
74043
|
-
return (await stat4(direct)).isDirectory() ? direct :
|
|
74465
|
+
return (await stat4(direct)).isDirectory() ? direct : path28.dirname(direct);
|
|
74044
74466
|
} catch {
|
|
74045
|
-
return
|
|
74467
|
+
return path28.join(workspaceRoot, "src", "creatives", arg);
|
|
74046
74468
|
}
|
|
74047
74469
|
}
|
|
74048
74470
|
var viewerCheckCommand = defineCommand138({
|
|
@@ -74052,7 +74474,7 @@ var viewerCheckCommand = defineCommand138({
|
|
|
74052
74474
|
const cwd = process.cwd();
|
|
74053
74475
|
const workspaceRoot = findWorkspaceRoot(cwd) ?? cwd;
|
|
74054
74476
|
const creativeDir = await creativeDirOf(String(args8.slug), workspaceRoot);
|
|
74055
|
-
const slug =
|
|
74477
|
+
const slug = path28.basename(creativeDir);
|
|
74056
74478
|
let creative;
|
|
74057
74479
|
try {
|
|
74058
74480
|
creative = await readCreativeForViewerCheck(creativeDir);
|
|
@@ -74069,7 +74491,7 @@ var viewerCheckCommand = defineCommand138({
|
|
|
74069
74491
|
}
|
|
74070
74492
|
const resolved = await resolveViewer(creative.sources, liveViewerLookups(workspaceRoot));
|
|
74071
74493
|
const input = { ...creative.ad, viewer: resolved.viewer };
|
|
74072
|
-
const cacheDir =
|
|
74494
|
+
const cacheDir = path28.join(workspaceRoot, "canvas", ".cache");
|
|
74073
74495
|
const spent = [];
|
|
74074
74496
|
const outcome2 = await checkViewer(input, {
|
|
74075
74497
|
cacheDir,
|
|
@@ -74300,7 +74722,7 @@ function runCostHint(stats, kind) {
|
|
|
74300
74722
|
async function textOf(ref2) {
|
|
74301
74723
|
const r = ref2;
|
|
74302
74724
|
try {
|
|
74303
|
-
if (typeof r?.path === "string") return await
|
|
74725
|
+
if (typeof r?.path === "string") return await readFile23(r.path, "utf8");
|
|
74304
74726
|
if (typeof r?.url === "string") {
|
|
74305
74727
|
const res = await fetch(r.url);
|
|
74306
74728
|
return res.ok ? await res.text() : null;
|
|
@@ -74367,7 +74789,7 @@ async function reviewStaticRun(opts) {
|
|
|
74367
74789
|
let native;
|
|
74368
74790
|
let palette = [];
|
|
74369
74791
|
try {
|
|
74370
|
-
const spec = readAdSpec(JSON.parse(await
|
|
74792
|
+
const spec = readAdSpec(JSON.parse(await readFile23(targets.promptPath, "utf8")));
|
|
74371
74793
|
copy = spec.copy.map((l) => l.text);
|
|
74372
74794
|
lines = spec.copy.map((l) => ({ text: l.text, lettering: l.lettering, ...l.role ? { role: l.role } : {} }));
|
|
74373
74795
|
if (spec.native) {
|
|
@@ -74388,7 +74810,7 @@ async function reviewStaticRun(opts) {
|
|
|
74388
74810
|
...allowedSceneWords(spec),
|
|
74389
74811
|
...spec.native ? nativeChrome(spec.native, spec.viewer?.market?.language) : []
|
|
74390
74812
|
];
|
|
74391
|
-
expectLogo = spec.logo && !spec.native;
|
|
74813
|
+
expectLogo = spec.logo && !spec.native && staticRunShape(opts.parsed).logo;
|
|
74392
74814
|
concept = conceptSummary(spec);
|
|
74393
74815
|
uncast = spec.native ? [] : uncastPeople(spec);
|
|
74394
74816
|
} catch {
|
|
@@ -74543,7 +74965,7 @@ function variantHints(judge, prefilter) {
|
|
|
74543
74965
|
}
|
|
74544
74966
|
|
|
74545
74967
|
// src/commands/canvas/static-spec-repair.ts
|
|
74546
|
-
import { readFile as
|
|
74968
|
+
import { readFile as readFile24, writeFile as writeFile14 } from "fs/promises";
|
|
74547
74969
|
import sharp3 from "sharp";
|
|
74548
74970
|
import { z as z66 } from "zod";
|
|
74549
74971
|
var Answer = z66.object({
|
|
@@ -74602,7 +75024,7 @@ function buildSpecRepairPrompt(spec, findings, schema) {
|
|
|
74602
75024
|
var REPAIR_TIMEOUT_MS = 15e4;
|
|
74603
75025
|
var SHOWN_WIDTH = 768;
|
|
74604
75026
|
async function repairSpec(opts) {
|
|
74605
|
-
const text3 = await
|
|
75027
|
+
const text3 = await readFile24(opts.promptPath, "utf8").catch(() => null);
|
|
74606
75028
|
if (text3 === null) return null;
|
|
74607
75029
|
let before;
|
|
74608
75030
|
try {
|
|
@@ -74741,7 +75163,7 @@ function failPreflight3(code, message, fix) {
|
|
|
74741
75163
|
process.exit(2);
|
|
74742
75164
|
}
|
|
74743
75165
|
async function canvasFileFor(opts) {
|
|
74744
|
-
if (opts.file) return { canvasPath:
|
|
75166
|
+
if (opts.file) return { canvasPath: path29.resolve(opts.file) };
|
|
74745
75167
|
if (!opts.slug) failPreflight3("missing_canvas", "Pass the canvas file, or --slug <slug> to run a creative by name.");
|
|
74746
75168
|
const cwd = process.cwd();
|
|
74747
75169
|
try {
|
|
@@ -74763,14 +75185,14 @@ async function executeCanvasRun(opts) {
|
|
|
74763
75185
|
const creativeSlug = creativeSlugFromCanvasPath(filePath) ?? void 0;
|
|
74764
75186
|
if (creativeSlug) await clearLegacyDirtyMarker(canvasRoot, creativeSlug);
|
|
74765
75187
|
await wireProofBadges(
|
|
74766
|
-
|
|
75188
|
+
path29.join(path29.dirname(filePath), "prompt.json"),
|
|
74767
75189
|
fetchBrandLogo,
|
|
74768
75190
|
(line) => process.stderr.write(`${line}
|
|
74769
75191
|
`)
|
|
74770
75192
|
);
|
|
74771
75193
|
if ((opts.autoFixRound ?? 0) === 0) {
|
|
74772
75194
|
await wireNamedPeople(
|
|
74773
|
-
|
|
75195
|
+
path29.join(path29.dirname(filePath), "prompt.json"),
|
|
74774
75196
|
avatarOfNamedPerson,
|
|
74775
75197
|
(line) => process.stderr.write(`${line}
|
|
74776
75198
|
`)
|
|
@@ -74962,7 +75384,7 @@ ${describeRewrites(prepared.rewrites)}
|
|
|
74962
75384
|
process.stdout.write(`[extend] placing ${opts.extendSizes.join(", ")} clear of what covers or cuts it, its margins painted
|
|
74963
75385
|
`);
|
|
74964
75386
|
}
|
|
74965
|
-
const cacheDir = opts.cacheDir ??
|
|
75387
|
+
const cacheDir = opts.cacheDir ?? path29.join(canvasRoot, ".cache");
|
|
74966
75388
|
const engine = createEngineFromEnv({
|
|
74967
75389
|
cacheDir,
|
|
74968
75390
|
outputsDir: opts.outputsDir ?? canvasRoot,
|
|
@@ -74985,7 +75407,7 @@ ${describeRewrites(prepared.rewrites)}
|
|
|
74985
75407
|
const canvasSha = sha256Hex(Buffer.from(canvasText));
|
|
74986
75408
|
const sourceSha = prepared.sourceSha;
|
|
74987
75409
|
const client = opts.record === false ? null : buildBackendClient();
|
|
74988
|
-
const outputsDir = opts.outputsDir ?
|
|
75410
|
+
const outputsDir = opts.outputsDir ? path29.resolve(opts.outputsDir) : canvasRoot;
|
|
74989
75411
|
const { runId, resumed, source, sameSource, concurrentRunId, rendering } = await resolveRunId({
|
|
74990
75412
|
explicitRunId: opts.runId,
|
|
74991
75413
|
adoptedRunId: adoptRunId,
|
|
@@ -75015,7 +75437,7 @@ ${describeRewrites(prepared.rewrites)}
|
|
|
75015
75437
|
}
|
|
75016
75438
|
const viewerUsage = [];
|
|
75017
75439
|
const viewerCheck = creativeSlug && isStaticAdCanvas(parsed) ? await viewerCheckBeforeRender({
|
|
75018
|
-
creativeDir:
|
|
75440
|
+
creativeDir: path29.dirname(filePath),
|
|
75019
75441
|
cacheDir,
|
|
75020
75442
|
slug: creativeSlug,
|
|
75021
75443
|
skip: viewerCheckSkip({
|
|
@@ -75026,7 +75448,7 @@ ${describeRewrites(prepared.rewrites)}
|
|
|
75026
75448
|
resumed: resumed && sameSource === true,
|
|
75027
75449
|
asked: opts.skipViewerCheck === true
|
|
75028
75450
|
}),
|
|
75029
|
-
lookups: liveViewerLookups(
|
|
75451
|
+
lookups: liveViewerLookups(path29.dirname(canvasRoot)),
|
|
75030
75452
|
model: VIEWER_CHECK_MODEL,
|
|
75031
75453
|
ask: gatewayViewerModel(VIEWER_CHECK_MODEL, (usage) => viewerUsage.push(usage))
|
|
75032
75454
|
}) : null;
|
|
@@ -75075,7 +75497,7 @@ ${describeRewrites(prepared.rewrites)}
|
|
|
75075
75497
|
if (snapshot && creativeSlug) await recordSnapshot(canvasRoot, creativeSlug, snapshot);
|
|
75076
75498
|
const recordMeta = {
|
|
75077
75499
|
creativeSlug,
|
|
75078
|
-
canvasPath:
|
|
75500
|
+
canvasPath: path29.relative(path29.dirname(canvasRoot), filePath) || void 0,
|
|
75079
75501
|
canvasSha,
|
|
75080
75502
|
// The fingerprint the dashboard compares against the current source to flag
|
|
75081
75503
|
// "edited since last render": the same `computeSourceSha` the edit-sync posts,
|
|
@@ -75163,7 +75585,7 @@ ${describeRewrites(prepared.rewrites)}
|
|
|
75163
75585
|
brief: opts.brief?.trim() || void 0,
|
|
75164
75586
|
onSpend: (usage) => reviewUsage.push(usage)
|
|
75165
75587
|
}) : null;
|
|
75166
|
-
const brandKit = reviewable ? null : await readStaticBrandKit(await findBrandRoot(
|
|
75588
|
+
const brandKit = reviewable ? null : await readStaticBrandKit(await findBrandRoot(path29.dirname(filePath)) ?? process.cwd());
|
|
75167
75589
|
const runShape = {
|
|
75168
75590
|
fix: fixInstruction !== void 0,
|
|
75169
75591
|
useVariant: opts.useVariant !== void 0,
|
|
@@ -75216,7 +75638,7 @@ ${describeRewrites(prepared.rewrites)}
|
|
|
75216
75638
|
] : [];
|
|
75217
75639
|
const findings = staticReview ? staticReview.review : review;
|
|
75218
75640
|
const ready = staticReview ? staticReview.verdict === "ready" || staticReview.verdict === "ready_with_notes" : review !== null && !review.some((f) => f.severity === "blocking");
|
|
75219
|
-
const definitionStatus = ready && creativeSlug ? await advanceDefinitionStatus(
|
|
75641
|
+
const definitionStatus = ready && creativeSlug ? await advanceDefinitionStatus(path29.join(path29.dirname(filePath), "_definition.md"), "rendered") : null;
|
|
75220
75642
|
if (poster) {
|
|
75221
75643
|
await flushBilled(
|
|
75222
75644
|
poster,
|
|
@@ -75448,14 +75870,14 @@ var rerunCommand = defineCommand140({
|
|
|
75448
75870
|
});
|
|
75449
75871
|
|
|
75450
75872
|
// src/commands/canvas/scaffold-static-ad.ts
|
|
75451
|
-
import { mkdir as mkdir11, readFile as
|
|
75452
|
-
import
|
|
75873
|
+
import { mkdir as mkdir11, readFile as readFile28, writeFile as writeFile17 } from "fs/promises";
|
|
75874
|
+
import path35 from "path";
|
|
75453
75875
|
import { defineCommand as defineCommand143 } from "citty";
|
|
75454
75876
|
|
|
75455
75877
|
// src/commands/canvas/creative-limit.ts
|
|
75456
75878
|
import { existsSync as existsSync7 } from "fs";
|
|
75457
|
-
import { mkdir as mkdir9, readdir as
|
|
75458
|
-
import
|
|
75879
|
+
import { mkdir as mkdir9, readdir as readdir13, writeFile as writeFile15 } from "fs/promises";
|
|
75880
|
+
import path30 from "path";
|
|
75459
75881
|
var NEW_CREATIVES_PER_CHAT = 10;
|
|
75460
75882
|
function claimNewCreative(made, slug) {
|
|
75461
75883
|
if (made.includes(slug)) return { allowed: true, count: made.length };
|
|
@@ -75463,10 +75885,10 @@ function claimNewCreative(made, slug) {
|
|
|
75463
75885
|
return { allowed: true, count: made.length + 1 };
|
|
75464
75886
|
}
|
|
75465
75887
|
function chatDir(canvasRoot, chatId) {
|
|
75466
|
-
return
|
|
75888
|
+
return path30.join(canvasRoot, ".creative-limit", (chatId ?? "no-chat").replace(/[^A-Za-z0-9_-]/g, "_"));
|
|
75467
75889
|
}
|
|
75468
75890
|
async function madeInChat(dir) {
|
|
75469
|
-
return
|
|
75891
|
+
return readdir13(dir).catch(() => []);
|
|
75470
75892
|
}
|
|
75471
75893
|
async function claimNewCreativeInWorkspace(canvasRoot, slug, chatId, definitionPath, exists3 = existsSync7) {
|
|
75472
75894
|
const dir = chatDir(canvasRoot, chatId);
|
|
@@ -75476,7 +75898,7 @@ async function claimNewCreativeInWorkspace(canvasRoot, slug, chatId, definitionP
|
|
|
75476
75898
|
if (claim.allowed && !made.includes(slug)) {
|
|
75477
75899
|
try {
|
|
75478
75900
|
await mkdir9(dir, { recursive: true });
|
|
75479
|
-
await writeFile15(
|
|
75901
|
+
await writeFile15(path30.join(dir, slug), "", { flag: "wx" });
|
|
75480
75902
|
} catch {
|
|
75481
75903
|
}
|
|
75482
75904
|
}
|
|
@@ -75497,7 +75919,7 @@ async function enforceNewCreativeLimit(slug, canvasPath) {
|
|
|
75497
75919
|
canvasOutputsRoot(canvasPath),
|
|
75498
75920
|
slug,
|
|
75499
75921
|
optionalChatId() ?? null,
|
|
75500
|
-
|
|
75922
|
+
path30.join(path30.dirname(path30.resolve(canvasPath)), "_definition.md")
|
|
75501
75923
|
);
|
|
75502
75924
|
if (claim.allowed) return;
|
|
75503
75925
|
process.stderr.write(`${JSON.stringify(creativeLimitRefusal(slug), null, 2)}
|
|
@@ -75506,22 +75928,22 @@ async function enforceNewCreativeLimit(slug, canvasPath) {
|
|
|
75506
75928
|
}
|
|
75507
75929
|
|
|
75508
75930
|
// src/commands/canvas/scaffold-common.ts
|
|
75509
|
-
import { access, readFile as
|
|
75931
|
+
import { access, readFile as readFile25 } from "fs/promises";
|
|
75510
75932
|
|
|
75511
75933
|
// src/commands/canvas/composition-path.ts
|
|
75512
75934
|
import { existsSync as existsSync8 } from "fs";
|
|
75513
|
-
import
|
|
75935
|
+
import path31 from "path";
|
|
75514
75936
|
function resolveShippedCanvasDir(name, startDir, exists3 = existsSync8, maxDepth = 8) {
|
|
75515
|
-
const rel =
|
|
75937
|
+
const rel = path31.join("canvas", name);
|
|
75516
75938
|
let dir = startDir;
|
|
75517
75939
|
for (let i = 0; i < maxDepth; i++) {
|
|
75518
|
-
const candidate =
|
|
75519
|
-
if (exists3(
|
|
75520
|
-
const parent =
|
|
75940
|
+
const candidate = path31.join(dir, rel);
|
|
75941
|
+
if (exists3(path31.join(candidate, "meta.json"))) return candidate;
|
|
75942
|
+
const parent = path31.dirname(dir);
|
|
75521
75943
|
if (parent === dir) break;
|
|
75522
75944
|
dir = parent;
|
|
75523
75945
|
}
|
|
75524
|
-
return
|
|
75946
|
+
return path31.resolve(startDir, "../../../", rel);
|
|
75525
75947
|
}
|
|
75526
75948
|
|
|
75527
75949
|
// src/commands/canvas/scaffold-common.ts
|
|
@@ -75538,7 +75960,7 @@ function resolveModel(kind, requested, fallback) {
|
|
|
75538
75960
|
}
|
|
75539
75961
|
async function loadAssetText(ref2, label) {
|
|
75540
75962
|
const r = ref2;
|
|
75541
|
-
if (typeof r?.path === "string") return
|
|
75963
|
+
if (typeof r?.path === "string") return readFile25(r.path, "utf8");
|
|
75542
75964
|
if (typeof r?.url === "string") {
|
|
75543
75965
|
const res = await fetch(r.url);
|
|
75544
75966
|
if (!res.ok) throw new Error(`failed to fetch ${label} (${res.status})`);
|
|
@@ -75632,7 +76054,7 @@ function parseDefinitionSourceFields(raw) {
|
|
|
75632
76054
|
}
|
|
75633
76055
|
return fields;
|
|
75634
76056
|
}
|
|
75635
|
-
function
|
|
76057
|
+
function oneOf2(values, value2) {
|
|
75636
76058
|
return values.find((allowed) => allowed === value2);
|
|
75637
76059
|
}
|
|
75638
76060
|
function parseDefinitionStrategyFields(raw) {
|
|
@@ -75646,10 +76068,10 @@ function parseDefinitionStrategyFields(raw) {
|
|
|
75646
76068
|
};
|
|
75647
76069
|
const ideaSlug = text3("idea");
|
|
75648
76070
|
const message = text3("message");
|
|
75649
|
-
const evidence =
|
|
76071
|
+
const evidence = oneOf2(EVIDENCE_VALUES, text3("evidence"));
|
|
75650
76072
|
const angle = text3("angle");
|
|
75651
76073
|
const persona = text3("persona");
|
|
75652
|
-
const awareness =
|
|
76074
|
+
const awareness = oneOf2(AWARENESS_VALUES, text3("awareness"));
|
|
75653
76075
|
const hypothesis = text3("hypothesis");
|
|
75654
76076
|
return {
|
|
75655
76077
|
...ideaSlug ? { ideaSlug } : {},
|
|
@@ -75686,16 +76108,16 @@ ${doc.toString()}---${raw.slice(match[0].length)}`;
|
|
|
75686
76108
|
}
|
|
75687
76109
|
|
|
75688
76110
|
// src/commands/canvas/scaffold-static-ad-paths.ts
|
|
75689
|
-
import
|
|
76111
|
+
import path32 from "path";
|
|
75690
76112
|
function resolveScaffoldStaticAdPaths(rawFile, out, cwd = process.cwd(), slug) {
|
|
75691
76113
|
const file = rawFile.trim();
|
|
75692
76114
|
const imageIsUrl = /^https?:\/\//i.test(file);
|
|
75693
|
-
const imageSource = imageIsUrl ? file :
|
|
75694
|
-
const outPath = out ?
|
|
75695
|
-
const blueprintPath2 =
|
|
75696
|
-
const creativeDir = slug ?
|
|
75697
|
-
const definitionPath = creativeDir ?
|
|
75698
|
-
const referencesDir = creativeDir ?
|
|
76115
|
+
const imageSource = imageIsUrl ? file : path32.resolve(cwd, file);
|
|
76116
|
+
const outPath = out ? path32.resolve(cwd, out) : slug ? path32.join(cwd, "src", "creatives", slug, `${slug}.canvas.json`) : imageIsUrl ? path32.join(cwd, "static-ad.canvas.json") : path32.join(path32.dirname(imageSource), "static-ad.canvas.json");
|
|
76117
|
+
const blueprintPath2 = path32.join(path32.dirname(outPath), "prompt.json");
|
|
76118
|
+
const creativeDir = slug ? path32.dirname(outPath) : null;
|
|
76119
|
+
const definitionPath = creativeDir ? path32.join(creativeDir, "_definition.md") : null;
|
|
76120
|
+
const referencesDir = creativeDir ? path32.join(creativeDir, "references") : null;
|
|
75699
76121
|
return { imageIsUrl, imageSource, outPath, blueprintPath: blueprintPath2, creativeDir, definitionPath, referencesDir };
|
|
75700
76122
|
}
|
|
75701
76123
|
var SCAFFOLD_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
@@ -75705,8 +76127,8 @@ function isValidScaffoldSlug(slug) {
|
|
|
75705
76127
|
}
|
|
75706
76128
|
|
|
75707
76129
|
// src/commands/canvas/sync-definition.ts
|
|
75708
|
-
import { readdir as
|
|
75709
|
-
import
|
|
76130
|
+
import { readdir as readdir14, readFile as readFile26, stat as stat5 } from "fs/promises";
|
|
76131
|
+
import path33 from "path";
|
|
75710
76132
|
import { defineCommand as defineCommand141 } from "citty";
|
|
75711
76133
|
|
|
75712
76134
|
// src/commands/canvas/definition-graph.ts
|
|
@@ -75763,14 +76185,14 @@ function buildDefinitionSyncPayload(input, graph, chatId) {
|
|
|
75763
76185
|
}
|
|
75764
76186
|
async function readDefinitionSourceFields(canvasPath) {
|
|
75765
76187
|
try {
|
|
75766
|
-
return parseDefinitionSourceFields(await
|
|
76188
|
+
return parseDefinitionSourceFields(await readFile26(path33.join(path33.dirname(canvasPath), "_definition.md"), "utf8"));
|
|
75767
76189
|
} catch {
|
|
75768
76190
|
return {};
|
|
75769
76191
|
}
|
|
75770
76192
|
}
|
|
75771
76193
|
async function readDefinitionStrategyFields(canvasPath) {
|
|
75772
76194
|
try {
|
|
75773
|
-
return parseDefinitionStrategyFields(await
|
|
76195
|
+
return parseDefinitionStrategyFields(await readFile26(path33.join(path33.dirname(canvasPath), "_definition.md"), "utf8"));
|
|
75774
76196
|
} catch {
|
|
75775
76197
|
return {};
|
|
75776
76198
|
}
|
|
@@ -75793,26 +76215,26 @@ async function syncCreativeDefinitionBestEffort(input) {
|
|
|
75793
76215
|
}
|
|
75794
76216
|
}
|
|
75795
76217
|
async function resolveCanvasPath(inputPath) {
|
|
75796
|
-
const resolved =
|
|
76218
|
+
const resolved = path33.resolve(inputPath);
|
|
75797
76219
|
let dir = resolved;
|
|
75798
76220
|
try {
|
|
75799
76221
|
if ((await stat5(resolved)).isFile()) {
|
|
75800
76222
|
if (resolved.endsWith(".canvas.json")) return resolved;
|
|
75801
|
-
dir =
|
|
76223
|
+
dir = path33.dirname(resolved);
|
|
75802
76224
|
}
|
|
75803
76225
|
} catch {
|
|
75804
|
-
dir = resolved.endsWith(".canvas.json") ?
|
|
76226
|
+
dir = resolved.endsWith(".canvas.json") ? path33.dirname(resolved) : resolved;
|
|
75805
76227
|
}
|
|
75806
76228
|
let entries;
|
|
75807
76229
|
try {
|
|
75808
|
-
entries = await
|
|
76230
|
+
entries = await readdir14(dir);
|
|
75809
76231
|
} catch {
|
|
75810
76232
|
return null;
|
|
75811
76233
|
}
|
|
75812
76234
|
const canvases = entries.filter((name) => name.endsWith(".canvas.json"));
|
|
75813
76235
|
const slug = creativeSlugFromCanvasPath(`${dir}/x/`);
|
|
75814
76236
|
const chosen = (slug ? canvases.find((name) => name === `${slug}.canvas.json`) : void 0) ?? canvases[0];
|
|
75815
|
-
return chosen ?
|
|
76237
|
+
return chosen ? path33.join(dir, chosen) : null;
|
|
75816
76238
|
}
|
|
75817
76239
|
var syncDefinitionCommand = defineCommand141({
|
|
75818
76240
|
meta: {
|
|
@@ -76082,8 +76504,8 @@ function mergeReferenceIntoBlueprint(blueprint, reference) {
|
|
|
76082
76504
|
}
|
|
76083
76505
|
|
|
76084
76506
|
// src/commands/winning-ads/fit.ts
|
|
76085
|
-
import { mkdir as mkdir10, readdir as
|
|
76086
|
-
import
|
|
76507
|
+
import { mkdir as mkdir10, readdir as readdir15, readFile as readFile27, writeFile as writeFile16 } from "fs/promises";
|
|
76508
|
+
import path34 from "path";
|
|
76087
76509
|
import { defineCommand as defineCommand142 } from "citty";
|
|
76088
76510
|
import sharp4 from "sharp";
|
|
76089
76511
|
import { z as z67 } from "zod";
|
|
@@ -76168,23 +76590,23 @@ var MAX_DOC_CHARS = 1500;
|
|
|
76168
76590
|
var MAX_ENTRY_CHARS = 400;
|
|
76169
76591
|
var KNOWLEDGE_COLLECTIONS = ["offers", "products", "personas"];
|
|
76170
76592
|
function readText(file) {
|
|
76171
|
-
return
|
|
76593
|
+
return readFile27(file, "utf8").catch(() => null);
|
|
76172
76594
|
}
|
|
76173
76595
|
async function readKnowledge(root) {
|
|
76174
76596
|
const parts = [];
|
|
76175
|
-
const company = await readText(
|
|
76597
|
+
const company = await readText(path34.join(root, "COMPANY.md"));
|
|
76176
76598
|
if (company) parts.push(`COMPANY.md:
|
|
76177
76599
|
${company.slice(0, MAX_DOC_CHARS)}`);
|
|
76178
|
-
const brand = await readText(
|
|
76600
|
+
const brand = await readText(path34.join(root, "src", "brand", "BRAND.md"));
|
|
76179
76601
|
if (brand) parts.push(`BRAND.md (start):
|
|
76180
76602
|
${brand.slice(0, MAX_DOC_CHARS)}`);
|
|
76181
76603
|
for (const collection of KNOWLEDGE_COLLECTIONS) {
|
|
76182
|
-
const dir =
|
|
76183
|
-
const files = (await
|
|
76604
|
+
const dir = path34.join(root, "src", "content", collection);
|
|
76605
|
+
const files = (await readdir15(dir, { recursive: true }).catch(() => [])).filter(
|
|
76184
76606
|
(f) => f.endsWith(".md")
|
|
76185
76607
|
);
|
|
76186
76608
|
for (const file of files.sort().slice(0, 12)) {
|
|
76187
|
-
const text3 = await readText(
|
|
76609
|
+
const text3 = await readText(path34.join(dir, file));
|
|
76188
76610
|
if (text3) parts.push(`${collection}/${file}:
|
|
76189
76611
|
${text3.slice(0, MAX_ENTRY_CHARS)}`);
|
|
76190
76612
|
}
|
|
@@ -76216,7 +76638,7 @@ async function readReviews() {
|
|
|
76216
76638
|
}
|
|
76217
76639
|
}
|
|
76218
76640
|
function fitCachePath(root, platform, adId) {
|
|
76219
|
-
return
|
|
76641
|
+
return path34.join(root, "canvas", ".cache", "fit", `${platform}-${adId.replace(/[^a-zA-Z0-9_-]/g, "_")}.json`);
|
|
76220
76642
|
}
|
|
76221
76643
|
async function readCachedFit(root, platform, adId) {
|
|
76222
76644
|
const raw = await readText(fitCachePath(root, platform, adId));
|
|
@@ -76271,7 +76693,7 @@ async function fitOnce(root, platform, adId, ask) {
|
|
|
76271
76693
|
const fit = await ask();
|
|
76272
76694
|
if (!fit) return null;
|
|
76273
76695
|
const file = fitCachePath(root, platform, adId);
|
|
76274
|
-
await mkdir10(
|
|
76696
|
+
await mkdir10(path34.dirname(file), { recursive: true });
|
|
76275
76697
|
await writeFile16(file, `${JSON.stringify(fit, null, 2)}
|
|
76276
76698
|
`, "utf8");
|
|
76277
76699
|
return fit;
|
|
@@ -76310,7 +76732,7 @@ async function fitReference(root, platform, adId) {
|
|
|
76310
76732
|
const prompt = buildFitPrompt({ library: libraryRead(ad), knowledge: knowledge2 });
|
|
76311
76733
|
const spent = [];
|
|
76312
76734
|
const answer = await askFit(jpeg, prompt, (u) => spent.push(u));
|
|
76313
|
-
await holdUnbilledSpend(
|
|
76735
|
+
await holdUnbilledSpend(path34.join(root, "canvas", ".cache"), spent);
|
|
76314
76736
|
return answer;
|
|
76315
76737
|
});
|
|
76316
76738
|
if (fit) return { ok: true, fit };
|
|
@@ -76363,7 +76785,7 @@ var fitCommand = defineCommand142({
|
|
|
76363
76785
|
|
|
76364
76786
|
// src/commands/canvas/scaffold-static-ad.ts
|
|
76365
76787
|
function toCanvasRelative2(canvasDir, file) {
|
|
76366
|
-
const rel =
|
|
76788
|
+
const rel = path35.relative(canvasDir, file).split(path35.sep).join("/");
|
|
76367
76789
|
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
76368
76790
|
}
|
|
76369
76791
|
async function uploadSourceAsReference(source, isUrl, client) {
|
|
@@ -76375,7 +76797,7 @@ async function uploadSourceAsReference(source, isUrl, client) {
|
|
|
76375
76797
|
throw new Error(`failed to download source image (${e instanceof Error ? e.message : String(e)})`);
|
|
76376
76798
|
}
|
|
76377
76799
|
} else {
|
|
76378
|
-
bytes = await
|
|
76800
|
+
bytes = await readFile28(source);
|
|
76379
76801
|
}
|
|
76380
76802
|
const safe = await toModelSafeImage(bytes);
|
|
76381
76803
|
const sha2562 = sha256Hex(safe.bytes);
|
|
@@ -76655,25 +77077,25 @@ async function lookupReference(args8) {
|
|
|
76655
77077
|
}
|
|
76656
77078
|
async function scaffoldFromSpec(args8, slug) {
|
|
76657
77079
|
if (!slug) return fail4("missing_slug", "--spec needs --slug: an ad started from a spec is a creative.");
|
|
76658
|
-
const specPath =
|
|
77080
|
+
const specPath = path35.resolve(process.cwd(), String(args8.spec));
|
|
76659
77081
|
let started;
|
|
76660
77082
|
try {
|
|
76661
|
-
started = readAdSpec(JSON.parse(await
|
|
77083
|
+
started = readAdSpec(JSON.parse(await readFile28(specPath, "utf8")));
|
|
76662
77084
|
} catch (e) {
|
|
76663
77085
|
return fail4("invalid_spec", `${specPath} is not an ad spec: ${e instanceof Error ? e.message : String(e)}`);
|
|
76664
77086
|
}
|
|
76665
|
-
const outPath =
|
|
76666
|
-
const dir =
|
|
76667
|
-
const blueprintPath2 =
|
|
76668
|
-
const definitionPath =
|
|
77087
|
+
const outPath = path35.join(process.cwd(), "src", "creatives", slug, `${slug}.canvas.json`);
|
|
77088
|
+
const dir = path35.dirname(outPath);
|
|
77089
|
+
const blueprintPath2 = path35.join(dir, "prompt.json");
|
|
77090
|
+
const definitionPath = path35.join(dir, "_definition.md");
|
|
76669
77091
|
await enforceNewCreativeLimit(slug, outPath);
|
|
76670
|
-
const existingDefinition = await fileExists(definitionPath) ? await
|
|
77092
|
+
const existingDefinition = await fileExists(definitionPath) ? await readFile28(definitionPath, "utf8") : null;
|
|
76671
77093
|
const who = await scaffoldViewer(args8, existingDefinition, liveViewerLookups(process.cwd()).adSetViewer);
|
|
76672
77094
|
if (!who.ok) return fail4("invalid_viewer", who.message);
|
|
76673
77095
|
await mkdir11(dir, { recursive: true });
|
|
76674
77096
|
const workspaceRoot = await findBrandRoot(dir) ?? process.cwd();
|
|
76675
77097
|
const kit = await readStaticBrandKit(workspaceRoot);
|
|
76676
|
-
const market = who.viewer?.market ?? marketFromCompanyDoc(await
|
|
77098
|
+
const market = who.viewer?.market ?? marketFromCompanyDoc(await readFile28(path35.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => null));
|
|
76677
77099
|
const brand = brandBlock(kit);
|
|
76678
77100
|
const platform = resolvePlatform(args8.platform ? String(args8.platform) : void 0);
|
|
76679
77101
|
const placementsResult = resolvePlacements(args8.placements ? String(args8.placements) : void 0, platform);
|
|
@@ -76820,14 +77242,14 @@ var scaffoldStaticAdCommand = defineCommand143({
|
|
|
76820
77242
|
}
|
|
76821
77243
|
const { imageIsUrl, imageSource, outPath, blueprintPath: blueprintPath2, definitionPath, referencesDir } = resolveScaffoldStaticAdPaths(sourceFile, args8.out ? String(args8.out) : void 0, process.cwd(), slug);
|
|
76822
77244
|
if (slug) await enforceNewCreativeLimit(slug, outPath);
|
|
76823
|
-
const existingDefinition = definitionPath && await fileExists(definitionPath) ? await
|
|
77245
|
+
const existingDefinition = definitionPath && await fileExists(definitionPath) ? await readFile28(definitionPath, "utf8") : null;
|
|
76824
77246
|
const who = await scaffoldViewer(args8, existingDefinition, liveViewerLookups(process.cwd()).adSetViewer);
|
|
76825
77247
|
if (!who.ok) return fail4("invalid_viewer", who.message);
|
|
76826
77248
|
const fitRoot = findWorkspaceRoot(process.cwd()) ?? process.cwd();
|
|
76827
77249
|
const fit = reference || lookup.status === "unavailable" ? await readCachedFit(fitRoot, reference?.platform ?? (lookup.status === "unavailable" ? lookup.platform : "meta"), reference?.ad_id ?? (lookup.status === "unavailable" ? lookup.adId : "")) : null;
|
|
76828
77250
|
const fitProblems = fit ? fitHints(fit, reference?.ad_id ?? "").filter((h) => h.startsWith("Pick another reference")) : [];
|
|
76829
77251
|
if (fitProblems.length > 0) return fail4("concept_does_not_fit", fitProblems.join(" "));
|
|
76830
|
-
await mkdir11(
|
|
77252
|
+
await mkdir11(path35.dirname(outPath), { recursive: true });
|
|
76831
77253
|
let models;
|
|
76832
77254
|
try {
|
|
76833
77255
|
models = resolveModels(args8);
|
|
@@ -76861,10 +77283,10 @@ var scaffoldStaticAdCommand = defineCommand143({
|
|
|
76861
77283
|
const persona = existingDefinition?.match(/^persona:\s*"?([^"\n]+)"?\s*$/m)?.[1];
|
|
76862
77284
|
const elements = castAvatarsOnStaticElements(vision.elements, cast.avatars, persona);
|
|
76863
77285
|
const uncast = staticAvatarsNotCast(elements, cast.handles);
|
|
76864
|
-
const canvasDir =
|
|
77286
|
+
const canvasDir = path35.dirname(outPath);
|
|
76865
77287
|
const workspaceRoot = await findBrandRoot(canvasDir) ?? process.cwd();
|
|
76866
77288
|
const kit = await readStaticBrandKit(workspaceRoot);
|
|
76867
|
-
const market = who.viewer?.market ?? marketFromCompanyDoc(await
|
|
77289
|
+
const market = who.viewer?.market ?? marketFromCompanyDoc(await readFile28(path35.join(workspaceRoot, "COMPANY.md"), "utf8").catch(() => null));
|
|
76868
77290
|
const brand = brandBlock(kit);
|
|
76869
77291
|
let canvasImagePath = imageSource;
|
|
76870
77292
|
let canvasImageIsUrl = imageIsUrl;
|
|
@@ -76928,7 +77350,7 @@ var scaffoldStaticAdCommand = defineCommand143({
|
|
|
76928
77350
|
} catch (e) {
|
|
76929
77351
|
return fail4("scaffold", e instanceof Error ? e.message : String(e));
|
|
76930
77352
|
}
|
|
76931
|
-
const validation = await validateScaffoldedStaticAd(canvas,
|
|
77353
|
+
const validation = await validateScaffoldedStaticAd(canvas, path35.dirname(outPath));
|
|
76932
77354
|
if (validation.blocking.length > 0) {
|
|
76933
77355
|
process.stderr.write(
|
|
76934
77356
|
`${JSON.stringify({ ok: false, error: { code: "validation", issues: validation.blocking } }, null, 2)}
|
|
@@ -77025,7 +77447,7 @@ var scaffoldStaticAdCommand = defineCommand143({
|
|
|
77025
77447
|
run_estimated_credits: validation.estimatedCredits
|
|
77026
77448
|
},
|
|
77027
77449
|
checklist: {
|
|
77028
|
-
edit_prompt: `Edit ${
|
|
77450
|
+
edit_prompt: `Edit ${path35.basename(blueprintPath2)}: it is the ad spec, read from your image, to rewrite into the ad you want. \`viewer\` (who sees it and what they already know), \`message\` (the one thing it tells them), \`copy\` (each line, \`in_scene\` or \`designed\`), \`scene\`, \`references\` (one picture each, by role), \`colors\` (each tied to an object, from the brand) and an optional \`direction\`. You never write a model prompt: every render prompt is compiled from this file and shown on its node.${reference ? " Its `reference` block is the library's read of the source ad (container, layout, mechanism, offer, text blocks): keep it as the account of why the ad won, and rebuild it for our brand." : ""}`,
|
|
77029
77451
|
mechanism: report.winning_mechanisms,
|
|
77030
77452
|
rebuild_the_mechanism: "`mechanism` is WHY the source ad works. Rebuild each device for our brand in prompt.json's copy and scene (a rhyme stays a rhyme in our words, a before/after stays a before/after of our product); never copy the source's literal copy.",
|
|
77031
77453
|
assets_to_supply: report.elements,
|
|
@@ -77087,8 +77509,8 @@ function clientLogoMissNote(decision, domain) {
|
|
|
77087
77509
|
}
|
|
77088
77510
|
|
|
77089
77511
|
// src/commands/canvas/scaffold-ad.ts
|
|
77090
|
-
import { copyFile, cp, mkdir as mkdir12, readFile as
|
|
77091
|
-
import
|
|
77512
|
+
import { copyFile, cp, mkdir as mkdir12, readFile as readFile30, stat as stat6, writeFile as writeFile19 } from "fs/promises";
|
|
77513
|
+
import path37 from "path";
|
|
77092
77514
|
import { defineCommand as defineCommand144 } from "citty";
|
|
77093
77515
|
|
|
77094
77516
|
// src/engine/scaffold/ad-spec.ts
|
|
@@ -77802,8 +78224,8 @@ function injectAdBrandOverlay(indexHtml, overlayHtml, style = {}) {
|
|
|
77802
78224
|
}
|
|
77803
78225
|
|
|
77804
78226
|
// src/commands/canvas/composition-dims.ts
|
|
77805
|
-
import { readFile as
|
|
77806
|
-
import
|
|
78227
|
+
import { readFile as readFile29, writeFile as writeFile18 } from "fs/promises";
|
|
78228
|
+
import path36 from "path";
|
|
77807
78229
|
function patchCompositionMeta(metaJson, dims) {
|
|
77808
78230
|
try {
|
|
77809
78231
|
const meta = JSON.parse(metaJson);
|
|
@@ -77818,11 +78240,11 @@ function patchCompositionHtml(html, dims) {
|
|
|
77818
78240
|
return html.replace(/(<meta\s+name="viewport"\s+content="width=)\d+(,\s*height=)\d+(")/i, `$1${dims.w}$2${dims.h}$3`).replace(/(width:\s*)\d+(px;\s*height:\s*)\d+(px;)/i, `$1${dims.w}$2${dims.h}$3`).replace(/(data-width=")\d+(")/i, `$1${dims.w}$2`).replace(/(data-height=")\d+(")/i, `$1${dims.h}$2`);
|
|
77819
78241
|
}
|
|
77820
78242
|
async function stampCompositionDims(compositionDir, dims) {
|
|
77821
|
-
const metaPath =
|
|
77822
|
-
const rawMeta = await
|
|
78243
|
+
const metaPath = path36.join(compositionDir, "meta.json");
|
|
78244
|
+
const rawMeta = await readFile29(metaPath, "utf8");
|
|
77823
78245
|
await writeFile18(metaPath, patchCompositionMeta(rawMeta, dims), "utf8");
|
|
77824
|
-
const htmlPath =
|
|
77825
|
-
const rawHtml = await
|
|
78246
|
+
const htmlPath = path36.join(compositionDir, "index.html");
|
|
78247
|
+
const rawHtml = await readFile29(htmlPath, "utf8");
|
|
77826
78248
|
await writeFile18(htmlPath, patchCompositionHtml(rawHtml, dims), "utf8");
|
|
77827
78249
|
}
|
|
77828
78250
|
|
|
@@ -77905,7 +78327,7 @@ var scaffoldAdCommand = defineCommand144({
|
|
|
77905
78327
|
const specPath = args8.spec;
|
|
77906
78328
|
let parsed;
|
|
77907
78329
|
try {
|
|
77908
|
-
parsed = JSON.parse(await
|
|
78330
|
+
parsed = JSON.parse(await readFile30(specPath, "utf-8"));
|
|
77909
78331
|
} catch (e) {
|
|
77910
78332
|
writeJson({
|
|
77911
78333
|
ok: false,
|
|
@@ -78066,7 +78488,7 @@ var scaffoldAdCommand = defineCommand144({
|
|
|
78066
78488
|
] : [];
|
|
78067
78489
|
const markPath = spec.data.brand?.logo?.trim();
|
|
78068
78490
|
if (!noBrand && markPath?.toLowerCase().endsWith(".svg")) {
|
|
78069
|
-
const svg = await
|
|
78491
|
+
const svg = await readFile30(markPath, "utf-8").catch(() => null);
|
|
78070
78492
|
if (svg === null) {
|
|
78071
78493
|
writeJson({
|
|
78072
78494
|
ok: false,
|
|
@@ -78118,8 +78540,8 @@ var scaffoldAdCommand = defineCommand144({
|
|
|
78118
78540
|
videoModel: chosenVideoModel,
|
|
78119
78541
|
...avatarVoice ? { avatarVoice } : {}
|
|
78120
78542
|
});
|
|
78121
|
-
const slug = args8.slug ??
|
|
78122
|
-
const outPath = args8.out ?? (args8.slug ?
|
|
78543
|
+
const slug = args8.slug ?? path37.basename(specPath).replace(/\.[^.]+$/, "");
|
|
78544
|
+
const outPath = args8.out ?? (args8.slug ? path37.join("src/creatives", slug, `${slug}.canvas.json`) : path37.join(path37.dirname(specPath), `${slug}.canvas.json`));
|
|
78123
78545
|
if (args8.slug) await enforceNewCreativeLimit(slug, outPath);
|
|
78124
78546
|
let outAr;
|
|
78125
78547
|
try {
|
|
@@ -78137,28 +78559,28 @@ var scaffoldAdCommand = defineCommand144({
|
|
|
78137
78559
|
return;
|
|
78138
78560
|
}
|
|
78139
78561
|
const outDims = canvasDims(outAr);
|
|
78140
|
-
const outDir =
|
|
78562
|
+
const outDir = path37.dirname(outPath);
|
|
78141
78563
|
await mkdir12(outDir, { recursive: true });
|
|
78142
|
-
const compositionDest =
|
|
78143
|
-
const captionsDest =
|
|
78564
|
+
const compositionDest = path37.join(outDir, "video-overlay-composition");
|
|
78565
|
+
const captionsDest = path37.join(outDir, "tiktok-captions-composition");
|
|
78144
78566
|
await cp(SHIPPED_OVERLAY_COMPOSITION_DIR, compositionDest, { recursive: true });
|
|
78145
78567
|
await cp(SHIPPED_CAPTIONS_DIR, captionsDest, { recursive: true });
|
|
78146
78568
|
await stampCompositionDims(compositionDest, outDims);
|
|
78147
78569
|
await stampCompositionDims(captionsDest, outDims);
|
|
78148
|
-
const captionsIndex =
|
|
78149
|
-
await writeFile19(captionsIndex, injectAdCaptionPlacement(await
|
|
78570
|
+
const captionsIndex = path37.join(captionsDest, "index.html");
|
|
78571
|
+
await writeFile19(captionsIndex, injectAdCaptionPlacement(await readFile30(captionsIndex, "utf-8"), outAr), "utf-8");
|
|
78150
78572
|
const face = await stageBrandDisplayFace(outDir, captionsDest);
|
|
78151
78573
|
if (face) {
|
|
78152
|
-
await writeFile19(captionsIndex, injectAdDisplayFace(await
|
|
78574
|
+
await writeFile19(captionsIndex, injectAdDisplayFace(await readFile30(captionsIndex, "utf-8"), face.file), "utf-8");
|
|
78153
78575
|
}
|
|
78154
|
-
const blueprintPath2 =
|
|
78155
|
-
const blueprintStylePath =
|
|
78576
|
+
const blueprintPath2 = path37.join(outDir, "prompt.json");
|
|
78577
|
+
const blueprintStylePath = path37.join(outDir, "prompt.style.json");
|
|
78156
78578
|
await writeSceneFiles(outDir, blueprint);
|
|
78157
78579
|
await writeFile19(blueprintStylePath, renderStyleProjectionFromValue(blueprint), "utf8");
|
|
78158
|
-
await writeFile19(
|
|
78580
|
+
await writeFile19(path37.join(outDir, AD_SPEC_FILE), `${JSON.stringify(specArtifact, null, 2)}
|
|
78159
78581
|
`, "utf8");
|
|
78160
78582
|
const logoPath = noBrand ? void 0 : spec.data.brand?.logo?.trim();
|
|
78161
|
-
const transcriptPath =
|
|
78583
|
+
const transcriptPath = path37.join(outDir, "transcript.json");
|
|
78162
78584
|
if (spec.data.voiceover === false) {
|
|
78163
78585
|
await writeFile19(transcriptPath, `${JSON.stringify(staticTranscript(spec.data, chosenVideoModel), null, 2)}
|
|
78164
78586
|
`, "utf-8");
|
|
@@ -78170,10 +78592,10 @@ var scaffoldAdCommand = defineCommand144({
|
|
|
78170
78592
|
// that stops the whole canvas. The same sheet model every other scaffold uses.
|
|
78171
78593
|
sheetModel: ACTOR_SHEET_MODEL,
|
|
78172
78594
|
videoModel: chosenVideoModel,
|
|
78173
|
-
overlayCompositionPath:
|
|
78174
|
-
captionsCompositionPath:
|
|
78175
|
-
blueprintPath:
|
|
78176
|
-
blueprintStylePath:
|
|
78595
|
+
overlayCompositionPath: path37.relative(outDir, compositionDest),
|
|
78596
|
+
captionsCompositionPath: path37.relative(outDir, captionsDest),
|
|
78597
|
+
blueprintPath: path37.relative(outDir, blueprintPath2),
|
|
78598
|
+
blueprintStylePath: path37.relative(outDir, blueprintStylePath),
|
|
78177
78599
|
aspect: spec.data.format.aspect_ratio,
|
|
78178
78600
|
resolution: spec.data.format.resolution,
|
|
78179
78601
|
// A beat is one clause and one card, so the cap has to clear a whole clause.
|
|
@@ -78212,17 +78634,17 @@ var scaffoldAdCommand = defineCommand144({
|
|
|
78212
78634
|
// the stock voice this exists to avoid. It is to convert the LATER extracts onto the
|
|
78213
78635
|
// first beat of the same speaker, which keeps a voice the clip really produced.
|
|
78214
78636
|
...avatar ? { keepRenderedVoice: true } : {},
|
|
78215
|
-
...spec.data.voiceover === false ? { staticTranscriptPath:
|
|
78637
|
+
...spec.data.voiceover === false ? { staticTranscriptPath: path37.relative(outDir, transcriptPath) } : {}
|
|
78216
78638
|
};
|
|
78217
78639
|
const canvas = scaffoldVideoCanvas(blueprint, adSpecCastElements(spec.data, avatar), opts);
|
|
78218
78640
|
const renderNode5 = canvas.nodes.find((n) => n.type === "hyperframe_render");
|
|
78219
|
-
const renderedDir = renderNode5 ?
|
|
78641
|
+
const renderedDir = renderNode5 ? path37.join(outDir, String(renderNode5.params?.composition ?? "")) : null;
|
|
78220
78642
|
let staged = null;
|
|
78221
78643
|
let markIsLight = false;
|
|
78222
78644
|
if (logoPath && renderedDir) {
|
|
78223
|
-
const ext =
|
|
78645
|
+
const ext = path37.extname(logoPath) || ".png";
|
|
78224
78646
|
try {
|
|
78225
|
-
await copyFile(logoPath,
|
|
78647
|
+
await copyFile(logoPath, path37.join(renderedDir, `brand-mark${ext}`));
|
|
78226
78648
|
staged = { file: `brand-mark${ext}`, alt: spec.data.brand?.name ?? "brand" };
|
|
78227
78649
|
markIsLight = await markInkIsLight(logoPath);
|
|
78228
78650
|
} catch {
|
|
@@ -78241,12 +78663,12 @@ var scaffoldAdCommand = defineCommand144({
|
|
|
78241
78663
|
plateIsDark: plateIsDark(spec.data.brand?.palette)
|
|
78242
78664
|
}) : "";
|
|
78243
78665
|
if (overlayHtml && renderedDir) {
|
|
78244
|
-
const indexPath =
|
|
78245
|
-
const html = await
|
|
78666
|
+
const indexPath = path37.join(renderedDir, "index.html");
|
|
78667
|
+
const html = await readFile30(indexPath, "utf-8");
|
|
78246
78668
|
await writeFile19(indexPath, injectAdBrandOverlay(html, overlayHtml, { aspect: outAr, ...face ? { face: AD_DISPLAY_FAMILY } : {} }), "utf-8");
|
|
78247
78669
|
}
|
|
78248
78670
|
await writeFile19(
|
|
78249
|
-
|
|
78671
|
+
path37.join(outDir, REBUILD_FILE),
|
|
78250
78672
|
`${JSON.stringify({ elements: adSpecCastElements(spec.data, avatar), opts }, null, 2)}
|
|
78251
78673
|
`,
|
|
78252
78674
|
"utf8"
|
|
@@ -78276,7 +78698,7 @@ var scaffoldAdCommand = defineCommand144({
|
|
|
78276
78698
|
return;
|
|
78277
78699
|
}
|
|
78278
78700
|
if (args8.slug) {
|
|
78279
|
-
const definitionPath =
|
|
78701
|
+
const definitionPath = path37.join(outDir, "_definition.md");
|
|
78280
78702
|
const definitionExists = await stat6(definitionPath).then(() => true, () => false);
|
|
78281
78703
|
if (!definitionExists) {
|
|
78282
78704
|
await writeFile19(
|
|
@@ -78394,9 +78816,9 @@ async function fetchClientLogo() {
|
|
|
78394
78816
|
if (!response.ok) return { ok: false, reason: clientLogoMissNote({ use: false, reason: "no_hits" }, domain) };
|
|
78395
78817
|
const bytes = Buffer.from(await response.arrayBuffer());
|
|
78396
78818
|
const extension = /\.(svg|png|jpg|jpeg|webp)(?:\?|$)/i.exec(decision.url)?.[1]?.toLowerCase() ?? "svg";
|
|
78397
|
-
const dir =
|
|
78819
|
+
const dir = path37.join(BRAND_DIR, "logos");
|
|
78398
78820
|
await mkdir12(dir, { recursive: true });
|
|
78399
|
-
const file =
|
|
78821
|
+
const file = path37.join(dir, `brand-mark.${extension}`);
|
|
78400
78822
|
await writeFile19(file, bytes);
|
|
78401
78823
|
return { ok: true, path: file };
|
|
78402
78824
|
} catch {
|
|
@@ -78415,14 +78837,14 @@ async function stageBrandDisplayFace(outDir, compositionDir) {
|
|
|
78415
78837
|
if (!face) return null;
|
|
78416
78838
|
try {
|
|
78417
78839
|
if ("path" in face.slot) {
|
|
78418
|
-
const file2 = `brand-face${
|
|
78419
|
-
await copyFile(face.slot.path,
|
|
78420
|
-
return { file: file2, label: `the font file \`${
|
|
78840
|
+
const file2 = `brand-face${path37.extname(face.slot.path).toLowerCase()}`;
|
|
78841
|
+
await copyFile(face.slot.path, path37.join(compositionDir, file2));
|
|
78842
|
+
return { file: file2, label: `the font file \`${path37.relative(root, face.slot.path)}\`` };
|
|
78421
78843
|
}
|
|
78422
78844
|
const { buffer } = await fetchExternalBytes(face.slot.url, { maxBytes: MAX_FONT_BYTES });
|
|
78423
78845
|
const extension = /\.(woff2|woff|ttf|otf)(?:\?|$)/i.exec(face.slot.url)?.[1]?.toLowerCase() ?? "woff2";
|
|
78424
78846
|
const file = `brand-face.${extension}`;
|
|
78425
|
-
await writeFile19(
|
|
78847
|
+
await writeFile19(path37.join(compositionDir, file), buffer);
|
|
78426
78848
|
return { file, label: `${face.family ?? "the brand's typeface"}, loaded as a web font` };
|
|
78427
78849
|
} catch {
|
|
78428
78850
|
return null;
|
|
@@ -78430,9 +78852,9 @@ async function stageBrandDisplayFace(outDir, compositionDir) {
|
|
|
78430
78852
|
}
|
|
78431
78853
|
|
|
78432
78854
|
// src/commands/canvas/scaffold-video.ts
|
|
78433
|
-
import { cp as cp2, mkdir as mkdir13, readFile as
|
|
78855
|
+
import { cp as cp2, mkdir as mkdir13, readFile as readFile33, rm as rm6, writeFile as writeFile20 } from "fs/promises";
|
|
78434
78856
|
import { tmpdir as tmpdir2 } from "os";
|
|
78435
|
-
import
|
|
78857
|
+
import path39 from "path";
|
|
78436
78858
|
import { defineCommand as defineCommand145 } from "citty";
|
|
78437
78859
|
|
|
78438
78860
|
// src/engine/scaffold/lib/model-router.ts
|
|
@@ -78472,7 +78894,7 @@ function routeVideoModel(input) {
|
|
|
78472
78894
|
|
|
78473
78895
|
// src/engine/nodes/local/lib/sceneDetect.ts
|
|
78474
78896
|
import { execFile as execFile2 } from "child_process";
|
|
78475
|
-
import { mkdtemp, readdir as
|
|
78897
|
+
import { mkdtemp, readdir as readdir16, readFile as readFile31, rm as rm5 } from "fs/promises";
|
|
78476
78898
|
import { tmpdir } from "os";
|
|
78477
78899
|
import { join as join3 } from "path";
|
|
78478
78900
|
import { promisify as promisify2 } from "util";
|
|
@@ -78546,9 +78968,9 @@ async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs)
|
|
|
78546
78968
|
],
|
|
78547
78969
|
{ encoding: "utf-8", maxBuffer: 32 * 1024 * 1024, timeout: timeoutMs }
|
|
78548
78970
|
);
|
|
78549
|
-
const csvName = (await
|
|
78971
|
+
const csvName = (await readdir16(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
|
|
78550
78972
|
if (!csvName) return [];
|
|
78551
|
-
return parsePySceneDetectCsvCuts(await
|
|
78973
|
+
return parsePySceneDetectCsvCuts(await readFile31(join3(outDir, csvName), "utf-8"));
|
|
78552
78974
|
} finally {
|
|
78553
78975
|
await rm5(outDir, { recursive: true, force: true });
|
|
78554
78976
|
}
|
|
@@ -78572,8 +78994,8 @@ async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
|
|
|
78572
78994
|
}
|
|
78573
78995
|
|
|
78574
78996
|
// src/commands/canvas/gitignore.ts
|
|
78575
|
-
import { appendFile, readFile as
|
|
78576
|
-
import
|
|
78997
|
+
import { appendFile, readFile as readFile32 } from "fs/promises";
|
|
78998
|
+
import path38 from "path";
|
|
78577
78999
|
function missingGitignoreEntries(existing, entries) {
|
|
78578
79000
|
const present2 = new Set(
|
|
78579
79001
|
existing.split("\n").map((l) => l.trim().replace(/\/+$/, "")).filter((l) => l.length > 0 && !l.startsWith("#"))
|
|
@@ -78581,10 +79003,10 @@ function missingGitignoreEntries(existing, entries) {
|
|
|
78581
79003
|
return entries.filter((e) => !present2.has(e.trim().replace(/\/+$/, "")));
|
|
78582
79004
|
}
|
|
78583
79005
|
async function ensureGitignore(dir, entries) {
|
|
78584
|
-
const file =
|
|
79006
|
+
const file = path38.join(dir, ".gitignore");
|
|
78585
79007
|
let existing;
|
|
78586
79008
|
try {
|
|
78587
|
-
existing = await
|
|
79009
|
+
existing = await readFile32(file, "utf8");
|
|
78588
79010
|
} catch {
|
|
78589
79011
|
return;
|
|
78590
79012
|
}
|
|
@@ -78626,7 +79048,7 @@ async function loadTranscriptBestEffort(ref2) {
|
|
|
78626
79048
|
async function stageCaptions(outDir, transcript) {
|
|
78627
79049
|
const text3 = transcript?.trim();
|
|
78628
79050
|
if (!text3 || text3 === "[]") return {};
|
|
78629
|
-
const compositionPath =
|
|
79051
|
+
const compositionPath = path39.join(outDir, "tiktok-captions-composition");
|
|
78630
79052
|
await cp2(SHIPPED_CAPTIONS_DIR, compositionPath, { recursive: true });
|
|
78631
79053
|
return { compositionPath };
|
|
78632
79054
|
}
|
|
@@ -78662,7 +79084,7 @@ var VIDEO_EXT_BY_MIME = {
|
|
|
78662
79084
|
"video/x-matroska": ".mkv"
|
|
78663
79085
|
};
|
|
78664
79086
|
function referenceVideoExt(url, contentType) {
|
|
78665
|
-
const fromPath =
|
|
79087
|
+
const fromPath = path39.extname(new URL(url).pathname).toLowerCase();
|
|
78666
79088
|
if (fromPath && fromPath.length <= 5) return fromPath;
|
|
78667
79089
|
const mime = (contentType ?? "").split(";")[0]?.trim().toLowerCase();
|
|
78668
79090
|
return mime && VIDEO_EXT_BY_MIME[mime] || ".mp4";
|
|
@@ -78688,7 +79110,7 @@ function framesModeReport(canvas, requested) {
|
|
|
78688
79110
|
return { frames_mode: mode, ...refused ? { frames_reuse_refused: refused } : {} };
|
|
78689
79111
|
}
|
|
78690
79112
|
async function materializeReferenceVideo(fileArg2) {
|
|
78691
|
-
if (!/^https?:\/\//i.test(fileArg2)) return
|
|
79113
|
+
if (!/^https?:\/\//i.test(fileArg2)) return path39.resolve(fileArg2);
|
|
78692
79114
|
let bytes;
|
|
78693
79115
|
let contentType;
|
|
78694
79116
|
try {
|
|
@@ -78700,7 +79122,7 @@ async function materializeReferenceVideo(fileArg2) {
|
|
|
78700
79122
|
throw new Error(`failed to download reference video: ${e instanceof Error ? e.message : String(e)}`);
|
|
78701
79123
|
}
|
|
78702
79124
|
if (bytes.length === 0) throw new Error("reference video download was empty");
|
|
78703
|
-
const dest =
|
|
79125
|
+
const dest = path39.join(
|
|
78704
79126
|
tmpdir2(),
|
|
78705
79127
|
`baker-ref-${sha256Hex(bytes).slice(0, 16)}${referenceVideoExt(fileArg2, contentType)}`
|
|
78706
79128
|
);
|
|
@@ -78927,12 +79349,12 @@ var scaffoldVideoCommand = defineCommand145({
|
|
|
78927
79349
|
} catch (e) {
|
|
78928
79350
|
return fail4("download", e instanceof Error ? e.message : String(e));
|
|
78929
79351
|
}
|
|
78930
|
-
const base =
|
|
78931
|
-
const outPath = args8.out ?
|
|
79352
|
+
const base = path39.basename(videoPath, path39.extname(videoPath));
|
|
79353
|
+
const outPath = args8.out ? path39.resolve(String(args8.out)) : slug ? path39.join(process.cwd(), "src", "creatives", slug, `${slug}.canvas.json`) : path39.join(path39.dirname(videoPath), `${base}.video.canvas.json`);
|
|
78932
79354
|
if (slug) await enforceNewCreativeLimit(slug, outPath);
|
|
78933
|
-
const outDir =
|
|
78934
|
-
const blueprintPath2 =
|
|
78935
|
-
const blueprintStylePath =
|
|
79355
|
+
const outDir = path39.dirname(outPath);
|
|
79356
|
+
const blueprintPath2 = path39.join(outDir, "prompt.json");
|
|
79357
|
+
const blueprintStylePath = path39.join(outDir, "prompt.style.json");
|
|
78936
79358
|
const frames = args8.frames === "reuse" ? "reuse" : "generate";
|
|
78937
79359
|
const maxScenes = args8["max-scenes"] ? Number(args8["max-scenes"]) : void 0;
|
|
78938
79360
|
if (Number.isFinite(maxScenes)) {
|
|
@@ -79001,12 +79423,12 @@ var scaffoldVideoCommand = defineCommand145({
|
|
|
79001
79423
|
`
|
|
79002
79424
|
);
|
|
79003
79425
|
}
|
|
79004
|
-
const compositionDest =
|
|
79426
|
+
const compositionDest = path39.join(outDir, "video-overlay-composition");
|
|
79005
79427
|
await cp2(SHIPPED_OVERLAY_COMPOSITION_DIR, compositionDest, { recursive: true });
|
|
79006
79428
|
await stampCompositionDims(compositionDest, outDims);
|
|
79007
|
-
const indexPath =
|
|
79429
|
+
const indexPath = path39.join(compositionDest, "index.html");
|
|
79008
79430
|
const overlayHtml = buildOverlayHtml(blueprint, { captionsActive: Boolean(transcript), videoModel });
|
|
79009
|
-
const indexHtml = await
|
|
79431
|
+
const indexHtml = await readFile33(indexPath, "utf8");
|
|
79010
79432
|
const injected = indexHtml.replace("<!--OVERLAYS-->", () => overlayHtml);
|
|
79011
79433
|
if (injected === indexHtml && overlayHtml.trim()) {
|
|
79012
79434
|
fail4(
|
|
@@ -79020,10 +79442,10 @@ var scaffoldVideoCommand = defineCommand145({
|
|
|
79020
79442
|
const opts = {
|
|
79021
79443
|
imageModel,
|
|
79022
79444
|
videoModel,
|
|
79023
|
-
overlayCompositionPath:
|
|
79024
|
-
captionsCompositionPath: captions.compositionPath ?
|
|
79025
|
-
blueprintPath:
|
|
79026
|
-
blueprintStylePath:
|
|
79445
|
+
overlayCompositionPath: path39.relative(outDir, compositionDest),
|
|
79446
|
+
captionsCompositionPath: captions.compositionPath ? path39.relative(outDir, captions.compositionPath) : void 0,
|
|
79447
|
+
blueprintPath: path39.relative(outDir, blueprintPath2),
|
|
79448
|
+
blueprintStylePath: path39.relative(outDir, blueprintStylePath),
|
|
79027
79449
|
frames,
|
|
79028
79450
|
ambient: Boolean(args8.ambient),
|
|
79029
79451
|
seamDedup: resolveSeamDedup(args8["seam-dedup"]),
|
|
@@ -79051,7 +79473,7 @@ var scaffoldVideoCommand = defineCommand145({
|
|
|
79051
79473
|
await writeFile20(outPath, `${JSON.stringify(canvas, null, 2)}
|
|
79052
79474
|
`, "utf8");
|
|
79053
79475
|
await writeFile20(
|
|
79054
|
-
|
|
79476
|
+
path39.join(outDir, REBUILD_FILE),
|
|
79055
79477
|
`${JSON.stringify({ elements, opts }, null, 2)}
|
|
79056
79478
|
`,
|
|
79057
79479
|
"utf8"
|
|
@@ -79078,7 +79500,7 @@ var scaffoldVideoCommand = defineCommand145({
|
|
|
79078
79500
|
await ensureGitignore(process.cwd(), ["canvas/", ".context/"]);
|
|
79079
79501
|
const sourceRef = videoSourceReference(blueprint, fileArg2);
|
|
79080
79502
|
if (slug) {
|
|
79081
|
-
const definitionPath =
|
|
79503
|
+
const definitionPath = path39.join(outDir, "_definition.md");
|
|
79082
79504
|
if (!await fileExists(definitionPath)) {
|
|
79083
79505
|
await writeFile20(
|
|
79084
79506
|
definitionPath,
|
|
@@ -79134,7 +79556,7 @@ var scaffoldVideoCommand = defineCommand145({
|
|
|
79134
79556
|
graph: canvas.metadata?.video?.graph_stats
|
|
79135
79557
|
},
|
|
79136
79558
|
checklist: {
|
|
79137
|
-
edit_prompt: `The blueprint is split so you edit ONE small file at a time \u2014 never a giant one. Per-scene content (a scene's dialogue, action, frame prompts, overlays) lives in \`scenes/sNN.json\` \u2014 edit the single scene you want to change. Global cast/palette/brand/copy lives in \`${
|
|
79559
|
+
edit_prompt: `The blueprint is split so you edit ONE small file at a time \u2014 never a giant one. Per-scene content (a scene's dialogue, action, frame prompts, overlays) lives in \`scenes/sNN.json\` \u2014 edit the single scene you want to change. Global cast/palette/brand/copy lives in \`${path39.basename(blueprintPath2)}\`. \`baker canvas validate\`/\`run\` re-assemble the blueprint and re-flow every edited scene back into the render (and regenerate ${path39.basename(blueprintStylePath)}, the projection each frame's target_blueprint reads) \u2014 so your scene edits reach the render automatically. Never hand-edit the inlined node prompts in the canvas or the derived ${path39.basename(blueprintStylePath)}; both are regenerated.`,
|
|
79138
79560
|
recurring_elements_to_supply: report.elements,
|
|
79139
79561
|
voices_to_confirm: report.dialogue.map((d) => ({
|
|
79140
79562
|
scene: d.scene,
|
|
@@ -79171,8 +79593,8 @@ var scaffoldVideoCommand = defineCommand145({
|
|
|
79171
79593
|
});
|
|
79172
79594
|
|
|
79173
79595
|
// src/commands/canvas/set-prompt.ts
|
|
79174
|
-
import { readFile as
|
|
79175
|
-
import
|
|
79596
|
+
import { readFile as readFile34, writeFile as writeFile21 } from "fs/promises";
|
|
79597
|
+
import path40 from "path";
|
|
79176
79598
|
import { defineCommand as defineCommand146 } from "citty";
|
|
79177
79599
|
function setNodePrompt(canvas, nodeId, text3) {
|
|
79178
79600
|
const nodes = canvas?.nodes;
|
|
@@ -79201,8 +79623,8 @@ var SetPromptError = class extends Error {
|
|
|
79201
79623
|
}
|
|
79202
79624
|
};
|
|
79203
79625
|
async function setPromptInCanvasFile(canvasPath, nodeId, text3) {
|
|
79204
|
-
const filePath =
|
|
79205
|
-
const original = await
|
|
79626
|
+
const filePath = path40.resolve(canvasPath);
|
|
79627
|
+
const original = await readFile34(filePath, "utf8");
|
|
79206
79628
|
let canvas;
|
|
79207
79629
|
try {
|
|
79208
79630
|
canvas = JSON.parse(original);
|
|
@@ -79224,7 +79646,7 @@ async function setPromptInCanvasFile(canvasPath, nodeId, text3) {
|
|
|
79224
79646
|
"projected_prompt",
|
|
79225
79647
|
`The prompt of "${nodeId}" is compiled from prompt.json on every validate and run, so setting it in the canvas would be overwritten.`,
|
|
79226
79648
|
{
|
|
79227
|
-
fix: `Edit the ad in ${
|
|
79649
|
+
fix: `Edit the ad in ${path40.join(path40.dirname(filePath), "prompt.json")} (its scene, copy, references or direction), then run \`baker canvas validate\` to see the compiled prompt on the node.`
|
|
79228
79650
|
}
|
|
79229
79651
|
);
|
|
79230
79652
|
}
|
|
@@ -79278,7 +79700,7 @@ var setPromptCommand = defineCommand146({
|
|
|
79278
79700
|
},
|
|
79279
79701
|
async run({ args: args8 }) {
|
|
79280
79702
|
let text3;
|
|
79281
|
-
if (args8["text-file"]) text3 = await
|
|
79703
|
+
if (args8["text-file"]) text3 = await readFile34(path40.resolve(String(args8["text-file"])), "utf8");
|
|
79282
79704
|
else if (args8.text !== void 0) text3 = String(args8.text);
|
|
79283
79705
|
else {
|
|
79284
79706
|
process.stderr.write(
|
|
@@ -79335,7 +79757,7 @@ var staleCommand = defineCommand147({
|
|
|
79335
79757
|
});
|
|
79336
79758
|
|
|
79337
79759
|
// src/commands/canvas/validate.ts
|
|
79338
|
-
import
|
|
79760
|
+
import path41 from "path";
|
|
79339
79761
|
import { defineCommand as defineCommand148 } from "citty";
|
|
79340
79762
|
var validateCommand = defineCommand148({
|
|
79341
79763
|
meta: {
|
|
@@ -79344,7 +79766,7 @@ var validateCommand = defineCommand148({
|
|
|
79344
79766
|
},
|
|
79345
79767
|
args: { file: { type: "positional", required: true, description: "Path to canvas JSON" } },
|
|
79346
79768
|
async run({ args: args8 }) {
|
|
79347
|
-
const filePath =
|
|
79769
|
+
const filePath = path41.resolve(String(args8.file));
|
|
79348
79770
|
let prepared;
|
|
79349
79771
|
try {
|
|
79350
79772
|
prepared = await prepareCanvas(filePath, { gate: true, log: (line) => process.stderr.write(`${line}
|
|
@@ -80302,326 +80724,6 @@ var searchCommand = defineCommand152({
|
|
|
80302
80724
|
}
|
|
80303
80725
|
});
|
|
80304
80726
|
|
|
80305
|
-
// src/commands/creatives/ideas-model.ts
|
|
80306
|
-
import { parse as parseYaml3 } from "yaml";
|
|
80307
|
-
var IDEA_ANGLES = [
|
|
80308
|
-
"pain_relief",
|
|
80309
|
-
"aspiration",
|
|
80310
|
-
"status",
|
|
80311
|
-
"fear",
|
|
80312
|
-
"FOMO",
|
|
80313
|
-
"identity",
|
|
80314
|
-
"contrarian",
|
|
80315
|
-
"education",
|
|
80316
|
-
"novelty",
|
|
80317
|
-
"authority",
|
|
80318
|
-
"community",
|
|
80319
|
-
"transformation",
|
|
80320
|
-
"convenience",
|
|
80321
|
-
"save_money",
|
|
80322
|
-
"save_time",
|
|
80323
|
-
"save_effort",
|
|
80324
|
-
"named_enemy",
|
|
80325
|
-
"objection_crusher",
|
|
80326
|
-
"villain_democratization",
|
|
80327
|
-
"commiseration",
|
|
80328
|
-
"recognition_mirror",
|
|
80329
|
-
"flipped_idiom"
|
|
80330
|
-
];
|
|
80331
|
-
var IDEA_VIDEO_FORMATS = [
|
|
80332
|
-
"video_ugc",
|
|
80333
|
-
"video_founder",
|
|
80334
|
-
"video_testimonial",
|
|
80335
|
-
"video_demo",
|
|
80336
|
-
"video_screen_recording",
|
|
80337
|
-
"video_explainer",
|
|
80338
|
-
"video_motion_graphics"
|
|
80339
|
-
];
|
|
80340
|
-
var IDEA_FORMATS = [...staticFormatPatternSchema.options, ...IDEA_VIDEO_FORMATS];
|
|
80341
|
-
var AWARENESS_STAGES = ["unaware", "problem_aware", "solution_aware", "product_aware", "most_aware"];
|
|
80342
|
-
var EVIDENCE_LEVELS = ["observed", "interpreted", "hypothesized", "generated"];
|
|
80343
|
-
var IDEA_STATUSES = ["idea", "built", "testing", "winner", "failed", "inconclusive", "retired"];
|
|
80344
|
-
var FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
80345
|
-
function frontmatterOf2(markdown) {
|
|
80346
|
-
const block2 = FRONTMATTER.exec(markdown)?.[1];
|
|
80347
|
-
if (block2 === void 0) return null;
|
|
80348
|
-
try {
|
|
80349
|
-
const parsed = parseYaml3(block2);
|
|
80350
|
-
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
80351
|
-
} catch {
|
|
80352
|
-
return null;
|
|
80353
|
-
}
|
|
80354
|
-
}
|
|
80355
|
-
function text2(value2) {
|
|
80356
|
-
if (typeof value2 === "string") return value2.trim() || void 0;
|
|
80357
|
-
if (typeof value2 === "number") return String(value2);
|
|
80358
|
-
return void 0;
|
|
80359
|
-
}
|
|
80360
|
-
function oneOf2(values, value2) {
|
|
80361
|
-
return typeof value2 === "string" && values.includes(value2) ? value2 : void 0;
|
|
80362
|
-
}
|
|
80363
|
-
function parseInsight(id, markdown) {
|
|
80364
|
-
const fm = frontmatterOf2(markdown);
|
|
80365
|
-
const quote = text2(fm?.quote);
|
|
80366
|
-
const source = text2(fm?.source);
|
|
80367
|
-
if (!fm || !quote || !source) return null;
|
|
80368
|
-
const force = text2(fm.force);
|
|
80369
|
-
const persona = text2(fm.persona);
|
|
80370
|
-
const sourceKind = text2(fm.sourceKind);
|
|
80371
|
-
return {
|
|
80372
|
-
id,
|
|
80373
|
-
type: text2(fm.type) ?? "other",
|
|
80374
|
-
quote,
|
|
80375
|
-
source,
|
|
80376
|
-
status: oneOf2(EVIDENCE_LEVELS, fm.status) ?? "observed",
|
|
80377
|
-
...sourceKind ? { sourceKind } : {},
|
|
80378
|
-
...force ? { force } : {},
|
|
80379
|
-
...persona ? { persona } : {}
|
|
80380
|
-
};
|
|
80381
|
-
}
|
|
80382
|
-
function evidenceList(value2) {
|
|
80383
|
-
if (Array.isArray(value2)) return value2.flatMap((e) => text2(e) ? [String(text2(e))] : []);
|
|
80384
|
-
const single = text2(value2);
|
|
80385
|
-
return single ? [single] : [];
|
|
80386
|
-
}
|
|
80387
|
-
function fieldProblems(f) {
|
|
80388
|
-
const problems = [];
|
|
80389
|
-
if (!f.who) problems.push("who is missing: the persona it is for");
|
|
80390
|
-
if (!f.message)
|
|
80391
|
-
problems.push(
|
|
80392
|
-
"message is missing: the one promise a stranger should get, what is offered and what changes for them"
|
|
80393
|
-
);
|
|
80394
|
-
if (!f.why) problems.push("why is missing: the brief finding it answers");
|
|
80395
|
-
if (!f.awareness) problems.push(`awareness must be one of ${AWARENESS_STAGES.join(", ")}`);
|
|
80396
|
-
if (!(f.angle && IDEA_ANGLES.includes(f.angle)))
|
|
80397
|
-
problems.push(`angle ${f.angle ? `"${f.angle}" ` : ""}must be one of ${IDEA_ANGLES.join(", ")}`);
|
|
80398
|
-
if (!(f.format && IDEA_FORMATS.includes(f.format)))
|
|
80399
|
-
problems.push(`format ${f.format ? `"${f.format}" ` : ""}must be one of ${IDEA_FORMATS.join(", ")}`);
|
|
80400
|
-
return problems;
|
|
80401
|
-
}
|
|
80402
|
-
function parseIdea(id, markdown) {
|
|
80403
|
-
const fm = frontmatterOf2(markdown) ?? {};
|
|
80404
|
-
const f = {
|
|
80405
|
-
who: text2(fm.who),
|
|
80406
|
-
message: text2(fm.message),
|
|
80407
|
-
why: text2(fm.why),
|
|
80408
|
-
angle: text2(fm.angle),
|
|
80409
|
-
format: text2(fm.format),
|
|
80410
|
-
awareness: oneOf2(AWARENESS_STAGES, fm.awareness)
|
|
80411
|
-
};
|
|
80412
|
-
const problems = fieldProblems(f);
|
|
80413
|
-
const { who, message, why, angle, format, awareness } = f;
|
|
80414
|
-
if (problems.length > 0 || !who || !message || !why || !awareness || !angle || !format) return { problems };
|
|
80415
|
-
return {
|
|
80416
|
-
idea: {
|
|
80417
|
-
id,
|
|
80418
|
-
who,
|
|
80419
|
-
message,
|
|
80420
|
-
evidence: evidenceList(fm.evidence),
|
|
80421
|
-
awareness,
|
|
80422
|
-
angle,
|
|
80423
|
-
format,
|
|
80424
|
-
job: fm.job === "closer" ? "closer" : "opener",
|
|
80425
|
-
why,
|
|
80426
|
-
status: oneOf2(IDEA_STATUSES, fm.status) ?? "idea"
|
|
80427
|
-
},
|
|
80428
|
-
problems
|
|
80429
|
-
};
|
|
80430
|
-
}
|
|
80431
|
-
var EVIDENCE_WEIGHT = {
|
|
80432
|
-
observed: 3,
|
|
80433
|
-
interpreted: 2,
|
|
80434
|
-
hypothesized: 1,
|
|
80435
|
-
generated: 0,
|
|
80436
|
-
explore: 0
|
|
80437
|
-
};
|
|
80438
|
-
var LIVE_STATUSES = ["built", "testing", "winner"];
|
|
80439
|
-
function claimsOf(message) {
|
|
80440
|
-
return [...message.matchAll(/\d[\d.,]*\s?(?:%|€|\$|£|x\b|k\b)?/gi)].map((m) => m[0].trim());
|
|
80441
|
-
}
|
|
80442
|
-
function figureOf(claim) {
|
|
80443
|
-
return claim.replace(/[^\d]/g, "");
|
|
80444
|
-
}
|
|
80445
|
-
function figuresIn(text3) {
|
|
80446
|
-
return new Set([...text3.matchAll(/\d(?:[\d.,]*\d)?/g)].map((m) => figureOf(m[0])));
|
|
80447
|
-
}
|
|
80448
|
-
function normalize(value2) {
|
|
80449
|
-
return value2.toLowerCase().replace(/\s+/g, " ").trim();
|
|
80450
|
-
}
|
|
80451
|
-
function sentencesOf(message) {
|
|
80452
|
-
return message.split(/[.!?]+(?:\s|$)/).filter((s) => s.trim().length > 0).length;
|
|
80453
|
-
}
|
|
80454
|
-
var FILLER = /* @__PURE__ */ new Set([
|
|
80455
|
-
"that",
|
|
80456
|
-
"this",
|
|
80457
|
-
"with",
|
|
80458
|
-
"your",
|
|
80459
|
-
"from",
|
|
80460
|
-
"they",
|
|
80461
|
-
"them",
|
|
80462
|
-
"what",
|
|
80463
|
-
"when",
|
|
80464
|
-
"have",
|
|
80465
|
-
"will",
|
|
80466
|
-
"into",
|
|
80467
|
-
"than",
|
|
80468
|
-
"more",
|
|
80469
|
-
"just",
|
|
80470
|
-
"para",
|
|
80471
|
-
"como",
|
|
80472
|
-
"pero",
|
|
80473
|
-
"sobre"
|
|
80474
|
-
]);
|
|
80475
|
-
var SAME_MESSAGE = 0.6;
|
|
80476
|
-
function messageWords(message) {
|
|
80477
|
-
return new Set(
|
|
80478
|
-
message.toLowerCase().split(/[^\p{L}]+/u).filter((w) => w.length >= 4 && !FILLER.has(w)).map((w) => w.replace(/s$/, ""))
|
|
80479
|
-
);
|
|
80480
|
-
}
|
|
80481
|
-
function sameMessage(a, b) {
|
|
80482
|
-
const wa = messageWords(a);
|
|
80483
|
-
const wb = messageWords(b);
|
|
80484
|
-
const shared = [...wa].filter((w) => wb.has(w)).length;
|
|
80485
|
-
const all = (/* @__PURE__ */ new Set([...wa, ...wb])).size;
|
|
80486
|
-
return normalize(a) === normalize(b) || all > 0 && shared / all >= SAME_MESSAGE;
|
|
80487
|
-
}
|
|
80488
|
-
function repeatedFailure(idea, failed) {
|
|
80489
|
-
const lost = failed.find(
|
|
80490
|
-
(f) => f.id !== idea.id && (sameMessage(f.message, idea.message) || f.angle === idea.angle && f.format === idea.format)
|
|
80491
|
-
);
|
|
80492
|
-
return lost ? [`says what "${lost.id}" said, and that idea failed in the account: change the message, not just the picture`] : [];
|
|
80493
|
-
}
|
|
80494
|
-
function backingOf(idea, insights, problems) {
|
|
80495
|
-
const backing = idea.evidence.flatMap((id) => {
|
|
80496
|
-
const insight = insights.get(id);
|
|
80497
|
-
if (!insight)
|
|
80498
|
-
problems.push(`evidence "${id}" is not in src/content/insights/: name an insight on file or leave it out`);
|
|
80499
|
-
return insight ? [insight] : [];
|
|
80500
|
-
});
|
|
80501
|
-
return [...backing].sort((a, b) => EVIDENCE_WEIGHT[b.status] - EVIDENCE_WEIGHT[a.status])[0];
|
|
80502
|
-
}
|
|
80503
|
-
function unsourcedClaims(text3, corpusFigures) {
|
|
80504
|
-
return claimsOf(text3).filter((claim) => {
|
|
80505
|
-
const figure = figureOf(claim);
|
|
80506
|
-
return figure !== "" && !corpusFigures.has(figure);
|
|
80507
|
-
});
|
|
80508
|
-
}
|
|
80509
|
-
function claimProblems(message, corpusFigures) {
|
|
80510
|
-
return unsourcedClaims(message, corpusFigures).map(
|
|
80511
|
-
(claim) => `"${claim}" is in no customer quote, COMPANY.md or Knowledge entry: source it or drop it`
|
|
80512
|
-
);
|
|
80513
|
-
}
|
|
80514
|
-
var MARKET_SOURCES = ["reddit", "forum", "web"];
|
|
80515
|
-
var TESTIMONIAL_FORMATS = ["review_screenshot", "testimonial_quote", "video_testimonial"];
|
|
80516
|
-
function fromMarket(insight) {
|
|
80517
|
-
return insight?.sourceKind !== void 0 && MARKET_SOURCES.includes(insight.sourceKind);
|
|
80518
|
-
}
|
|
80519
|
-
function borrowedProof(idea, insights) {
|
|
80520
|
-
return idea.evidence.find((id) => {
|
|
80521
|
-
const insight = insights.get(id);
|
|
80522
|
-
return fromMarket(insight) && insight?.type === "proof";
|
|
80523
|
-
});
|
|
80524
|
-
}
|
|
80525
|
-
function fakeReviewProblem(idea, insights) {
|
|
80526
|
-
if (!TESTIMONIAL_FORMATS.includes(idea.format)) return [];
|
|
80527
|
-
const cited = idea.evidence.flatMap((id) => insights.get(id) ?? []);
|
|
80528
|
-
if (cited.length === 0 || !cited.every(fromMarket)) return [];
|
|
80529
|
-
return [
|
|
80530
|
-
`${idea.format} needs a quote from our own customer (a review, testimonial, call or survey); every one cited was said by someone in the market. Pick another format, or cite our own customer`
|
|
80531
|
-
];
|
|
80532
|
-
}
|
|
80533
|
-
function warningsOf(idea, evidence, personas, insights) {
|
|
80534
|
-
const warnings = [];
|
|
80535
|
-
const borrowed = borrowedProof(idea, insights);
|
|
80536
|
-
if (borrowed) {
|
|
80537
|
-
warnings.push(
|
|
80538
|
-
`"${borrowed}" was said by someone in the market, not a customer of ours: use their words for the problem, never show it as our review, testimonial or result`
|
|
80539
|
-
);
|
|
80540
|
-
}
|
|
80541
|
-
if (sentencesOf(idea.message) > 1 || idea.message.split(/\s+/).length > 25) {
|
|
80542
|
-
warnings.push("the message says more than one thing: keep one promise, the rest goes to the ad text");
|
|
80543
|
-
}
|
|
80544
|
-
if (personas.length > 0 && !personas.includes(idea.who)) {
|
|
80545
|
-
warnings.push(`who "${idea.who}" is not a persona in the Knowledge (${personas.join(", ")})`);
|
|
80546
|
-
}
|
|
80547
|
-
if (evidence === "explore") {
|
|
80548
|
-
warnings.push("no customer evidence: an explore bet. `baker research customers` may find some");
|
|
80549
|
-
}
|
|
80550
|
-
return warnings;
|
|
80551
|
-
}
|
|
80552
|
-
function checkOne(idea, ctx) {
|
|
80553
|
-
const problems = [];
|
|
80554
|
-
const best = backingOf(idea, ctx.insights, problems);
|
|
80555
|
-
const evidence = best?.status ?? "explore";
|
|
80556
|
-
problems.push(...claimProblems(idea.message, ctx.corpusFigures), ...fakeReviewProblem(idea, ctx.insights));
|
|
80557
|
-
const duplicate = ctx.duplicateOf.get(idea.id);
|
|
80558
|
-
if (duplicate) {
|
|
80559
|
-
problems.push(
|
|
80560
|
-
`same angle and format, or the same message, as "${duplicate}": to the platform it is the same ad. Change the angle or the format`
|
|
80561
|
-
);
|
|
80562
|
-
}
|
|
80563
|
-
const gaps = [];
|
|
80564
|
-
if (!ctx.covered.has(idea.awareness)) gaps.push(`no live ad speaks to ${idea.awareness} people`);
|
|
80565
|
-
if (idea.job === "closer" && ctx.closersLive === 0) gaps.push("no live ad closes warm people");
|
|
80566
|
-
return {
|
|
80567
|
-
id: idea.id,
|
|
80568
|
-
status: idea.status,
|
|
80569
|
-
message: idea.message,
|
|
80570
|
-
evidence,
|
|
80571
|
-
...best ? { quote: best.quote } : {},
|
|
80572
|
-
awareness: idea.awareness,
|
|
80573
|
-
angle: idea.angle,
|
|
80574
|
-
format: idea.format,
|
|
80575
|
-
problems,
|
|
80576
|
-
warnings: [...warningsOf(idea, evidence, ctx.personas, ctx.insights), ...repeatedFailure(idea, ctx.failed)],
|
|
80577
|
-
gaps,
|
|
80578
|
-
score: EVIDENCE_WEIGHT[evidence] + gaps.length - (problems.length > 0 ? 10 : 0)
|
|
80579
|
-
};
|
|
80580
|
-
}
|
|
80581
|
-
function duplicatesOf(ideas, insights) {
|
|
80582
|
-
const active = ideas.filter((i) => i.status !== "retired" && i.status !== "failed");
|
|
80583
|
-
const strength = (idea) => Math.max(0, ...idea.evidence.map((id) => EVIDENCE_WEIGHT[insights.get(id)?.status ?? "explore"]));
|
|
80584
|
-
const position = new Map(ideas.map((idea, index) => [idea.id, index]));
|
|
80585
|
-
const order = [...active].sort(
|
|
80586
|
-
(a, b) => Number(LIVE_STATUSES.includes(b.status)) - Number(LIVE_STATUSES.includes(a.status)) || strength(b) - strength(a) || (position.get(a.id) ?? 0) - (position.get(b.id) ?? 0)
|
|
80587
|
-
);
|
|
80588
|
-
const seenPairs = /* @__PURE__ */ new Map();
|
|
80589
|
-
const seenMessages = /* @__PURE__ */ new Map();
|
|
80590
|
-
const duplicateOf = /* @__PURE__ */ new Map();
|
|
80591
|
-
for (const idea of order) {
|
|
80592
|
-
const pair = `${idea.angle}\xD7${idea.format}`;
|
|
80593
|
-
const message = normalize(idea.message);
|
|
80594
|
-
const earlier = seenPairs.get(pair) ?? seenMessages.get(message);
|
|
80595
|
-
if (earlier) duplicateOf.set(idea.id, earlier);
|
|
80596
|
-
if (!seenPairs.has(pair)) seenPairs.set(pair, idea.id);
|
|
80597
|
-
if (!seenMessages.has(message)) seenMessages.set(message, idea.id);
|
|
80598
|
-
}
|
|
80599
|
-
return duplicateOf;
|
|
80600
|
-
}
|
|
80601
|
-
function checkIdeas(input) {
|
|
80602
|
-
const insights = new Map(input.insights.map((insight) => [insight.id, insight]));
|
|
80603
|
-
const ownQuotes = input.insights.filter((i) => !fromMarket(i)).map((i) => i.quote);
|
|
80604
|
-
const corpusFigures = figuresIn(`${input.knowledge}
|
|
80605
|
-
${ownQuotes.join("\n")}`);
|
|
80606
|
-
const covered = new Set(input.coveredAwareness);
|
|
80607
|
-
const duplicateOf = duplicatesOf(input.ideas, insights);
|
|
80608
|
-
const checks = input.ideas.map(
|
|
80609
|
-
(idea) => checkOne(idea, {
|
|
80610
|
-
insights,
|
|
80611
|
-
corpusFigures,
|
|
80612
|
-
covered,
|
|
80613
|
-
duplicateOf,
|
|
80614
|
-
personas: input.personas,
|
|
80615
|
-
closersLive: input.closersLive,
|
|
80616
|
-
failed: input.ideas.filter((i) => i.status === "failed")
|
|
80617
|
-
})
|
|
80618
|
-
);
|
|
80619
|
-
const toBuild = (c) => c.status === "idea";
|
|
80620
|
-
return checks.sort(
|
|
80621
|
-
(a, b) => Number(toBuild(b)) - Number(toBuild(a)) || b.score - a.score || a.id.localeCompare(b.id)
|
|
80622
|
-
);
|
|
80623
|
-
}
|
|
80624
|
-
|
|
80625
80727
|
// src/commands/creatives/brief-model.ts
|
|
80626
80728
|
var MIN_IMPRESSIONS2 = 1e3;
|
|
80627
80729
|
var LOW_CTR = 1.5;
|
|
@@ -80731,85 +80833,6 @@ function marketMap(ads, source) {
|
|
|
80731
80833
|
|
|
80732
80834
|
// src/commands/creatives/ideas.ts
|
|
80733
80835
|
import { defineCommand as defineCommand153 } from "citty";
|
|
80734
|
-
|
|
80735
|
-
// src/commands/creatives/ideas-workspace.ts
|
|
80736
|
-
import { readdir as readdir16, readFile as readFile34 } from "fs/promises";
|
|
80737
|
-
import path41 from "path";
|
|
80738
|
-
var IDEAS_DIR = path41.join("src", "creatives", "_ideas");
|
|
80739
|
-
var INSIGHTS_DIR = path41.join("src", "content", "insights");
|
|
80740
|
-
async function markdownFiles(dir, recursive = false) {
|
|
80741
|
-
const entries = await readdir16(dir, { recursive }).catch(() => []);
|
|
80742
|
-
return entries.filter((f) => f.endsWith(".md")).sort();
|
|
80743
|
-
}
|
|
80744
|
-
var idOf2 = (file) => file.replace(/\\/g, "/").replace(/\.md$/, "");
|
|
80745
|
-
async function readInsights(root) {
|
|
80746
|
-
const dir = path41.join(root, INSIGHTS_DIR);
|
|
80747
|
-
const insights = [];
|
|
80748
|
-
for (const file of await markdownFiles(dir, true)) {
|
|
80749
|
-
const insight = parseInsight(idOf2(file), await readFile34(path41.join(dir, file), "utf8").catch(() => ""));
|
|
80750
|
-
if (insight) insights.push(insight);
|
|
80751
|
-
}
|
|
80752
|
-
return insights;
|
|
80753
|
-
}
|
|
80754
|
-
async function readIdeas(root) {
|
|
80755
|
-
const dir = path41.join(root, IDEAS_DIR);
|
|
80756
|
-
const ideas = [];
|
|
80757
|
-
const unreadable = [];
|
|
80758
|
-
for (const file of await markdownFiles(dir)) {
|
|
80759
|
-
const id = idOf2(file);
|
|
80760
|
-
const parsed = parseIdea(id, await readFile34(path41.join(dir, file), "utf8").catch(() => ""));
|
|
80761
|
-
if (parsed.idea) ideas.push(parsed.idea);
|
|
80762
|
-
else unreadable.push({ id, problems: parsed.problems });
|
|
80763
|
-
}
|
|
80764
|
-
return { ideas, unreadable };
|
|
80765
|
-
}
|
|
80766
|
-
async function readPersonaIds(root) {
|
|
80767
|
-
return (await markdownFiles(path41.join(root, "src", "content", "personas"), true)).map(idOf2);
|
|
80768
|
-
}
|
|
80769
|
-
var MAX_KNOWLEDGE_CHARS = 2e5;
|
|
80770
|
-
async function readKnowledgeCorpus(root) {
|
|
80771
|
-
const parts = [];
|
|
80772
|
-
const company = await readFile34(path41.join(root, "COMPANY.md"), "utf8").catch(() => "");
|
|
80773
|
-
if (company) parts.push(company);
|
|
80774
|
-
const content = path41.join(root, "src", "content");
|
|
80775
|
-
for (const file of await markdownFiles(content, true)) {
|
|
80776
|
-
parts.push(await readFile34(path41.join(content, file), "utf8").catch(() => ""));
|
|
80777
|
-
if (parts.join("").length > MAX_KNOWLEDGE_CHARS) break;
|
|
80778
|
-
}
|
|
80779
|
-
return parts.join("\n").slice(0, MAX_KNOWLEDGE_CHARS);
|
|
80780
|
-
}
|
|
80781
|
-
async function readCreativeRecords(root) {
|
|
80782
|
-
const dir = path41.join(root, "src", "creatives");
|
|
80783
|
-
const entries = await readdir16(dir, { withFileTypes: true }).catch(() => []);
|
|
80784
|
-
const records = [];
|
|
80785
|
-
for (const entry of entries) {
|
|
80786
|
-
if (!entry.isDirectory() || entry.name.startsWith("_")) continue;
|
|
80787
|
-
const raw = await readFile34(path41.join(dir, entry.name, "_definition.md"), "utf8").catch(() => null);
|
|
80788
|
-
if (raw === null) continue;
|
|
80789
|
-
const fm = frontmatterOf2(raw) ?? {};
|
|
80790
|
-
const pick2 = (key) => typeof fm[key] === "string" && fm[key] ? { [key]: fm[key] } : {};
|
|
80791
|
-
records.push({
|
|
80792
|
-
slug: entry.name,
|
|
80793
|
-
...pick2("status"),
|
|
80794
|
-
...pick2("idea"),
|
|
80795
|
-
...pick2("awareness"),
|
|
80796
|
-
...pick2("angle"),
|
|
80797
|
-
...pick2("kind")
|
|
80798
|
-
});
|
|
80799
|
-
}
|
|
80800
|
-
return records;
|
|
80801
|
-
}
|
|
80802
|
-
var SHOWN_STATUSES = /* @__PURE__ */ new Set(["rendered", "published"]);
|
|
80803
|
-
function coverageOf(ideas, creatives) {
|
|
80804
|
-
const live = ideas.filter((i) => i.status === "built" || i.status === "testing" || i.status === "winner");
|
|
80805
|
-
const awareness = new Set(live.map((i) => i.awareness));
|
|
80806
|
-
for (const creative of creatives) {
|
|
80807
|
-
if (creative.awareness && SHOWN_STATUSES.has(creative.status ?? "")) awareness.add(creative.awareness);
|
|
80808
|
-
}
|
|
80809
|
-
return { coveredAwareness: [...awareness].sort(), closersLive: live.filter((i) => i.job === "closer").length };
|
|
80810
|
-
}
|
|
80811
|
-
|
|
80812
|
-
// src/commands/creatives/ideas.ts
|
|
80813
80836
|
var BUILD_NEXT = 3;
|
|
80814
80837
|
var DESCRIPTION4 = `Check and rank the ad ideas in src/creatives/_ideas/: is each backed by something a customer said (src/content/insights/), does it claim only what the Knowledge holds, is it a different ad from the others, does it fill a gap the live ads leave. Free and local.
|
|
80815
80838
|
|
|
@@ -80847,7 +80870,11 @@ function ideasHints(report) {
|
|
|
80847
80870
|
hints2.push("No idea cites an insight: put the insight ids that back each one in its `evidence`.");
|
|
80848
80871
|
}
|
|
80849
80872
|
const next = buildNext(report.ideas);
|
|
80850
|
-
if (next.length
|
|
80873
|
+
if (next.length === 1) hints2.push(`Build: \`baker creatives make ${next[0]}\`.`);
|
|
80874
|
+
if (next.length > 1)
|
|
80875
|
+
hints2.push(
|
|
80876
|
+
`Build ${next.join(", ")} at once: one \`creative-producer\` per idea, all spawned in one message, each given its idea id. Never build them one after another: each render takes about five minutes.`
|
|
80877
|
+
);
|
|
80851
80878
|
return hints2;
|
|
80852
80879
|
}
|
|
80853
80880
|
function buildNext(ideas, count2 = BUILD_NEXT) {
|
|
@@ -81957,7 +81984,7 @@ async function recordIdea(root, slug, idea, evidence) {
|
|
|
81957
81984
|
}
|
|
81958
81985
|
async function draftedClaims(root, prompt) {
|
|
81959
81986
|
const [knowledge2, insights] = await Promise.all([readKnowledgeCorpus(root), readInsights(root)]);
|
|
81960
|
-
const own = insights.filter(
|
|
81987
|
+
const own = insights.filter(sourcesClaims).map((i) => i.quote);
|
|
81961
81988
|
try {
|
|
81962
81989
|
return unsourcedCopyLines(prompt, `${knowledge2}
|
|
81963
81990
|
${own.join("\n")}`);
|