@koda-sl/baker-cli 0.138.0 → 0.139.0-dev.da48e17cb
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 +15 -0
- package/dist/{chunk-TAGQCU36.js → chunk-TXNOPO6P.js} +4 -4
- package/dist/chunk-TXNOPO6P.js.map +1 -0
- package/dist/cli.js +1206 -182
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-TAGQCU36.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
toModelSafeImage,
|
|
35
35
|
ulid,
|
|
36
36
|
validateCanvasDeep
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-TXNOPO6P.js";
|
|
38
38
|
import {
|
|
39
39
|
csvOrJson,
|
|
40
40
|
daysAgoIso,
|
|
@@ -73,7 +73,7 @@ import {
|
|
|
73
73
|
} from "./chunk-RK67WL4O.js";
|
|
74
74
|
|
|
75
75
|
// src/cli.ts
|
|
76
|
-
import { defineCommand as
|
|
76
|
+
import { defineCommand as defineCommand176, runMain } from "citty";
|
|
77
77
|
|
|
78
78
|
// src/commands/actions/index.ts
|
|
79
79
|
import { defineCommand as defineCommand18 } from "citty";
|
|
@@ -5455,11 +5455,11 @@ function microsFlag(value, flag) {
|
|
|
5455
5455
|
if (value === void 0 || value === null || value === "") {
|
|
5456
5456
|
return void 0;
|
|
5457
5457
|
}
|
|
5458
|
-
const
|
|
5459
|
-
if (Number.isNaN(
|
|
5458
|
+
const num2 = Number(value);
|
|
5459
|
+
if (Number.isNaN(num2) || num2 <= 0) {
|
|
5460
5460
|
failWriteValidation(`${flag} must be a positive amount (major currency units, e.g. 50 or 50.00)`);
|
|
5461
5461
|
}
|
|
5462
|
-
return toMicros(
|
|
5462
|
+
return toMicros(num2);
|
|
5463
5463
|
}
|
|
5464
5464
|
function listFlag(value) {
|
|
5465
5465
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -5487,11 +5487,11 @@ function rawTextEntries(value) {
|
|
|
5487
5487
|
const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
5488
5488
|
return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
5489
5489
|
}
|
|
5490
|
-
function rawFileEntries(
|
|
5491
|
-
if (typeof
|
|
5490
|
+
function rawFileEntries(path27) {
|
|
5491
|
+
if (typeof path27 !== "string" || path27.length === 0) {
|
|
5492
5492
|
return [];
|
|
5493
5493
|
}
|
|
5494
|
-
return readFileSync2(
|
|
5494
|
+
return readFileSync2(path27, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
5495
5495
|
}
|
|
5496
5496
|
function keywordEntries(args) {
|
|
5497
5497
|
const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
|
|
@@ -5514,19 +5514,19 @@ function keywordEntries(args) {
|
|
|
5514
5514
|
}
|
|
5515
5515
|
return entries;
|
|
5516
5516
|
}
|
|
5517
|
-
function loadJsonFileArg(
|
|
5518
|
-
if (typeof
|
|
5517
|
+
function loadJsonFileArg(path27) {
|
|
5518
|
+
if (typeof path27 !== "string" || path27.length === 0) {
|
|
5519
5519
|
return {};
|
|
5520
5520
|
}
|
|
5521
5521
|
try {
|
|
5522
|
-
const parsed = JSON.parse(readFileSync2(
|
|
5522
|
+
const parsed = JSON.parse(readFileSync2(path27, "utf8"));
|
|
5523
5523
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
5524
|
-
failWriteValidation(`${
|
|
5524
|
+
failWriteValidation(`${path27} must contain a JSON object`);
|
|
5525
5525
|
}
|
|
5526
5526
|
return parsed;
|
|
5527
5527
|
} catch (err) {
|
|
5528
5528
|
if (err instanceof SyntaxError) {
|
|
5529
|
-
failWriteValidation(`${
|
|
5529
|
+
failWriteValidation(`${path27} is not valid JSON: ${err.message}`);
|
|
5530
5530
|
}
|
|
5531
5531
|
throw err;
|
|
5532
5532
|
}
|
|
@@ -5637,10 +5637,10 @@ async function stageUpdate(kind, customerId, target, payload) {
|
|
|
5637
5637
|
async function stageTarget(kind, customerId, target) {
|
|
5638
5638
|
await stageGoogleOp({ kind, customerId, target });
|
|
5639
5639
|
}
|
|
5640
|
-
async function draftAction(
|
|
5640
|
+
async function draftAction(path27, body) {
|
|
5641
5641
|
try {
|
|
5642
5642
|
const chatId = requireChatId();
|
|
5643
|
-
const response = await apiPost(
|
|
5643
|
+
const response = await apiPost(path27, { chatId, ...body });
|
|
5644
5644
|
writeJsonEnvelope(response);
|
|
5645
5645
|
} catch (err) {
|
|
5646
5646
|
handleGoogleError(err);
|
|
@@ -9203,19 +9203,19 @@ function failWriteValidation2(message) {
|
|
|
9203
9203
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
9204
9204
|
process.exit(1);
|
|
9205
9205
|
}
|
|
9206
|
-
function loadJsonFileArg2(
|
|
9207
|
-
if (typeof
|
|
9206
|
+
function loadJsonFileArg2(path27) {
|
|
9207
|
+
if (typeof path27 !== "string" || path27.length === 0) {
|
|
9208
9208
|
return {};
|
|
9209
9209
|
}
|
|
9210
9210
|
try {
|
|
9211
|
-
const parsed = JSON.parse(readFileSync6(
|
|
9211
|
+
const parsed = JSON.parse(readFileSync6(path27, "utf8"));
|
|
9212
9212
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9213
|
-
failWriteValidation2(`${
|
|
9213
|
+
failWriteValidation2(`${path27} must contain a JSON object`);
|
|
9214
9214
|
}
|
|
9215
9215
|
return parsed;
|
|
9216
9216
|
} catch (err) {
|
|
9217
9217
|
if (err instanceof SyntaxError) {
|
|
9218
|
-
failWriteValidation2(`${
|
|
9218
|
+
failWriteValidation2(`${path27} is not valid JSON: ${err.message}`);
|
|
9219
9219
|
}
|
|
9220
9220
|
throw err;
|
|
9221
9221
|
}
|
|
@@ -9300,15 +9300,15 @@ function parseLocaleFlag(value) {
|
|
|
9300
9300
|
}
|
|
9301
9301
|
return { language: match[1], country: match[2].toUpperCase() };
|
|
9302
9302
|
}
|
|
9303
|
-
function loadTargetingFileArg(
|
|
9304
|
-
if (typeof
|
|
9303
|
+
function loadTargetingFileArg(path27) {
|
|
9304
|
+
if (typeof path27 !== "string" || path27.length === 0) {
|
|
9305
9305
|
return void 0;
|
|
9306
9306
|
}
|
|
9307
|
-
const parsed = loadJsonFileArg2(
|
|
9307
|
+
const parsed = loadJsonFileArg2(path27);
|
|
9308
9308
|
const criteria = parsed.targetingCriteria ?? parsed;
|
|
9309
9309
|
if (!criteria.include) {
|
|
9310
9310
|
failWriteValidation2(
|
|
9311
|
-
`${
|
|
9311
|
+
`${path27} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
|
|
9312
9312
|
);
|
|
9313
9313
|
}
|
|
9314
9314
|
return criteria;
|
|
@@ -9343,14 +9343,14 @@ function parseCsvLine(line) {
|
|
|
9343
9343
|
cells.push(current);
|
|
9344
9344
|
return cells.map((cell) => cell.trim());
|
|
9345
9345
|
}
|
|
9346
|
-
function parseListFileArg(
|
|
9347
|
-
if (typeof
|
|
9346
|
+
function parseListFileArg(path27, maxRows) {
|
|
9347
|
+
if (typeof path27 !== "string" || path27.length === 0) {
|
|
9348
9348
|
return void 0;
|
|
9349
9349
|
}
|
|
9350
|
-
const raw = readFileSync6(
|
|
9350
|
+
const raw = readFileSync6(path27, "utf8");
|
|
9351
9351
|
const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
9352
9352
|
if (lines.length < 2) {
|
|
9353
|
-
failWriteValidation2(`${
|
|
9353
|
+
failWriteValidation2(`${path27} needs a header row and at least one data row`);
|
|
9354
9354
|
}
|
|
9355
9355
|
const columns = parseCsvLine(lines[0]).map((column) => column.trim());
|
|
9356
9356
|
const rows = [];
|
|
@@ -9369,7 +9369,7 @@ function parseListFileArg(path23, maxRows) {
|
|
|
9369
9369
|
}
|
|
9370
9370
|
}
|
|
9371
9371
|
if (rows.length > maxRows) {
|
|
9372
|
-
failWriteValidation2(`${
|
|
9372
|
+
failWriteValidation2(`${path27} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
|
|
9373
9373
|
}
|
|
9374
9374
|
return { columns, rows };
|
|
9375
9375
|
}
|
|
@@ -11452,11 +11452,11 @@ var updateStatusSchema = z14.enum(UPDATE_STATUSES);
|
|
|
11452
11452
|
function currencyMinimums2(currencyCode) {
|
|
11453
11453
|
return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
|
|
11454
11454
|
}
|
|
11455
|
-
function validateDailyBudgetFloor(money, ctx,
|
|
11455
|
+
function validateDailyBudgetFloor(money, ctx, path27) {
|
|
11456
11456
|
if (money?.currencyCode) {
|
|
11457
11457
|
const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
|
|
11458
11458
|
if (Number(money.amount) < min) {
|
|
11459
|
-
ctx.addIssue({ code: "custom", path:
|
|
11459
|
+
ctx.addIssue({ code: "custom", path: path27, message: `below the ${min} ${money.currencyCode} daily minimum` });
|
|
11460
11460
|
}
|
|
11461
11461
|
}
|
|
11462
11462
|
}
|
|
@@ -11944,19 +11944,19 @@ function failWriteValidation3(message) {
|
|
|
11944
11944
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
11945
11945
|
process.exit(1);
|
|
11946
11946
|
}
|
|
11947
|
-
function loadJsonFileArg3(
|
|
11948
|
-
if (typeof
|
|
11947
|
+
function loadJsonFileArg3(path27) {
|
|
11948
|
+
if (typeof path27 !== "string" || path27.length === 0) {
|
|
11949
11949
|
return {};
|
|
11950
11950
|
}
|
|
11951
11951
|
try {
|
|
11952
|
-
const parsed = JSON.parse(readFileSync8(
|
|
11952
|
+
const parsed = JSON.parse(readFileSync8(path27, "utf8"));
|
|
11953
11953
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
11954
|
-
failWriteValidation3(`${
|
|
11954
|
+
failWriteValidation3(`${path27} must contain a JSON object`);
|
|
11955
11955
|
}
|
|
11956
11956
|
return parsed;
|
|
11957
11957
|
} catch (err) {
|
|
11958
11958
|
if (err instanceof SyntaxError) {
|
|
11959
|
-
failWriteValidation3(`${
|
|
11959
|
+
failWriteValidation3(`${path27} is not valid JSON: ${err.message}`);
|
|
11960
11960
|
}
|
|
11961
11961
|
throw err;
|
|
11962
11962
|
}
|
|
@@ -16123,13 +16123,13 @@ function referenceRank(type) {
|
|
|
16123
16123
|
function orderSlotsByIdentity(slots) {
|
|
16124
16124
|
return [...slots].sort((a, b) => referenceRank(a.type) - referenceRank(b.type));
|
|
16125
16125
|
}
|
|
16126
|
-
function buildFramePrompt(edge, sceneIndex, framePrompt,
|
|
16126
|
+
function buildFramePrompt(edge, sceneIndex, framePrompt, present2, hasAnchor, mode, sceneStyle, imageModel) {
|
|
16127
16127
|
const profile = imageModel ? imageProfileFor(imageModel) : void 0;
|
|
16128
16128
|
const exclusionsLast = profile?.constraintPlacement === "last";
|
|
16129
16129
|
const photoreal = profile ? profile.photorealCue : true;
|
|
16130
16130
|
const EDGE = edge.toUpperCase();
|
|
16131
16131
|
const legend = [
|
|
16132
|
-
...
|
|
16132
|
+
...present2.map((s) => `- ${s.label} \u2014 ${roleForSlot(s)}`),
|
|
16133
16133
|
...hasAnchor ? [
|
|
16134
16134
|
"- ORIGINAL_FRAME \u2014 use ONLY for composition: framing, camera angle, shot size, subject placement, pose, and proportions. IGNORE its text, logo, brand name, colors, AND the identity of every person/animal/object in it \u2014 those come from the labeled reference images above, never from this frame. It is a DIFFERENT brand's footage with DIFFERENT actors, here ONLY to anchor where things sit and how the shot is framed (e.g. a profile/side angle stays a profile/side angle), never who they are or what palette to use."
|
|
16135
16135
|
] : []
|
|
@@ -16187,7 +16187,7 @@ function buildFramePrompt(edge, sceneIndex, framePrompt, present, hasAnchor, mod
|
|
|
16187
16187
|
// RECAST is the whole point of a transform: the dropped el_* images define who/
|
|
16188
16188
|
// what is on screen, NOT the source footage and NOT the prose. Without this, the
|
|
16189
16189
|
// model reproduces the original ad's people (a proven failure mode).
|
|
16190
|
-
...
|
|
16190
|
+
...present2.length > 0 ? [
|
|
16191
16191
|
"IDENTITY & AESTHETIC \u2014 RECAST (this is a transform, not a copy):",
|
|
16192
16192
|
"Identity comes from the reference image, never from the source footage or this prose. Render every",
|
|
16193
16193
|
"person, animal, product, and set to MATCH its labeled reference image above \u2014 that image is the ONLY",
|
|
@@ -16242,17 +16242,17 @@ function ingestFrameRef(url, edge, ctx, nodes) {
|
|
|
16242
16242
|
ctx.ingestCache?.set(url, ref);
|
|
16243
16243
|
return ref;
|
|
16244
16244
|
}
|
|
16245
|
-
function buildFrameRef(edge, url, framePrompt,
|
|
16245
|
+
function buildFrameRef(edge, url, framePrompt, present2, ctx, nodes) {
|
|
16246
16246
|
const tag = ctx.tag ?? "";
|
|
16247
16247
|
if (ctx.reuse && url) return ingestFrameRef(url, edge, ctx, nodes);
|
|
16248
|
-
const castSlots =
|
|
16248
|
+
const castSlots = present2.filter((s) => {
|
|
16249
16249
|
const t = s.type.toLowerCase();
|
|
16250
16250
|
return t === "person" || t === "animal";
|
|
16251
16251
|
});
|
|
16252
16252
|
const useOriginalAnchor = Boolean(url) && castSlots.length === 0;
|
|
16253
16253
|
const hasOriginal = useOriginalAnchor;
|
|
16254
16254
|
const originalRef = useOriginalAnchor && url ? ingestFrameRef(url, edge, ctx, nodes) : void 0;
|
|
16255
|
-
const presentOrdered = orderSlotsByIdentity(
|
|
16255
|
+
const presentOrdered = orderSlotsByIdentity(present2);
|
|
16256
16256
|
const reference = [...presentOrdered.map((s) => s.ref), ...originalRef ? [originalRef] : []];
|
|
16257
16257
|
const imageProfile = imageProfileFor(ctx.imageModel);
|
|
16258
16258
|
const genParams = {
|
|
@@ -16388,13 +16388,13 @@ function spokenTextParts(scene, nativeLine, loc, profile) {
|
|
|
16388
16388
|
if (transcript) parts.push(`Transcript: ${loc(transcript)}`);
|
|
16389
16389
|
return parts;
|
|
16390
16390
|
}
|
|
16391
|
-
function buildSeedancePrompt(scene, sceneIndex,
|
|
16391
|
+
function buildSeedancePrompt(scene, sceneIndex, present2, mode, audio, nativeLine, nativeLang, uiRouted, videoModel) {
|
|
16392
16392
|
const profile = (videoModel ? clipProfileFor(videoModel) : void 0) ?? SEEDANCE_PROFILE;
|
|
16393
16393
|
const loc = (s) => nativeLine ? localizeNumeralsForNative(s, nativeLang) : s;
|
|
16394
16394
|
const routed = uiRouted ?? (compositeRegionsOf(scene) !== null && isUiOnlyComposite(compositeRegionsOf(scene) ?? []));
|
|
16395
16395
|
const plate = routed ? compositePlateRegion(scene) ?? {} : null;
|
|
16396
16396
|
const parts = visualBriefParts(scene, sceneIndex, plate);
|
|
16397
|
-
const refs = plate ?
|
|
16397
|
+
const refs = plate ? present2.filter((s) => !UI_SURFACE_RE.test(s.description ?? "")) : present2;
|
|
16398
16398
|
if (refs.length > 0) {
|
|
16399
16399
|
parts.push(
|
|
16400
16400
|
`Keep these consistent with their references: ${refs.map((s) => `${s.label} (${s.description ?? s.type})`).join("; ")}`
|
|
@@ -16433,15 +16433,15 @@ function sceneOutTransition(scene, isLast) {
|
|
|
16433
16433
|
const xfade = type ? XFADE_BY_TYPE[type] : void 0;
|
|
16434
16434
|
return xfade ? { xfade, dur: TRANSITION_DEFAULT_S } : null;
|
|
16435
16435
|
}
|
|
16436
|
-
function sceneShootMode(scene,
|
|
16436
|
+
function sceneShootMode(scene, present2, nativeTurn, cameraOn, casts) {
|
|
16437
16437
|
const talking = Boolean(nativeTurn) || cameraOn && (scene.dialogue ?? []).some(
|
|
16438
16438
|
(d) => d.line?.trim() && isOnCameraSpeaker(d.speaker ?? "voiceover", casts, cameraOn)
|
|
16439
16439
|
);
|
|
16440
16440
|
return deriveShootMode({
|
|
16441
16441
|
explicit: scene.shoot_mode,
|
|
16442
16442
|
talking,
|
|
16443
|
-
hasPerson:
|
|
16444
|
-
hasProduct:
|
|
16443
|
+
hasPerson: present2.some((s) => s.type.toLowerCase() === "person" || s.type.toLowerCase() === "animal"),
|
|
16444
|
+
hasProduct: present2.some((s) => s.type.toLowerCase() === "product")
|
|
16445
16445
|
});
|
|
16446
16446
|
}
|
|
16447
16447
|
function emitSceneNativeAudio(i, nativeTurn, ambientBroll, lengths, clock, nodes, voTracks, nativeSegments, clipRef) {
|
|
@@ -16485,12 +16485,12 @@ function sameSpeakerTrackEntries(segments) {
|
|
|
16485
16485
|
return sorted.map((s, k) => {
|
|
16486
16486
|
const next = sorted[k + 1];
|
|
16487
16487
|
const naturalLen = s.end_s - s.start_s;
|
|
16488
|
-
const
|
|
16489
|
-
const capped =
|
|
16488
|
+
const cap3 = next ? Math.min(s.end_s, next.start_s) - s.start_s : naturalLen;
|
|
16489
|
+
const capped = cap3 < naturalLen - 1e-3;
|
|
16490
16490
|
return {
|
|
16491
16491
|
slot: `seg${k}`,
|
|
16492
16492
|
start_s: s.start_s,
|
|
16493
|
-
...capped ? { duration_s: Math.round(Math.max(0.1,
|
|
16493
|
+
...capped ? { duration_s: Math.round(Math.max(0.1, cap3) * 1e3) / 1e3 } : {}
|
|
16494
16494
|
};
|
|
16495
16495
|
});
|
|
16496
16496
|
}
|
|
@@ -16529,7 +16529,7 @@ function buildPerSpeakerVoiceConversion(segments, totalMs, nodes) {
|
|
|
16529
16529
|
}
|
|
16530
16530
|
return tracks;
|
|
16531
16531
|
}
|
|
16532
|
-
function emitSceneClip(i, scene,
|
|
16532
|
+
function emitSceneClip(i, scene, present2, mode, nativeTurn, ambientBroll, frames, lengths, out, opts, nodes, tag = "", defer = false) {
|
|
16533
16533
|
const profile = clipProfileFor(opts.videoModel) ?? SEEDANCE_PROFILE;
|
|
16534
16534
|
const clipParams = {
|
|
16535
16535
|
model: opts.videoModel,
|
|
@@ -16539,7 +16539,7 @@ function emitSceneClip(i, scene, present, mode, nativeTurn, ambientBroll, frames
|
|
|
16539
16539
|
prompt: buildSeedancePrompt(
|
|
16540
16540
|
scene,
|
|
16541
16541
|
i,
|
|
16542
|
-
|
|
16542
|
+
present2,
|
|
16543
16543
|
mode,
|
|
16544
16544
|
Boolean(nativeTurn) || ambientBroll,
|
|
16545
16545
|
nativeTurn?.text,
|
|
@@ -16638,9 +16638,9 @@ function uiRoutedRuns(input) {
|
|
|
16638
16638
|
flushRun();
|
|
16639
16639
|
return runs;
|
|
16640
16640
|
}
|
|
16641
|
-
function sceneIsFullScreenUi(scene,
|
|
16641
|
+
function sceneIsFullScreenUi(scene, present2) {
|
|
16642
16642
|
if (scene.narrative_role?.trim() === "cta") return false;
|
|
16643
|
-
const hasCast =
|
|
16643
|
+
const hasCast = present2.some((s) => {
|
|
16644
16644
|
const t = s.type.toLowerCase();
|
|
16645
16645
|
return t === "person" || t === "animal";
|
|
16646
16646
|
});
|
|
@@ -16675,14 +16675,14 @@ function presenterIndexOf(regions, hasNative) {
|
|
|
16675
16675
|
if (flagged >= 0) return flagged;
|
|
16676
16676
|
return hasNative ? 0 : -1;
|
|
16677
16677
|
}
|
|
16678
|
-
function slotsForRegion(
|
|
16679
|
-
return
|
|
16678
|
+
function slotsForRegion(present2, isPresenter) {
|
|
16679
|
+
return present2.filter((s) => {
|
|
16680
16680
|
const t = s.type.toLowerCase();
|
|
16681
16681
|
const person = t === "person" || t === "animal";
|
|
16682
16682
|
return isPresenter ? person : !person;
|
|
16683
16683
|
});
|
|
16684
16684
|
}
|
|
16685
|
-
function buildCompositeScene(layout, regions, comp, scene, i,
|
|
16685
|
+
function buildCompositeScene(layout, regions, comp, scene, i, present2, mode, nativeTurn, lengths, out, opts, nodes) {
|
|
16686
16686
|
const dims = canvasDims(opts.outAr);
|
|
16687
16687
|
const presIdx = presenterIndexOf(regions, Boolean(nativeTurn));
|
|
16688
16688
|
const regionRefs = [];
|
|
@@ -16690,7 +16690,7 @@ function buildCompositeScene(layout, regions, comp, scene, i, present, mode, nat
|
|
|
16690
16690
|
regions.forEach((region, r) => {
|
|
16691
16691
|
const isPresenter = r === presIdx;
|
|
16692
16692
|
const tag = `_r${r}`;
|
|
16693
|
-
const regionSlots = slotsForRegion(
|
|
16693
|
+
const regionSlots = slotsForRegion(present2, isPresenter);
|
|
16694
16694
|
const ctx = {
|
|
16695
16695
|
sceneIndex: i,
|
|
16696
16696
|
genAr: opts.genAr,
|
|
@@ -16790,14 +16790,14 @@ function sceneTiming(scene, isLast, nativeTurn, durationSet = SEEDANCE_DURATIONS
|
|
|
16790
16790
|
const genDur = ceilToDurations(durationSet, Math.max(trimTarget, speech));
|
|
16791
16791
|
return { dur, out, trimTarget, genDur, speech };
|
|
16792
16792
|
}
|
|
16793
|
-
function emitCompositeScene(composite, scene, i,
|
|
16793
|
+
function emitCompositeScene(composite, scene, i, present2, mode, nativeTurn, lengths, out, opts, clock, nodes, voTracks, nativeSegments, clips) {
|
|
16794
16794
|
const built = buildCompositeScene(
|
|
16795
16795
|
composite.layout,
|
|
16796
16796
|
composite.regions,
|
|
16797
16797
|
composite.comp,
|
|
16798
16798
|
scene,
|
|
16799
16799
|
i,
|
|
16800
|
-
|
|
16800
|
+
present2,
|
|
16801
16801
|
mode,
|
|
16802
16802
|
nativeTurn,
|
|
16803
16803
|
{ dur: lengths.dur, trimTarget: lengths.trimTarget, genDur: lengths.genDur },
|
|
@@ -16902,9 +16902,9 @@ function emitGraphicScene(i, scene, lengths, out, surfaceIngests, nodes, clips)
|
|
|
16902
16902
|
clips.push({ ref: `$ref:${refId}.asset`, scene_s: lengths.dur, out, still: { fit: "pad" } });
|
|
16903
16903
|
}
|
|
16904
16904
|
var BRAND_CARD_RE = /\b(?:solid|plain|flat|brand|logo|wordmark|end[- ]?card|cta card|title card|colou?r background|background colou?r)\b/i;
|
|
16905
|
-
function sceneIsBrandCard(scene,
|
|
16905
|
+
function sceneIsBrandCard(scene, present2, isCta) {
|
|
16906
16906
|
if (!isCta) return false;
|
|
16907
|
-
const hasCast =
|
|
16907
|
+
const hasCast = present2.some((s) => {
|
|
16908
16908
|
const t = s.type.toLowerCase();
|
|
16909
16909
|
return t === "person" || t === "animal";
|
|
16910
16910
|
});
|
|
@@ -17240,7 +17240,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
17240
17240
|
const anchor = phrase.shownScenes[0];
|
|
17241
17241
|
const anchorScene = env.blueprint.scenes[anchor];
|
|
17242
17242
|
if (!anchorScene) return;
|
|
17243
|
-
const
|
|
17243
|
+
const present2 = slotsForScene(env.slots, anchor);
|
|
17244
17244
|
const nativeTurn = {
|
|
17245
17245
|
sceneIndex: anchor,
|
|
17246
17246
|
speaker: phrase.speaker,
|
|
@@ -17250,7 +17250,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
17250
17250
|
voiceNode,
|
|
17251
17251
|
native: true
|
|
17252
17252
|
};
|
|
17253
|
-
const mode = sceneShootMode(anchorScene,
|
|
17253
|
+
const mode = sceneShootMode(anchorScene, present2, nativeTurn, env.cameraOn, env.casts);
|
|
17254
17254
|
const ctx = {
|
|
17255
17255
|
sceneIndex: anchor,
|
|
17256
17256
|
genAr: env.genAr,
|
|
@@ -17293,7 +17293,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
17293
17293
|
prompt: buildSeedancePrompt(
|
|
17294
17294
|
anchorScene,
|
|
17295
17295
|
anchor,
|
|
17296
|
-
|
|
17296
|
+
present2,
|
|
17297
17297
|
mode,
|
|
17298
17298
|
true,
|
|
17299
17299
|
phrase.text,
|
|
@@ -17410,7 +17410,7 @@ function emitPhraseTts(phrase, voiceNode, idx, used, clock, nodes, out, language
|
|
|
17410
17410
|
});
|
|
17411
17411
|
}
|
|
17412
17412
|
function emitCompositeInTimeline(composite, scene, i, isLast, env, canonical, ensureVoiceNode, usedVoIds, nodes, out) {
|
|
17413
|
-
const
|
|
17413
|
+
const present2 = slotsForScene(env.slots, i);
|
|
17414
17414
|
const onCam = (scene.dialogue ?? []).filter(
|
|
17415
17415
|
(l) => Boolean(l.line?.trim()) && isOnCameraSpeaker(l.speaker ?? "voiceover", env.casts, env.cameraOn)
|
|
17416
17416
|
);
|
|
@@ -17431,13 +17431,13 @@ function emitCompositeInTimeline(composite, scene, i, isLast, env, canonical, en
|
|
|
17431
17431
|
speech_words: wordCount(text)
|
|
17432
17432
|
});
|
|
17433
17433
|
}
|
|
17434
|
-
const mode = sceneShootMode(scene,
|
|
17434
|
+
const mode = sceneShootMode(scene, present2, nativeTurn, env.cameraOn, env.casts);
|
|
17435
17435
|
const lengths = sceneTiming(scene, isLast, nativeTurn, clipProfileFor(env.opts.videoModel)?.durationSet);
|
|
17436
17436
|
emitCompositeScene(
|
|
17437
17437
|
composite,
|
|
17438
17438
|
scene,
|
|
17439
17439
|
i,
|
|
17440
|
-
|
|
17440
|
+
present2,
|
|
17441
17441
|
mode,
|
|
17442
17442
|
nativeTurn,
|
|
17443
17443
|
lengths,
|
|
@@ -17532,8 +17532,8 @@ function brollKeyframes(scene, i, env, ctx, lengths, prevEndFrame, nodes) {
|
|
|
17532
17532
|
return { first, last, sharesPrevFrame };
|
|
17533
17533
|
}
|
|
17534
17534
|
function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
17535
|
-
const
|
|
17536
|
-
const mode = sceneShootMode(scene,
|
|
17535
|
+
const present2 = slotsForScene(env.slots, i);
|
|
17536
|
+
const mode = sceneShootMode(scene, present2, void 0, env.cameraOn, env.casts);
|
|
17537
17537
|
const ambientBroll = Boolean(env.opts.ambient) && mode !== "ugc_selfie";
|
|
17538
17538
|
const lengths = sceneTiming(scene, isLast, void 0, clipProfileFor(env.opts.videoModel)?.durationSet);
|
|
17539
17539
|
const ctx = {
|
|
@@ -17550,13 +17550,13 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
|
17550
17550
|
out.routes.set(i, "graphic_plate");
|
|
17551
17551
|
return void 0;
|
|
17552
17552
|
}
|
|
17553
|
-
if (!env.reuse && sceneIsFullScreenUi(scene,
|
|
17553
|
+
if (!env.reuse && sceneIsFullScreenUi(scene, present2)) {
|
|
17554
17554
|
emitScreenScene(i, scene, lengths, lengths.out, env.surfaceIngests, nodes, out.clips);
|
|
17555
17555
|
out.routes.set(i, "screen_still");
|
|
17556
17556
|
return void 0;
|
|
17557
17557
|
}
|
|
17558
17558
|
const isCta = scene.narrative_role?.trim() === "cta" || isLast;
|
|
17559
|
-
if (!env.reuse && sceneIsBrandCard(scene,
|
|
17559
|
+
if (!env.reuse && sceneIsBrandCard(scene, present2, isCta)) {
|
|
17560
17560
|
emitBrandCardScene(i, lengths, lengths.out, env.outAr, brandPlateColor(env.blueprint), nodes, out.clips);
|
|
17561
17561
|
out.routes.set(i, "brand_card");
|
|
17562
17562
|
return void 0;
|
|
@@ -17571,7 +17571,7 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
|
17571
17571
|
const clip = emitSceneClip(
|
|
17572
17572
|
i,
|
|
17573
17573
|
scene,
|
|
17574
|
-
|
|
17574
|
+
present2,
|
|
17575
17575
|
mode,
|
|
17576
17576
|
void 0,
|
|
17577
17577
|
ambientBroll,
|
|
@@ -18333,7 +18333,7 @@ function sceneSpokenText(scene) {
|
|
|
18333
18333
|
return (scene.dialogue ?? []).map((d) => d.line?.trim()).filter((l) => Boolean(l)).join(" ") || null;
|
|
18334
18334
|
}
|
|
18335
18335
|
function buildMotionBoard(blueprint) {
|
|
18336
|
-
const
|
|
18336
|
+
const round5 = (n) => Math.round(n * 100) / 100;
|
|
18337
18337
|
let cursor = 0;
|
|
18338
18338
|
return blueprint.scenes.map((scene, i) => {
|
|
18339
18339
|
const start_s = scene.start_s ?? cursor;
|
|
@@ -18345,15 +18345,15 @@ function buildMotionBoard(blueprint) {
|
|
|
18345
18345
|
const graphics = [
|
|
18346
18346
|
...(overlays.success ? overlays.data : []).filter((ov) => ov.text?.trim()).map((ov) => ({
|
|
18347
18347
|
kind: "text",
|
|
18348
|
-
at_s:
|
|
18349
|
-
dur_s:
|
|
18348
|
+
at_s: round5(ov.appears_at_s ?? start_s),
|
|
18349
|
+
dur_s: round5(ov.duration_s ?? 2.5),
|
|
18350
18350
|
position: ov.position ?? "bottom_center",
|
|
18351
18351
|
text: ov.text?.trim()
|
|
18352
18352
|
})),
|
|
18353
18353
|
...(floats.success ? floats.data : []).map((fe) => ({
|
|
18354
18354
|
kind: "graphic",
|
|
18355
|
-
at_s:
|
|
18356
|
-
dur_s:
|
|
18355
|
+
at_s: round5(fe.appears_at_s ?? start_s),
|
|
18356
|
+
dur_s: round5(fe.duration_s ?? 2.5),
|
|
18357
18357
|
position: fe.position ?? "bottom_center",
|
|
18358
18358
|
label: fe.brand_name || fe.what_it_represents || fe.description || fe.kind || "element"
|
|
18359
18359
|
}))
|
|
@@ -18361,7 +18361,7 @@ function buildMotionBoard(blueprint) {
|
|
|
18361
18361
|
return {
|
|
18362
18362
|
scene: i,
|
|
18363
18363
|
role: resolveSceneRole(scene, i, blueprint.scenes.length),
|
|
18364
|
-
window_s: [
|
|
18364
|
+
window_s: [round5(start_s), round5(end_s)],
|
|
18365
18365
|
// A continuation b-roll scene shares the previous scene's end frame as its start
|
|
18366
18366
|
// (no own `s<i>_start` node), so point the storyboard at that shared keyframe.
|
|
18367
18367
|
storyboard_frames: [scene.continues_previous && i > 0 ? `s${i - 1}_end` : `s${i}_start`],
|
|
@@ -19026,10 +19026,10 @@ function runDirsToPrune(entries, keep, currentRunId) {
|
|
|
19026
19026
|
return runs.slice(0, Math.max(0, runs.length - keep));
|
|
19027
19027
|
}
|
|
19028
19028
|
async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
|
|
19029
|
-
const { readdir:
|
|
19029
|
+
const { readdir: readdir8 } = await import("fs/promises");
|
|
19030
19030
|
let entries;
|
|
19031
19031
|
try {
|
|
19032
|
-
entries = await
|
|
19032
|
+
entries = await readdir8(outputsDir);
|
|
19033
19033
|
} catch {
|
|
19034
19034
|
return;
|
|
19035
19035
|
}
|
|
@@ -20801,10 +20801,10 @@ function resolveShippedCanvasDir(name, startDir, exists = existsSync3, maxDepth
|
|
|
20801
20801
|
import { appendFile, readFile as readFile13 } from "fs/promises";
|
|
20802
20802
|
import path19 from "path";
|
|
20803
20803
|
function missingGitignoreEntries(existing, entries) {
|
|
20804
|
-
const
|
|
20804
|
+
const present2 = new Set(
|
|
20805
20805
|
existing.split("\n").map((l) => l.trim().replace(/\/+$/, "")).filter((l) => l.length > 0 && !l.startsWith("#"))
|
|
20806
20806
|
);
|
|
20807
|
-
return entries.filter((e) => !
|
|
20807
|
+
return entries.filter((e) => !present2.has(e.trim().replace(/\/+$/, "")));
|
|
20808
20808
|
}
|
|
20809
20809
|
async function ensureGitignore(dir, entries) {
|
|
20810
20810
|
const file = path19.join(dir, ".gitignore");
|
|
@@ -22076,12 +22076,12 @@ function listFlowSlugs() {
|
|
|
22076
22076
|
return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_") && entry.name !== ".gitkeep").map((entry) => entry.name).sort();
|
|
22077
22077
|
}
|
|
22078
22078
|
function readFlowTree(slug) {
|
|
22079
|
-
const
|
|
22080
|
-
if (!existsSync4(
|
|
22079
|
+
const path27 = join3(flowsDir(), slug, "_data.json");
|
|
22080
|
+
if (!existsSync4(path27)) {
|
|
22081
22081
|
failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
|
|
22082
22082
|
}
|
|
22083
22083
|
try {
|
|
22084
|
-
return JSON.parse(readFileSync9(
|
|
22084
|
+
return JSON.parse(readFileSync9(path27, "utf-8"));
|
|
22085
22085
|
} catch (error) {
|
|
22086
22086
|
failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
|
|
22087
22087
|
}
|
|
@@ -23237,9 +23237,9 @@ async function readImageBuffer(pathOrUrl) {
|
|
|
23237
23237
|
}
|
|
23238
23238
|
return readFile18(pathOrUrl);
|
|
23239
23239
|
}
|
|
23240
|
-
async function isDirectory(
|
|
23240
|
+
async function isDirectory(path27) {
|
|
23241
23241
|
try {
|
|
23242
|
-
const s = await stat4(
|
|
23242
|
+
const s = await stat4(path27);
|
|
23243
23243
|
return s.isDirectory();
|
|
23244
23244
|
} catch {
|
|
23245
23245
|
return false;
|
|
@@ -24939,7 +24939,7 @@ function emitError3(err) {
|
|
|
24939
24939
|
process.exit(1);
|
|
24940
24940
|
}
|
|
24941
24941
|
function coerceRawArgs(args) {
|
|
24942
|
-
const
|
|
24942
|
+
const num2 = (v) => {
|
|
24943
24943
|
if (typeof v === "number") return v;
|
|
24944
24944
|
if (typeof v === "string" && v.length > 0) {
|
|
24945
24945
|
const n = Number(v);
|
|
@@ -24954,8 +24954,8 @@ function coerceRawArgs(args) {
|
|
|
24954
24954
|
color: str(args.color),
|
|
24955
24955
|
"remove-bg": bool(args["remove-bg"]),
|
|
24956
24956
|
"shrink-to-content": bool(args["shrink-to-content"]),
|
|
24957
|
-
height:
|
|
24958
|
-
width:
|
|
24957
|
+
height: num2(args.height),
|
|
24958
|
+
width: num2(args.width),
|
|
24959
24959
|
size: str(args.size),
|
|
24960
24960
|
fit: str(args.fit),
|
|
24961
24961
|
output: str(args.output),
|
|
@@ -25802,8 +25802,1031 @@ Paid transforms (run on the Convex backend, cost-tracked):
|
|
|
25802
25802
|
}
|
|
25803
25803
|
});
|
|
25804
25804
|
|
|
25805
|
-
// src/commands/
|
|
25805
|
+
// src/commands/landing/index.ts
|
|
25806
|
+
import { defineCommand as defineCommand134 } from "citty";
|
|
25807
|
+
|
|
25808
|
+
// src/commands/landing/critique.ts
|
|
25809
|
+
import { stat as stat6 } from "fs/promises";
|
|
25810
|
+
import path26 from "path";
|
|
25806
25811
|
import { defineCommand as defineCommand133 } from "citty";
|
|
25812
|
+
|
|
25813
|
+
// src/engine/landing/lib/brand-tokens.ts
|
|
25814
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
25815
|
+
import path23 from "path";
|
|
25816
|
+
|
|
25817
|
+
// src/engine/landing/lib/color.ts
|
|
25818
|
+
var NEUTRAL_COLOR_KEYWORDS = /* @__PURE__ */ new Set([
|
|
25819
|
+
"transparent",
|
|
25820
|
+
"currentcolor",
|
|
25821
|
+
"black",
|
|
25822
|
+
"white",
|
|
25823
|
+
"gray",
|
|
25824
|
+
"grey",
|
|
25825
|
+
"silver",
|
|
25826
|
+
"dimgray",
|
|
25827
|
+
"dimgrey",
|
|
25828
|
+
"darkgray",
|
|
25829
|
+
"darkgrey",
|
|
25830
|
+
"lightgray",
|
|
25831
|
+
"lightgrey",
|
|
25832
|
+
"gainsboro",
|
|
25833
|
+
"whitesmoke"
|
|
25834
|
+
]);
|
|
25835
|
+
function hexChannels(color) {
|
|
25836
|
+
const long = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})(?:[0-9a-f]{2})?$/i);
|
|
25837
|
+
if (long)
|
|
25838
|
+
return [
|
|
25839
|
+
Number.parseInt(long[1] ?? "0", 16),
|
|
25840
|
+
Number.parseInt(long[2] ?? "0", 16),
|
|
25841
|
+
Number.parseInt(long[3] ?? "0", 16)
|
|
25842
|
+
];
|
|
25843
|
+
const short = color.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])(?:[0-9a-f])?$/i);
|
|
25844
|
+
if (short) {
|
|
25845
|
+
const dup = (s) => Number.parseInt(`${s ?? "0"}${s ?? "0"}`, 16);
|
|
25846
|
+
return [dup(short[1]), dup(short[2]), dup(short[3])];
|
|
25847
|
+
}
|
|
25848
|
+
return null;
|
|
25849
|
+
}
|
|
25850
|
+
function parseColor2(raw) {
|
|
25851
|
+
const c = String(raw || "").trim().toLowerCase();
|
|
25852
|
+
if (!c || c === "transparent") return null;
|
|
25853
|
+
const rgb = c.match(/rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:[\s,/]+([\d.]+%?))?\s*\)/i);
|
|
25854
|
+
if (rgb) {
|
|
25855
|
+
const alpha = rgb[4];
|
|
25856
|
+
const a = alpha === void 0 ? 1 : alpha.endsWith("%") ? Number.parseFloat(alpha) / 100 : Number.parseFloat(alpha);
|
|
25857
|
+
return { r: Number(rgb[1]), g: Number(rgb[2]), b: Number(rgb[3]), a };
|
|
25858
|
+
}
|
|
25859
|
+
const hex = hexChannels(c);
|
|
25860
|
+
if (hex) return { r: hex[0], g: hex[1], b: hex[2], a: 1 };
|
|
25861
|
+
return null;
|
|
25862
|
+
}
|
|
25863
|
+
function isNeutralAuthoredColor(rawColor) {
|
|
25864
|
+
const c = String(rawColor || "").trim().toLowerCase();
|
|
25865
|
+
if (!c) return false;
|
|
25866
|
+
if (NEUTRAL_COLOR_KEYWORDS.has(c)) return true;
|
|
25867
|
+
if (/^rgba?\(/i.test(c)) {
|
|
25868
|
+
const channels2 = c.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i);
|
|
25869
|
+
if (channels2) {
|
|
25870
|
+
const values = [1, 2, 3].map((i) => Number(channels2[i]));
|
|
25871
|
+
return Math.max(...values) - Math.min(...values) < 30;
|
|
25872
|
+
}
|
|
25873
|
+
return false;
|
|
25874
|
+
}
|
|
25875
|
+
const oklch = c.match(/oklch\(\s*[\d.]+%?\s+([\d.-]+)/i);
|
|
25876
|
+
if (oklch) return Number.parseFloat(oklch[1] ?? "0") < 0.02;
|
|
25877
|
+
const lch = c.match(/lch\(\s*[\d.]+%?\s+([\d.-]+)/i);
|
|
25878
|
+
if (lch) return Number.parseFloat(lch[1] ?? "0") < 3;
|
|
25879
|
+
const hsl = c.match(/hsla?\(\s*[\d.-]+\s*,?\s*([\d.]+)%/i);
|
|
25880
|
+
if (hsl) return Number.parseFloat(hsl[1] ?? "0") < 10;
|
|
25881
|
+
const channels = hexChannels(c);
|
|
25882
|
+
if (channels) return Math.max(...channels) - Math.min(...channels) < 30;
|
|
25883
|
+
return false;
|
|
25884
|
+
}
|
|
25885
|
+
function hasChroma(c, threshold = 30) {
|
|
25886
|
+
if (!c) return false;
|
|
25887
|
+
return Math.max(c.r, c.g, c.b) - Math.min(c.r, c.g, c.b) >= threshold;
|
|
25888
|
+
}
|
|
25889
|
+
function getHue(c) {
|
|
25890
|
+
if (!c) return 0;
|
|
25891
|
+
const r = c.r / 255;
|
|
25892
|
+
const g = c.g / 255;
|
|
25893
|
+
const b = c.b / 255;
|
|
25894
|
+
const max = Math.max(r, g, b);
|
|
25895
|
+
const min = Math.min(r, g, b);
|
|
25896
|
+
if (max === min) return 0;
|
|
25897
|
+
const d = max - min;
|
|
25898
|
+
let h;
|
|
25899
|
+
if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
25900
|
+
else if (max === g) h = ((b - r) / d + 2) / 6;
|
|
25901
|
+
else h = ((r - g) / d + 4) / 6;
|
|
25902
|
+
return Math.round(h * 360);
|
|
25903
|
+
}
|
|
25904
|
+
function isAiPurpleHue(hue) {
|
|
25905
|
+
return hue >= 260 && hue <= 310;
|
|
25906
|
+
}
|
|
25907
|
+
function isCreamColor(c) {
|
|
25908
|
+
if (Math.min(c.r, c.g, c.b) < 209) return false;
|
|
25909
|
+
if (!(c.r >= c.g && c.g >= c.b)) return false;
|
|
25910
|
+
const warmth = c.r - c.b;
|
|
25911
|
+
return warmth >= 6 && warmth <= 48;
|
|
25912
|
+
}
|
|
25913
|
+
|
|
25914
|
+
// src/engine/landing/lib/brand-tokens.ts
|
|
25915
|
+
var EMPTY = { fonts: /* @__PURE__ */ new Set(), colors: [], hasTokens: false };
|
|
25916
|
+
function parseBrandTokens(globalCss, brandMd) {
|
|
25917
|
+
const fonts = /* @__PURE__ */ new Set();
|
|
25918
|
+
const colors = [];
|
|
25919
|
+
parseGlobalCss(globalCss, fonts, colors);
|
|
25920
|
+
parseBrandMd(brandMd, fonts, colors);
|
|
25921
|
+
return { fonts, colors, hasTokens: fonts.size > 0 || colors.length > 0 };
|
|
25922
|
+
}
|
|
25923
|
+
function parseGlobalCss(globalCss, fonts, colors) {
|
|
25924
|
+
for (const m of globalCss.matchAll(/--font-[\w-]*\s*:\s*([^;]+);/gi)) {
|
|
25925
|
+
const first = (m[1] ?? "").split(",")[0]?.trim().replace(/^['"]|['"]$/g, "").toLowerCase();
|
|
25926
|
+
if (first) fonts.add(first);
|
|
25927
|
+
}
|
|
25928
|
+
for (const m of globalCss.matchAll(
|
|
25929
|
+
/--color-[\w-]*\s*:\s*(#[0-9a-f]{3,8}\b|rgba?\([^)]+\)|oklch\([^)]+\)|hsla?\([^)]+\))/gi
|
|
25930
|
+
)) {
|
|
25931
|
+
const c = parseColor2(m[1] ?? "");
|
|
25932
|
+
if (c) colors.push(c);
|
|
25933
|
+
}
|
|
25934
|
+
}
|
|
25935
|
+
function parseBrandMd(brandMd, fonts, colors) {
|
|
25936
|
+
for (const m of brandMd.matchAll(/#[0-9a-f]{6}\b/gi)) {
|
|
25937
|
+
const c = parseColor2(m[0]);
|
|
25938
|
+
if (c) colors.push(c);
|
|
25939
|
+
}
|
|
25940
|
+
for (const line of brandMd.split("\n")) {
|
|
25941
|
+
if (!/\b(?:font|typeface|family)\b/i.test(line)) continue;
|
|
25942
|
+
const named = line.match(/["'`]([A-Za-z][\w ]{1,30})["'`]/g);
|
|
25943
|
+
if (named) for (const n of named) fonts.add(n.slice(1, -1).trim().toLowerCase());
|
|
25944
|
+
}
|
|
25945
|
+
}
|
|
25946
|
+
async function loadBrandTokens(projectRoot) {
|
|
25947
|
+
const globalCss = await safeRead(path23.join(projectRoot, "src", "styles", "global.css"));
|
|
25948
|
+
const brandMd = await safeRead(path23.join(projectRoot, "src", "brand", "BRAND.md"));
|
|
25949
|
+
if (!globalCss && !brandMd) return EMPTY;
|
|
25950
|
+
return parseBrandTokens(globalCss, brandMd);
|
|
25951
|
+
}
|
|
25952
|
+
async function safeRead(file) {
|
|
25953
|
+
try {
|
|
25954
|
+
return await readFile20(file, "utf8");
|
|
25955
|
+
} catch {
|
|
25956
|
+
return "";
|
|
25957
|
+
}
|
|
25958
|
+
}
|
|
25959
|
+
function brandCommitsFont(brand, snippet) {
|
|
25960
|
+
if (brand.fonts.size === 0) return false;
|
|
25961
|
+
const lower = snippet.toLowerCase();
|
|
25962
|
+
for (const f of brand.fonts) if (f.length >= 3 && lower.includes(f)) return true;
|
|
25963
|
+
return false;
|
|
25964
|
+
}
|
|
25965
|
+
function brandCommitsAiHue(brand) {
|
|
25966
|
+
return brand.colors.some((c) => hasChroma(c, 40) && isAiPurpleHue(getHue(c)));
|
|
25967
|
+
}
|
|
25968
|
+
function brandCommitsCream(brand) {
|
|
25969
|
+
return brand.colors.some((c) => isCreamColor(c));
|
|
25970
|
+
}
|
|
25971
|
+
|
|
25972
|
+
// src/engine/landing/lib/constants.ts
|
|
25973
|
+
var OVERUSED_FONTS = /* @__PURE__ */ new Set([
|
|
25974
|
+
// Older monoculture (still ubiquitous):
|
|
25975
|
+
"inter",
|
|
25976
|
+
"roboto",
|
|
25977
|
+
"open sans",
|
|
25978
|
+
"lato",
|
|
25979
|
+
"montserrat",
|
|
25980
|
+
"arial",
|
|
25981
|
+
"helvetica",
|
|
25982
|
+
// Newer monoculture (the Anthropic-skill / Vercel / GitHub default wave):
|
|
25983
|
+
"fraunces",
|
|
25984
|
+
"instrument sans",
|
|
25985
|
+
"instrument serif",
|
|
25986
|
+
"geist",
|
|
25987
|
+
"geist sans",
|
|
25988
|
+
"geist mono",
|
|
25989
|
+
"mona sans",
|
|
25990
|
+
"plus jakarta sans",
|
|
25991
|
+
"space grotesk",
|
|
25992
|
+
"space mono",
|
|
25993
|
+
"recoleta",
|
|
25994
|
+
"dm sans",
|
|
25995
|
+
"dm serif display",
|
|
25996
|
+
"dm serif text",
|
|
25997
|
+
"outfit",
|
|
25998
|
+
"syne",
|
|
25999
|
+
"cormorant",
|
|
26000
|
+
"playfair display",
|
|
26001
|
+
"lora",
|
|
26002
|
+
"crimson",
|
|
26003
|
+
"newsreader",
|
|
26004
|
+
"ibm plex",
|
|
26005
|
+
"ibm plex sans",
|
|
26006
|
+
"ibm plex serif"
|
|
26007
|
+
]);
|
|
26008
|
+
var GENERIC_FONTS = /* @__PURE__ */ new Set([
|
|
26009
|
+
"serif",
|
|
26010
|
+
"sans-serif",
|
|
26011
|
+
"monospace",
|
|
26012
|
+
"cursive",
|
|
26013
|
+
"fantasy",
|
|
26014
|
+
"system-ui",
|
|
26015
|
+
"ui-serif",
|
|
26016
|
+
"ui-sans-serif",
|
|
26017
|
+
"ui-monospace",
|
|
26018
|
+
"ui-rounded",
|
|
26019
|
+
"-apple-system",
|
|
26020
|
+
"blinkmacsystemfont",
|
|
26021
|
+
"segoe ui",
|
|
26022
|
+
"inherit",
|
|
26023
|
+
"initial",
|
|
26024
|
+
"unset",
|
|
26025
|
+
"revert"
|
|
26026
|
+
]);
|
|
26027
|
+
var EM_DASH_FLOOR = 8;
|
|
26028
|
+
var EM_DASH_CHARS_PER_DASH = 500;
|
|
26029
|
+
var TAILWIND_TEXT_PX = {
|
|
26030
|
+
"text-xs": 12,
|
|
26031
|
+
"text-sm": 14,
|
|
26032
|
+
"text-base": 16,
|
|
26033
|
+
"text-lg": 18,
|
|
26034
|
+
"text-xl": 20,
|
|
26035
|
+
"text-2xl": 24,
|
|
26036
|
+
"text-3xl": 30,
|
|
26037
|
+
"text-4xl": 36,
|
|
26038
|
+
"text-5xl": 48,
|
|
26039
|
+
"text-6xl": 60,
|
|
26040
|
+
"text-7xl": 72,
|
|
26041
|
+
"text-8xl": 96,
|
|
26042
|
+
"text-9xl": 128
|
|
26043
|
+
};
|
|
26044
|
+
var BUZZWORDS = [
|
|
26045
|
+
"streamline your",
|
|
26046
|
+
"empower your",
|
|
26047
|
+
"supercharge your",
|
|
26048
|
+
"unleash your",
|
|
26049
|
+
"unleash the power",
|
|
26050
|
+
"leverage the power",
|
|
26051
|
+
"built for the modern",
|
|
26052
|
+
"trusted by leading",
|
|
26053
|
+
"trusted by the world",
|
|
26054
|
+
"best-in-class",
|
|
26055
|
+
"industry-leading",
|
|
26056
|
+
"world-class",
|
|
26057
|
+
"enterprise-grade",
|
|
26058
|
+
"next-generation",
|
|
26059
|
+
"cutting-edge",
|
|
26060
|
+
"transform your business",
|
|
26061
|
+
"revolutionize",
|
|
26062
|
+
"game-changer",
|
|
26063
|
+
"game changing",
|
|
26064
|
+
"mission-critical",
|
|
26065
|
+
"best of breed",
|
|
26066
|
+
"future-proof",
|
|
26067
|
+
"future proof",
|
|
26068
|
+
"seamless experience",
|
|
26069
|
+
"seamlessly integrate",
|
|
26070
|
+
"drive engagement",
|
|
26071
|
+
"drive growth",
|
|
26072
|
+
"drive results",
|
|
26073
|
+
"harness the power"
|
|
26074
|
+
];
|
|
26075
|
+
var AI_PURPLE_HEXES = /* @__PURE__ */ new Set([
|
|
26076
|
+
"#7c3aed",
|
|
26077
|
+
"#8b5cf6",
|
|
26078
|
+
"#a855f7",
|
|
26079
|
+
"#9333ea",
|
|
26080
|
+
"#7e22ce",
|
|
26081
|
+
"#6d28d9",
|
|
26082
|
+
"#6366f1",
|
|
26083
|
+
"#764ba2",
|
|
26084
|
+
"#667eea"
|
|
26085
|
+
]);
|
|
26086
|
+
var CREAM_TAILWIND_BG = /* @__PURE__ */ new Set([
|
|
26087
|
+
"bg-amber-50",
|
|
26088
|
+
"bg-amber-100",
|
|
26089
|
+
"bg-orange-50",
|
|
26090
|
+
"bg-orange-100",
|
|
26091
|
+
"bg-yellow-50",
|
|
26092
|
+
"bg-stone-50",
|
|
26093
|
+
"bg-stone-100",
|
|
26094
|
+
"bg-stone-200"
|
|
26095
|
+
]);
|
|
26096
|
+
var RULE_META = {
|
|
26097
|
+
"gradient-text": {
|
|
26098
|
+
family: "color",
|
|
26099
|
+
severity: "block",
|
|
26100
|
+
note: "Gradient text is a top AI tell. Emphasis comes from weight or size, not a clipped gradient fill."
|
|
26101
|
+
},
|
|
26102
|
+
"broken-image": {
|
|
26103
|
+
family: "integrity",
|
|
26104
|
+
severity: "block",
|
|
26105
|
+
note: "An <img> with an empty or placeholder src ships a broken image. Source a real asset via `baker images`."
|
|
26106
|
+
},
|
|
26107
|
+
"side-tab": {
|
|
26108
|
+
family: "borders_depth",
|
|
26109
|
+
severity: "warn",
|
|
26110
|
+
note: "A thick colored border on one edge is the 'side-tab' AI tell. Use full borders, a background, or spacing instead."
|
|
26111
|
+
},
|
|
26112
|
+
"border-accent-on-rounded": {
|
|
26113
|
+
family: "borders_depth",
|
|
26114
|
+
severity: "warn",
|
|
26115
|
+
note: "A colored top/bottom accent bar on a rounded card is an AI tell. Let the card's own surface carry the accent."
|
|
26116
|
+
},
|
|
26117
|
+
"overused-font": {
|
|
26118
|
+
family: "typography",
|
|
26119
|
+
severity: "warn",
|
|
26120
|
+
brandAware: "font",
|
|
26121
|
+
note: "This face is a training-data default. Pick a face with a point of view \u2014 unless BRAND.md commits it."
|
|
26122
|
+
},
|
|
26123
|
+
"single-font": {
|
|
26124
|
+
family: "typography",
|
|
26125
|
+
severity: "advisory",
|
|
26126
|
+
note: "Only one face across the page flattens hierarchy. A display/body pair usually reads richer."
|
|
26127
|
+
},
|
|
26128
|
+
"flat-type-hierarchy": {
|
|
26129
|
+
family: "typography",
|
|
26130
|
+
severity: "warn",
|
|
26131
|
+
note: "Type steps are too close (<2\xD7 span). Widen the scale so the primary, secondary, and body are obvious at a glance."
|
|
26132
|
+
},
|
|
26133
|
+
"ai-color-palette": {
|
|
26134
|
+
family: "color",
|
|
26135
|
+
severity: "warn",
|
|
26136
|
+
brandAware: "color",
|
|
26137
|
+
note: "Purple/indigo-on-heading or purple\u2192cyan gradients are the AI palette cluster. Commit to the brand's own hues."
|
|
26138
|
+
},
|
|
26139
|
+
"cream-palette": {
|
|
26140
|
+
family: "color",
|
|
26141
|
+
severity: "warn",
|
|
26142
|
+
brandAware: "color",
|
|
26143
|
+
note: "Cream/parchment ground is the 'measured rendition' AI default. Rework from the brand's saturated materials."
|
|
26144
|
+
},
|
|
26145
|
+
"gray-on-color": {
|
|
26146
|
+
family: "color",
|
|
26147
|
+
severity: "warn",
|
|
26148
|
+
note: "Gray text on a colored surface reads muddy. Tint secondary text from the surface hue, never neutral gray."
|
|
26149
|
+
},
|
|
26150
|
+
"bounce-easing": {
|
|
26151
|
+
family: "motion",
|
|
26152
|
+
severity: "warn",
|
|
26153
|
+
note: "Bounce/elastic easing feels dated. Use natural deceleration (e.g. cubic-bezier(0.16, 1, 0.3, 1))."
|
|
26154
|
+
},
|
|
26155
|
+
"layout-transition": {
|
|
26156
|
+
family: "motion",
|
|
26157
|
+
severity: "advisory",
|
|
26158
|
+
note: "Transitioning width/height/padding/margin janks. Animate transform/opacity, or clip-path/max-height with care."
|
|
26159
|
+
},
|
|
26160
|
+
"monotonous-spacing": {
|
|
26161
|
+
family: "spacing",
|
|
26162
|
+
severity: "warn",
|
|
26163
|
+
note: "One spacing value dominates. Create rhythm \u2014 tight groups, generous separation, more space above a heading than below."
|
|
26164
|
+
},
|
|
26165
|
+
"marketing-buzzword": {
|
|
26166
|
+
family: "copy",
|
|
26167
|
+
severity: "warn",
|
|
26168
|
+
note: "SaaS filler ('supercharge your\u2026') is generic. Name names, use numbers, say the specific thing only this product proves."
|
|
26169
|
+
},
|
|
26170
|
+
"aphoristic-cadence": {
|
|
26171
|
+
family: "copy",
|
|
26172
|
+
severity: "warn",
|
|
26173
|
+
note: "Manufactured-contrast cadence ('Not an X. A Y.') is an AI copy tell. Vary sentence shape; make a real claim."
|
|
26174
|
+
},
|
|
26175
|
+
"em-dash-overuse": {
|
|
26176
|
+
family: "copy",
|
|
26177
|
+
severity: "warn",
|
|
26178
|
+
note: "Dense em-dashes are an AI cadence tell (and brand copy bans them). Recast with commas, colons, or full stops."
|
|
26179
|
+
},
|
|
26180
|
+
"dark-glow": {
|
|
26181
|
+
family: "borders_depth",
|
|
26182
|
+
severity: "warn",
|
|
26183
|
+
note: "A zero-offset colored glow is decoration, not depth. Shadows carry an offset and a soft blur."
|
|
26184
|
+
},
|
|
26185
|
+
marquee: {
|
|
26186
|
+
family: "motion",
|
|
26187
|
+
severity: "warn",
|
|
26188
|
+
note: "Auto-scrolling marquees are a dated tell. Let the visitor control the pace, or drop the motion."
|
|
26189
|
+
}
|
|
26190
|
+
};
|
|
26191
|
+
var SEVERITY_WEIGHT = {
|
|
26192
|
+
block: 0.5,
|
|
26193
|
+
warn: 0.2,
|
|
26194
|
+
advisory: 0.05
|
|
26195
|
+
};
|
|
26196
|
+
|
|
26197
|
+
// src/engine/landing/lib/rules.ts
|
|
26198
|
+
var cap2 = (m, i) => m[i] ?? "";
|
|
26199
|
+
var num = (m, i) => Number(m[i] ?? 0);
|
|
26200
|
+
var hasRounded = (line) => /\brounded(?:-\w+)?\b/.test(line) || /border-radius/i.test(line);
|
|
26201
|
+
var isSafeElement = (line) => /<(?:blockquote|nav[\s>]|pre[\s>]|code[\s>]|a\s|input[\s>]|span[\s>])/i.test(line);
|
|
26202
|
+
function isNeutralBorderColor(str) {
|
|
26203
|
+
const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
|
|
26204
|
+
if (!m) return false;
|
|
26205
|
+
return isNeutralAuthoredColor(m[1] ?? "");
|
|
26206
|
+
}
|
|
26207
|
+
var LINE_MATCHERS = [
|
|
26208
|
+
// ── Side-tab: a thick colored border on one edge ──────────────────────────
|
|
26209
|
+
{
|
|
26210
|
+
id: "side-tab",
|
|
26211
|
+
regex: /\bborder-[lrse]-(\d+)\b/g,
|
|
26212
|
+
test: (m, line) => hasRounded(line) ? num(m, 1) >= 2 : num(m, 1) >= 4,
|
|
26213
|
+
fmt: (m) => cap2(m, 0)
|
|
26214
|
+
},
|
|
26215
|
+
{
|
|
26216
|
+
id: "side-tab",
|
|
26217
|
+
regex: /border-(?:left|right)\s*:\s*(\d+)px\s+solid[^;]*/gi,
|
|
26218
|
+
test: (m, line) => {
|
|
26219
|
+
if (isSafeElement(line)) return false;
|
|
26220
|
+
if (isNeutralBorderColor(cap2(m, 0))) return false;
|
|
26221
|
+
return hasRounded(line) ? num(m, 1) >= 2 : num(m, 1) >= 3;
|
|
26222
|
+
},
|
|
26223
|
+
fmt: (m) => cap2(m, 0).replace(/\s*;?\s*$/, "")
|
|
26224
|
+
},
|
|
26225
|
+
{
|
|
26226
|
+
id: "side-tab",
|
|
26227
|
+
regex: /border-(?:left|right)-width\s*:\s*(\d+)px/gi,
|
|
26228
|
+
test: (m, line) => !isSafeElement(line) && num(m, 1) >= 3,
|
|
26229
|
+
fmt: (m) => cap2(m, 0)
|
|
26230
|
+
},
|
|
26231
|
+
{
|
|
26232
|
+
id: "side-tab",
|
|
26233
|
+
regex: /border-inline-(?:start|end)(?:-width)?\s*:\s*(\d+)px/gi,
|
|
26234
|
+
test: (m, line) => !isSafeElement(line) && num(m, 1) >= 3,
|
|
26235
|
+
fmt: (m) => cap2(m, 0)
|
|
26236
|
+
},
|
|
26237
|
+
// ── Border accent on a rounded card (top/bottom colored bar) ──────────────
|
|
26238
|
+
{
|
|
26239
|
+
id: "border-accent-on-rounded",
|
|
26240
|
+
regex: /\bborder-[tb]-(\d+)\b/g,
|
|
26241
|
+
test: (m, line) => hasRounded(line) && num(m, 1) >= 1,
|
|
26242
|
+
fmt: (m) => cap2(m, 0)
|
|
26243
|
+
},
|
|
26244
|
+
{
|
|
26245
|
+
id: "border-accent-on-rounded",
|
|
26246
|
+
regex: /border-(?:top|bottom)\s*:\s*(\d+)px\s+solid/gi,
|
|
26247
|
+
test: (m, line) => num(m, 1) >= 3 && hasRounded(line),
|
|
26248
|
+
fmt: (m) => cap2(m, 0)
|
|
26249
|
+
},
|
|
26250
|
+
// ── Gradient text ─────────────────────────────────────────────────────────
|
|
26251
|
+
{
|
|
26252
|
+
id: "gradient-text",
|
|
26253
|
+
regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
|
|
26254
|
+
test: (_m, line) => /gradient/i.test(line),
|
|
26255
|
+
fmt: () => "background-clip: text + gradient"
|
|
26256
|
+
},
|
|
26257
|
+
{
|
|
26258
|
+
id: "gradient-text",
|
|
26259
|
+
regex: /\bbg-clip-text\b/g,
|
|
26260
|
+
test: (_m, line) => /\bbg-gradient-to-|\bbg-\[linear-gradient/i.test(line),
|
|
26261
|
+
fmt: () => "bg-clip-text + gradient"
|
|
26262
|
+
},
|
|
26263
|
+
// ── Gray text on a colored background (Tailwind) ──────────────────────────
|
|
26264
|
+
{
|
|
26265
|
+
id: "gray-on-color",
|
|
26266
|
+
regex: /\btext-(?:gray|slate|zinc|neutral|stone)-(\d+)\b/g,
|
|
26267
|
+
test: (_m, line) => /\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/.test(
|
|
26268
|
+
line
|
|
26269
|
+
),
|
|
26270
|
+
fmt: (m, line) => {
|
|
26271
|
+
const bg = line.match(
|
|
26272
|
+
/\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/
|
|
26273
|
+
);
|
|
26274
|
+
return `${cap2(m, 0)} on ${bg?.[0] || "?"}`;
|
|
26275
|
+
}
|
|
26276
|
+
},
|
|
26277
|
+
// ── AI color palette (Tailwind purple/indigo + banned hero hexes) ─────────
|
|
26278
|
+
{
|
|
26279
|
+
id: "ai-color-palette",
|
|
26280
|
+
regex: /\btext-(?:purple|violet|indigo)-(\d+)\b/g,
|
|
26281
|
+
test: (_m, line) => /\btext-(?:[2-9]xl)\b|<h[1-3]/i.test(line),
|
|
26282
|
+
fmt: (m) => `${cap2(m, 0)} on heading`
|
|
26283
|
+
},
|
|
26284
|
+
{
|
|
26285
|
+
id: "ai-color-palette",
|
|
26286
|
+
regex: /\bfrom-(?:purple|violet|indigo)-(\d+)\b/g,
|
|
26287
|
+
test: (_m, line) => /\bto-(?:purple|violet|indigo|blue|cyan|pink|fuchsia)-\d+\b/.test(line),
|
|
26288
|
+
fmt: (m) => `${cap2(m, 0)} gradient`
|
|
26289
|
+
},
|
|
26290
|
+
{
|
|
26291
|
+
id: "ai-color-palette",
|
|
26292
|
+
regex: /#[0-9a-f]{6}\b/gi,
|
|
26293
|
+
test: (m) => AI_PURPLE_HEXES.has(cap2(m, 0).toLowerCase()),
|
|
26294
|
+
fmt: (m) => `${cap2(m, 0)} (AI-cluster purple)`
|
|
26295
|
+
},
|
|
26296
|
+
// ── Cream/parchment ground (Tailwind + authored background hex) ───────────
|
|
26297
|
+
{
|
|
26298
|
+
id: "cream-palette",
|
|
26299
|
+
regex: /\bbg-(?:amber|orange|yellow|stone)-(?:50|100|200)\b/g,
|
|
26300
|
+
test: (m) => CREAM_TAILWIND_BG.has(cap2(m, 0)),
|
|
26301
|
+
fmt: (m) => `${cap2(m, 0)} (cream ground)`
|
|
26302
|
+
},
|
|
26303
|
+
{
|
|
26304
|
+
id: "cream-palette",
|
|
26305
|
+
regex: /background(?:-color)?\s*:\s*(#[0-9a-f]{6}\b|rgba?\([^)]+\))/gi,
|
|
26306
|
+
test: (m) => {
|
|
26307
|
+
const c = parseColor2(cap2(m, 1));
|
|
26308
|
+
return c !== null && isCreamColor(c);
|
|
26309
|
+
},
|
|
26310
|
+
fmt: (m) => `${cap2(m, 1)} (cream ground)`
|
|
26311
|
+
},
|
|
26312
|
+
// ── Bounce / elastic easing ───────────────────────────────────────────────
|
|
26313
|
+
{
|
|
26314
|
+
id: "bounce-easing",
|
|
26315
|
+
regex: /\banimate-bounce\b/g,
|
|
26316
|
+
test: () => true,
|
|
26317
|
+
fmt: () => "animate-bounce (Tailwind)"
|
|
26318
|
+
},
|
|
26319
|
+
{
|
|
26320
|
+
id: "bounce-easing",
|
|
26321
|
+
regex: /animation(?:-name)?\s*:\s*([^;{}]*(?:bounce|elastic|wobble|jiggle|spring)[^;{}]*)/gi,
|
|
26322
|
+
test: () => true,
|
|
26323
|
+
fmt: (m) => `animation: ${cap2(m, 1).trim()}`
|
|
26324
|
+
},
|
|
26325
|
+
{
|
|
26326
|
+
id: "bounce-easing",
|
|
26327
|
+
regex: /cubic-bezier\(\s*([\d.-]+)\s*,\s*([\d.-]+)\s*,\s*([\d.-]+)\s*,\s*([\d.-]+)\s*\)/g,
|
|
26328
|
+
test: (m) => {
|
|
26329
|
+
const y1 = Number.parseFloat(cap2(m, 2));
|
|
26330
|
+
const y2 = Number.parseFloat(cap2(m, 4));
|
|
26331
|
+
return y1 < -0.1 || y1 > 1.1 || y2 < -0.1 || y2 > 1.1;
|
|
26332
|
+
},
|
|
26333
|
+
fmt: (m) => `cubic-bezier(${cap2(m, 1)}, ${cap2(m, 2)}, ${cap2(m, 3)}, ${cap2(m, 4)})`
|
|
26334
|
+
},
|
|
26335
|
+
// ── Transitioning layout properties (jank) ────────────────────────────────
|
|
26336
|
+
{
|
|
26337
|
+
id: "layout-transition",
|
|
26338
|
+
regex: /transition(?:-property)?\s*:\s*([^;{}]+)/gi,
|
|
26339
|
+
test: (m) => {
|
|
26340
|
+
const val = cap2(m, 1).toLowerCase();
|
|
26341
|
+
if (/\ball\b/.test(val)) return false;
|
|
26342
|
+
return /\b(?:(?:max|min)-)?(?:width|height)\b|\bpadding\b|\bmargin\b/.test(val);
|
|
26343
|
+
},
|
|
26344
|
+
fmt: (m) => {
|
|
26345
|
+
const found = cap2(m, 1).match(
|
|
26346
|
+
/\b(?:(?:max|min)-)?(?:width|height)\b|\bpadding(?:-(?:top|right|bottom|left))?\b|\bmargin(?:-(?:top|right|bottom|left))?\b/gi
|
|
26347
|
+
);
|
|
26348
|
+
return `transition: ${found ? found.join(", ") : cap2(m, 1).trim()}`;
|
|
26349
|
+
}
|
|
26350
|
+
},
|
|
26351
|
+
// ── Broken image ──────────────────────────────────────────────────────────
|
|
26352
|
+
{
|
|
26353
|
+
id: "broken-image",
|
|
26354
|
+
regex: /<img\b[^>]*?\bsrc\s*=\s*(?:""|''|"\s+"|'\s+'|"#"|'#')/gi,
|
|
26355
|
+
test: () => true,
|
|
26356
|
+
fmt: (m) => cap2(m, 0).slice(0, 100)
|
|
26357
|
+
},
|
|
26358
|
+
{
|
|
26359
|
+
id: "broken-image",
|
|
26360
|
+
regex: /<img\b(?:(?!\bsrc\s*=)[^>])*>/gi,
|
|
26361
|
+
test: (m) => !/\bsrc\s*=/i.test(cap2(m, 0)),
|
|
26362
|
+
fmt: (m) => cap2(m, 0).slice(0, 100)
|
|
26363
|
+
}
|
|
26364
|
+
];
|
|
26365
|
+
function stripHtmlToText(html) {
|
|
26366
|
+
return html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ");
|
|
26367
|
+
}
|
|
26368
|
+
var ANALYZERS = [
|
|
26369
|
+
// Overused primary font (font-family declarations + Google Fonts URLs).
|
|
26370
|
+
(text, file) => {
|
|
26371
|
+
const out = [];
|
|
26372
|
+
const seen = /* @__PURE__ */ new Set();
|
|
26373
|
+
for (const m of text.matchAll(/font-family\s*:\s*([^;}{]+)/gi)) {
|
|
26374
|
+
const first = firstFamily(cap2(m, 1));
|
|
26375
|
+
if (first && !GENERIC_FONTS.has(first) && OVERUSED_FONTS.has(first) && !seen.has(first)) {
|
|
26376
|
+
seen.add(first);
|
|
26377
|
+
out.push({ id: "overused-font", snippet: `font-family: ${first}`, file, line: lineOf(text, m.index ?? 0) });
|
|
26378
|
+
}
|
|
26379
|
+
}
|
|
26380
|
+
for (const m of text.matchAll(/fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi)) {
|
|
26381
|
+
for (const fam of googleFontFamilies(cap2(m, 0))) {
|
|
26382
|
+
if (OVERUSED_FONTS.has(fam) && !seen.has(fam)) {
|
|
26383
|
+
seen.add(fam);
|
|
26384
|
+
out.push({ id: "overused-font", snippet: `Google Fonts: ${fam}`, file, line: lineOf(text, m.index ?? 0) });
|
|
26385
|
+
}
|
|
26386
|
+
}
|
|
26387
|
+
}
|
|
26388
|
+
return out;
|
|
26389
|
+
},
|
|
26390
|
+
// Single font across a substantial page.
|
|
26391
|
+
(text, file) => {
|
|
26392
|
+
const fonts = /* @__PURE__ */ new Set();
|
|
26393
|
+
for (const m of text.matchAll(/font-family\s*:\s*([^;}{]+)/gi)) {
|
|
26394
|
+
for (const f of cap2(m, 1).split(",").map(
|
|
26395
|
+
(x) => x.trim().replace(/^['"]|['"]$/g, "").toLowerCase()
|
|
26396
|
+
)) {
|
|
26397
|
+
if (f && !GENERIC_FONTS.has(f)) fonts.add(f);
|
|
26398
|
+
}
|
|
26399
|
+
}
|
|
26400
|
+
for (const f of allGoogleFontFamilies(text)) fonts.add(f);
|
|
26401
|
+
if (fonts.size !== 1 || text.split("\n").length < 20) return [];
|
|
26402
|
+
const only = [...fonts][0] ?? "";
|
|
26403
|
+
return [{ id: "single-font", snippet: `only font used is ${only}`, file }];
|
|
26404
|
+
},
|
|
26405
|
+
// Flat type hierarchy (all sizes span <2×).
|
|
26406
|
+
(text, file) => {
|
|
26407
|
+
const sizes = /* @__PURE__ */ new Set();
|
|
26408
|
+
const REM = 16;
|
|
26409
|
+
for (const m of text.matchAll(/font-size\s*:\s*([\d.]+)(px|rem|em)\b/gi)) {
|
|
26410
|
+
const px = cap2(m, 2) === "px" ? num(m, 1) : num(m, 1) * REM;
|
|
26411
|
+
if (px > 0 && px < 200) sizes.add(Math.round(px * 10) / 10);
|
|
26412
|
+
}
|
|
26413
|
+
for (const [cls, px] of Object.entries(TAILWIND_TEXT_PX)) {
|
|
26414
|
+
if (new RegExp(`\\b${cls}\\b`).test(text)) sizes.add(px);
|
|
26415
|
+
}
|
|
26416
|
+
if (sizes.size < 3) return [];
|
|
26417
|
+
const sorted = [...sizes].sort((a, b) => a - b);
|
|
26418
|
+
const min = sorted[0] ?? 1;
|
|
26419
|
+
const max = sorted[sorted.length - 1] ?? 1;
|
|
26420
|
+
const ratio = max / min;
|
|
26421
|
+
if (ratio >= 2) return [];
|
|
26422
|
+
return [
|
|
26423
|
+
{
|
|
26424
|
+
id: "flat-type-hierarchy",
|
|
26425
|
+
snippet: `sizes ${sorted.map((s) => `${s}px`).join(", ")} (ratio ${ratio.toFixed(1)}:1)`,
|
|
26426
|
+
file
|
|
26427
|
+
}
|
|
26428
|
+
];
|
|
26429
|
+
},
|
|
26430
|
+
// Monotonous spacing (one value dominates).
|
|
26431
|
+
(text, file) => {
|
|
26432
|
+
const vals = [];
|
|
26433
|
+
for (const m of text.matchAll(/(?:padding|margin)(?:-(?:top|right|bottom|left))?\s*:\s*(\d+)px/gi)) {
|
|
26434
|
+
const v = num(m, 1);
|
|
26435
|
+
if (v > 0 && v < 200) vals.push(v);
|
|
26436
|
+
}
|
|
26437
|
+
for (const m of text.matchAll(/(?:padding|margin)(?:-(?:top|right|bottom|left))?\s*:\s*([\d.]+)rem/gi)) {
|
|
26438
|
+
const v = Math.round(Number.parseFloat(cap2(m, 1)) * 16);
|
|
26439
|
+
if (v > 0 && v < 200) vals.push(v);
|
|
26440
|
+
}
|
|
26441
|
+
for (const m of text.matchAll(/\b(?:p|px|py|pt|pb|pl|pr|m|mx|my|mt|mb|ml|mr|gap)-(\d+)\b/g))
|
|
26442
|
+
vals.push(num(m, 1) * 4);
|
|
26443
|
+
const rounded = vals.map((v) => Math.round(v / 4) * 4);
|
|
26444
|
+
if (rounded.length < 10) return [];
|
|
26445
|
+
const counts = {};
|
|
26446
|
+
for (const v of rounded) counts[v] = (counts[v] || 0) + 1;
|
|
26447
|
+
const maxCount = Math.max(...Object.values(counts));
|
|
26448
|
+
const pct = maxCount / rounded.length;
|
|
26449
|
+
const unique = [...new Set(rounded)].filter((v) => v > 0);
|
|
26450
|
+
if (pct <= 0.6 || unique.length > 3) return [];
|
|
26451
|
+
const dominant = Object.entries(counts).sort((a, b) => b[1] - a[1])[0]?.[0] ?? "?";
|
|
26452
|
+
return [
|
|
26453
|
+
{
|
|
26454
|
+
id: "monotonous-spacing",
|
|
26455
|
+
snippet: `~${dominant}px used ${maxCount}/${rounded.length} times (${Math.round(pct * 100)}%)`,
|
|
26456
|
+
file
|
|
26457
|
+
}
|
|
26458
|
+
];
|
|
26459
|
+
},
|
|
26460
|
+
// Em-dash overuse (density-gated).
|
|
26461
|
+
(text, file) => {
|
|
26462
|
+
const body = stripHtmlToText(text).replace(/—|�*8212;|�*2014;/gi, "\u2014");
|
|
26463
|
+
const count = (body.match(/[—]|--(?=\S)/g) ?? []).length;
|
|
26464
|
+
if (count < EM_DASH_FLOOR) return [];
|
|
26465
|
+
if (body.length > count * EM_DASH_CHARS_PER_DASH) return [];
|
|
26466
|
+
return [{ id: "em-dash-overuse", snippet: `${count} em-dashes in body copy`, file }];
|
|
26467
|
+
},
|
|
26468
|
+
// Marketing buzzwords.
|
|
26469
|
+
(text, file) => {
|
|
26470
|
+
const body = stripHtmlToText(text);
|
|
26471
|
+
const lower = body.toLowerCase();
|
|
26472
|
+
let count = 0;
|
|
26473
|
+
let firstSample = "";
|
|
26474
|
+
for (const phrase of BUZZWORDS) {
|
|
26475
|
+
let from = 0;
|
|
26476
|
+
for (; ; ) {
|
|
26477
|
+
const idx = lower.indexOf(phrase, from);
|
|
26478
|
+
if (idx === -1) break;
|
|
26479
|
+
count++;
|
|
26480
|
+
if (!firstSample) {
|
|
26481
|
+
firstSample = body.slice(Math.max(0, idx - 12), Math.min(body.length, idx + phrase.length + 12)).trim();
|
|
26482
|
+
}
|
|
26483
|
+
from = idx + phrase.length;
|
|
26484
|
+
}
|
|
26485
|
+
}
|
|
26486
|
+
if (count === 0) return [];
|
|
26487
|
+
return [
|
|
26488
|
+
{
|
|
26489
|
+
id: "marketing-buzzword",
|
|
26490
|
+
snippet: `${count} buzzword phrase${count === 1 ? "" : "s"}: "${firstSample}"`,
|
|
26491
|
+
file
|
|
26492
|
+
}
|
|
26493
|
+
];
|
|
26494
|
+
},
|
|
26495
|
+
// Aphoristic cadence (manufactured contrast + short rebuttal).
|
|
26496
|
+
(text, file) => {
|
|
26497
|
+
const body = stripHtmlToText(text);
|
|
26498
|
+
let count = 0;
|
|
26499
|
+
let firstSample = "";
|
|
26500
|
+
for (const m of body.matchAll(/\bNot an? [a-z][^.!?]{1,40}[.!]\s+[A-Z][^.!?]{1,60}[.!]/g)) {
|
|
26501
|
+
count++;
|
|
26502
|
+
if (!firstSample) firstSample = cap2(m, 0).trim().slice(0, 80);
|
|
26503
|
+
}
|
|
26504
|
+
for (const m of body.matchAll(/\b[A-Z][^.!?]{4,80}[.!]\s+(No|Just)\s+[a-z][^.!?]{2,60}[.!]/g)) {
|
|
26505
|
+
count++;
|
|
26506
|
+
if (!firstSample) firstSample = cap2(m, 0).trim().slice(0, 80);
|
|
26507
|
+
}
|
|
26508
|
+
if (count < 3) return [];
|
|
26509
|
+
return [{ id: "aphoristic-cadence", snippet: `${count} aphoristic constructions: "${firstSample}"`, file }];
|
|
26510
|
+
},
|
|
26511
|
+
// Dark glow: a zero-offset chromatic shadow used as decoration.
|
|
26512
|
+
(text, file) => {
|
|
26513
|
+
const out = [];
|
|
26514
|
+
for (const m of text.matchAll(/(?:box|text)-shadow\s*:\s*([^;{}]+)/gi)) {
|
|
26515
|
+
if (shadowIsGlow(cap2(m, 1))) {
|
|
26516
|
+
out.push({
|
|
26517
|
+
id: "dark-glow",
|
|
26518
|
+
snippet: `shadow: ${cap2(m, 1).trim().slice(0, 60)}`,
|
|
26519
|
+
file,
|
|
26520
|
+
line: lineOf(text, m.index ?? 0)
|
|
26521
|
+
});
|
|
26522
|
+
}
|
|
26523
|
+
}
|
|
26524
|
+
return out;
|
|
26525
|
+
},
|
|
26526
|
+
// Marquee (<marquee> element).
|
|
26527
|
+
(text, file) => /<marquee\b/i.test(text) ? [{ id: "marquee", snippet: "<marquee> element", file }] : []
|
|
26528
|
+
];
|
|
26529
|
+
function shadowIsGlow(value) {
|
|
26530
|
+
const first = (value.split(/,(?![^(]*\))/)[0] ?? "").trim();
|
|
26531
|
+
const colorMatch = first.match(/#[0-9a-f]{3,8}\b|rgba?\([^)]+\)|hsla?\([^)]+\)/i);
|
|
26532
|
+
if (!colorMatch) return false;
|
|
26533
|
+
const lengthsPart = first.replace(colorMatch[0], " ").replace(/\binset\b/gi, " ");
|
|
26534
|
+
const offsets = lengthsPart.match(/-?\d*\.?\d+(?:px|rem|em)?/g);
|
|
26535
|
+
if (!offsets || offsets.length < 3) return false;
|
|
26536
|
+
const x = Number.parseFloat(offsets[0] ?? "0");
|
|
26537
|
+
const y = Number.parseFloat(offsets[1] ?? "0");
|
|
26538
|
+
const blur = Number.parseFloat(offsets[2] ?? "0");
|
|
26539
|
+
if (!(x === 0 && y === 0 && blur > 4)) return false;
|
|
26540
|
+
const c = parseColor2(colorMatch[0]);
|
|
26541
|
+
return c !== null && hasChroma(c, 30);
|
|
26542
|
+
}
|
|
26543
|
+
function firstFamily(decl) {
|
|
26544
|
+
return (decl.split(",")[0] ?? "").trim().replace(/^['"]|['"]$/g, "").toLowerCase();
|
|
26545
|
+
}
|
|
26546
|
+
function googleFontFamilies(url) {
|
|
26547
|
+
const q = url.indexOf("?");
|
|
26548
|
+
if (q === -1) return [];
|
|
26549
|
+
const params = new URLSearchParams(url.slice(q + 1).replace(/&/g, "&"));
|
|
26550
|
+
const out = [];
|
|
26551
|
+
for (const value of params.getAll("family")) {
|
|
26552
|
+
for (const part of value.split("|")) out.push((part.split(":")[0] ?? "").trim().toLowerCase());
|
|
26553
|
+
}
|
|
26554
|
+
return out.filter(Boolean);
|
|
26555
|
+
}
|
|
26556
|
+
function allGoogleFontFamilies(text) {
|
|
26557
|
+
const out = [];
|
|
26558
|
+
for (const m of text.matchAll(/fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi))
|
|
26559
|
+
out.push(...googleFontFamilies(cap2(m, 0)));
|
|
26560
|
+
return out;
|
|
26561
|
+
}
|
|
26562
|
+
function lineOf(text, index) {
|
|
26563
|
+
let line = 1;
|
|
26564
|
+
for (let i = 0; i < index && i < text.length; i++) if (text[i] === "\n") line++;
|
|
26565
|
+
return line;
|
|
26566
|
+
}
|
|
26567
|
+
function detectSource(source) {
|
|
26568
|
+
const { path: file, text } = source;
|
|
26569
|
+
const findings = [];
|
|
26570
|
+
const lines = text.split("\n");
|
|
26571
|
+
for (const matcher of LINE_MATCHERS) {
|
|
26572
|
+
for (let i = 0; i < lines.length; i++) {
|
|
26573
|
+
const line = lines[i] ?? "";
|
|
26574
|
+
for (const m of line.matchAll(matcher.regex)) {
|
|
26575
|
+
if (matcher.test(m, line)) findings.push({ id: matcher.id, snippet: matcher.fmt(m, line), file, line: i + 1 });
|
|
26576
|
+
}
|
|
26577
|
+
}
|
|
26578
|
+
}
|
|
26579
|
+
for (const analyzer of ANALYZERS) findings.push(...analyzer(text, file));
|
|
26580
|
+
return dedupe(findings);
|
|
26581
|
+
}
|
|
26582
|
+
function dedupe(findings) {
|
|
26583
|
+
const out = [];
|
|
26584
|
+
for (const f of findings) {
|
|
26585
|
+
const dupe = out.some(
|
|
26586
|
+
(d) => d.id === f.id && d.snippet === f.snippet && Math.abs((d.line ?? 0) - (f.line ?? 0)) <= 2
|
|
26587
|
+
);
|
|
26588
|
+
if (!dupe) out.push(f);
|
|
26589
|
+
}
|
|
26590
|
+
return out;
|
|
26591
|
+
}
|
|
26592
|
+
|
|
26593
|
+
// src/engine/landing/lib/critique.ts
|
|
26594
|
+
var FAMILIES = ["typography", "color", "borders_depth", "motion", "spacing", "copy", "integrity"];
|
|
26595
|
+
function round4(n) {
|
|
26596
|
+
return Math.round(n * 100) / 100;
|
|
26597
|
+
}
|
|
26598
|
+
function clamp012(n) {
|
|
26599
|
+
return Math.max(0, Math.min(1, n));
|
|
26600
|
+
}
|
|
26601
|
+
function applyBrand(id, snippet, severity, brand) {
|
|
26602
|
+
const meta = RULE_META[id];
|
|
26603
|
+
if (!meta?.brandAware || !brand.hasTokens) return { severity, brandDrift: false };
|
|
26604
|
+
const matched = meta.brandAware === "font" && brandCommitsFont(brand, snippet) || meta.brandAware === "color" && id === "ai-color-palette" && brandCommitsAiHue(brand) || meta.brandAware === "color" && id === "cream-palette" && brandCommitsCream(brand);
|
|
26605
|
+
return matched ? { severity: "advisory", brandDrift: true } : { severity, brandDrift: false };
|
|
26606
|
+
}
|
|
26607
|
+
function critiqueLanding(input) {
|
|
26608
|
+
const sources = Array.isArray(input.sources) ? input.sources : [];
|
|
26609
|
+
const findings = [];
|
|
26610
|
+
for (const source of sources) {
|
|
26611
|
+
for (const raw of detectSource(source)) {
|
|
26612
|
+
const meta = RULE_META[raw.id];
|
|
26613
|
+
if (!meta) continue;
|
|
26614
|
+
const { severity, brandDrift } = applyBrand(raw.id, raw.snippet, meta.severity, input.brand);
|
|
26615
|
+
findings.push({
|
|
26616
|
+
id: raw.id,
|
|
26617
|
+
severity,
|
|
26618
|
+
family: meta.family,
|
|
26619
|
+
snippet: raw.snippet,
|
|
26620
|
+
file: raw.file,
|
|
26621
|
+
line: raw.line,
|
|
26622
|
+
note: meta.note,
|
|
26623
|
+
...brandDrift ? { brandDrift: true } : {}
|
|
26624
|
+
});
|
|
26625
|
+
}
|
|
26626
|
+
}
|
|
26627
|
+
const dimensions = FAMILIES.map((family) => {
|
|
26628
|
+
const fam = findings.filter((f) => f.family === family);
|
|
26629
|
+
const penalty = fam.reduce((s, f) => s + SEVERITY_WEIGHT[f.severity], 0);
|
|
26630
|
+
const score = round4(clamp012(1 - penalty));
|
|
26631
|
+
const note = fam.length === 0 ? "clean" : `${fam.length} finding(s): ${describeCounts(fam)}`;
|
|
26632
|
+
return { dimension: family, score, note };
|
|
26633
|
+
});
|
|
26634
|
+
const overall = round4(dimensions.reduce((s, d) => s + d.score, 0) / dimensions.length);
|
|
26635
|
+
const counts = {
|
|
26636
|
+
block: findings.filter((f) => f.severity === "block").length,
|
|
26637
|
+
warn: findings.filter((f) => f.severity === "warn").length,
|
|
26638
|
+
advisory: findings.filter((f) => f.severity === "advisory").length
|
|
26639
|
+
};
|
|
26640
|
+
return { advisory: true, overall, dimensions, findings, counts };
|
|
26641
|
+
}
|
|
26642
|
+
function describeCounts(findings) {
|
|
26643
|
+
const b = findings.filter((f) => f.severity === "block").length;
|
|
26644
|
+
const w = findings.filter((f) => f.severity === "warn").length;
|
|
26645
|
+
const a = findings.filter((f) => f.severity === "advisory").length;
|
|
26646
|
+
return [b ? `${b} block` : "", w ? `${w} warn` : "", a ? `${a} advisory` : ""].filter(Boolean).join(", ");
|
|
26647
|
+
}
|
|
26648
|
+
|
|
26649
|
+
// src/commands/landing/snapshot.ts
|
|
26650
|
+
import { mkdir as mkdir7, writeFile as writeFile10 } from "fs/promises";
|
|
26651
|
+
import path24 from "path";
|
|
26652
|
+
var CRITIC_VERSION = "1";
|
|
26653
|
+
function critiqueCacheDir(projectRoot) {
|
|
26654
|
+
return path24.join(projectRoot, ".cache", "landing-critique");
|
|
26655
|
+
}
|
|
26656
|
+
function snapshotPath(projectRoot, slug) {
|
|
26657
|
+
return path24.join(critiqueCacheDir(projectRoot), `${slug}.json`);
|
|
26658
|
+
}
|
|
26659
|
+
async function writeCritiqueSnapshot(projectRoot, snapshot) {
|
|
26660
|
+
await mkdir7(critiqueCacheDir(projectRoot), { recursive: true });
|
|
26661
|
+
await writeFile10(snapshotPath(projectRoot, snapshot.slug), `${JSON.stringify(snapshot, null, 2)}
|
|
26662
|
+
`, "utf8");
|
|
26663
|
+
}
|
|
26664
|
+
|
|
26665
|
+
// src/commands/landing/source-version.ts
|
|
26666
|
+
import { readdir as readdir7, readFile as readFile21, stat as stat5 } from "fs/promises";
|
|
26667
|
+
import path25 from "path";
|
|
26668
|
+
async function landingSourceRelPaths(landingDir) {
|
|
26669
|
+
const rel = [];
|
|
26670
|
+
if (await isFile(path25.join(landingDir, "index.astro"))) rel.push("index.astro");
|
|
26671
|
+
const componentsDir = path25.join(landingDir, "_components");
|
|
26672
|
+
for (const abs of await walkAstro(componentsDir)) {
|
|
26673
|
+
rel.push(path25.relative(landingDir, abs).split(path25.sep).join("/"));
|
|
26674
|
+
}
|
|
26675
|
+
return rel.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
26676
|
+
}
|
|
26677
|
+
async function readLandingSources(landingDir) {
|
|
26678
|
+
const rel = await landingSourceRelPaths(landingDir);
|
|
26679
|
+
const out = [];
|
|
26680
|
+
for (const r of rel) out.push({ path: r, text: await readFile21(path25.join(landingDir, r), "utf8") });
|
|
26681
|
+
return out;
|
|
26682
|
+
}
|
|
26683
|
+
async function computeLandingSourceSha(landingDir) {
|
|
26684
|
+
const rel = await landingSourceRelPaths(landingDir);
|
|
26685
|
+
const parts = [];
|
|
26686
|
+
for (const r of rel) {
|
|
26687
|
+
let bytes;
|
|
26688
|
+
try {
|
|
26689
|
+
bytes = await readFile21(path25.join(landingDir, r));
|
|
26690
|
+
} catch {
|
|
26691
|
+
bytes = Buffer.alloc(0);
|
|
26692
|
+
}
|
|
26693
|
+
parts.push(Buffer.from(`${r}\0${bytes.length}\0`), bytes);
|
|
26694
|
+
}
|
|
26695
|
+
return sha256Hex(Buffer.concat(parts));
|
|
26696
|
+
}
|
|
26697
|
+
async function isFile(p) {
|
|
26698
|
+
try {
|
|
26699
|
+
return (await stat5(p)).isFile();
|
|
26700
|
+
} catch {
|
|
26701
|
+
return false;
|
|
26702
|
+
}
|
|
26703
|
+
}
|
|
26704
|
+
async function walkAstro(dir) {
|
|
26705
|
+
let entries;
|
|
26706
|
+
try {
|
|
26707
|
+
entries = await readdir7(dir, { withFileTypes: true });
|
|
26708
|
+
} catch {
|
|
26709
|
+
return [];
|
|
26710
|
+
}
|
|
26711
|
+
const out = [];
|
|
26712
|
+
for (const entry of entries) {
|
|
26713
|
+
const abs = path25.join(dir, entry.name);
|
|
26714
|
+
if (entry.isDirectory()) out.push(...await walkAstro(abs));
|
|
26715
|
+
else if (entry.isFile() && entry.name.endsWith(".astro")) out.push(abs);
|
|
26716
|
+
}
|
|
26717
|
+
return out;
|
|
26718
|
+
}
|
|
26719
|
+
|
|
26720
|
+
// src/commands/landing/critique.ts
|
|
26721
|
+
registerSchema({
|
|
26722
|
+
command: "landing.critique",
|
|
26723
|
+
description: "Deterministic design-quality critic for a landing page (ADVISORY \u2014 never blocks, always exits 0). Flags the known AI 'slop' tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images\u2026) tiered block/warn/advisory, and records the critique the publish quality gate requires.",
|
|
26724
|
+
args: {
|
|
26725
|
+
slug: { type: "string", description: "Landing slug (folder under src/pages/)", required: true },
|
|
26726
|
+
full: {
|
|
26727
|
+
type: "boolean",
|
|
26728
|
+
description: "Also print advisory-tier findings (default: block + warn only)",
|
|
26729
|
+
required: false
|
|
26730
|
+
}
|
|
26731
|
+
}
|
|
26732
|
+
});
|
|
26733
|
+
var critiqueCommand2 = defineCommand133({
|
|
26734
|
+
meta: {
|
|
26735
|
+
name: "critique",
|
|
26736
|
+
description: "Deterministic design-quality critic for a landing (ADVISORY \u2014 never blocks). Flags the known AI design tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images) tiered block/warn/advisory, respecting the client's BRAND.md as the allowlist. Also records the critique that `baker publish` requires \u2014 run it before finishing a landing."
|
|
26737
|
+
},
|
|
26738
|
+
args: {
|
|
26739
|
+
slug: { type: "positional", required: true, description: "Landing slug (folder under src/pages/)" },
|
|
26740
|
+
full: { type: "boolean", description: "Also print advisory-tier findings", default: false }
|
|
26741
|
+
},
|
|
26742
|
+
async run({ args }) {
|
|
26743
|
+
const slug = String(args.slug);
|
|
26744
|
+
const projectRoot = process.cwd();
|
|
26745
|
+
const landingDir = path26.resolve(projectRoot, "src", "pages", slug);
|
|
26746
|
+
if (!await isDir(landingDir)) {
|
|
26747
|
+
process.stderr.write(
|
|
26748
|
+
`${JSON.stringify(
|
|
26749
|
+
{ ok: false, error: { code: "not-found", message: `No landing at src/pages/${slug}/` } },
|
|
26750
|
+
null,
|
|
26751
|
+
2
|
|
26752
|
+
)}
|
|
26753
|
+
`
|
|
26754
|
+
);
|
|
26755
|
+
process.exit(2);
|
|
26756
|
+
return;
|
|
26757
|
+
}
|
|
26758
|
+
const [sources, brand, sourceSha] = await Promise.all([
|
|
26759
|
+
readLandingSources(landingDir),
|
|
26760
|
+
loadBrandTokens(projectRoot),
|
|
26761
|
+
computeLandingSourceSha(landingDir)
|
|
26762
|
+
]);
|
|
26763
|
+
const report = critiqueLanding({ slug, sources, brand });
|
|
26764
|
+
try {
|
|
26765
|
+
await writeCritiqueSnapshot(projectRoot, {
|
|
26766
|
+
slug,
|
|
26767
|
+
sourceSha,
|
|
26768
|
+
criticVersion: CRITIC_VERSION,
|
|
26769
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26770
|
+
blockCount: report.counts.block,
|
|
26771
|
+
warnCount: report.counts.warn
|
|
26772
|
+
});
|
|
26773
|
+
} catch {
|
|
26774
|
+
}
|
|
26775
|
+
const shown = report.findings.filter((f) => args.full ? true : f.severity !== "advisory");
|
|
26776
|
+
process.stdout.write(
|
|
26777
|
+
`${JSON.stringify(
|
|
26778
|
+
{
|
|
26779
|
+
ok: true,
|
|
26780
|
+
advisory: true,
|
|
26781
|
+
slug,
|
|
26782
|
+
overall: report.overall,
|
|
26783
|
+
counts: report.counts,
|
|
26784
|
+
dimensions: report.dimensions,
|
|
26785
|
+
findings: shown.map(present),
|
|
26786
|
+
hint: report.counts.block > 0 ? "Block-tier tells are held at publish \u2014 fix them. Warn/advisory are guidance. Scores are 0\u20131 (higher is better)." : "No block-tier tells. Warn/advisory findings are guidance, not gates. Re-run after edits so the publish gate stays fresh."
|
|
26787
|
+
},
|
|
26788
|
+
null,
|
|
26789
|
+
2
|
|
26790
|
+
)}
|
|
26791
|
+
`
|
|
26792
|
+
);
|
|
26793
|
+
}
|
|
26794
|
+
});
|
|
26795
|
+
function present(f) {
|
|
26796
|
+
return {
|
|
26797
|
+
id: f.id,
|
|
26798
|
+
severity: f.severity,
|
|
26799
|
+
family: f.family,
|
|
26800
|
+
where: f.line ? `${f.file}:${f.line}` : f.file,
|
|
26801
|
+
snippet: f.snippet,
|
|
26802
|
+
note: f.note,
|
|
26803
|
+
...f.brandDrift ? { brandDrift: true } : {}
|
|
26804
|
+
};
|
|
26805
|
+
}
|
|
26806
|
+
async function isDir(p) {
|
|
26807
|
+
try {
|
|
26808
|
+
return (await stat6(p)).isDirectory();
|
|
26809
|
+
} catch {
|
|
26810
|
+
return false;
|
|
26811
|
+
}
|
|
26812
|
+
}
|
|
26813
|
+
|
|
26814
|
+
// src/commands/landing/index.ts
|
|
26815
|
+
var landingCommand = defineCommand134({
|
|
26816
|
+
meta: {
|
|
26817
|
+
name: "landing",
|
|
26818
|
+
description: `Design-quality tools for landing pages (src/pages/<slug>/).
|
|
26819
|
+
|
|
26820
|
+
Subcommands:
|
|
26821
|
+
baker landing critique <slug> \u2014 deterministic design-quality critic (advisory): flags the known AI 'slop' tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images) tiered block/warn/advisory, respecting the client's BRAND.md. Records the critique the publish quality gate requires \u2014 run it before finishing a landing.`
|
|
26822
|
+
},
|
|
26823
|
+
subCommands: {
|
|
26824
|
+
critique: critiqueCommand2
|
|
26825
|
+
}
|
|
26826
|
+
});
|
|
26827
|
+
|
|
26828
|
+
// src/commands/mcp/index.ts
|
|
26829
|
+
import { defineCommand as defineCommand135 } from "citty";
|
|
25807
26830
|
var SCOPES = ["user", "user_org", "company", "org"];
|
|
25808
26831
|
function parseScope(raw) {
|
|
25809
26832
|
const scope = raw === void 0 ? "company" : String(raw);
|
|
@@ -25842,7 +26865,7 @@ registerSchema({
|
|
|
25842
26865
|
description: "List the custom MCP servers this company's chats see (org + company + your own user scope).",
|
|
25843
26866
|
args: {}
|
|
25844
26867
|
});
|
|
25845
|
-
var listCommand8 =
|
|
26868
|
+
var listCommand8 = defineCommand135({
|
|
25846
26869
|
meta: { name: "list", description: "List custom MCP servers visible to this company's chats." },
|
|
25847
26870
|
run: async () => {
|
|
25848
26871
|
try {
|
|
@@ -25867,7 +26890,7 @@ registerSchema({
|
|
|
25867
26890
|
header: { type: "string", description: 'Auth header "Key: Value" (repeatable)', required: false }
|
|
25868
26891
|
}
|
|
25869
26892
|
});
|
|
25870
|
-
var addCommand =
|
|
26893
|
+
var addCommand = defineCommand135({
|
|
25871
26894
|
meta: {
|
|
25872
26895
|
name: "add",
|
|
25873
26896
|
description: `Register a custom MCP server. Tools appear as mcp__<name>__* on the NEXT message.
|
|
@@ -25908,7 +26931,7 @@ registerSchema({
|
|
|
25908
26931
|
description: "Remove a company custom MCP server by name.",
|
|
25909
26932
|
args: { name: { type: "string", description: "Server name to remove", required: true } }
|
|
25910
26933
|
});
|
|
25911
|
-
var removeCommand4 =
|
|
26934
|
+
var removeCommand4 = defineCommand135({
|
|
25912
26935
|
meta: {
|
|
25913
26936
|
name: "remove",
|
|
25914
26937
|
description: `Remove a company custom MCP server by name.
|
|
@@ -25930,7 +26953,7 @@ Example:
|
|
|
25930
26953
|
}
|
|
25931
26954
|
}
|
|
25932
26955
|
});
|
|
25933
|
-
var mcpCommand =
|
|
26956
|
+
var mcpCommand = defineCommand135({
|
|
25934
26957
|
meta: {
|
|
25935
26958
|
name: "mcp",
|
|
25936
26959
|
description: `Custom MCP servers for this company \u2014 point the agent at any HTTPS MCP endpoint.
|
|
@@ -25954,10 +26977,10 @@ Examples:
|
|
|
25954
26977
|
});
|
|
25955
26978
|
|
|
25956
26979
|
// src/commands/research/index.ts
|
|
25957
|
-
import { defineCommand as
|
|
26980
|
+
import { defineCommand as defineCommand146 } from "citty";
|
|
25958
26981
|
|
|
25959
26982
|
// src/commands/research/advertisers.ts
|
|
25960
|
-
import { defineCommand as
|
|
26983
|
+
import { defineCommand as defineCommand136 } from "citty";
|
|
25961
26984
|
|
|
25962
26985
|
// src/commands/research/output.ts
|
|
25963
26986
|
var RESEARCH_DATA_NOTE = "Estimates based on third-party SERP data \u2014 not exact figures. Use for directional insights, not precise measurement.";
|
|
@@ -26070,7 +27093,7 @@ var FIELDS3 = {
|
|
|
26070
27093
|
etv: "Estimated traffic value (USD)",
|
|
26071
27094
|
visibility: "SERP visibility score (0-1)"
|
|
26072
27095
|
};
|
|
26073
|
-
var advertisersCommand =
|
|
27096
|
+
var advertisersCommand = defineCommand136({
|
|
26074
27097
|
meta: {
|
|
26075
27098
|
name: "advertisers",
|
|
26076
27099
|
description: `Find domains competing for a keyword in Google SERPs.
|
|
@@ -26117,7 +27140,7 @@ Examples:
|
|
|
26117
27140
|
});
|
|
26118
27141
|
|
|
26119
27142
|
// src/commands/research/autocomplete.ts
|
|
26120
|
-
import { defineCommand as
|
|
27143
|
+
import { defineCommand as defineCommand137 } from "citty";
|
|
26121
27144
|
registerSchema({
|
|
26122
27145
|
command: "research.autocomplete",
|
|
26123
27146
|
description: "Get Google Autocomplete suggestions for a seed keyword. Useful for keyword expansion and discovering what people actually search for. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en).",
|
|
@@ -26140,7 +27163,7 @@ registerSchema({
|
|
|
26140
27163
|
var FIELDS4 = {
|
|
26141
27164
|
suggestion: "Autocomplete suggestion from Google"
|
|
26142
27165
|
};
|
|
26143
|
-
var autocompleteCommand =
|
|
27166
|
+
var autocompleteCommand = defineCommand137({
|
|
26144
27167
|
meta: {
|
|
26145
27168
|
name: "autocomplete",
|
|
26146
27169
|
description: `Get Google Autocomplete suggestions for keyword expansion.
|
|
@@ -26186,7 +27209,7 @@ Examples:
|
|
|
26186
27209
|
});
|
|
26187
27210
|
|
|
26188
27211
|
// src/commands/research/countries.ts
|
|
26189
|
-
import { defineCommand as
|
|
27212
|
+
import { defineCommand as defineCommand138 } from "citty";
|
|
26190
27213
|
registerSchema({
|
|
26191
27214
|
command: "research.countries",
|
|
26192
27215
|
description: "List all supported country codes for --location flag in research commands.",
|
|
@@ -26243,7 +27266,7 @@ var FIELDS5 = {
|
|
|
26243
27266
|
code: "Country code to pass as --location",
|
|
26244
27267
|
name: "Country name"
|
|
26245
27268
|
};
|
|
26246
|
-
var countriesCommand =
|
|
27269
|
+
var countriesCommand = defineCommand138({
|
|
26247
27270
|
meta: {
|
|
26248
27271
|
name: "countries",
|
|
26249
27272
|
description: "List all supported country codes for --location flag."
|
|
@@ -26254,7 +27277,7 @@ var countriesCommand = defineCommand136({
|
|
|
26254
27277
|
});
|
|
26255
27278
|
|
|
26256
27279
|
// src/commands/research/intent.ts
|
|
26257
|
-
import { defineCommand as
|
|
27280
|
+
import { defineCommand as defineCommand139 } from "citty";
|
|
26258
27281
|
registerSchema({
|
|
26259
27282
|
command: "research.intent",
|
|
26260
27283
|
description: "Classify Google Search intent for keywords. Determines if someone searching is looking to buy, research, or navigate. IMPORTANT: If --language is omitted, defaults to English (en). The response includes a query_context object showing which language was used.",
|
|
@@ -26277,7 +27300,7 @@ var FIELDS6 = {
|
|
|
26277
27300
|
intent: "Primary Google Search intent: informational, navigational, commercial, transactional",
|
|
26278
27301
|
probability: "Confidence score 0.0-1.0"
|
|
26279
27302
|
};
|
|
26280
|
-
var intentCommand =
|
|
27303
|
+
var intentCommand = defineCommand139({
|
|
26281
27304
|
meta: {
|
|
26282
27305
|
name: "intent",
|
|
26283
27306
|
description: `Classify Google Search intent for keywords. Returns intent type and confidence.
|
|
@@ -26325,7 +27348,7 @@ Examples:
|
|
|
26325
27348
|
});
|
|
26326
27349
|
|
|
26327
27350
|
// src/commands/research/keyword-gap.ts
|
|
26328
|
-
import { defineCommand as
|
|
27351
|
+
import { defineCommand as defineCommand140 } from "citty";
|
|
26329
27352
|
registerSchema({
|
|
26330
27353
|
command: "research.keyword-gap",
|
|
26331
27354
|
description: "Find keywords a competitor ranks for (organic or paid) that you don't. Discovers expansion opportunities. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en). The response includes a query_context object showing which location/language were used.",
|
|
@@ -26354,7 +27377,7 @@ var FIELDS7 = {
|
|
|
26354
27377
|
cpc: "Cost per click USD",
|
|
26355
27378
|
their_position: "Competitor's ranking position"
|
|
26356
27379
|
};
|
|
26357
|
-
var keywordGapCommand =
|
|
27380
|
+
var keywordGapCommand = defineCommand140({
|
|
26358
27381
|
meta: {
|
|
26359
27382
|
name: "keyword-gap",
|
|
26360
27383
|
description: `Find keywords a competitor has that you don't. Supports pagination via --offset.
|
|
@@ -26428,7 +27451,7 @@ Examples:
|
|
|
26428
27451
|
});
|
|
26429
27452
|
|
|
26430
27453
|
// src/commands/research/keywords-for-site.ts
|
|
26431
|
-
import { defineCommand as
|
|
27454
|
+
import { defineCommand as defineCommand141 } from "citty";
|
|
26432
27455
|
registerSchema({
|
|
26433
27456
|
command: "research.keywords-for-site",
|
|
26434
27457
|
description: "Get keywords a competitor targets in Google. Use --type paid to see only paid keywords, --type organic for organic only. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en). The response includes a query_context object showing which location/language were used.",
|
|
@@ -26461,7 +27484,7 @@ var FIELDS8 = {
|
|
|
26461
27484
|
competition: "LOW, MEDIUM, or HIGH",
|
|
26462
27485
|
competition_index: "Competition score 0-100"
|
|
26463
27486
|
};
|
|
26464
|
-
var keywordsForSiteCommand =
|
|
27487
|
+
var keywordsForSiteCommand = defineCommand141({
|
|
26465
27488
|
meta: {
|
|
26466
27489
|
name: "keywords-for-site",
|
|
26467
27490
|
description: `Get keywords a competitor targets in Google. Use --type to filter paid/organic.
|
|
@@ -26514,7 +27537,7 @@ Examples:
|
|
|
26514
27537
|
});
|
|
26515
27538
|
|
|
26516
27539
|
// src/commands/research/languages.ts
|
|
26517
|
-
import { defineCommand as
|
|
27540
|
+
import { defineCommand as defineCommand142 } from "citty";
|
|
26518
27541
|
registerSchema({
|
|
26519
27542
|
command: "research.languages",
|
|
26520
27543
|
description: "List all supported language codes for --language flag in research commands.",
|
|
@@ -26544,7 +27567,7 @@ var FIELDS9 = {
|
|
|
26544
27567
|
code: "Language code to pass as --language",
|
|
26545
27568
|
name: "Language name (also accepted by --language)"
|
|
26546
27569
|
};
|
|
26547
|
-
var languagesCommand2 =
|
|
27570
|
+
var languagesCommand2 = defineCommand142({
|
|
26548
27571
|
meta: {
|
|
26549
27572
|
name: "languages",
|
|
26550
27573
|
description: "List all supported language codes for --language flag."
|
|
@@ -26555,7 +27578,7 @@ var languagesCommand2 = defineCommand140({
|
|
|
26555
27578
|
});
|
|
26556
27579
|
|
|
26557
27580
|
// src/commands/research/lighthouse.ts
|
|
26558
|
-
import { defineCommand as
|
|
27581
|
+
import { defineCommand as defineCommand143 } from "citty";
|
|
26559
27582
|
registerSchema({
|
|
26560
27583
|
command: "research.lighthouse",
|
|
26561
27584
|
description: "Landing page performance audit. Returns metrics that affect Google Ads Quality Score and CPC.",
|
|
@@ -26574,7 +27597,7 @@ var FIELDS10 = {
|
|
|
26574
27597
|
speed_index_ms: "Speed Index in ms (good: < 3400)",
|
|
26575
27598
|
interactive_ms: "Time to Interactive in ms (good: < 3800)"
|
|
26576
27599
|
};
|
|
26577
|
-
var lighthouseCommand =
|
|
27600
|
+
var lighthouseCommand = defineCommand143({
|
|
26578
27601
|
meta: {
|
|
26579
27602
|
name: "lighthouse",
|
|
26580
27603
|
description: `Landing page performance audit. Metrics affecting Google Ads Quality Score.
|
|
@@ -26612,7 +27635,7 @@ Examples:
|
|
|
26612
27635
|
});
|
|
26613
27636
|
|
|
26614
27637
|
// src/commands/research/relevant-pages.ts
|
|
26615
|
-
import { defineCommand as
|
|
27638
|
+
import { defineCommand as defineCommand144 } from "citty";
|
|
26616
27639
|
registerSchema({
|
|
26617
27640
|
command: "research.relevant-pages",
|
|
26618
27641
|
description: "Get the top pages of a competitor domain with organic traffic and ranking data. Shows which pages drive the most traffic. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en).",
|
|
@@ -26638,7 +27661,7 @@ var FIELDS11 = {
|
|
|
26638
27661
|
keywords: "Total organic keywords the page ranks for",
|
|
26639
27662
|
top_10: "Keywords in positions 1-10"
|
|
26640
27663
|
};
|
|
26641
|
-
var relevantPagesCommand =
|
|
27664
|
+
var relevantPagesCommand = defineCommand144({
|
|
26642
27665
|
meta: {
|
|
26643
27666
|
name: "relevant-pages",
|
|
26644
27667
|
description: `Get the top pages of a competitor domain with traffic data.
|
|
@@ -26684,7 +27707,7 @@ Examples:
|
|
|
26684
27707
|
});
|
|
26685
27708
|
|
|
26686
27709
|
// src/commands/research/web.ts
|
|
26687
|
-
import { defineCommand as
|
|
27710
|
+
import { defineCommand as defineCommand145 } from "citty";
|
|
26688
27711
|
registerSchema({
|
|
26689
27712
|
command: "research.web",
|
|
26690
27713
|
description: "Search the web with AI to answer marketing questions \u2014 competitors, ICP, pricing, pain points, market trends. Three depth levels: medium (quick, default), high (thorough), xhigh (exhaustive deep research).",
|
|
@@ -26735,7 +27758,7 @@ async function runDeepResearch(question) {
|
|
|
26735
27758
|
}
|
|
26736
27759
|
throw new Error("Deep research timed out");
|
|
26737
27760
|
}
|
|
26738
|
-
var webCommand =
|
|
27761
|
+
var webCommand = defineCommand145({
|
|
26739
27762
|
meta: {
|
|
26740
27763
|
name: "web",
|
|
26741
27764
|
description: `Search the web with AI to answer any open-ended marketing question. Uses live internet data via Google Search.
|
|
@@ -26795,7 +27818,7 @@ Examples:
|
|
|
26795
27818
|
});
|
|
26796
27819
|
|
|
26797
27820
|
// src/commands/research/index.ts
|
|
26798
|
-
var researchCommand =
|
|
27821
|
+
var researchCommand = defineCommand146({
|
|
26799
27822
|
meta: {
|
|
26800
27823
|
name: "research",
|
|
26801
27824
|
description: `Competitive intelligence and AI-powered research commands.
|
|
@@ -26835,10 +27858,10 @@ Examples:
|
|
|
26835
27858
|
});
|
|
26836
27859
|
|
|
26837
27860
|
// src/commands/scheduled-actions/index.ts
|
|
26838
|
-
import { defineCommand as
|
|
27861
|
+
import { defineCommand as defineCommand153 } from "citty";
|
|
26839
27862
|
|
|
26840
27863
|
// src/commands/scheduled-actions/create.ts
|
|
26841
|
-
import { defineCommand as
|
|
27864
|
+
import { defineCommand as defineCommand147 } from "citty";
|
|
26842
27865
|
|
|
26843
27866
|
// src/commands/scheduled-actions/shared.ts
|
|
26844
27867
|
var TEMP_SCHEDULED_ACTION_PREFIX = "temp_sched_";
|
|
@@ -26953,7 +27976,7 @@ registerSchema({
|
|
|
26953
27976
|
prompt: { type: "string", description: "Additional prompt instructions for the spawned agent", required: false }
|
|
26954
27977
|
}
|
|
26955
27978
|
});
|
|
26956
|
-
var createCommand2 =
|
|
27979
|
+
var createCommand2 = defineCommand147({
|
|
26957
27980
|
meta: {
|
|
26958
27981
|
name: "create",
|
|
26959
27982
|
description: 'Stage a scheduled action. Example: baker scheduled-actions create --name "Weekly report" --description "..." --cron "0 9 * * MON"'
|
|
@@ -27002,7 +28025,7 @@ var createCommand2 = defineCommand145({
|
|
|
27002
28025
|
});
|
|
27003
28026
|
|
|
27004
28027
|
// src/commands/scheduled-actions/delete.ts
|
|
27005
|
-
import { defineCommand as
|
|
28028
|
+
import { defineCommand as defineCommand148 } from "citty";
|
|
27006
28029
|
registerSchema({
|
|
27007
28030
|
command: "scheduled-actions.delete",
|
|
27008
28031
|
description: "Stage deletion of a published scheduled action or cancellation of a temp_sched_* draft creation.",
|
|
@@ -27010,7 +28033,7 @@ registerSchema({
|
|
|
27010
28033
|
id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
|
|
27011
28034
|
}
|
|
27012
28035
|
});
|
|
27013
|
-
var deleteCommand2 =
|
|
28036
|
+
var deleteCommand2 = defineCommand148({
|
|
27014
28037
|
meta: {
|
|
27015
28038
|
name: "delete",
|
|
27016
28039
|
description: "Stage scheduled action deletion. Example: baker scheduled-actions delete <id-or-temp_sched_id>"
|
|
@@ -27039,7 +28062,7 @@ var deleteCommand2 = defineCommand146({
|
|
|
27039
28062
|
});
|
|
27040
28063
|
|
|
27041
28064
|
// src/commands/scheduled-actions/get.ts
|
|
27042
|
-
import { defineCommand as
|
|
28065
|
+
import { defineCommand as defineCommand149 } from "citty";
|
|
27043
28066
|
registerSchema({
|
|
27044
28067
|
command: "scheduled-actions.get",
|
|
27045
28068
|
description: "Get a published scheduled action or a temp_sched_* draft-created scheduled action.",
|
|
@@ -27047,7 +28070,7 @@ registerSchema({
|
|
|
27047
28070
|
id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
|
|
27048
28071
|
}
|
|
27049
28072
|
});
|
|
27050
|
-
var getCommand3 =
|
|
28073
|
+
var getCommand3 = defineCommand149({
|
|
27051
28074
|
meta: {
|
|
27052
28075
|
name: "get",
|
|
27053
28076
|
description: "Get a scheduled action. Example: baker scheduled-actions get <id-or-temp_sched_id>"
|
|
@@ -27084,13 +28107,13 @@ var getCommand3 = defineCommand147({
|
|
|
27084
28107
|
});
|
|
27085
28108
|
|
|
27086
28109
|
// src/commands/scheduled-actions/list.ts
|
|
27087
|
-
import { defineCommand as
|
|
28110
|
+
import { defineCommand as defineCommand150 } from "citty";
|
|
27088
28111
|
registerSchema({
|
|
27089
28112
|
command: "scheduled-actions.list",
|
|
27090
28113
|
description: "List published scheduled actions. Includes draft state when BAKER_CHAT_ID is set.",
|
|
27091
28114
|
args: {}
|
|
27092
28115
|
});
|
|
27093
|
-
var listCommand9 =
|
|
28116
|
+
var listCommand9 = defineCommand150({
|
|
27094
28117
|
meta: {
|
|
27095
28118
|
name: "list",
|
|
27096
28119
|
description: "List scheduled actions. Includes staged draft ops when BAKER_CHAT_ID is set."
|
|
@@ -27111,7 +28134,7 @@ var listCommand9 = defineCommand148({
|
|
|
27111
28134
|
});
|
|
27112
28135
|
|
|
27113
28136
|
// src/commands/scheduled-actions/trigger.ts
|
|
27114
|
-
import { defineCommand as
|
|
28137
|
+
import { defineCommand as defineCommand151 } from "citty";
|
|
27115
28138
|
registerSchema({
|
|
27116
28139
|
command: "scheduled-actions.trigger",
|
|
27117
28140
|
description: "Immediately trigger a published scheduled action. Does not require BAKER_CHAT_ID and rejects temp_sched_* IDs.",
|
|
@@ -27119,7 +28142,7 @@ registerSchema({
|
|
|
27119
28142
|
id: { type: "string", description: "Published scheduled action ID", required: true }
|
|
27120
28143
|
}
|
|
27121
28144
|
});
|
|
27122
|
-
var triggerCommand =
|
|
28145
|
+
var triggerCommand = defineCommand151({
|
|
27123
28146
|
meta: {
|
|
27124
28147
|
name: "trigger",
|
|
27125
28148
|
description: "Immediately trigger a published scheduled action. Example: baker scheduled-actions trigger <id>"
|
|
@@ -27156,7 +28179,7 @@ var triggerCommand = defineCommand149({
|
|
|
27156
28179
|
});
|
|
27157
28180
|
|
|
27158
28181
|
// src/commands/scheduled-actions/update.ts
|
|
27159
|
-
import { defineCommand as
|
|
28182
|
+
import { defineCommand as defineCommand152 } from "citty";
|
|
27160
28183
|
registerSchema({
|
|
27161
28184
|
command: "scheduled-actions.update",
|
|
27162
28185
|
description: "Stage an update to a published scheduled action or temp_sched_* draft-created scheduled action.",
|
|
@@ -27181,7 +28204,7 @@ registerSchema({
|
|
|
27181
28204
|
prompt: { type: "string", description: "Replacement additional spawned-agent instructions", required: false }
|
|
27182
28205
|
}
|
|
27183
28206
|
});
|
|
27184
|
-
var updateCommand2 =
|
|
28207
|
+
var updateCommand2 = defineCommand152({
|
|
27185
28208
|
meta: {
|
|
27186
28209
|
name: "update",
|
|
27187
28210
|
description: "Stage a scheduled action update. Example: baker scheduled-actions update <id> --enabled false"
|
|
@@ -27252,7 +28275,7 @@ var updateCommand2 = defineCommand150({
|
|
|
27252
28275
|
});
|
|
27253
28276
|
|
|
27254
28277
|
// src/commands/scheduled-actions/index.ts
|
|
27255
|
-
var scheduledActionsCommand =
|
|
28278
|
+
var scheduledActionsCommand = defineCommand153({
|
|
27256
28279
|
meta: {
|
|
27257
28280
|
name: "scheduled-actions",
|
|
27258
28281
|
description: `Manage Scheduled Actions. Subcommands: list, get, create, update, delete, trigger.
|
|
@@ -27278,8 +28301,8 @@ Examples:
|
|
|
27278
28301
|
});
|
|
27279
28302
|
|
|
27280
28303
|
// src/commands/schema.ts
|
|
27281
|
-
import { defineCommand as
|
|
27282
|
-
var schemaCommand =
|
|
28304
|
+
import { defineCommand as defineCommand154 } from "citty";
|
|
28305
|
+
var schemaCommand = defineCommand154({
|
|
27283
28306
|
meta: {
|
|
27284
28307
|
name: "schema",
|
|
27285
28308
|
description: "Inspect command argument schemas (for AI agent introspection). Lists all commands if no argument given. Example: baker schema images.search"
|
|
@@ -27315,7 +28338,7 @@ var schemaCommand = defineCommand152({
|
|
|
27315
28338
|
});
|
|
27316
28339
|
|
|
27317
28340
|
// src/commands/tags/index.ts
|
|
27318
|
-
import { defineCommand as
|
|
28341
|
+
import { defineCommand as defineCommand155 } from "citty";
|
|
27319
28342
|
|
|
27320
28343
|
// src/commands/tags/shared.ts
|
|
27321
28344
|
function failApi3(err) {
|
|
@@ -27381,7 +28404,7 @@ async function listTags(json) {
|
|
|
27381
28404
|
failApi3(err);
|
|
27382
28405
|
}
|
|
27383
28406
|
}
|
|
27384
|
-
var listCommand10 =
|
|
28407
|
+
var listCommand10 = defineCommand155({
|
|
27385
28408
|
meta: {
|
|
27386
28409
|
name: "list",
|
|
27387
28410
|
description: "Effective tags for this chat (production + staged), with each tag's full readable config (secrets excluded) \u2014 reuse a stored value to pre-fill a change rather than asking the user. Refs printed here are what flow side-effect tagIds should use. Example: baker tags list"
|
|
@@ -27400,7 +28423,7 @@ async function listDraft3() {
|
|
|
27400
28423
|
failApi3(err);
|
|
27401
28424
|
}
|
|
27402
28425
|
}
|
|
27403
|
-
var draftCommand3 =
|
|
28426
|
+
var draftCommand3 = defineCommand155({
|
|
27404
28427
|
meta: {
|
|
27405
28428
|
name: "draft",
|
|
27406
28429
|
description: "Review the tag changes staged in this chat (read-only). Staged changes were approved via request_tag_input and apply when the chat is published; to amend or drop one, propose a follow-up change through the same tool (a delete on a tag_temp_* ref drops the staged create)."
|
|
@@ -27409,7 +28432,7 @@ var draftCommand3 = defineCommand153({
|
|
|
27409
28432
|
await listDraft3();
|
|
27410
28433
|
}
|
|
27411
28434
|
});
|
|
27412
|
-
var tagsCommand3 =
|
|
28435
|
+
var tagsCommand3 = defineCommand155({
|
|
27413
28436
|
meta: {
|
|
27414
28437
|
name: "tags",
|
|
27415
28438
|
description: `Read the client's marketing/analytics tags (Meta pixel, GA4, Google Ads, GTM, Clarity, Hotjar, \u2026) \u2014 production tags plus the changes staged in this chat.
|
|
@@ -27434,10 +28457,10 @@ Examples:
|
|
|
27434
28457
|
});
|
|
27435
28458
|
|
|
27436
28459
|
// src/commands/testimonials/index.ts
|
|
27437
|
-
import { defineCommand as
|
|
28460
|
+
import { defineCommand as defineCommand159 } from "citty";
|
|
27438
28461
|
|
|
27439
28462
|
// src/commands/testimonials/get.ts
|
|
27440
|
-
import { defineCommand as
|
|
28463
|
+
import { defineCommand as defineCommand156 } from "citty";
|
|
27441
28464
|
registerSchema({
|
|
27442
28465
|
command: "testimonials.get",
|
|
27443
28466
|
description: "Get a single testimonial by ID",
|
|
@@ -27445,7 +28468,7 @@ registerSchema({
|
|
|
27445
28468
|
id: { type: "string", description: "Testimonial ID", required: true }
|
|
27446
28469
|
}
|
|
27447
28470
|
});
|
|
27448
|
-
var getCommand4 =
|
|
28471
|
+
var getCommand4 = defineCommand156({
|
|
27449
28472
|
meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
|
|
27450
28473
|
args: {
|
|
27451
28474
|
id: { type: "positional", description: "Testimonial ID", required: false },
|
|
@@ -27482,7 +28505,7 @@ var getCommand4 = defineCommand154({
|
|
|
27482
28505
|
});
|
|
27483
28506
|
|
|
27484
28507
|
// src/commands/testimonials/list.ts
|
|
27485
|
-
import { defineCommand as
|
|
28508
|
+
import { defineCommand as defineCommand157 } from "citty";
|
|
27486
28509
|
registerSchema({
|
|
27487
28510
|
command: "testimonials.list",
|
|
27488
28511
|
description: "List testimonials with optional filters.",
|
|
@@ -27512,7 +28535,7 @@ registerSchema({
|
|
|
27512
28535
|
limit: { type: "number", description: "Max results (default 50)", required: false, default: 50 }
|
|
27513
28536
|
}
|
|
27514
28537
|
});
|
|
27515
|
-
var listCommand11 =
|
|
28538
|
+
var listCommand11 = defineCommand157({
|
|
27516
28539
|
meta: {
|
|
27517
28540
|
name: "list",
|
|
27518
28541
|
description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
|
|
@@ -27561,7 +28584,7 @@ var listCommand11 = defineCommand155({
|
|
|
27561
28584
|
});
|
|
27562
28585
|
|
|
27563
28586
|
// src/commands/testimonials/search.ts
|
|
27564
|
-
import { defineCommand as
|
|
28587
|
+
import { defineCommand as defineCommand158 } from "citty";
|
|
27565
28588
|
function languageBiasHint(results, requestedLanguage) {
|
|
27566
28589
|
if (requestedLanguage) {
|
|
27567
28590
|
return null;
|
|
@@ -27639,7 +28662,7 @@ function buildSearchRequest(query, args) {
|
|
|
27639
28662
|
}
|
|
27640
28663
|
return body;
|
|
27641
28664
|
}
|
|
27642
|
-
var searchCommand2 =
|
|
28665
|
+
var searchCommand2 = defineCommand158({
|
|
27643
28666
|
meta: {
|
|
27644
28667
|
name: "search",
|
|
27645
28668
|
description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
|
|
@@ -27695,7 +28718,7 @@ var searchCommand2 = defineCommand156({
|
|
|
27695
28718
|
var tagsCommand4 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
|
|
27696
28719
|
|
|
27697
28720
|
// src/commands/testimonials/index.ts
|
|
27698
|
-
var testimonialsCommand =
|
|
28721
|
+
var testimonialsCommand = defineCommand159({
|
|
27699
28722
|
meta: {
|
|
27700
28723
|
name: "testimonials",
|
|
27701
28724
|
description: `Find and browse testimonials in Baker. Subcommands: search, get, list, tags.
|
|
@@ -27716,10 +28739,10 @@ Examples:
|
|
|
27716
28739
|
});
|
|
27717
28740
|
|
|
27718
28741
|
// src/commands/videos/index.ts
|
|
27719
|
-
import { defineCommand as
|
|
28742
|
+
import { defineCommand as defineCommand164 } from "citty";
|
|
27720
28743
|
|
|
27721
28744
|
// src/commands/videos/delete.ts
|
|
27722
|
-
import { defineCommand as
|
|
28745
|
+
import { defineCommand as defineCommand160 } from "citty";
|
|
27723
28746
|
registerSchema({
|
|
27724
28747
|
command: "videos.delete",
|
|
27725
28748
|
description: "Delete a video by ID",
|
|
@@ -27733,7 +28756,7 @@ registerSchema({
|
|
|
27733
28756
|
}
|
|
27734
28757
|
}
|
|
27735
28758
|
});
|
|
27736
|
-
var deleteCommand3 =
|
|
28759
|
+
var deleteCommand3 = defineCommand160({
|
|
27737
28760
|
meta: {
|
|
27738
28761
|
name: "delete",
|
|
27739
28762
|
description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
|
|
@@ -27774,7 +28797,7 @@ var deleteCommand3 = defineCommand158({
|
|
|
27774
28797
|
});
|
|
27775
28798
|
|
|
27776
28799
|
// src/commands/videos/get.ts
|
|
27777
|
-
import { defineCommand as
|
|
28800
|
+
import { defineCommand as defineCommand161 } from "citty";
|
|
27778
28801
|
registerSchema({
|
|
27779
28802
|
command: "videos.get",
|
|
27780
28803
|
description: "Get a single video by ID",
|
|
@@ -27782,7 +28805,7 @@ registerSchema({
|
|
|
27782
28805
|
id: { type: "string", description: "Video ID", required: true }
|
|
27783
28806
|
}
|
|
27784
28807
|
});
|
|
27785
|
-
var getCommand5 =
|
|
28808
|
+
var getCommand5 = defineCommand161({
|
|
27786
28809
|
meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
|
|
27787
28810
|
args: {
|
|
27788
28811
|
id: { type: "positional", description: "Video ID", required: false },
|
|
@@ -27819,7 +28842,7 @@ var getCommand5 = defineCommand159({
|
|
|
27819
28842
|
});
|
|
27820
28843
|
|
|
27821
28844
|
// src/commands/videos/search.ts
|
|
27822
|
-
import { defineCommand as
|
|
28845
|
+
import { defineCommand as defineCommand162 } from "citty";
|
|
27823
28846
|
registerSchema({
|
|
27824
28847
|
command: "videos.search",
|
|
27825
28848
|
description: "Search videos by text query. Only returns ready videos.",
|
|
@@ -27829,7 +28852,7 @@ registerSchema({
|
|
|
27829
28852
|
tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
|
|
27830
28853
|
}
|
|
27831
28854
|
});
|
|
27832
|
-
var searchCommand3 =
|
|
28855
|
+
var searchCommand3 = defineCommand162({
|
|
27833
28856
|
meta: {
|
|
27834
28857
|
name: "search",
|
|
27835
28858
|
description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
|
|
@@ -27879,9 +28902,9 @@ var searchCommand3 = defineCommand160({
|
|
|
27879
28902
|
var tagsCommand5 = makeTagsCommand("videos", "video", "/api/videos/tags");
|
|
27880
28903
|
|
|
27881
28904
|
// src/commands/videos/upload.ts
|
|
27882
|
-
import { readFile as
|
|
28905
|
+
import { readFile as readFile22, stat as stat7 } from "fs/promises";
|
|
27883
28906
|
import { extname as extname3 } from "path";
|
|
27884
|
-
import { defineCommand as
|
|
28907
|
+
import { defineCommand as defineCommand163 } from "citty";
|
|
27885
28908
|
var MIME_MAP = {
|
|
27886
28909
|
".mp4": "video/mp4",
|
|
27887
28910
|
".mov": "video/quicktime",
|
|
@@ -27915,7 +28938,7 @@ function detectContentType(filePath) {
|
|
|
27915
28938
|
}
|
|
27916
28939
|
return mime;
|
|
27917
28940
|
}
|
|
27918
|
-
var uploadCommand2 =
|
|
28941
|
+
var uploadCommand2 = defineCommand163({
|
|
27919
28942
|
meta: {
|
|
27920
28943
|
name: "upload",
|
|
27921
28944
|
description: "Upload a video file to Baker via Mux direct upload. Auto-detects content type. Example: baker videos upload ./demo.mp4"
|
|
@@ -27934,7 +28957,7 @@ var uploadCommand2 = defineCommand161({
|
|
|
27934
28957
|
}
|
|
27935
28958
|
const contentType = args["content-type"] || detectContentType(filePath);
|
|
27936
28959
|
if (args["dry-run"]) {
|
|
27937
|
-
const fileStats = await
|
|
28960
|
+
const fileStats = await stat7(filePath);
|
|
27938
28961
|
writeJson({
|
|
27939
28962
|
ok: true,
|
|
27940
28963
|
dryRun: true,
|
|
@@ -27944,7 +28967,7 @@ var uploadCommand2 = defineCommand161({
|
|
|
27944
28967
|
return;
|
|
27945
28968
|
}
|
|
27946
28969
|
const { uploadUrl, videoId } = await apiPost("/api/videos/upload", {});
|
|
27947
|
-
const fileBuffer = await
|
|
28970
|
+
const fileBuffer = await readFile22(filePath);
|
|
27948
28971
|
const uploadResponse = await fetch(uploadUrl, {
|
|
27949
28972
|
method: "PUT",
|
|
27950
28973
|
headers: { "Content-Type": contentType },
|
|
@@ -27969,7 +28992,7 @@ var uploadCommand2 = defineCommand161({
|
|
|
27969
28992
|
});
|
|
27970
28993
|
|
|
27971
28994
|
// src/commands/videos/index.ts
|
|
27972
|
-
var videosCommand =
|
|
28995
|
+
var videosCommand = defineCommand164({
|
|
27973
28996
|
meta: {
|
|
27974
28997
|
name: "videos",
|
|
27975
28998
|
description: `Find and manage videos in Baker. Subcommands: search, get, upload, delete, tags.
|
|
@@ -27992,10 +29015,10 @@ Examples:
|
|
|
27992
29015
|
});
|
|
27993
29016
|
|
|
27994
29017
|
// src/commands/winning-ads/index.ts
|
|
27995
|
-
import { defineCommand as
|
|
29018
|
+
import { defineCommand as defineCommand175 } from "citty";
|
|
27996
29019
|
|
|
27997
29020
|
// src/commands/winning-ads/advertisers.ts
|
|
27998
|
-
import { defineCommand as
|
|
29021
|
+
import { defineCommand as defineCommand165 } from "citty";
|
|
27999
29022
|
|
|
28000
29023
|
// src/commands/winning-ads/shared.ts
|
|
28001
29024
|
function splitList(value) {
|
|
@@ -28048,7 +29071,7 @@ function advertiserNormalizer(record, full) {
|
|
|
28048
29071
|
last_synced_at: record.last_synced_at ?? null
|
|
28049
29072
|
};
|
|
28050
29073
|
}
|
|
28051
|
-
var advertisersCommand2 =
|
|
29074
|
+
var advertisersCommand2 = defineCommand165({
|
|
28052
29075
|
meta: {
|
|
28053
29076
|
name: "advertisers",
|
|
28054
29077
|
description: 'List corpus advertisers by name or domain. Find your own advertiser for --exclude-advertiser, or a competitor for --advertiser-id / winners. Example: baker winning-ads advertisers "Deel" --output md'
|
|
@@ -28106,7 +29129,7 @@ var advertisersCommand2 = defineCommand163({
|
|
|
28106
29129
|
});
|
|
28107
29130
|
|
|
28108
29131
|
// src/commands/winning-ads/brief.ts
|
|
28109
|
-
import { defineCommand as
|
|
29132
|
+
import { defineCommand as defineCommand166 } from "citty";
|
|
28110
29133
|
registerSchema({
|
|
28111
29134
|
command: "winning-ads.brief",
|
|
28112
29135
|
description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
|
|
@@ -28152,7 +29175,7 @@ function parseDna(raw) {
|
|
|
28152
29175
|
}
|
|
28153
29176
|
return parsed;
|
|
28154
29177
|
}
|
|
28155
|
-
var briefCommand =
|
|
29178
|
+
var briefCommand = defineCommand166({
|
|
28156
29179
|
meta: {
|
|
28157
29180
|
name: "brief",
|
|
28158
29181
|
description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
|
|
@@ -28188,7 +29211,7 @@ var briefCommand = defineCommand164({
|
|
|
28188
29211
|
});
|
|
28189
29212
|
|
|
28190
29213
|
// src/commands/winning-ads/feed.ts
|
|
28191
|
-
import { defineCommand as
|
|
29214
|
+
import { defineCommand as defineCommand167 } from "citty";
|
|
28192
29215
|
function buildFeedParams(input) {
|
|
28193
29216
|
const params = {};
|
|
28194
29217
|
const advertiser = splitList(input.advertiser);
|
|
@@ -28240,7 +29263,7 @@ registerSchema({
|
|
|
28240
29263
|
format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
|
|
28241
29264
|
}
|
|
28242
29265
|
});
|
|
28243
|
-
var feedCommand =
|
|
29266
|
+
var feedCommand = defineCommand167({
|
|
28244
29267
|
meta: {
|
|
28245
29268
|
name: "feed",
|
|
28246
29269
|
description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
|
|
@@ -28325,7 +29348,7 @@ var feedCommand = defineCommand165({
|
|
|
28325
29348
|
});
|
|
28326
29349
|
|
|
28327
29350
|
// src/commands/winning-ads/follow.ts
|
|
28328
|
-
import { defineCommand as
|
|
29351
|
+
import { defineCommand as defineCommand168 } from "citty";
|
|
28329
29352
|
var PLATFORMS = ["meta", "linkedin"];
|
|
28330
29353
|
registerSchema({
|
|
28331
29354
|
command: "winning-ads.follow",
|
|
@@ -28340,7 +29363,7 @@ registerSchema({
|
|
|
28340
29363
|
label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
|
|
28341
29364
|
}
|
|
28342
29365
|
});
|
|
28343
|
-
var followCommand =
|
|
29366
|
+
var followCommand = defineCommand168({
|
|
28344
29367
|
meta: {
|
|
28345
29368
|
name: "follow",
|
|
28346
29369
|
description: 'Follow a brand to track ALL its ads \u2014 every platform and country. --platform is how we read your input, not a limit. A domain tracks both Meta + LinkedIn. Example: baker winning-ads follow "deel.com" --platform meta'
|
|
@@ -28387,7 +29410,7 @@ var followCommand = defineCommand166({
|
|
|
28387
29410
|
});
|
|
28388
29411
|
|
|
28389
29412
|
// src/commands/winning-ads/following.ts
|
|
28390
|
-
import { defineCommand as
|
|
29413
|
+
import { defineCommand as defineCommand169 } from "citty";
|
|
28391
29414
|
registerSchema({
|
|
28392
29415
|
command: "winning-ads.following",
|
|
28393
29416
|
description: "List the brands you follow in your ad-dna library, with each one's status (ready vs still adding) and cached ad counts.",
|
|
@@ -28420,7 +29443,7 @@ function followingNormalizer(record, full) {
|
|
|
28420
29443
|
platforms: Array.isArray(record.platforms) ? record.platforms : []
|
|
28421
29444
|
};
|
|
28422
29445
|
}
|
|
28423
|
-
var followingCommand =
|
|
29446
|
+
var followingCommand = defineCommand169({
|
|
28424
29447
|
meta: {
|
|
28425
29448
|
name: "following",
|
|
28426
29449
|
description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
|
|
@@ -28455,7 +29478,7 @@ var followingCommand = defineCommand167({
|
|
|
28455
29478
|
});
|
|
28456
29479
|
|
|
28457
29480
|
// src/commands/winning-ads/patterns.ts
|
|
28458
|
-
import { defineCommand as
|
|
29481
|
+
import { defineCommand as defineCommand170 } from "citty";
|
|
28459
29482
|
registerSchema({
|
|
28460
29483
|
command: "winning-ads.patterns",
|
|
28461
29484
|
description: "Mine what separates two cohorts of ads: pass a comma-list of winning ad ids (--winners) and a comma-list of weaker ad ids (--duds). Returns the discriminating DNA fields.",
|
|
@@ -28494,7 +29517,7 @@ function discriminatorRow(record) {
|
|
|
28494
29517
|
top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
|
|
28495
29518
|
};
|
|
28496
29519
|
}
|
|
28497
|
-
var patternsCommand =
|
|
29520
|
+
var patternsCommand = defineCommand170({
|
|
28498
29521
|
meta: {
|
|
28499
29522
|
name: "patterns",
|
|
28500
29523
|
description: "Discover what separates winning ads from weak ones. Example: baker winning-ads patterns --winners a_1,a_2,a_3 --duds a_9,a_8 --output md"
|
|
@@ -28550,7 +29573,7 @@ var patternsCommand = defineCommand168({
|
|
|
28550
29573
|
});
|
|
28551
29574
|
|
|
28552
29575
|
// src/commands/winning-ads/search.ts
|
|
28553
|
-
import { defineCommand as
|
|
29576
|
+
import { defineCommand as defineCommand171 } from "citty";
|
|
28554
29577
|
registerSchema({
|
|
28555
29578
|
command: "winning-ads.search",
|
|
28556
29579
|
description: "Search the ad-dna corpus of scored winning ads. Returns a lean shortlist (advertiser, summary, scores, media_url) to pick a reference to reproduce.",
|
|
@@ -28658,7 +29681,7 @@ function buildSearchBody(args) {
|
|
|
28658
29681
|
}
|
|
28659
29682
|
return body;
|
|
28660
29683
|
}
|
|
28661
|
-
var searchCommand4 =
|
|
29684
|
+
var searchCommand4 = defineCommand171({
|
|
28662
29685
|
meta: {
|
|
28663
29686
|
name: "search",
|
|
28664
29687
|
description: "Search winning reference ads. Example: baker winning-ads search 'B2B SaaS before/after AI automation' --platform meta --format static --winner-category winner --exclude-advertiser adv_123 --output md"
|
|
@@ -28773,7 +29796,7 @@ var searchCommand4 = defineCommand169({
|
|
|
28773
29796
|
});
|
|
28774
29797
|
|
|
28775
29798
|
// src/commands/winning-ads/seeds.ts
|
|
28776
|
-
import { defineCommand as
|
|
29799
|
+
import { defineCommand as defineCommand172 } from "citty";
|
|
28777
29800
|
function leanRow(r) {
|
|
28778
29801
|
return {
|
|
28779
29802
|
key: r.key,
|
|
@@ -28801,7 +29824,7 @@ function makeSeedCommand(opts) {
|
|
|
28801
29824
|
limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
|
|
28802
29825
|
}
|
|
28803
29826
|
});
|
|
28804
|
-
return
|
|
29827
|
+
return defineCommand172({
|
|
28805
29828
|
meta: { name: opts.name, description: opts.description },
|
|
28806
29829
|
args: {
|
|
28807
29830
|
platform: { type: "string", description: "Single platform to segment on", required: false },
|
|
@@ -28850,7 +29873,7 @@ var formatsCommand = makeSeedCommand({
|
|
|
28850
29873
|
});
|
|
28851
29874
|
|
|
28852
29875
|
// src/commands/winning-ads/unfollow.ts
|
|
28853
|
-
import { defineCommand as
|
|
29876
|
+
import { defineCommand as defineCommand173 } from "citty";
|
|
28854
29877
|
registerSchema({
|
|
28855
29878
|
command: "winning-ads.unfollow",
|
|
28856
29879
|
description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
|
|
@@ -28858,7 +29881,7 @@ registerSchema({
|
|
|
28858
29881
|
advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
|
|
28859
29882
|
}
|
|
28860
29883
|
});
|
|
28861
|
-
var unfollowCommand =
|
|
29884
|
+
var unfollowCommand = defineCommand173({
|
|
28862
29885
|
meta: {
|
|
28863
29886
|
name: "unfollow",
|
|
28864
29887
|
description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
|
|
@@ -28879,7 +29902,7 @@ var unfollowCommand = defineCommand171({
|
|
|
28879
29902
|
});
|
|
28880
29903
|
|
|
28881
29904
|
// src/commands/winning-ads/winners.ts
|
|
28882
|
-
import { defineCommand as
|
|
29905
|
+
import { defineCommand as defineCommand174 } from "citty";
|
|
28883
29906
|
registerSchema({
|
|
28884
29907
|
command: "winning-ads.winners",
|
|
28885
29908
|
description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
|
|
@@ -28889,7 +29912,7 @@ registerSchema({
|
|
|
28889
29912
|
platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
|
|
28890
29913
|
}
|
|
28891
29914
|
});
|
|
28892
|
-
var winnersCommand =
|
|
29915
|
+
var winnersCommand = defineCommand174({
|
|
28893
29916
|
meta: {
|
|
28894
29917
|
name: "winners",
|
|
28895
29918
|
description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
|
|
@@ -28939,7 +29962,7 @@ var winnersCommand = defineCommand172({
|
|
|
28939
29962
|
});
|
|
28940
29963
|
|
|
28941
29964
|
// src/commands/winning-ads/index.ts
|
|
28942
|
-
var winningAdsCommand =
|
|
29965
|
+
var winningAdsCommand = defineCommand175({
|
|
28943
29966
|
meta: {
|
|
28944
29967
|
name: "winning-ads",
|
|
28945
29968
|
description: `Search the ad-dna corpus of scored "winning" ads for reference creatives to reproduce, and manage the brands your library tracks. Proxied through the Baker backend (BAKER_API_KEY) \u2014 no separate token needed.
|
|
@@ -29004,7 +30027,7 @@ function getCliVersion() {
|
|
|
29004
30027
|
}
|
|
29005
30028
|
|
|
29006
30029
|
// src/cli.ts
|
|
29007
|
-
var main =
|
|
30030
|
+
var main = defineCommand176({
|
|
29008
30031
|
meta: {
|
|
29009
30032
|
name: "baker",
|
|
29010
30033
|
version: getCliVersion(),
|
|
@@ -29026,6 +30049,7 @@ Introspection: Run 'baker schema <command>' to inspect argument schemas.`
|
|
|
29026
30049
|
creatives: creativesCommand3,
|
|
29027
30050
|
flows: flowsCommand,
|
|
29028
30051
|
images: imagesCommand,
|
|
30052
|
+
landing: landingCommand,
|
|
29029
30053
|
videos: videosCommand,
|
|
29030
30054
|
testimonials: testimonialsCommand,
|
|
29031
30055
|
canvas: canvasCommand,
|