@koda-sl/baker-cli 0.186.0-dev.6d88981c0 → 0.186.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -35
- package/dist/{chunk-PJ5KH27L.js → chunk-OWDTSPY4.js} +136 -12
- package/dist/chunk-OWDTSPY4.js.map +1 -0
- package/dist/cli.js +408 -2747
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -2
- package/dist/chunk-PJ5KH27L.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
isPersistedAssetRef,
|
|
30
30
|
looksLikeHttpUrl,
|
|
31
31
|
maxInputReferences,
|
|
32
|
+
maxInputSlot,
|
|
32
33
|
nearestClipDuration,
|
|
33
34
|
nearestSupportedAspectRatio,
|
|
34
35
|
nearestSupportedImageSize,
|
|
@@ -46,7 +47,7 @@ import {
|
|
|
46
47
|
toModelSafeImage,
|
|
47
48
|
ulid,
|
|
48
49
|
validateCanvasDeep
|
|
49
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-OWDTSPY4.js";
|
|
50
51
|
import {
|
|
51
52
|
csvOrJson,
|
|
52
53
|
daysAgoIso,
|
|
@@ -89,7 +90,7 @@ import {
|
|
|
89
90
|
} from "./chunk-YL3HDEIJ.js";
|
|
90
91
|
|
|
91
92
|
// src/cli.ts
|
|
92
|
-
import { defineCommand as
|
|
93
|
+
import { defineCommand as defineCommand199, runMain } from "citty";
|
|
93
94
|
|
|
94
95
|
// src/commands/actions/index.ts
|
|
95
96
|
import { defineCommand as defineCommand18 } from "citty";
|
|
@@ -3487,9 +3488,10 @@ var imagesIngestResponseSchema = z11.object({
|
|
|
3487
3488
|
import { z as z12 } from "zod";
|
|
3488
3489
|
var studioGenerationStatusSchema = z12.enum(["running", "completed", "partial", "failed"]);
|
|
3489
3490
|
var studioVariantStatusSchema = z12.enum(["pending", "ready", "failed"]);
|
|
3490
|
-
var studioClipModeSchema = z12.enum(["first_frame", "references"]);
|
|
3491
|
+
var studioClipModeSchema = z12.enum(["first_frame", "references", "text"]);
|
|
3491
3492
|
var videoGenerateModelSchema = z12.enum([
|
|
3492
3493
|
"bytedance/seedance-2.0",
|
|
3494
|
+
"bytedance/seedance-2.5",
|
|
3493
3495
|
"google/gemini-omni-flash",
|
|
3494
3496
|
"google/veo-3.1",
|
|
3495
3497
|
"google/veo-3.1-fast",
|
|
@@ -3504,7 +3506,9 @@ var MAX_STUDIO_IMAGES = 8;
|
|
|
3504
3506
|
var MAX_STUDIO_CLIPS = 3;
|
|
3505
3507
|
var MAX_STUDIO_PROMPT_CHARS = 32e3;
|
|
3506
3508
|
var MAX_STUDIO_IMAGE_REFERENCES = 16;
|
|
3507
|
-
var MAX_STUDIO_CLIP_IMAGES =
|
|
3509
|
+
var MAX_STUDIO_CLIP_IMAGES = 30;
|
|
3510
|
+
var MAX_STUDIO_CLIP_REFS = 10;
|
|
3511
|
+
var MAX_STUDIO_CLIP_SECONDS = 30;
|
|
3508
3512
|
var aspectRatioSchema = z12.string().regex(/^[1-9]\d*:[1-9]\d*$/, "Write the shape as width:height, like 1:1, 4:5 or 16:9");
|
|
3509
3513
|
var seedSchema = z12.coerce.number().int().min(0).max(4294967295);
|
|
3510
3514
|
var studioGenerateRequestSchema = z12.object({
|
|
@@ -3537,15 +3541,39 @@ var studioGenerateRequestSchema = z12.object({
|
|
|
3537
3541
|
backgroundRgbColor: rgbTriple.optional()
|
|
3538
3542
|
});
|
|
3539
3543
|
var studioAnimateRequestSchema = z12.object({
|
|
3540
|
-
/**
|
|
3544
|
+
/**
|
|
3545
|
+
* What MOVES, when a still fixes the look. With no still it is the whole shot,
|
|
3546
|
+
* because the clip is then generated from the prompt alone.
|
|
3547
|
+
*/
|
|
3541
3548
|
prompt: z12.string().min(1).max(MAX_STUDIO_PROMPT_CHARS),
|
|
3542
|
-
|
|
3549
|
+
/**
|
|
3550
|
+
* The still(s) the clip is built from. MAY BE EMPTY — every curated video model
|
|
3551
|
+
* also does text-to-video, so a prompt on its own is a complete request. The
|
|
3552
|
+
* CLI still requires one unless the caller asks for text-to-video explicitly,
|
|
3553
|
+
* because "I forgot the image" and "I meant no image" look identical here.
|
|
3554
|
+
*/
|
|
3555
|
+
imageIds: z12.array(z12.string()).max(MAX_STUDIO_CLIP_IMAGES),
|
|
3556
|
+
/**
|
|
3557
|
+
* Clips this render builds on — extend, edit, or carry the motion of. Addressed
|
|
3558
|
+
* by the batch and slot they were rendered in, because a studio take is a plain
|
|
3559
|
+
* public file the provider can fetch while a library video is a Mux asset that
|
|
3560
|
+
* is not. Seedance 2.5 only; the backend refuses them on any other model.
|
|
3561
|
+
*/
|
|
3562
|
+
clipRefs: z12.array(z12.object({ generationId: z12.string(), slot: z12.coerce.number().int().min(0) })).max(MAX_STUDIO_CLIP_REFS).optional(),
|
|
3563
|
+
/**
|
|
3564
|
+
* The same thing from the VIDEO LIBRARY — footage the company uploaded or
|
|
3565
|
+
* synced rather than rendered here. Ids, not URLs: a library video is a Mux
|
|
3566
|
+
* asset with no downloadable file until one is encoded for it, which the
|
|
3567
|
+
* backend does on first use.
|
|
3568
|
+
*/
|
|
3569
|
+
videoIds: z12.array(z12.string()).max(MAX_STUDIO_CLIP_REFS).optional(),
|
|
3543
3570
|
clipMode: studioClipModeSchema.optional(),
|
|
3544
3571
|
model: videoGenerateModelSchema.optional(),
|
|
3545
3572
|
aspectRatio: aspectRatioSchema.optional(),
|
|
3546
|
-
//
|
|
3547
|
-
// one actually offers is a per-model enum settled
|
|
3548
|
-
|
|
3573
|
+
// 30s is the longest clip any curated model renders (Seedance 2.5); which
|
|
3574
|
+
// lengths the chosen one actually offers is a per-model enum settled
|
|
3575
|
+
// server-side, and most of the roster stops well short of this.
|
|
3576
|
+
duration: z12.coerce.number().int().min(1).max(MAX_STUDIO_CLIP_SECONDS).optional(),
|
|
3549
3577
|
resolution: z12.string().optional(),
|
|
3550
3578
|
count: z12.coerce.number().int().min(1).max(MAX_STUDIO_CLIPS).optional(),
|
|
3551
3579
|
generateAudio: z12.boolean().optional(),
|
|
@@ -3554,7 +3582,9 @@ var studioAnimateRequestSchema = z12.object({
|
|
|
3554
3582
|
personGeneration: z12.enum(["allow_all", "allow_adult"]).optional(),
|
|
3555
3583
|
enhancePrompt: z12.boolean().optional(),
|
|
3556
3584
|
conditioningScale: z12.coerce.number().optional(),
|
|
3557
|
-
cfgScale: z12.coerce.number().min(0).max(1).optional()
|
|
3585
|
+
cfgScale: z12.coerce.number().min(0).max(1).optional(),
|
|
3586
|
+
/** Seedance 2.5 only. Left unset the lane sends `false` — see `resolveClipRequest`. */
|
|
3587
|
+
watermark: z12.boolean().optional()
|
|
3558
3588
|
});
|
|
3559
3589
|
var studioVariantSchema = z12.object({
|
|
3560
3590
|
slot: z12.number(),
|
|
@@ -3592,7 +3622,8 @@ var studioClipSettingsSchema = z12.object({
|
|
|
3592
3622
|
personGeneration: z12.string().optional(),
|
|
3593
3623
|
enhancePrompt: z12.boolean().optional(),
|
|
3594
3624
|
conditioningScale: z12.number().optional(),
|
|
3595
|
-
cfgScale: z12.number().optional()
|
|
3625
|
+
cfgScale: z12.number().optional(),
|
|
3626
|
+
watermark: z12.boolean().optional()
|
|
3596
3627
|
});
|
|
3597
3628
|
var studioGenerationSchema = z12.object({
|
|
3598
3629
|
generationId: z12.string(),
|
|
@@ -7531,19 +7562,19 @@ Examples:
|
|
|
7531
7562
|
},
|
|
7532
7563
|
run: async ({ args }) => {
|
|
7533
7564
|
const customerId = await resolveCustomerId(args);
|
|
7534
|
-
const
|
|
7565
|
+
const window = resolveChangesWindow({
|
|
7535
7566
|
days: args.days ? Number(args.days) : void 0,
|
|
7536
7567
|
scope: args.scope
|
|
7537
7568
|
});
|
|
7538
|
-
if (!
|
|
7539
|
-
writeJsonEnvelope({ ok: false, error: { ...
|
|
7569
|
+
if (!window.ok) {
|
|
7570
|
+
writeJsonEnvelope({ ok: false, error: { ...window.error, retryable: false } });
|
|
7540
7571
|
process.exit(1);
|
|
7541
7572
|
return;
|
|
7542
7573
|
}
|
|
7543
7574
|
const body = {
|
|
7544
7575
|
customerId,
|
|
7545
|
-
days:
|
|
7546
|
-
scope:
|
|
7576
|
+
days: window.days,
|
|
7577
|
+
scope: window.scope,
|
|
7547
7578
|
limit: args.limit ? Number(args.limit) : 50
|
|
7548
7579
|
};
|
|
7549
7580
|
const managerId = getManagerIdForCustomer(customerId);
|
|
@@ -7560,7 +7591,7 @@ Examples:
|
|
|
7560
7591
|
const first = data[0];
|
|
7561
7592
|
const fields = first ? Object.keys(first) : [];
|
|
7562
7593
|
const fieldDescs = getFieldDescriptions(fields);
|
|
7563
|
-
writeJsonEnvelope({ ok: true, data, fields: fieldDescs, hints:
|
|
7594
|
+
writeJsonEnvelope({ ok: true, data, fields: fieldDescs, hints: window.hints });
|
|
7564
7595
|
} catch (err) {
|
|
7565
7596
|
if (err instanceof ApiError) {
|
|
7566
7597
|
writeAdsJson(parseApiError(err.message, "", customerId, err.code));
|
|
@@ -7917,8 +7948,8 @@ function countStaccato(text) {
|
|
|
7917
7948
|
let first = "";
|
|
7918
7949
|
let runStart = 0;
|
|
7919
7950
|
for (const [i, s] of sentences.entries()) {
|
|
7920
|
-
const
|
|
7921
|
-
if (
|
|
7951
|
+
const words2 = s.split(/\s+/).filter(Boolean).length;
|
|
7952
|
+
if (words2 > 0 && words2 <= STACCATO_MAX_WORDS) {
|
|
7922
7953
|
if (run === 0) runStart = i;
|
|
7923
7954
|
run++;
|
|
7924
7955
|
if (run === STACCATO_RUN) {
|
|
@@ -7936,10 +7967,10 @@ function countTitleCaseHeadings(text) {
|
|
|
7936
7967
|
let first = "";
|
|
7937
7968
|
for (const m of text.matchAll(/^\s{0,3}#{1,6}\s+(.+)$/gm)) {
|
|
7938
7969
|
const heading = (m[1] ?? "").trim();
|
|
7939
|
-
const
|
|
7940
|
-
if (
|
|
7941
|
-
const capitalized =
|
|
7942
|
-
if (capitalized /
|
|
7970
|
+
const words2 = heading.split(/\s+/).filter((w) => new RegExp("\\p{L}", "u").test(w));
|
|
7971
|
+
if (words2.length < 4) continue;
|
|
7972
|
+
const capitalized = words2.filter((w) => new RegExp("^\\p{Lu}", "u").test(w)).length;
|
|
7973
|
+
if (capitalized / words2.length < 0.8) continue;
|
|
7943
7974
|
count++;
|
|
7944
7975
|
if (!first) first = heading;
|
|
7945
7976
|
}
|
|
@@ -8268,11 +8299,11 @@ function rawTextEntries(value) {
|
|
|
8268
8299
|
const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
8269
8300
|
return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
8270
8301
|
}
|
|
8271
|
-
function rawFileEntries(
|
|
8272
|
-
if (typeof
|
|
8302
|
+
function rawFileEntries(path28) {
|
|
8303
|
+
if (typeof path28 !== "string" || path28.length === 0) {
|
|
8273
8304
|
return [];
|
|
8274
8305
|
}
|
|
8275
|
-
return readFileSync2(
|
|
8306
|
+
return readFileSync2(path28, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
8276
8307
|
}
|
|
8277
8308
|
function keywordEntries(args) {
|
|
8278
8309
|
const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
|
|
@@ -8295,19 +8326,19 @@ function keywordEntries(args) {
|
|
|
8295
8326
|
}
|
|
8296
8327
|
return entries;
|
|
8297
8328
|
}
|
|
8298
|
-
function loadJsonFileArg(
|
|
8299
|
-
if (typeof
|
|
8329
|
+
function loadJsonFileArg(path28) {
|
|
8330
|
+
if (typeof path28 !== "string" || path28.length === 0) {
|
|
8300
8331
|
return {};
|
|
8301
8332
|
}
|
|
8302
8333
|
try {
|
|
8303
|
-
const parsed = JSON.parse(readFileSync2(
|
|
8334
|
+
const parsed = JSON.parse(readFileSync2(path28, "utf8"));
|
|
8304
8335
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8305
|
-
failWriteValidation(`${
|
|
8336
|
+
failWriteValidation(`${path28} must contain a JSON object`);
|
|
8306
8337
|
}
|
|
8307
8338
|
return parsed;
|
|
8308
8339
|
} catch (err) {
|
|
8309
8340
|
if (err instanceof SyntaxError) {
|
|
8310
|
-
failWriteValidation(`${
|
|
8341
|
+
failWriteValidation(`${path28} is not valid JSON: ${err.message}`);
|
|
8311
8342
|
}
|
|
8312
8343
|
throw err;
|
|
8313
8344
|
}
|
|
@@ -8437,10 +8468,10 @@ async function stageUpdate(kind, customerId, target, payload, hints) {
|
|
|
8437
8468
|
async function stageTarget(kind, customerId, target, hints) {
|
|
8438
8469
|
await stageGoogleOp({ kind, customerId, target }, hints);
|
|
8439
8470
|
}
|
|
8440
|
-
async function draftAction(
|
|
8471
|
+
async function draftAction(path28, body, chat) {
|
|
8441
8472
|
try {
|
|
8442
8473
|
const chatId = resolveChatId(chat);
|
|
8443
|
-
const response = await apiPost(
|
|
8474
|
+
const response = await apiPost(path28, { chatId, ...body });
|
|
8444
8475
|
writeJsonEnvelope(response);
|
|
8445
8476
|
} catch (err) {
|
|
8446
8477
|
handleGoogleError(err);
|
|
@@ -11108,19 +11139,19 @@ function failWriteValidation2(message) {
|
|
|
11108
11139
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
11109
11140
|
process.exit(1);
|
|
11110
11141
|
}
|
|
11111
|
-
function loadJsonFileArg2(
|
|
11112
|
-
if (typeof
|
|
11142
|
+
function loadJsonFileArg2(path28) {
|
|
11143
|
+
if (typeof path28 !== "string" || path28.length === 0) {
|
|
11113
11144
|
return {};
|
|
11114
11145
|
}
|
|
11115
11146
|
try {
|
|
11116
|
-
const parsed = JSON.parse(readFileSync4(
|
|
11147
|
+
const parsed = JSON.parse(readFileSync4(path28, "utf8"));
|
|
11117
11148
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
11118
|
-
failWriteValidation2(`${
|
|
11149
|
+
failWriteValidation2(`${path28} must contain a JSON object`);
|
|
11119
11150
|
}
|
|
11120
11151
|
return parsed;
|
|
11121
11152
|
} catch (err) {
|
|
11122
11153
|
if (err instanceof SyntaxError) {
|
|
11123
|
-
failWriteValidation2(`${
|
|
11154
|
+
failWriteValidation2(`${path28} is not valid JSON: ${err.message}`);
|
|
11124
11155
|
}
|
|
11125
11156
|
throw err;
|
|
11126
11157
|
}
|
|
@@ -11205,15 +11236,15 @@ function parseLocaleFlag(value) {
|
|
|
11205
11236
|
}
|
|
11206
11237
|
return { language: match[1], country: match[2].toUpperCase() };
|
|
11207
11238
|
}
|
|
11208
|
-
function loadTargetingFileArg(
|
|
11209
|
-
if (typeof
|
|
11239
|
+
function loadTargetingFileArg(path28) {
|
|
11240
|
+
if (typeof path28 !== "string" || path28.length === 0) {
|
|
11210
11241
|
return void 0;
|
|
11211
11242
|
}
|
|
11212
|
-
const parsed = loadJsonFileArg2(
|
|
11243
|
+
const parsed = loadJsonFileArg2(path28);
|
|
11213
11244
|
const criteria = parsed.targetingCriteria ?? parsed;
|
|
11214
11245
|
if (!criteria.include) {
|
|
11215
11246
|
failWriteValidation2(
|
|
11216
|
-
`${
|
|
11247
|
+
`${path28} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
|
|
11217
11248
|
);
|
|
11218
11249
|
}
|
|
11219
11250
|
return criteria;
|
|
@@ -11248,14 +11279,14 @@ function parseCsvLine(line) {
|
|
|
11248
11279
|
cells.push(current);
|
|
11249
11280
|
return cells.map((cell) => cell.trim());
|
|
11250
11281
|
}
|
|
11251
|
-
function parseListFileArg(
|
|
11252
|
-
if (typeof
|
|
11282
|
+
function parseListFileArg(path28, maxRows) {
|
|
11283
|
+
if (typeof path28 !== "string" || path28.length === 0) {
|
|
11253
11284
|
return void 0;
|
|
11254
11285
|
}
|
|
11255
|
-
const raw = readFileSync4(
|
|
11286
|
+
const raw = readFileSync4(path28, "utf8");
|
|
11256
11287
|
const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
11257
11288
|
if (lines.length < 2) {
|
|
11258
|
-
failWriteValidation2(`${
|
|
11289
|
+
failWriteValidation2(`${path28} needs a header row and at least one data row`);
|
|
11259
11290
|
}
|
|
11260
11291
|
const columns = parseCsvLine(lines[0]).map((column) => column.trim());
|
|
11261
11292
|
const rows = [];
|
|
@@ -11274,7 +11305,7 @@ function parseListFileArg(path35, maxRows) {
|
|
|
11274
11305
|
}
|
|
11275
11306
|
}
|
|
11276
11307
|
if (rows.length > maxRows) {
|
|
11277
|
-
failWriteValidation2(`${
|
|
11308
|
+
failWriteValidation2(`${path28} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
|
|
11278
11309
|
}
|
|
11279
11310
|
return { columns, rows };
|
|
11280
11311
|
}
|
|
@@ -11370,11 +11401,11 @@ function readPositionals(args) {
|
|
|
11370
11401
|
function splitIdList(raw) {
|
|
11371
11402
|
return raw.split(",").map((id) => id.trim()).filter(Boolean);
|
|
11372
11403
|
}
|
|
11373
|
-
function idsFileEntries(
|
|
11374
|
-
if (typeof
|
|
11404
|
+
function idsFileEntries(path28) {
|
|
11405
|
+
if (typeof path28 !== "string" || path28.length === 0) {
|
|
11375
11406
|
return [];
|
|
11376
11407
|
}
|
|
11377
|
-
return readFileSync4(
|
|
11408
|
+
return readFileSync4(path28, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
|
|
11378
11409
|
}
|
|
11379
11410
|
function requireTargets(args, entity) {
|
|
11380
11411
|
const positionals = readPositionals(args);
|
|
@@ -13997,9 +14028,9 @@ function compactRow(row) {
|
|
|
13997
14028
|
...destination.postUrn ? { postUrn: destination.postUrn } : {}
|
|
13998
14029
|
};
|
|
13999
14030
|
}
|
|
14000
|
-
function readPath(row,
|
|
14031
|
+
function readPath(row, path28) {
|
|
14001
14032
|
let current = row;
|
|
14002
|
-
for (const segment of
|
|
14033
|
+
for (const segment of path28.split(".")) {
|
|
14003
14034
|
const record = asRecord2(current);
|
|
14004
14035
|
if (!record) return void 0;
|
|
14005
14036
|
current = record[segment];
|
|
@@ -14009,10 +14040,10 @@ function readPath(row, path35) {
|
|
|
14009
14040
|
function projectFields(rows, paths) {
|
|
14010
14041
|
return rows.map((row) => {
|
|
14011
14042
|
const projected = {};
|
|
14012
|
-
for (const
|
|
14013
|
-
const value = readPath(row,
|
|
14043
|
+
for (const path28 of paths) {
|
|
14044
|
+
const value = readPath(row, path28);
|
|
14014
14045
|
if (value !== void 0) {
|
|
14015
|
-
projected[
|
|
14046
|
+
projected[path28] = value;
|
|
14016
14047
|
}
|
|
14017
14048
|
}
|
|
14018
14049
|
return projected;
|
|
@@ -15219,11 +15250,11 @@ var updateStatusSchema = z19.enum(UPDATE_STATUSES);
|
|
|
15219
15250
|
function currencyMinimums2(currencyCode) {
|
|
15220
15251
|
return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
|
|
15221
15252
|
}
|
|
15222
|
-
function validateDailyBudgetFloor(money, ctx,
|
|
15253
|
+
function validateDailyBudgetFloor(money, ctx, path28) {
|
|
15223
15254
|
if (money?.currencyCode) {
|
|
15224
15255
|
const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
|
|
15225
15256
|
if (Number(money.amount) < min) {
|
|
15226
|
-
ctx.addIssue({ code: "custom", path:
|
|
15257
|
+
ctx.addIssue({ code: "custom", path: path28, message: `below the ${min} ${money.currencyCode} daily minimum` });
|
|
15227
15258
|
}
|
|
15228
15259
|
}
|
|
15229
15260
|
}
|
|
@@ -15866,19 +15897,19 @@ function failWriteValidation3(message) {
|
|
|
15866
15897
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
15867
15898
|
process.exit(1);
|
|
15868
15899
|
}
|
|
15869
|
-
function loadJsonFileArg3(
|
|
15870
|
-
if (typeof
|
|
15900
|
+
function loadJsonFileArg3(path28) {
|
|
15901
|
+
if (typeof path28 !== "string" || path28.length === 0) {
|
|
15871
15902
|
return {};
|
|
15872
15903
|
}
|
|
15873
15904
|
try {
|
|
15874
|
-
const parsed = JSON.parse(readFileSync8(
|
|
15905
|
+
const parsed = JSON.parse(readFileSync8(path28, "utf8"));
|
|
15875
15906
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15876
|
-
failWriteValidation3(`${
|
|
15907
|
+
failWriteValidation3(`${path28} must contain a JSON object`);
|
|
15877
15908
|
}
|
|
15878
15909
|
return parsed;
|
|
15879
15910
|
} catch (err) {
|
|
15880
15911
|
if (err instanceof SyntaxError) {
|
|
15881
|
-
failWriteValidation3(`${
|
|
15912
|
+
failWriteValidation3(`${path28} is not valid JSON: ${err.message}`);
|
|
15882
15913
|
}
|
|
15883
15914
|
throw err;
|
|
15884
15915
|
}
|
|
@@ -19940,11 +19971,11 @@ function estSpeechS(text) {
|
|
|
19940
19971
|
var OBSERVED_WPS_MIN = 1;
|
|
19941
19972
|
var OBSERVED_WPS_MAX = 6;
|
|
19942
19973
|
function estSpeechWindowS(text, startS, endS) {
|
|
19943
|
-
const
|
|
19944
|
-
const
|
|
19945
|
-
if (
|
|
19946
|
-
const wps =
|
|
19947
|
-
if (wps >= OBSERVED_WPS_MIN && wps <= OBSERVED_WPS_MAX) return
|
|
19974
|
+
const words2 = wordCount(text);
|
|
19975
|
+
const window = (endS ?? 0) - (startS ?? 0);
|
|
19976
|
+
if (words2 > 0 && window > 0.3) {
|
|
19977
|
+
const wps = words2 / window;
|
|
19978
|
+
if (wps >= OBSERVED_WPS_MIN && wps <= OBSERVED_WPS_MAX) return window;
|
|
19948
19979
|
}
|
|
19949
19980
|
return estSpeechS(text);
|
|
19950
19981
|
}
|
|
@@ -20799,10 +20830,10 @@ function scrubFloatSentences(text, floatDescs) {
|
|
|
20799
20830
|
if (floatDescs.length === 0 || !text) return text;
|
|
20800
20831
|
const tokenSets = floatDescs.map((d) => new Set(floatTokens(d)));
|
|
20801
20832
|
const kept = text.split(/(?<=[.!?])\s+/).filter((sentence) => {
|
|
20802
|
-
const
|
|
20833
|
+
const words2 = new Set(floatTokens(sentence));
|
|
20803
20834
|
return !tokenSets.some((ts) => {
|
|
20804
20835
|
let hits = 0;
|
|
20805
|
-
for (const w of
|
|
20836
|
+
for (const w of words2) if (ts.has(w)) hits++;
|
|
20806
20837
|
return hits >= 2;
|
|
20807
20838
|
});
|
|
20808
20839
|
}).join(" ").trim();
|
|
@@ -22993,15 +23024,15 @@ function collectClipAdvisories(scene, i, out) {
|
|
|
22993
23024
|
const round22 = (n) => Math.round(n * 100) / 100;
|
|
22994
23025
|
const original = scene.duration_s ?? 5;
|
|
22995
23026
|
if (original > 15) out.clamped.push({ scene: i, original_s: original, clip_s: snapToSeedance(original) });
|
|
22996
|
-
const
|
|
22997
|
-
if (
|
|
22998
|
-
out.oversize.push({ scene: i, scene_s: round22(
|
|
23027
|
+
const window = sceneDurationS(scene);
|
|
23028
|
+
if (window > SEEDANCE_SAFE_MAX_S)
|
|
23029
|
+
out.oversize.push({ scene: i, scene_s: round22(window), clip_s: ceilToSeedance(window) });
|
|
22999
23030
|
const speech = (scene.dialogue ?? []).reduce(
|
|
23000
23031
|
(s, line) => s + (line.line ? estSpeechWindowS(line.line, line.start_s, line.end_s) : 0),
|
|
23001
23032
|
0
|
|
23002
23033
|
);
|
|
23003
|
-
if (speech >
|
|
23004
|
-
out.overstuffed.push({ scene: i, scene_s: round22(
|
|
23034
|
+
if (speech > window * OVERSTUFF_RATIO)
|
|
23035
|
+
out.overstuffed.push({ scene: i, scene_s: round22(window), est_speech_s: round22(speech) });
|
|
23005
23036
|
}
|
|
23006
23037
|
function videoReport(input, elementsInput) {
|
|
23007
23038
|
const blueprint = VideoBlueprint.parse(input);
|
|
@@ -26674,12 +26705,12 @@ function listFlowSlugs() {
|
|
|
26674
26705
|
return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_") && entry.name !== ".gitkeep").map((entry) => entry.name).sort();
|
|
26675
26706
|
}
|
|
26676
26707
|
function readFlowTree(slug) {
|
|
26677
|
-
const
|
|
26678
|
-
if (!existsSync4(
|
|
26708
|
+
const path28 = join3(flowsDir(), slug, "_data.json");
|
|
26709
|
+
if (!existsSync4(path28)) {
|
|
26679
26710
|
failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
|
|
26680
26711
|
}
|
|
26681
26712
|
try {
|
|
26682
|
-
return JSON.parse(readFileSync9(
|
|
26713
|
+
return JSON.parse(readFileSync9(path28, "utf-8"));
|
|
26683
26714
|
} catch (error) {
|
|
26684
26715
|
failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
|
|
26685
26716
|
}
|
|
@@ -27092,10 +27123,10 @@ async function stageOps(ops) {
|
|
|
27092
27123
|
handleError(err);
|
|
27093
27124
|
}
|
|
27094
27125
|
}
|
|
27095
|
-
async function draftAction2(
|
|
27126
|
+
async function draftAction2(path28, body, chat) {
|
|
27096
27127
|
const chatId = resolveChatId(chat);
|
|
27097
27128
|
try {
|
|
27098
|
-
const data = await apiPost(
|
|
27129
|
+
const data = await apiPost(path28, { chatId, ...body });
|
|
27099
27130
|
writeJsonEnvelope({ ok: true, data });
|
|
27100
27131
|
return data;
|
|
27101
27132
|
} catch (err) {
|
|
@@ -29119,9 +29150,9 @@ async function readImageBuffer(pathOrUrl) {
|
|
|
29119
29150
|
}
|
|
29120
29151
|
return readFile19(pathOrUrl);
|
|
29121
29152
|
}
|
|
29122
|
-
async function isDirectory(
|
|
29153
|
+
async function isDirectory(path28) {
|
|
29123
29154
|
try {
|
|
29124
|
-
const s = await stat4(
|
|
29155
|
+
const s = await stat4(path28);
|
|
29125
29156
|
return s.isDirectory();
|
|
29126
29157
|
} catch {
|
|
29127
29158
|
return false;
|
|
@@ -31584,11 +31615,11 @@ Full guide: __tooling__/docs/tools/baker/images.md`
|
|
|
31584
31615
|
});
|
|
31585
31616
|
|
|
31586
31617
|
// src/commands/landing/index.ts
|
|
31587
|
-
import { defineCommand as
|
|
31618
|
+
import { defineCommand as defineCommand142 } from "citty";
|
|
31588
31619
|
|
|
31589
31620
|
// src/commands/landing/critique.ts
|
|
31590
31621
|
import { readdir as readdir8, stat as stat6 } from "fs/promises";
|
|
31591
|
-
import
|
|
31622
|
+
import path27 from "path";
|
|
31592
31623
|
import { defineCommand as defineCommand141 } from "citty";
|
|
31593
31624
|
|
|
31594
31625
|
// src/engine/landing/lib/brand-tokens.ts
|
|
@@ -31997,11 +32028,6 @@ var RULE_META = {
|
|
|
31997
32028
|
severity: "block",
|
|
31998
32029
|
note: "Gradient text is a top AI tell. Emphasis comes from weight or size, not a clipped gradient fill."
|
|
31999
32030
|
},
|
|
32000
|
-
"copied-reference-copy": {
|
|
32001
|
-
family: "originality",
|
|
32002
|
-
severity: "block",
|
|
32003
|
-
note: "This line is lifted from a section you consulted in the inspiration library. Reference sections are for structure and mechanism, never words \u2014 a visitor who has seen the original reads this as a clone, and the claim is not yours to make. Rewrite it from the client's own offer."
|
|
32004
|
-
},
|
|
32005
32031
|
"broken-image": {
|
|
32006
32032
|
family: "integrity",
|
|
32007
32033
|
severity: "block",
|
|
@@ -32182,64 +32208,6 @@ var SEVERITY_WEIGHT = {
|
|
|
32182
32208
|
advisory: 0.05
|
|
32183
32209
|
};
|
|
32184
32210
|
|
|
32185
|
-
// src/engine/landing/lib/originality.ts
|
|
32186
|
-
var MIN_COMPARABLE_LENGTH = 12;
|
|
32187
|
-
var NEAR_MATCH_RATIO = 0.8;
|
|
32188
|
-
function normalize(value) {
|
|
32189
|
-
return value.toLowerCase().replace(/[‘’“”]/g, "'").replace(/[^a-z0-9']+/g, " ").trim();
|
|
32190
|
-
}
|
|
32191
|
-
function words2(value) {
|
|
32192
|
-
return normalize(value).split(" ").filter(Boolean);
|
|
32193
|
-
}
|
|
32194
|
-
function copyOverlapRatio(candidate, reference) {
|
|
32195
|
-
const referenceWords = words2(reference);
|
|
32196
|
-
if (referenceWords.length === 0) return 0;
|
|
32197
|
-
const candidateWords = new Set(words2(candidate));
|
|
32198
|
-
const shared = referenceWords.filter((word) => candidateWords.has(word)).length;
|
|
32199
|
-
return shared / referenceWords.length;
|
|
32200
|
-
}
|
|
32201
|
-
function isVerbatimReuse(candidate, reference) {
|
|
32202
|
-
const normalizedCandidate = normalize(candidate);
|
|
32203
|
-
const normalizedReference = normalize(reference);
|
|
32204
|
-
if (normalizedReference.length < MIN_COMPARABLE_LENGTH) return false;
|
|
32205
|
-
if (normalizedCandidate.includes(normalizedReference)) return true;
|
|
32206
|
-
return copyOverlapRatio(candidate, reference) >= NEAR_MATCH_RATIO;
|
|
32207
|
-
}
|
|
32208
|
-
function extractVisibleStrings(text) {
|
|
32209
|
-
const found = [];
|
|
32210
|
-
const lines = text.split("\n");
|
|
32211
|
-
for (const [index, line] of lines.entries()) {
|
|
32212
|
-
for (const match of line.matchAll(/>([^<>{}]{12,200})</g)) {
|
|
32213
|
-
const value = match[1]?.trim();
|
|
32214
|
-
if (value && /[a-zA-Z]{3}/.test(value)) found.push({ value, line: index + 1 });
|
|
32215
|
-
}
|
|
32216
|
-
}
|
|
32217
|
-
return found;
|
|
32218
|
-
}
|
|
32219
|
-
function detectOriginality(sources, references) {
|
|
32220
|
-
if (references.length === 0) return [];
|
|
32221
|
-
const findings = [];
|
|
32222
|
-
const seen = /* @__PURE__ */ new Set();
|
|
32223
|
-
for (const source of sources) {
|
|
32224
|
-
for (const { value, line } of extractVisibleStrings(source.text)) {
|
|
32225
|
-
for (const reference of references) {
|
|
32226
|
-
const hit = reference.copyStrings.find((copy) => isVerbatimReuse(value, copy));
|
|
32227
|
-
if (!hit) continue;
|
|
32228
|
-
const key = `${source.path}:${line}:${normalize(hit)}`;
|
|
32229
|
-
if (seen.has(key)) continue;
|
|
32230
|
-
seen.add(key);
|
|
32231
|
-
findings.push({
|
|
32232
|
-
id: "copied-reference-copy",
|
|
32233
|
-
snippet: value.slice(0, 120),
|
|
32234
|
-
file: source.path,
|
|
32235
|
-
line
|
|
32236
|
-
});
|
|
32237
|
-
}
|
|
32238
|
-
}
|
|
32239
|
-
}
|
|
32240
|
-
return findings;
|
|
32241
|
-
}
|
|
32242
|
-
|
|
32243
32211
|
// src/engine/landing/lib/rules.ts
|
|
32244
32212
|
var cap2 = (m, i) => m[i] ?? "";
|
|
32245
32213
|
var num = (m, i) => Number(m[i] ?? 0);
|
|
@@ -32644,8 +32612,8 @@ var ANALYZERS = [
|
|
|
32644
32612
|
const lines = text.split("\n");
|
|
32645
32613
|
for (const m of text.matchAll(/(?:-webkit-)?background-clip\s*:\s*text/gi)) {
|
|
32646
32614
|
const line = lineOf(text, m.index ?? 0);
|
|
32647
|
-
const
|
|
32648
|
-
if (/gradient\(/i.test(
|
|
32615
|
+
const window = lines.slice(Math.max(0, line - 7), Math.min(lines.length, line + 6)).join("\n");
|
|
32616
|
+
if (/gradient\(/i.test(window)) {
|
|
32649
32617
|
out.push({ id: "gradient-text", snippet: "background-clip: text + gradient", file, line });
|
|
32650
32618
|
}
|
|
32651
32619
|
}
|
|
@@ -32767,16 +32735,7 @@ function dedupe(findings) {
|
|
|
32767
32735
|
}
|
|
32768
32736
|
|
|
32769
32737
|
// src/engine/landing/lib/critique.ts
|
|
32770
|
-
var FAMILIES = [
|
|
32771
|
-
"typography",
|
|
32772
|
-
"color",
|
|
32773
|
-
"borders_depth",
|
|
32774
|
-
"motion",
|
|
32775
|
-
"spacing",
|
|
32776
|
-
"copy",
|
|
32777
|
-
"integrity",
|
|
32778
|
-
"originality"
|
|
32779
|
-
];
|
|
32738
|
+
var FAMILIES = ["typography", "color", "borders_depth", "motion", "spacing", "copy", "integrity"];
|
|
32780
32739
|
function round4(n) {
|
|
32781
32740
|
return Math.round(n * 100) / 100;
|
|
32782
32741
|
}
|
|
@@ -32795,7 +32754,6 @@ function critiqueLanding(input) {
|
|
|
32795
32754
|
const raws = [];
|
|
32796
32755
|
for (const source of sources) raws.push(...detectSource(source));
|
|
32797
32756
|
raws.push(...detectPage(sources));
|
|
32798
|
-
raws.push(...detectOriginality(sources, input.references ?? []));
|
|
32799
32757
|
for (const raw of raws) {
|
|
32800
32758
|
const meta = RULE_META[raw.id];
|
|
32801
32759
|
if (!meta) continue;
|
|
@@ -32833,82 +32791,41 @@ function describeCounts(findings) {
|
|
|
32833
32791
|
return [b ? `${b} block` : "", w ? `${w} warn` : "", a ? `${a} advisory` : ""].filter(Boolean).join(", ");
|
|
32834
32792
|
}
|
|
32835
32793
|
|
|
32836
|
-
// src/engine/landing/lib/referenceStore.ts
|
|
32837
|
-
import { mkdir as mkdir8, readFile as readFile21, writeFile as writeFile11 } from "fs/promises";
|
|
32838
|
-
import path25 from "path";
|
|
32839
|
-
var REFERENCES_FILE = ".cache/inspiration-refs.json";
|
|
32840
|
-
var REFERENCE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
32841
|
-
async function readReferences(projectRoot) {
|
|
32842
|
-
try {
|
|
32843
|
-
const raw = await readFile21(path25.join(projectRoot, REFERENCES_FILE), "utf8");
|
|
32844
|
-
const parsed = JSON.parse(raw);
|
|
32845
|
-
if (!Array.isArray(parsed)) return [];
|
|
32846
|
-
const cutoff = Date.now() - REFERENCE_TTL_MS;
|
|
32847
|
-
return parsed.filter((entry) => {
|
|
32848
|
-
if (typeof entry !== "object" || entry === null) return false;
|
|
32849
|
-
const candidate = entry;
|
|
32850
|
-
if (typeof candidate.sectionId !== "string" || !Array.isArray(candidate.copyStrings)) return false;
|
|
32851
|
-
const at = Date.parse(candidate.consultedAt ?? "");
|
|
32852
|
-
return Number.isNaN(at) ? true : at >= cutoff;
|
|
32853
|
-
});
|
|
32854
|
-
} catch {
|
|
32855
|
-
return [];
|
|
32856
|
-
}
|
|
32857
|
-
}
|
|
32858
|
-
async function recordReference(projectRoot, reference) {
|
|
32859
|
-
return await recordReferences(projectRoot, [reference]);
|
|
32860
|
-
}
|
|
32861
|
-
async function recordReferences(projectRoot, references) {
|
|
32862
|
-
if (references.length === 0) return true;
|
|
32863
|
-
try {
|
|
32864
|
-
const existing = await readReferences(projectRoot);
|
|
32865
|
-
const replaced = new Set(references.map((reference) => reference.sectionId));
|
|
32866
|
-
const merged = [...existing.filter((entry) => !replaced.has(entry.sectionId)), ...references];
|
|
32867
|
-
const file = path25.join(projectRoot, REFERENCES_FILE);
|
|
32868
|
-
await mkdir8(path25.dirname(file), { recursive: true });
|
|
32869
|
-
await writeFile11(file, `${JSON.stringify(merged, null, 2)}
|
|
32870
|
-
`);
|
|
32871
|
-
return true;
|
|
32872
|
-
} catch {
|
|
32873
|
-
return false;
|
|
32874
|
-
}
|
|
32875
|
-
}
|
|
32876
|
-
|
|
32877
32794
|
// src/commands/landing/snapshot.ts
|
|
32878
|
-
import { mkdir as
|
|
32879
|
-
import
|
|
32795
|
+
import { mkdir as mkdir8, rename as rename2, writeFile as writeFile11 } from "fs/promises";
|
|
32796
|
+
import path25 from "path";
|
|
32880
32797
|
var CRITIC_VERSION = "2";
|
|
32881
32798
|
function critiqueCacheDir(projectRoot) {
|
|
32882
|
-
return
|
|
32799
|
+
return path25.join(projectRoot, ".cache", "landing-critique");
|
|
32883
32800
|
}
|
|
32884
32801
|
function snapshotPath(projectRoot, slug) {
|
|
32885
|
-
return
|
|
32802
|
+
return path25.join(critiqueCacheDir(projectRoot), `${slug}.json`);
|
|
32886
32803
|
}
|
|
32887
32804
|
async function writeCritiqueSnapshot(projectRoot, snapshot) {
|
|
32888
|
-
await
|
|
32805
|
+
await mkdir8(critiqueCacheDir(projectRoot), { recursive: true });
|
|
32889
32806
|
const dest = snapshotPath(projectRoot, snapshot.slug);
|
|
32890
32807
|
const tmp = `${dest}.tmp`;
|
|
32891
|
-
await
|
|
32808
|
+
await writeFile11(tmp, `${JSON.stringify(snapshot, null, 2)}
|
|
32892
32809
|
`, "utf8");
|
|
32893
32810
|
await rename2(tmp, dest);
|
|
32894
32811
|
}
|
|
32895
32812
|
|
|
32896
32813
|
// src/commands/landing/source-version.ts
|
|
32897
|
-
import { readdir as readdir7, readFile as
|
|
32898
|
-
import
|
|
32814
|
+
import { readdir as readdir7, readFile as readFile21, stat as stat5 } from "fs/promises";
|
|
32815
|
+
import path26 from "path";
|
|
32899
32816
|
async function landingSourceRelPaths(landingDir) {
|
|
32900
32817
|
const rel = [];
|
|
32901
|
-
if (await isFile(
|
|
32902
|
-
const componentsDir =
|
|
32818
|
+
if (await isFile(path26.join(landingDir, "index.astro"))) rel.push("index.astro");
|
|
32819
|
+
const componentsDir = path26.join(landingDir, "_components");
|
|
32903
32820
|
for (const abs of await walkAstro(componentsDir)) {
|
|
32904
|
-
rel.push(
|
|
32821
|
+
rel.push(path26.relative(landingDir, abs).split(path26.sep).join("/"));
|
|
32905
32822
|
}
|
|
32906
32823
|
return rel.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
32907
32824
|
}
|
|
32908
32825
|
async function readLandingSources(landingDir) {
|
|
32909
32826
|
const rel = await landingSourceRelPaths(landingDir);
|
|
32910
32827
|
const out = [];
|
|
32911
|
-
for (const r of rel) out.push({ path: r, text: await
|
|
32828
|
+
for (const r of rel) out.push({ path: r, text: await readFile21(path26.join(landingDir, r), "utf8") });
|
|
32912
32829
|
return out;
|
|
32913
32830
|
}
|
|
32914
32831
|
async function computeLandingSourceSha(landingDir) {
|
|
@@ -32917,7 +32834,7 @@ async function computeLandingSourceSha(landingDir) {
|
|
|
32917
32834
|
for (const r of rel) {
|
|
32918
32835
|
let bytes;
|
|
32919
32836
|
try {
|
|
32920
|
-
bytes = await
|
|
32837
|
+
bytes = await readFile21(path26.join(landingDir, r));
|
|
32921
32838
|
} catch {
|
|
32922
32839
|
bytes = Buffer.alloc(0);
|
|
32923
32840
|
}
|
|
@@ -32941,7 +32858,7 @@ async function walkAstro(dir) {
|
|
|
32941
32858
|
}
|
|
32942
32859
|
const out = [];
|
|
32943
32860
|
for (const entry of entries) {
|
|
32944
|
-
const abs =
|
|
32861
|
+
const abs = path26.join(dir, entry.name);
|
|
32945
32862
|
if (entry.isDirectory()) out.push(...await walkAstro(abs));
|
|
32946
32863
|
else if (entry.isFile() && entry.name.endsWith(".astro")) out.push(abs);
|
|
32947
32864
|
}
|
|
@@ -33002,14 +32919,14 @@ var critiqueCommand2 = defineCommand141({
|
|
|
33002
32919
|
{ availableSlugs: await listLandingSlugs(projectRoot) }
|
|
33003
32920
|
);
|
|
33004
32921
|
}
|
|
33005
|
-
if (!await isDir(
|
|
32922
|
+
if (!await isDir(path27.resolve(projectRoot, "src", "pages", slug))) {
|
|
33006
32923
|
fail5("NOT_FOUND", `No landing at src/pages/${slug}/`, {
|
|
33007
32924
|
availableSlugs: await listLandingSlugs(projectRoot)
|
|
33008
32925
|
});
|
|
33009
32926
|
}
|
|
33010
32927
|
}
|
|
33011
|
-
const
|
|
33012
|
-
const results = await Promise.all(slugs.map((slug) => critiqueOne(projectRoot, slug, brand
|
|
32928
|
+
const brand = await loadBrandTokens(projectRoot);
|
|
32929
|
+
const results = await Promise.all(slugs.map((slug) => critiqueOne(projectRoot, slug, brand)));
|
|
33013
32930
|
const landings = results.map(({ slug, report }) => ({
|
|
33014
32931
|
slug,
|
|
33015
32932
|
overall: report.overall,
|
|
@@ -33040,10 +32957,10 @@ var critiqueCommand2 = defineCommand141({
|
|
|
33040
32957
|
);
|
|
33041
32958
|
}
|
|
33042
32959
|
});
|
|
33043
|
-
async function critiqueOne(projectRoot, slug, brand
|
|
33044
|
-
const landingDir =
|
|
32960
|
+
async function critiqueOne(projectRoot, slug, brand) {
|
|
32961
|
+
const landingDir = path27.resolve(projectRoot, "src", "pages", slug);
|
|
33045
32962
|
const [sources, sourceSha] = await Promise.all([readLandingSources(landingDir), computeLandingSourceSha(landingDir)]);
|
|
33046
|
-
const report = critiqueLanding({ slug, sources, brand
|
|
32963
|
+
const report = critiqueLanding({ slug, sources, brand });
|
|
33047
32964
|
let snapshotFailed = false;
|
|
33048
32965
|
try {
|
|
33049
32966
|
await writeCritiqueSnapshot(projectRoot, {
|
|
@@ -33061,7 +32978,7 @@ async function critiqueOne(projectRoot, slug, brand, references) {
|
|
|
33061
32978
|
}
|
|
33062
32979
|
async function listLandingSlugs(projectRoot) {
|
|
33063
32980
|
try {
|
|
33064
|
-
const entries = await readdir8(
|
|
32981
|
+
const entries = await readdir8(path27.join(projectRoot, "src", "pages"), { withFileTypes: true });
|
|
33065
32982
|
return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).map((e) => e.name).sort();
|
|
33066
32983
|
} catch {
|
|
33067
32984
|
return [];
|
|
@@ -33086,2336 +33003,8 @@ async function isDir(p) {
|
|
|
33086
33003
|
}
|
|
33087
33004
|
}
|
|
33088
33005
|
|
|
33089
|
-
// src/commands/landing/inspiration/index.ts
|
|
33090
|
-
import { defineCommand as defineCommand149 } from "citty";
|
|
33091
|
-
|
|
33092
|
-
// src/commands/landing/inspiration/add.ts
|
|
33093
|
-
import { defineCommand as defineCommand142 } from "citty";
|
|
33094
|
-
|
|
33095
|
-
// src/commands/landing/inspiration/shared.ts
|
|
33096
|
-
var INSPIRATION_HINTS = {
|
|
33097
|
-
adapt: "Reference only. Re-express this in the client's BRAND.md palette, type and imagery register. Reusing a headline, subhead or CTA verbatim is a Tier 0 message-match failure (references/gotchas.md) and `baker landing critique` will block the publish.",
|
|
33098
|
-
structureNotCopy: "Take the structural decision, not the furniture: what the eye hits first, the grid ratio, what was deliberately left out. Your copy must come from the client's own offer."
|
|
33099
|
-
};
|
|
33100
|
-
function fidelityHint(fidelity) {
|
|
33101
|
-
if (fidelity == null) return null;
|
|
33102
|
-
if (fidelity >= 0.9) return null;
|
|
33103
|
-
if (fidelity >= 0.75) {
|
|
33104
|
-
return `Reproduction fidelity ${fidelity.toFixed(2)} \u2014 the markup is close but not exact. Trust the screenshot over the code.`;
|
|
33105
|
-
}
|
|
33106
|
-
return `Reproduction fidelity ${fidelity.toFixed(2)} \u2014 this section is scroll- or JS-driven and the extracted markup does NOT render like the original. Use the screenshot and the notes; treat the code as a hint only.`;
|
|
33107
|
-
}
|
|
33108
|
-
function reportError(error) {
|
|
33109
|
-
if (error instanceof ApiError) {
|
|
33110
|
-
writeJson({ ok: false, error: { code: error.code, message: error.message } });
|
|
33111
|
-
} else {
|
|
33112
|
-
writeJson({
|
|
33113
|
-
ok: false,
|
|
33114
|
-
error: { code: "UNKNOWN", message: error instanceof Error ? error.message : String(error) }
|
|
33115
|
-
});
|
|
33116
|
-
}
|
|
33117
|
-
process.exit(1);
|
|
33118
|
-
}
|
|
33119
|
-
function splitList(value) {
|
|
33120
|
-
if (!value) return void 0;
|
|
33121
|
-
const parts = value.split(",").map((part) => part.trim()).filter(Boolean);
|
|
33122
|
-
return parts.length > 0 ? parts : void 0;
|
|
33123
|
-
}
|
|
33124
|
-
function parseNumber(value) {
|
|
33125
|
-
if (value === void 0 || value === "") return void 0;
|
|
33126
|
-
const parsed = Number(value);
|
|
33127
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
33128
|
-
}
|
|
33129
|
-
|
|
33130
|
-
// src/commands/landing/inspiration/add.ts
|
|
33131
|
-
registerSchema({
|
|
33132
|
-
command: "landing.inspiration.add",
|
|
33133
|
-
description: "Add a landing page to the reference library and save it to this company. Returns immediately \u2014 studying a page takes a few minutes, so never wait on it. Use it when the client names a site they admire, or when you find a competitor page worth learning from.",
|
|
33134
|
-
args: {
|
|
33135
|
-
url: { type: "string", description: "Landing page address", required: true },
|
|
33136
|
-
note: { type: "string", description: "Why this page is worth keeping", required: false }
|
|
33137
|
-
}
|
|
33138
|
-
});
|
|
33139
|
-
var addCommand = defineCommand142({
|
|
33140
|
-
meta: {
|
|
33141
|
-
name: "add",
|
|
33142
|
-
description: "Add a landing page to the reference library. Example: baker landing inspiration add https://linear.app --note 'the client likes this density'"
|
|
33143
|
-
},
|
|
33144
|
-
args: {
|
|
33145
|
-
url: { type: "positional", description: "Landing page address", required: true },
|
|
33146
|
-
note: { type: "string", description: "Why this page is worth keeping", required: false }
|
|
33147
|
-
},
|
|
33148
|
-
run: async ({ args }) => {
|
|
33149
|
-
try {
|
|
33150
|
-
const data = await apiPost("/api/landing-inspiration/add", {
|
|
33151
|
-
url: args.url,
|
|
33152
|
-
note: args.note,
|
|
33153
|
-
favorite: true
|
|
33154
|
-
});
|
|
33155
|
-
const hints = data.alreadyKnown ? [
|
|
33156
|
-
"This page was already in the library, so nothing was re-studied \u2014 its sections are searchable now.",
|
|
33157
|
-
`Search it with: baker landing inspiration search --domain ${new URL(data.canonicalUrl).hostname}`
|
|
33158
|
-
] : [
|
|
33159
|
-
"Studying this page takes a few minutes. Do NOT wait on it \u2014 carry on, and search for it later in the turn or in a later one.",
|
|
33160
|
-
"Meanwhile, search what is already in the library: baker landing inspiration search '<what you want to see>' --scope all"
|
|
33161
|
-
];
|
|
33162
|
-
writeJson({
|
|
33163
|
-
ok: true,
|
|
33164
|
-
data: { id: data.sourceId, url: data.canonicalUrl, status: data.status, already_known: data.alreadyKnown },
|
|
33165
|
-
hints
|
|
33166
|
-
});
|
|
33167
|
-
} catch (error) {
|
|
33168
|
-
reportError(error);
|
|
33169
|
-
}
|
|
33170
|
-
}
|
|
33171
|
-
});
|
|
33172
|
-
|
|
33173
|
-
// src/commands/landing/inspiration/code.ts
|
|
33174
|
-
import { mkdir as mkdir10, writeFile as writeFile13 } from "fs/promises";
|
|
33175
|
-
import path29 from "path";
|
|
33176
|
-
import { defineCommand as defineCommand143 } from "citty";
|
|
33177
|
-
registerSchema({
|
|
33178
|
-
command: "landing.inspiration.code",
|
|
33179
|
-
description: "Write a reference section's standalone HTML+CSS to .baker/inspiration/<id>/ so you can read how it is built. Reference only \u2014 the structure is the lesson, the words are not yours to reuse. Consulting a section records it, and `baker landing critique` blocks a publish that ships its copy verbatim.",
|
|
33180
|
-
args: {
|
|
33181
|
-
id: { type: "string", description: "Section id from search", required: true },
|
|
33182
|
-
full: { type: "boolean", description: "Print the markup inline as well as writing it", required: false }
|
|
33183
|
-
}
|
|
33184
|
-
});
|
|
33185
|
-
var codeCommand = defineCommand143({
|
|
33186
|
-
meta: {
|
|
33187
|
-
name: "code",
|
|
33188
|
-
description: "Write one reference section's standalone markup to disk. Example: baker landing inspiration code k57abc\u2026 \u2014 read it for structure, then build your own."
|
|
33189
|
-
},
|
|
33190
|
-
args: {
|
|
33191
|
-
id: { type: "positional", description: "Section id from search", required: true },
|
|
33192
|
-
full: { type: "boolean", description: "Also print the markup inline", required: false, default: false }
|
|
33193
|
-
},
|
|
33194
|
-
run: async ({ args }) => {
|
|
33195
|
-
try {
|
|
33196
|
-
const id = args.id;
|
|
33197
|
-
const data = await apiGet("/api/landing-inspiration/section-code", { id });
|
|
33198
|
-
const dir = path29.join(process.cwd(), ".baker", "inspiration", id);
|
|
33199
|
-
await mkdir10(dir, { recursive: true });
|
|
33200
|
-
const file = path29.join(dir, "section.html");
|
|
33201
|
-
await writeFile13(file, data.html);
|
|
33202
|
-
const recorded = await recordReference(process.cwd(), {
|
|
33203
|
-
sectionId: id,
|
|
33204
|
-
sourceUrl: data.sourceUrl,
|
|
33205
|
-
copyStrings: data.copyStrings,
|
|
33206
|
-
consultedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
33207
|
-
});
|
|
33208
|
-
const hints = [INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt];
|
|
33209
|
-
const fidelity = fidelityHint(data.fidelity);
|
|
33210
|
-
if (fidelity) hints.push(fidelity);
|
|
33211
|
-
if (data.fidelityNote) hints.push(data.fidelityNote);
|
|
33212
|
-
if (!recorded) {
|
|
33213
|
-
hints.push(
|
|
33214
|
-
"Could not record this reference (.cache/ not writable?) \u2014 the originality check at publish will not be able to see it, so be especially careful not to reuse its copy."
|
|
33215
|
-
);
|
|
33216
|
-
}
|
|
33217
|
-
writeJson({
|
|
33218
|
-
ok: true,
|
|
33219
|
-
data: {
|
|
33220
|
-
id,
|
|
33221
|
-
file: path29.relative(process.cwd(), file),
|
|
33222
|
-
bytes: data.html.length,
|
|
33223
|
-
fidelity: data.fidelity,
|
|
33224
|
-
css_custom_properties: data.cssCustomProperties,
|
|
33225
|
-
reproduction_notes: data.reproductionNotes,
|
|
33226
|
-
adaptation_notes: data.adaptationNotes,
|
|
33227
|
-
source_url: data.sourceUrl,
|
|
33228
|
-
...args.full ? { html: data.html } : {}
|
|
33229
|
-
},
|
|
33230
|
-
hints
|
|
33231
|
-
});
|
|
33232
|
-
} catch (error) {
|
|
33233
|
-
reportError(error);
|
|
33234
|
-
}
|
|
33235
|
-
}
|
|
33236
|
-
});
|
|
33237
|
-
|
|
33238
|
-
// src/commands/landing/inspiration/favorites.ts
|
|
33239
|
-
import { defineCommand as defineCommand144 } from "citty";
|
|
33240
|
-
registerSchema({
|
|
33241
|
-
command: "landing.inspiration.favorites",
|
|
33242
|
-
description: "List the reference sections this company has saved. This is what `search` looks at by default, so it is the client's own taste profile \u2014 read it before proposing a direction.",
|
|
33243
|
-
args: {
|
|
33244
|
-
type: { type: "string", description: "Comma list of section types to filter by", required: false },
|
|
33245
|
-
limit: { type: "number", description: "Max results (default 30)", required: false }
|
|
33246
|
-
}
|
|
33247
|
-
});
|
|
33248
|
-
var favoritesCommand = defineCommand144({
|
|
33249
|
-
meta: {
|
|
33250
|
-
name: "favorites",
|
|
33251
|
-
description: "List this company's saved reference sections. Example: baker landing inspiration favorites --type hero,pricing"
|
|
33252
|
-
},
|
|
33253
|
-
args: {
|
|
33254
|
-
type: { type: "string", description: "Comma list of section types", required: false },
|
|
33255
|
-
limit: { type: "string", description: "Max results (default 30)", required: false }
|
|
33256
|
-
},
|
|
33257
|
-
run: async ({ args }) => {
|
|
33258
|
-
try {
|
|
33259
|
-
const params = {};
|
|
33260
|
-
const types = splitList(args.type);
|
|
33261
|
-
if (types) params.type = types.join(",");
|
|
33262
|
-
const limit = parseNumber(args.limit);
|
|
33263
|
-
params.limit = String(limit ?? 30);
|
|
33264
|
-
const data = await apiGet("/api/landing-inspiration/favorites", params);
|
|
33265
|
-
const sections = Array.isArray(data?.sections) ? data.sections : [];
|
|
33266
|
-
writeJson({
|
|
33267
|
-
ok: true,
|
|
33268
|
-
data: {
|
|
33269
|
-
sections: sections.map((section) => ({
|
|
33270
|
-
id: section.id,
|
|
33271
|
-
section: section.sectionType,
|
|
33272
|
-
composition: section.composition,
|
|
33273
|
-
look: section.visualRegister,
|
|
33274
|
-
why_it_works: section.whyItWorks,
|
|
33275
|
-
domain: section.domain
|
|
33276
|
-
}))
|
|
33277
|
-
},
|
|
33278
|
-
hints: sections.length === 0 ? [
|
|
33279
|
-
"Nothing saved yet. Add a page the client admires with `baker landing inspiration add <url>`, or browse the whole library with `baker landing inspiration search --scope all`."
|
|
33280
|
-
] : [
|
|
33281
|
-
"These are the client's taste signals \u2014 read them as direction, not as a component library.",
|
|
33282
|
-
INSPIRATION_HINTS.adapt
|
|
33283
|
-
]
|
|
33284
|
-
});
|
|
33285
|
-
} catch (error) {
|
|
33286
|
-
reportError(error);
|
|
33287
|
-
}
|
|
33288
|
-
}
|
|
33289
|
-
});
|
|
33290
|
-
registerSchema({
|
|
33291
|
-
command: "landing.inspiration.favorite",
|
|
33292
|
-
description: "Save a reference section (or a whole page) to this company, so it shows up in the default search scope.",
|
|
33293
|
-
args: {
|
|
33294
|
-
id: { type: "string", description: "Section id, or page id with --page", required: true },
|
|
33295
|
-
page: { type: "boolean", description: "Treat the id as a page rather than a section", required: false },
|
|
33296
|
-
note: { type: "string", description: "Why this is worth keeping", required: false }
|
|
33297
|
-
}
|
|
33298
|
-
});
|
|
33299
|
-
var favoriteCommand = defineCommand144({
|
|
33300
|
-
meta: {
|
|
33301
|
-
name: "favorite",
|
|
33302
|
-
description: "Save a reference section to this company. Example: baker landing inspiration favorite k57abc\u2026"
|
|
33303
|
-
},
|
|
33304
|
-
args: {
|
|
33305
|
-
id: { type: "positional", description: "Section id (or page id with --page)", required: true },
|
|
33306
|
-
page: { type: "boolean", description: "Treat the id as a page", required: false, default: false },
|
|
33307
|
-
note: { type: "string", description: "Why this is worth keeping", required: false }
|
|
33308
|
-
},
|
|
33309
|
-
run: async ({ args }) => {
|
|
33310
|
-
try {
|
|
33311
|
-
const id = args.id;
|
|
33312
|
-
const body = args.page ? { sourceId: id } : { sectionId: id };
|
|
33313
|
-
const data = await apiPost("/api/landing-inspiration/favorite", {
|
|
33314
|
-
...body,
|
|
33315
|
-
note: args.note
|
|
33316
|
-
});
|
|
33317
|
-
writeJson({ ok: true, data: { id, favorited: data.favorited } });
|
|
33318
|
-
} catch (error) {
|
|
33319
|
-
reportError(error);
|
|
33320
|
-
}
|
|
33321
|
-
}
|
|
33322
|
-
});
|
|
33323
|
-
registerSchema({
|
|
33324
|
-
command: "landing.inspiration.unfavorite",
|
|
33325
|
-
description: "Remove a reference section (or page) from this company's saved set.",
|
|
33326
|
-
args: {
|
|
33327
|
-
id: { type: "string", description: "Section id, or page id with --page", required: true },
|
|
33328
|
-
page: { type: "boolean", description: "Treat the id as a page rather than a section", required: false }
|
|
33329
|
-
}
|
|
33330
|
-
});
|
|
33331
|
-
var unfavoriteCommand = defineCommand144({
|
|
33332
|
-
meta: {
|
|
33333
|
-
name: "unfavorite",
|
|
33334
|
-
description: "Remove a reference section from this company's saved set. Example: baker landing inspiration unfavorite k57abc\u2026"
|
|
33335
|
-
},
|
|
33336
|
-
args: {
|
|
33337
|
-
id: { type: "positional", description: "Section id (or page id with --page)", required: true },
|
|
33338
|
-
page: { type: "boolean", description: "Treat the id as a page", required: false, default: false }
|
|
33339
|
-
},
|
|
33340
|
-
run: async ({ args }) => {
|
|
33341
|
-
try {
|
|
33342
|
-
const id = args.id;
|
|
33343
|
-
const body = args.page ? { sourceId: id } : { sectionId: id };
|
|
33344
|
-
const data = await apiPost("/api/landing-inspiration/unfavorite", body);
|
|
33345
|
-
writeJson({ ok: true, data: { id, favorited: data.favorited } });
|
|
33346
|
-
} catch (error) {
|
|
33347
|
-
reportError(error);
|
|
33348
|
-
}
|
|
33349
|
-
}
|
|
33350
|
-
});
|
|
33351
|
-
|
|
33352
|
-
// src/commands/landing/inspiration/page.ts
|
|
33353
|
-
import { defineCommand as defineCommand145 } from "citty";
|
|
33354
|
-
registerSchema({
|
|
33355
|
-
command: "landing.inspiration.page",
|
|
33356
|
-
description: "Show a whole reference page as a sequence: every section top to bottom with its type and the idea behind it. This is the view to use when the question is how a good page is ORDERED rather than what one section looks like.",
|
|
33357
|
-
args: { id: { type: "string", description: "Page id (from a search result's source id)", required: true } }
|
|
33358
|
-
});
|
|
33359
|
-
var pageCommand = defineCommand145({
|
|
33360
|
-
meta: {
|
|
33361
|
-
name: "page",
|
|
33362
|
-
description: "Show how a reference page sequences its sections. Example: baker landing inspiration page j91xyz\u2026 \u2014 the blueprint, not the pixels."
|
|
33363
|
-
},
|
|
33364
|
-
args: { id: { type: "positional", description: "Page id", required: true } },
|
|
33365
|
-
run: async ({ args }) => {
|
|
33366
|
-
try {
|
|
33367
|
-
const data = await apiGet("/api/landing-inspiration/page", { id: args.id });
|
|
33368
|
-
if (data.status !== "indexed") {
|
|
33369
|
-
writeJson({
|
|
33370
|
-
ok: true,
|
|
33371
|
-
data: { id: data.id, url: data.url, status: data.status, sections: [] },
|
|
33372
|
-
hints: [
|
|
33373
|
-
data.status === "error" ? "We couldn't read this page. Try a different address, or a different page on the same site." : "Still being studied \u2014 check back later in the turn or in a later one."
|
|
33374
|
-
]
|
|
33375
|
-
});
|
|
33376
|
-
return;
|
|
33377
|
-
}
|
|
33378
|
-
writeJson({
|
|
33379
|
-
ok: true,
|
|
33380
|
-
data: {
|
|
33381
|
-
id: data.id,
|
|
33382
|
-
url: data.url,
|
|
33383
|
-
domain: data.domain,
|
|
33384
|
-
title: data.title,
|
|
33385
|
-
archetype: data.archetype,
|
|
33386
|
-
stack: data.detectedStack,
|
|
33387
|
-
page_fidelity: data.pageFidelity,
|
|
33388
|
-
blueprint: data.sectionOrder,
|
|
33389
|
-
sections: data.sections.map((section) => ({
|
|
33390
|
-
id: section.id,
|
|
33391
|
-
position: section.index,
|
|
33392
|
-
section: section.sectionType,
|
|
33393
|
-
composition: section.composition,
|
|
33394
|
-
look: section.visualRegister,
|
|
33395
|
-
motion: section.motionSummary,
|
|
33396
|
-
why_it_works: section.whyItWorks,
|
|
33397
|
-
craft: section.craftScore
|
|
33398
|
-
}))
|
|
33399
|
-
},
|
|
33400
|
-
hints: [
|
|
33401
|
-
"The blueprint is the section order top to bottom \u2014 the answer to 'how do good pages in this category sequence themselves'.",
|
|
33402
|
-
"Use `baker landing inspiration view <section id>` for any section worth a closer look.",
|
|
33403
|
-
INSPIRATION_HINTS.adapt
|
|
33404
|
-
]
|
|
33405
|
-
});
|
|
33406
|
-
} catch (error) {
|
|
33407
|
-
reportError(error);
|
|
33408
|
-
}
|
|
33409
|
-
}
|
|
33410
|
-
});
|
|
33411
|
-
|
|
33412
|
-
// src/commands/landing/inspiration/scrape.ts
|
|
33413
|
-
import { readFile as readFile23 } from "fs/promises";
|
|
33414
|
-
import path32 from "path";
|
|
33415
|
-
import { defineCommand as defineCommand146 } from "citty";
|
|
33416
|
-
|
|
33417
|
-
// src/engine/landing/lib/capturedReferences.ts
|
|
33418
|
-
var MAX_STRINGS_PER_SECTION = 40;
|
|
33419
|
-
function bodyOf(markup) {
|
|
33420
|
-
const body = markup.indexOf("<body");
|
|
33421
|
-
return body === -1 ? markup : markup.slice(body);
|
|
33422
|
-
}
|
|
33423
|
-
function capturedCopyStrings(markup) {
|
|
33424
|
-
const seen = /* @__PURE__ */ new Set();
|
|
33425
|
-
for (const { value } of extractVisibleStrings(bodyOf(markup))) {
|
|
33426
|
-
seen.add(value);
|
|
33427
|
-
if (seen.size >= MAX_STRINGS_PER_SECTION) break;
|
|
33428
|
-
}
|
|
33429
|
-
return [...seen];
|
|
33430
|
-
}
|
|
33431
|
-
|
|
33432
|
-
// src/engine/landing-library/blocked.ts
|
|
33433
|
-
var CHALLENGE_PHRASES = [
|
|
33434
|
-
"just a moment",
|
|
33435
|
-
"attention required",
|
|
33436
|
-
"verify you are human",
|
|
33437
|
-
"checking your browser",
|
|
33438
|
-
"enable javascript and cookies to continue",
|
|
33439
|
-
"unusual traffic",
|
|
33440
|
-
"access denied",
|
|
33441
|
-
"you have been blocked",
|
|
33442
|
-
"request unsuccessful",
|
|
33443
|
-
"are you a robot",
|
|
33444
|
-
"security check",
|
|
33445
|
-
"ddos protection",
|
|
33446
|
-
"captcha"
|
|
33447
|
-
];
|
|
33448
|
-
var CHALLENGE_MARKERS = ["cf-browser-verification", "cf_chl_", "px-captcha", "_incapsula_", "distil_r_captcha"];
|
|
33449
|
-
function detectBlockedPage(page) {
|
|
33450
|
-
if (page.status !== null && page.status >= 400) {
|
|
33451
|
-
return {
|
|
33452
|
-
code: "HTTP_ERROR",
|
|
33453
|
-
message: `The site returned ${page.status} instead of the page \u2014 it may be blocking automated visits.`
|
|
33454
|
-
};
|
|
33455
|
-
}
|
|
33456
|
-
const haystack = `${page.title}
|
|
33457
|
-
${page.bodyText.slice(0, 2e3)}`.toLowerCase();
|
|
33458
|
-
const phrase = CHALLENGE_PHRASES.find((candidate) => haystack.includes(candidate));
|
|
33459
|
-
if (phrase) {
|
|
33460
|
-
return { code: "BOT_CHALLENGE", message: "The site showed a security check instead of the page." };
|
|
33461
|
-
}
|
|
33462
|
-
const html = page.html?.toLowerCase() ?? "";
|
|
33463
|
-
if (CHALLENGE_MARKERS.some((marker) => html.includes(marker))) {
|
|
33464
|
-
return { code: "BOT_CHALLENGE", message: "The site showed a security check instead of the page." };
|
|
33465
|
-
}
|
|
33466
|
-
return null;
|
|
33467
|
-
}
|
|
33468
|
-
var BlockedPageError = class extends Error {
|
|
33469
|
-
code;
|
|
33470
|
-
constructor(blocked) {
|
|
33471
|
-
super(blocked.message);
|
|
33472
|
-
this.name = "BlockedPageError";
|
|
33473
|
-
this.code = blocked.code;
|
|
33474
|
-
}
|
|
33475
|
-
};
|
|
33476
|
-
|
|
33477
|
-
// src/engine/landing-library/run.ts
|
|
33478
|
-
import { mkdir as mkdir11, writeFile as writeFile15 } from "fs/promises";
|
|
33479
|
-
import path31 from "path";
|
|
33480
|
-
|
|
33481
|
-
// src/engine/landing-library/browser.ts
|
|
33482
|
-
import { createRequire } from "module";
|
|
33483
|
-
var require_ = createRequire(import.meta.url);
|
|
33484
|
-
var pwSpecifier = ["play", "wright"].join("");
|
|
33485
|
-
var DESKTOP_VIEWPORT = { width: 1440, height: 900 };
|
|
33486
|
-
var MOBILE_VIEWPORT = { width: 390, height: 844 };
|
|
33487
|
-
var DEVICE_SCALE_FACTOR = 2;
|
|
33488
|
-
async function launchBrowser() {
|
|
33489
|
-
const playwright = require_(pwSpecifier);
|
|
33490
|
-
return await playwright.chromium.launch({
|
|
33491
|
-
headless: true,
|
|
33492
|
-
args: ["--hide-scrollbars", "--disable-blink-features=AutomationControlled", "--mute-audio"]
|
|
33493
|
-
});
|
|
33494
|
-
}
|
|
33495
|
-
async function newPage(browser, viewport, opts = { motion: false }) {
|
|
33496
|
-
const context = await browser.newContext({
|
|
33497
|
-
viewport,
|
|
33498
|
-
deviceScaleFactor: DEVICE_SCALE_FACTOR,
|
|
33499
|
-
// Still captures freeze motion so screenshots are reproducible; the motion
|
|
33500
|
-
// pass re-opens a context with animation enabled.
|
|
33501
|
-
reducedMotion: opts.motion ? "no-preference" : "reduce",
|
|
33502
|
-
userAgent: viewport.width < 500 ? "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"
|
|
33503
|
-
});
|
|
33504
|
-
const page = await context.newPage();
|
|
33505
|
-
return { context, page };
|
|
33506
|
-
}
|
|
33507
|
-
|
|
33508
|
-
// src/engine/landing-library/usedCss.ts
|
|
33509
|
-
async function collectUsedCss(page, selector) {
|
|
33510
|
-
const collected = await page.evaluate(inPageCollectUsedCss, selector);
|
|
33511
|
-
const extra = [];
|
|
33512
|
-
for (const href of collected.unreadableHrefs) {
|
|
33513
|
-
const text = await fetchStylesheet(page, href);
|
|
33514
|
-
if (text) extra.push(`/* ${href} */
|
|
33515
|
-
${text}`);
|
|
33516
|
-
}
|
|
33517
|
-
return { ...collected, css: [collected.css, ...extra].filter(Boolean).join("\n\n") };
|
|
33518
|
-
}
|
|
33519
|
-
async function fetchStylesheet(page, href) {
|
|
33520
|
-
try {
|
|
33521
|
-
const response = await page.request.get(href, { timeout: 1e4 });
|
|
33522
|
-
if (!response.ok()) return null;
|
|
33523
|
-
return await response.text();
|
|
33524
|
-
} catch {
|
|
33525
|
-
return null;
|
|
33526
|
-
}
|
|
33527
|
-
}
|
|
33528
|
-
var inPageCollectUsedCss = (selector) => {
|
|
33529
|
-
const root = document.querySelector(selector);
|
|
33530
|
-
const empty = {
|
|
33531
|
-
css: "",
|
|
33532
|
-
variables: "",
|
|
33533
|
-
inheritedSeed: "",
|
|
33534
|
-
unreadableHrefs: [],
|
|
33535
|
-
stats: { totalRules: 0, keptRules: 0, fontFaces: 0, keyframes: 0 }
|
|
33536
|
-
};
|
|
33537
|
-
if (!root) return empty;
|
|
33538
|
-
const INHERITED = [
|
|
33539
|
-
"color",
|
|
33540
|
-
"font-family",
|
|
33541
|
-
"font-size",
|
|
33542
|
-
"font-weight",
|
|
33543
|
-
"line-height",
|
|
33544
|
-
"letter-spacing",
|
|
33545
|
-
"text-align",
|
|
33546
|
-
"background-color",
|
|
33547
|
-
"-webkit-font-smoothing"
|
|
33548
|
-
];
|
|
33549
|
-
const unreadableHrefs = [];
|
|
33550
|
-
const kept = [];
|
|
33551
|
-
const fontFaces = [];
|
|
33552
|
-
const keyframesByName = /* @__PURE__ */ new Map();
|
|
33553
|
-
let totalRules = 0;
|
|
33554
|
-
let keptStyleRules = 0;
|
|
33555
|
-
const matchesInSection = (selectorText) => {
|
|
33556
|
-
for (const part of selectorText.split(",")) {
|
|
33557
|
-
const base = part.replace(/::[a-zA-Z-]+(\([^)]*\))?/g, "").replace(/:(hover|focus|focus-visible|focus-within|active|visited|target|checked|disabled)\b/g, "").trim();
|
|
33558
|
-
if (!base) continue;
|
|
33559
|
-
try {
|
|
33560
|
-
if (root.matches(base) || root.querySelector(base)) return true;
|
|
33561
|
-
} catch {
|
|
33562
|
-
return true;
|
|
33563
|
-
}
|
|
33564
|
-
}
|
|
33565
|
-
return false;
|
|
33566
|
-
};
|
|
33567
|
-
const walk = (rules, sink) => {
|
|
33568
|
-
for (const rule of Array.from(rules)) {
|
|
33569
|
-
totalRules++;
|
|
33570
|
-
if (rule instanceof CSSStyleRule) {
|
|
33571
|
-
if (matchesInSection(rule.selectorText)) {
|
|
33572
|
-
sink.push(rule.cssText);
|
|
33573
|
-
keptStyleRules++;
|
|
33574
|
-
}
|
|
33575
|
-
continue;
|
|
33576
|
-
}
|
|
33577
|
-
if (rule instanceof CSSFontFaceRule) {
|
|
33578
|
-
fontFaces.push(rule.cssText);
|
|
33579
|
-
continue;
|
|
33580
|
-
}
|
|
33581
|
-
if (rule instanceof CSSKeyframesRule) {
|
|
33582
|
-
keyframesByName.set(rule.name, rule.cssText);
|
|
33583
|
-
continue;
|
|
33584
|
-
}
|
|
33585
|
-
const grouping = rule instanceof CSSMediaRule || rule instanceof CSSSupportsRule || typeof CSSLayerBlockRule !== "undefined" && rule instanceof CSSLayerBlockRule || typeof CSSContainerRule !== "undefined" && rule instanceof CSSContainerRule;
|
|
33586
|
-
if (grouping) {
|
|
33587
|
-
const inner = [];
|
|
33588
|
-
walk(rule.cssRules, inner);
|
|
33589
|
-
if (inner.length === 0) continue;
|
|
33590
|
-
const condition = rule.conditionText ?? "";
|
|
33591
|
-
const prelude = rule instanceof CSSMediaRule ? `@media ${condition}` : rule.cssText.split("{")[0]?.trim();
|
|
33592
|
-
sink.push(`${prelude} {
|
|
33593
|
-
${inner.join("\n")}
|
|
33594
|
-
}`);
|
|
33595
|
-
}
|
|
33596
|
-
}
|
|
33597
|
-
};
|
|
33598
|
-
for (const sheet of Array.from(document.styleSheets)) {
|
|
33599
|
-
const owner = sheet.ownerNode;
|
|
33600
|
-
if (owner?.hasAttribute?.("data-baker-freeze")) continue;
|
|
33601
|
-
try {
|
|
33602
|
-
walk(sheet.cssRules, kept);
|
|
33603
|
-
} catch {
|
|
33604
|
-
if (sheet.href) unreadableHrefs.push(sheet.href);
|
|
33605
|
-
}
|
|
33606
|
-
}
|
|
33607
|
-
const keptText = kept.join("\n");
|
|
33608
|
-
const usedKeyframes = [];
|
|
33609
|
-
for (const [name, text] of keyframesByName) {
|
|
33610
|
-
if (new RegExp(`(^|[\\s:,])${name}([\\s;,}]|$)`).test(keptText)) usedKeyframes.push(text);
|
|
33611
|
-
}
|
|
33612
|
-
const rootStyle = getComputedStyle(root);
|
|
33613
|
-
const variableDeclarations = [];
|
|
33614
|
-
for (const property of Array.from(rootStyle)) {
|
|
33615
|
-
if (!property.startsWith("--")) continue;
|
|
33616
|
-
const value = rootStyle.getPropertyValue(property).trim();
|
|
33617
|
-
if (value) variableDeclarations.push(` ${property}: ${value};`);
|
|
33618
|
-
}
|
|
33619
|
-
const seed = INHERITED.map((property) => ` ${property}: ${rootStyle.getPropertyValue(property)};`).join("\n");
|
|
33620
|
-
return {
|
|
33621
|
-
css: [...fontFaces, ...usedKeyframes, ...kept].join("\n"),
|
|
33622
|
-
variables: variableDeclarations.length ? `:root {
|
|
33623
|
-
${variableDeclarations.join("\n")}
|
|
33624
|
-
}` : "",
|
|
33625
|
-
inheritedSeed: seed,
|
|
33626
|
-
unreadableHrefs,
|
|
33627
|
-
stats: {
|
|
33628
|
-
totalRules,
|
|
33629
|
-
keptRules: keptStyleRules,
|
|
33630
|
-
fontFaces: fontFaces.length,
|
|
33631
|
-
keyframes: usedKeyframes.length
|
|
33632
|
-
}
|
|
33633
|
-
};
|
|
33634
|
-
};
|
|
33635
|
-
|
|
33636
|
-
// src/engine/landing-library/bundle.ts
|
|
33637
|
-
async function buildSectionBundle(page, selector, pageUrl) {
|
|
33638
|
-
const used = await collectUsedCss(page, selector);
|
|
33639
|
-
const extracted = await page.evaluate(inPageExtractMarkup, {
|
|
33640
|
-
selector,
|
|
33641
|
-
pageUrl,
|
|
33642
|
-
rootAttribute: SECTION_ROOT_ATTRIBUTE
|
|
33643
|
-
});
|
|
33644
|
-
const css = [
|
|
33645
|
-
"*, *::before, *::after { box-sizing: border-box; }",
|
|
33646
|
-
"html, body { margin: 0; padding: 0; }",
|
|
33647
|
-
used.variables,
|
|
33648
|
-
`body {
|
|
33649
|
-
${used.inheritedSeed}
|
|
33650
|
-
}`,
|
|
33651
|
-
used.css
|
|
33652
|
-
].filter(Boolean).join("\n\n");
|
|
33653
|
-
const html = `<!doctype html>
|
|
33654
|
-
<html ${extracted.rootAttributes}>
|
|
33655
|
-
<head>
|
|
33656
|
-
<meta charset="utf-8">
|
|
33657
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
33658
|
-
<title>Section from ${escapeHtml2(extracted.host)}</title>
|
|
33659
|
-
<style>
|
|
33660
|
-
${css}
|
|
33661
|
-
</style>
|
|
33662
|
-
</head>
|
|
33663
|
-
<body ${extracted.bodyAttributes}>
|
|
33664
|
-
${wrapInLayoutContext(extracted.html, extracted.layoutContext)}
|
|
33665
|
-
</body>
|
|
33666
|
-
</html>
|
|
33667
|
-
`;
|
|
33668
|
-
return { html, css, assetUrls: extracted.assetUrls, stats: used.stats };
|
|
33669
|
-
}
|
|
33670
|
-
function escapeHtml2(value) {
|
|
33671
|
-
return value.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c] ?? c);
|
|
33672
|
-
}
|
|
33673
|
-
var SECTION_ROOT_ATTRIBUTE = "data-baker-section-root";
|
|
33674
|
-
function wrapInLayoutContext(html, context) {
|
|
33675
|
-
let wrapped = context.width > 0 && context.width < context.viewportWidth ? `<div style="width:${context.width}px;margin:0 auto;">
|
|
33676
|
-
${html}
|
|
33677
|
-
</div>` : html;
|
|
33678
|
-
for (const ancestor of [...context.ancestors].reverse()) {
|
|
33679
|
-
const attributes = ancestor.attributes ? ` ${ancestor.attributes}` : "";
|
|
33680
|
-
const container = ancestor.containerType && ancestor.containerType !== "normal" ? `container-type:${ancestor.containerType};${ancestor.containerName && ancestor.containerName !== "none" ? `container-name:${ancestor.containerName};` : ""}` : "";
|
|
33681
|
-
wrapped = `<${ancestor.tag}${attributes} style="${NEUTRALIZED_ANCESTOR_STYLE}${container}">
|
|
33682
|
-
${wrapped}
|
|
33683
|
-
</${ancestor.tag}>`;
|
|
33684
|
-
}
|
|
33685
|
-
return wrapped;
|
|
33686
|
-
}
|
|
33687
|
-
var NEUTRALIZED_ANCESTOR_STYLE = [
|
|
33688
|
-
"display:block !important",
|
|
33689
|
-
"position:static !important",
|
|
33690
|
-
"margin:0 !important",
|
|
33691
|
-
"padding:0 !important",
|
|
33692
|
-
"border:0 !important",
|
|
33693
|
-
"width:auto !important",
|
|
33694
|
-
"min-width:0 !important",
|
|
33695
|
-
"max-width:none !important",
|
|
33696
|
-
"height:auto !important",
|
|
33697
|
-
"min-height:0 !important",
|
|
33698
|
-
"max-height:none !important",
|
|
33699
|
-
"transform:none !important",
|
|
33700
|
-
"overflow:visible !important",
|
|
33701
|
-
""
|
|
33702
|
-
].join(";");
|
|
33703
|
-
var inPageExtractMarkup = ({
|
|
33704
|
-
selector,
|
|
33705
|
-
pageUrl,
|
|
33706
|
-
rootAttribute
|
|
33707
|
-
}) => {
|
|
33708
|
-
const root = document.querySelector(selector);
|
|
33709
|
-
const emptyContext = { ancestors: [], width: 0, viewportWidth: 0 };
|
|
33710
|
-
const serializeAttributes = (element) => {
|
|
33711
|
-
if (!element) return "";
|
|
33712
|
-
return Array.from(element.attributes).filter((attribute) => attribute.name !== "style").map((attribute) => `${attribute.name}="${attribute.value.replace(/"/g, """)}"`).join(" ");
|
|
33713
|
-
};
|
|
33714
|
-
const rootAttributes = serializeAttributes(document.documentElement) || 'lang="en"';
|
|
33715
|
-
const bodyAttributes = serializeAttributes(document.body);
|
|
33716
|
-
if (!root) {
|
|
33717
|
-
return {
|
|
33718
|
-
html: "",
|
|
33719
|
-
assetUrls: [],
|
|
33720
|
-
host: "",
|
|
33721
|
-
layoutContext: emptyContext,
|
|
33722
|
-
rootAttributes,
|
|
33723
|
-
bodyAttributes
|
|
33724
|
-
};
|
|
33725
|
-
}
|
|
33726
|
-
const ancestors = [];
|
|
33727
|
-
for (let ancestor = root.parentElement; ancestor && ancestor !== document.body; ) {
|
|
33728
|
-
const style = getComputedStyle(ancestor);
|
|
33729
|
-
ancestors.unshift({
|
|
33730
|
-
tag: /^[a-zA-Z][a-zA-Z0-9-]*$/.test(ancestor.tagName) ? ancestor.tagName.toLowerCase() : "div",
|
|
33731
|
-
attributes: Array.from(ancestor.attributes).filter((a) => a.name === "class" || a.name === "id" || a.name.startsWith("data-")).map((a) => `${a.name}="${a.value.replace(/"/g, """)}"`).join(" "),
|
|
33732
|
-
containerType: style.containerType,
|
|
33733
|
-
containerName: style.containerName
|
|
33734
|
-
});
|
|
33735
|
-
ancestor = ancestor.parentElement;
|
|
33736
|
-
}
|
|
33737
|
-
const layoutContext = {
|
|
33738
|
-
ancestors,
|
|
33739
|
-
width: Math.round(root.getBoundingClientRect().width),
|
|
33740
|
-
viewportWidth: window.innerWidth
|
|
33741
|
-
};
|
|
33742
|
-
const absolute = (value) => {
|
|
33743
|
-
try {
|
|
33744
|
-
return new URL(value, pageUrl).href;
|
|
33745
|
-
} catch {
|
|
33746
|
-
return value;
|
|
33747
|
-
}
|
|
33748
|
-
};
|
|
33749
|
-
const clone = root.cloneNode(true);
|
|
33750
|
-
clone.setAttribute(rootAttribute, "");
|
|
33751
|
-
const assetUrls = /* @__PURE__ */ new Set();
|
|
33752
|
-
for (const element of [clone, ...Array.from(clone.querySelectorAll("*"))]) {
|
|
33753
|
-
for (const attribute of ["src", "href", "poster"]) {
|
|
33754
|
-
const value = element.getAttribute(attribute);
|
|
33755
|
-
if (!value || value.startsWith("data:") || value.startsWith("#")) continue;
|
|
33756
|
-
const url = absolute(value);
|
|
33757
|
-
element.setAttribute(attribute, url);
|
|
33758
|
-
if (attribute !== "href" || element.tagName === "LINK") assetUrls.add(url);
|
|
33759
|
-
}
|
|
33760
|
-
const srcset = element.getAttribute("srcset");
|
|
33761
|
-
if (srcset) {
|
|
33762
|
-
element.setAttribute(
|
|
33763
|
-
"srcset",
|
|
33764
|
-
srcset.split(",").map((candidate) => {
|
|
33765
|
-
const [url, descriptor] = candidate.trim().split(/\s+/, 2);
|
|
33766
|
-
if (!url) return candidate;
|
|
33767
|
-
const resolved = absolute(url);
|
|
33768
|
-
assetUrls.add(resolved);
|
|
33769
|
-
return descriptor ? `${resolved} ${descriptor}` : resolved;
|
|
33770
|
-
}).join(", ")
|
|
33771
|
-
);
|
|
33772
|
-
}
|
|
33773
|
-
const style = element.getAttribute("style");
|
|
33774
|
-
if (style?.includes("url(")) {
|
|
33775
|
-
element.setAttribute(
|
|
33776
|
-
"style",
|
|
33777
|
-
style.replace(/url\((['"]?)([^'")]+)\1\)/g, (_match, quote, url) => {
|
|
33778
|
-
if (url.startsWith("data:")) return `url(${quote}${url}${quote})`;
|
|
33779
|
-
const resolved = absolute(url);
|
|
33780
|
-
assetUrls.add(resolved);
|
|
33781
|
-
return `url(${quote}${resolved}${quote})`;
|
|
33782
|
-
})
|
|
33783
|
-
);
|
|
33784
|
-
}
|
|
33785
|
-
}
|
|
33786
|
-
return {
|
|
33787
|
-
html: clone.outerHTML,
|
|
33788
|
-
assetUrls: Array.from(assetUrls),
|
|
33789
|
-
host: location.host,
|
|
33790
|
-
layoutContext,
|
|
33791
|
-
rootAttributes,
|
|
33792
|
-
bodyAttributes
|
|
33793
|
-
};
|
|
33794
|
-
};
|
|
33795
|
-
|
|
33796
|
-
// src/engine/landing-library/capture.ts
|
|
33797
|
-
async function captureSection(page, section) {
|
|
33798
|
-
const locator = page.locator(section.selector).first();
|
|
33799
|
-
if (await locator.count().catch(() => 0)) {
|
|
33800
|
-
const shot = await locator.screenshot({ type: "png", timeout: 15e3 }).catch(() => null);
|
|
33801
|
-
if (shot) return shot;
|
|
33802
|
-
}
|
|
33803
|
-
const doc = await page.evaluate(() => ({
|
|
33804
|
-
width: Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0),
|
|
33805
|
-
height: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0)
|
|
33806
|
-
}));
|
|
33807
|
-
const width = Math.min(section.rect.width, doc.width - section.rect.left);
|
|
33808
|
-
const height = Math.min(section.rect.height, doc.height - section.rect.top);
|
|
33809
|
-
if (width <= 0 || height <= 0) return null;
|
|
33810
|
-
return await page.screenshot({
|
|
33811
|
-
type: "png",
|
|
33812
|
-
clip: { x: section.rect.left, y: section.rect.top, width, height },
|
|
33813
|
-
timeout: 15e3
|
|
33814
|
-
}).catch(() => null);
|
|
33815
|
-
}
|
|
33816
|
-
async function captureSectionOnMobile(page, section) {
|
|
33817
|
-
const locator = page.locator(section.selector).first();
|
|
33818
|
-
if (!await locator.count().catch(() => 0)) return null;
|
|
33819
|
-
if (!await locator.isVisible().catch(() => false)) return null;
|
|
33820
|
-
return await locator.screenshot({ type: "png", timeout: 15e3 }).catch(() => null);
|
|
33821
|
-
}
|
|
33822
|
-
|
|
33823
|
-
// src/engine/landing-library/fidelity.ts
|
|
33824
|
-
import sharp3 from "sharp";
|
|
33825
|
-
var COMPARISON_SIZE = 32;
|
|
33826
|
-
function pixelSimilarity(a, b) {
|
|
33827
|
-
const length = Math.min(a.length, b.length);
|
|
33828
|
-
if (length === 0) return 0;
|
|
33829
|
-
let total = 0;
|
|
33830
|
-
for (let i = 0; i < length; i++) {
|
|
33831
|
-
total += Math.abs((a[i] ?? 0) - (b[i] ?? 0));
|
|
33832
|
-
}
|
|
33833
|
-
return 1 - total / (length * 255);
|
|
33834
|
-
}
|
|
33835
|
-
async function scoreFidelity(live, rendered) {
|
|
33836
|
-
const [liveMeta, renderedMeta] = await Promise.all([sharp3(live).metadata(), sharp3(rendered).metadata()]);
|
|
33837
|
-
const liveSize = { width: liveMeta.width ?? 0, height: liveMeta.height ?? 0 };
|
|
33838
|
-
const renderedSize = { width: renderedMeta.width ?? 0, height: renderedMeta.height ?? 0 };
|
|
33839
|
-
const [livePixels, renderedPixels] = await Promise.all([toGreyGrid(live), toGreyGrid(rendered)]);
|
|
33840
|
-
const score = pixelSimilarity(livePixels, renderedPixels);
|
|
33841
|
-
const heightRatio = liveSize.height > 0 && renderedSize.height > 0 ? Math.min(liveSize.height, renderedSize.height) / Math.max(liveSize.height, renderedSize.height) : 0;
|
|
33842
|
-
return {
|
|
33843
|
-
score,
|
|
33844
|
-
liveSize,
|
|
33845
|
-
renderedSize,
|
|
33846
|
-
...heightRatio < 0.8 ? { note: `height differs by ${Math.round((1 - heightRatio) * 100)}% \u2014 the bundle reflowed` } : {}
|
|
33847
|
-
};
|
|
33848
|
-
}
|
|
33849
|
-
async function toGreyGrid(image) {
|
|
33850
|
-
const raw = await sharp3(image).greyscale().resize(COMPARISON_SIZE, COMPARISON_SIZE, { fit: "fill" }).raw().toBuffer();
|
|
33851
|
-
return new Uint8Array(raw);
|
|
33852
|
-
}
|
|
33853
|
-
|
|
33854
|
-
// src/engine/landing-library/motion.ts
|
|
33855
|
-
var EMPTY_MOTION = {
|
|
33856
|
-
hasMotion: false,
|
|
33857
|
-
// `none` rather than "no motion": it is the `MOTION_KINDS` member for this,
|
|
33858
|
-
// so a section with no movement is findable by the same facet as everything
|
|
33859
|
-
// else instead of being a special case only prose describes.
|
|
33860
|
-
summary: "none",
|
|
33861
|
-
entrance: [],
|
|
33862
|
-
hover: [],
|
|
33863
|
-
scroll: [],
|
|
33864
|
-
loop: [],
|
|
33865
|
-
libraries: [],
|
|
33866
|
-
respectsReducedMotion: false
|
|
33867
|
-
};
|
|
33868
|
-
function isWorthFilming(motion) {
|
|
33869
|
-
return motion.entrance.length + motion.scroll.length + motion.loop.length > 0;
|
|
33870
|
-
}
|
|
33871
|
-
async function collectMotion(page, selector) {
|
|
33872
|
-
const raw = await page.evaluate(inPageCollectMotion, selector).catch(() => null);
|
|
33873
|
-
if (!raw) return EMPTY_MOTION;
|
|
33874
|
-
return { ...raw, summary: summarizeMotion(raw) };
|
|
33875
|
-
}
|
|
33876
|
-
var HOVER_TRANSFORMS = /* @__PURE__ */ new Set(["lift", "grow", "tilt", "shift", "spin", "shadow", "animate"]);
|
|
33877
|
-
var KEYFRAME_PATTERNS = [
|
|
33878
|
-
[/marquee|ticker|scroll(ing)?-?(x|left|right)/, "marquee"],
|
|
33879
|
-
[/parallax/, "parallax"],
|
|
33880
|
-
[/sticky|pin(ned)?/, "sticky-pin"],
|
|
33881
|
-
[/count(er|up)/, "counter"],
|
|
33882
|
-
[/zoom|scale|pulse|grow/, "zoom"],
|
|
33883
|
-
[/fade|opacity|appear/, "fade"],
|
|
33884
|
-
[/slide|translate|in-?(left|right|up|down)/, "slide-in"]
|
|
33885
|
-
];
|
|
33886
|
-
function toMotionKinds(kind, channel) {
|
|
33887
|
-
if (channel === "hover") return [HOVER_TRANSFORMS.has(kind) ? "hover-lift" : "fade"];
|
|
33888
|
-
const name = kind.toLowerCase();
|
|
33889
|
-
const matched = KEYFRAME_PATTERNS.filter(([pattern]) => pattern.test(name)).map(([, motionKind]) => motionKind);
|
|
33890
|
-
if (matched.length > 0) return matched;
|
|
33891
|
-
return [channel === "loop" ? "marquee" : "scroll-reveal"];
|
|
33892
|
-
}
|
|
33893
|
-
function motionKindsOf(motion) {
|
|
33894
|
-
const kinds = /* @__PURE__ */ new Set();
|
|
33895
|
-
const collect = (effects, channel) => {
|
|
33896
|
-
for (const effect of effects) {
|
|
33897
|
-
for (const kind of toMotionKinds(effect.kind, channel)) kinds.add(kind);
|
|
33898
|
-
}
|
|
33899
|
-
};
|
|
33900
|
-
collect(motion.entrance, "entrance");
|
|
33901
|
-
collect(motion.hover, "hover");
|
|
33902
|
-
collect(motion.scroll, "scroll");
|
|
33903
|
-
collect(motion.loop, "loop");
|
|
33904
|
-
const delays = new Set(motion.entrance.map((effect) => effect.delayMs ?? 0).filter((ms) => ms > 0));
|
|
33905
|
-
if (delays.size > 1) kinds.add("stagger");
|
|
33906
|
-
return [...kinds];
|
|
33907
|
-
}
|
|
33908
|
-
function summarizeMotion(motion) {
|
|
33909
|
-
const kinds = motionKindsOf(motion);
|
|
33910
|
-
if (kinds.length === 0 && motion.libraries.length === 0) return "none";
|
|
33911
|
-
const parts = [...kinds];
|
|
33912
|
-
if (motion.libraries.length > 0) parts.push(`via ${motion.libraries.join("/")}`);
|
|
33913
|
-
return parts.join(", ") + (motion.respectsReducedMotion ? "" : " (ignores reduced-motion)");
|
|
33914
|
-
}
|
|
33915
|
-
var inPageCollectMotion = (selector) => {
|
|
33916
|
-
const root = document.querySelector(selector);
|
|
33917
|
-
const empty = {
|
|
33918
|
-
hasMotion: false,
|
|
33919
|
-
entrance: [],
|
|
33920
|
-
hover: [],
|
|
33921
|
-
scroll: [],
|
|
33922
|
-
loop: [],
|
|
33923
|
-
libraries: [],
|
|
33924
|
-
respectsReducedMotion: false
|
|
33925
|
-
};
|
|
33926
|
-
if (!root) return empty;
|
|
33927
|
-
const entrance = [];
|
|
33928
|
-
const hover = [];
|
|
33929
|
-
const scroll = [];
|
|
33930
|
-
const loop = [];
|
|
33931
|
-
const keyframesByName = /* @__PURE__ */ new Map();
|
|
33932
|
-
let respectsReducedMotion = false;
|
|
33933
|
-
const inSection = (selectorText) => {
|
|
33934
|
-
for (const part of selectorText.split(",")) {
|
|
33935
|
-
const base = part.replace(/::[a-zA-Z-]+(\([^)]*\))?/g, "").replace(/:(hover|focus|focus-visible|focus-within|active|visited|target|checked|disabled)\b/g, "").trim();
|
|
33936
|
-
if (!base) continue;
|
|
33937
|
-
try {
|
|
33938
|
-
if (root.matches(base) || root.querySelector(base)) return true;
|
|
33939
|
-
} catch {
|
|
33940
|
-
return true;
|
|
33941
|
-
}
|
|
33942
|
-
}
|
|
33943
|
-
return false;
|
|
33944
|
-
};
|
|
33945
|
-
const toMs = (value) => {
|
|
33946
|
-
const first = value.split(",")[0]?.trim() ?? "";
|
|
33947
|
-
if (first.endsWith("ms")) return Number.parseFloat(first);
|
|
33948
|
-
if (first.endsWith("s")) return Number.parseFloat(first) * 1e3;
|
|
33949
|
-
return void 0;
|
|
33950
|
-
};
|
|
33951
|
-
const classifyKeyframes = (body) => {
|
|
33952
|
-
const text = body.toLowerCase();
|
|
33953
|
-
const fades = /opacity\s*:\s*0(\.0+)?\s*[;}]/.test(text);
|
|
33954
|
-
if (/translatey\(\s*-?\d/.test(text)) {
|
|
33955
|
-
const upward = /translatey\(\s*(\d|\.)/.test(text);
|
|
33956
|
-
return fades ? upward ? "fade-up" : "fade-down" : upward ? "slide-up" : "slide-down";
|
|
33957
|
-
}
|
|
33958
|
-
if (/translatex\(\s*-?\d/.test(text)) return fades ? "fade-in-x" : "slide-in-x";
|
|
33959
|
-
if (/scale\(/.test(text)) return fades ? "fade-zoom" : "zoom";
|
|
33960
|
-
if (/rotate\(/.test(text)) return "spin";
|
|
33961
|
-
if (fades) return "fade";
|
|
33962
|
-
return "animate";
|
|
33963
|
-
};
|
|
33964
|
-
const classifyHover = (style) => {
|
|
33965
|
-
const transform = style.transform ?? "";
|
|
33966
|
-
if (/translatey\(\s*-/i.test(transform)) return "lift";
|
|
33967
|
-
if (/scale\(\s*(1\.\d|[2-9])/i.test(transform)) return "grow";
|
|
33968
|
-
if (/rotate\(/i.test(transform)) return "tilt";
|
|
33969
|
-
if (transform && transform !== "none") return "shift";
|
|
33970
|
-
if (style.boxShadow) return "shadow";
|
|
33971
|
-
if (style.opacity) return "dim";
|
|
33972
|
-
if (style.backgroundColor || style.color) return "recolor";
|
|
33973
|
-
return null;
|
|
33974
|
-
};
|
|
33975
|
-
const readStyleRule = (rule, insideScrollTimeline) => {
|
|
33976
|
-
const style = rule.style;
|
|
33977
|
-
const isHover = /:hover\b/.test(rule.selectorText);
|
|
33978
|
-
if (isHover) {
|
|
33979
|
-
const kind = classifyHover(style);
|
|
33980
|
-
if (!kind || !inSection(rule.selectorText)) return;
|
|
33981
|
-
hover.push({
|
|
33982
|
-
kind,
|
|
33983
|
-
selector: rule.selectorText.slice(0, 120),
|
|
33984
|
-
durationMs: toMs(style.transitionDuration ?? ""),
|
|
33985
|
-
easing: style.transitionTimingFunction || void 0
|
|
33986
|
-
});
|
|
33987
|
-
return;
|
|
33988
|
-
}
|
|
33989
|
-
const animationName = style.animationName;
|
|
33990
|
-
if (!animationName || animationName === "none") return;
|
|
33991
|
-
if (!inSection(rule.selectorText)) return;
|
|
33992
|
-
const effect = {
|
|
33993
|
-
kind: animationName,
|
|
33994
|
-
selector: rule.selectorText.slice(0, 120),
|
|
33995
|
-
durationMs: toMs(style.animationDuration ?? ""),
|
|
33996
|
-
delayMs: toMs(style.animationDelay ?? ""),
|
|
33997
|
-
easing: style.animationTimingFunction || void 0
|
|
33998
|
-
};
|
|
33999
|
-
const infinite = (style.animationIterationCount ?? "").includes("infinite");
|
|
34000
|
-
const scrollDriven = insideScrollTimeline || Boolean(style.getPropertyValue("animation-timeline"));
|
|
34001
|
-
if (scrollDriven) scroll.push(effect);
|
|
34002
|
-
else if (infinite) loop.push(effect);
|
|
34003
|
-
else entrance.push(effect);
|
|
34004
|
-
};
|
|
34005
|
-
const walk = (rules, insideScrollTimeline) => {
|
|
34006
|
-
for (const rule of Array.from(rules)) {
|
|
34007
|
-
if (rule instanceof CSSKeyframesRule) {
|
|
34008
|
-
keyframesByName.set(rule.name, rule.cssText);
|
|
34009
|
-
continue;
|
|
34010
|
-
}
|
|
34011
|
-
if (rule instanceof CSSStyleRule) {
|
|
34012
|
-
try {
|
|
34013
|
-
readStyleRule(rule, insideScrollTimeline);
|
|
34014
|
-
} catch {
|
|
34015
|
-
}
|
|
34016
|
-
continue;
|
|
34017
|
-
}
|
|
34018
|
-
if (rule instanceof CSSMediaRule) {
|
|
34019
|
-
if (rule.conditionText.includes("prefers-reduced-motion")) {
|
|
34020
|
-
respectsReducedMotion = true;
|
|
34021
|
-
continue;
|
|
34022
|
-
}
|
|
34023
|
-
walk(rule.cssRules, insideScrollTimeline);
|
|
34024
|
-
continue;
|
|
34025
|
-
}
|
|
34026
|
-
const grouping = rule;
|
|
34027
|
-
if (grouping.cssRules) walk(grouping.cssRules, insideScrollTimeline);
|
|
34028
|
-
}
|
|
34029
|
-
};
|
|
34030
|
-
for (const sheet of Array.from(document.styleSheets)) {
|
|
34031
|
-
if (sheet.ownerNode?.hasAttribute?.("data-baker-freeze")) continue;
|
|
34032
|
-
try {
|
|
34033
|
-
walk(sheet.cssRules, false);
|
|
34034
|
-
} catch {
|
|
34035
|
-
}
|
|
34036
|
-
}
|
|
34037
|
-
for (const effect of [...entrance, ...loop, ...scroll]) {
|
|
34038
|
-
const body = keyframesByName.get(effect.kind);
|
|
34039
|
-
if (!body) continue;
|
|
34040
|
-
const classified = classifyKeyframes(body);
|
|
34041
|
-
effect.kind = loop.includes(effect) && classified.startsWith("slide") ? "marquee" : classified;
|
|
34042
|
-
}
|
|
34043
|
-
const libraries = [];
|
|
34044
|
-
const scoped = window ?? {};
|
|
34045
|
-
if (scoped.gsap || document.querySelector("[data-gsap]")) libraries.push("gsap");
|
|
34046
|
-
if (document.querySelector("[data-framer-name], [data-projection-id]")) libraries.push("framer-motion");
|
|
34047
|
-
if (document.querySelector("[data-aos]")) libraries.push("aos");
|
|
34048
|
-
if (scoped.Lenis || document.querySelector("[data-lenis]")) libraries.push("lenis");
|
|
34049
|
-
if (document.querySelector("[data-scroll], [data-scroll-container]")) libraries.push("locomotive");
|
|
34050
|
-
return {
|
|
34051
|
-
hasMotion: entrance.length + hover.length + scroll.length + loop.length + libraries.length > 0,
|
|
34052
|
-
entrance: entrance.slice(0, 12),
|
|
34053
|
-
hover: hover.slice(0, 12),
|
|
34054
|
-
scroll: scroll.slice(0, 12),
|
|
34055
|
-
loop: loop.slice(0, 12),
|
|
34056
|
-
libraries,
|
|
34057
|
-
respectsReducedMotion
|
|
34058
|
-
};
|
|
34059
|
-
};
|
|
34060
|
-
|
|
34061
|
-
// src/engine/landing-library/motionTake.ts
|
|
34062
|
-
import sharp4 from "sharp";
|
|
34063
|
-
|
|
34064
|
-
// src/engine/landing-library/prepare.ts
|
|
34065
|
-
var CONSENT_SELECTORS = [
|
|
34066
|
-
"#onetrust-accept-btn-handler",
|
|
34067
|
-
"#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll",
|
|
34068
|
-
"button#didomi-notice-agree-button",
|
|
34069
|
-
"[aria-label='Accept all']",
|
|
34070
|
-
"[data-testid='uc-accept-all-button']",
|
|
34071
|
-
".cc-allow",
|
|
34072
|
-
".cookie-accept"
|
|
34073
|
-
];
|
|
34074
|
-
var CONSENT_TEXTS = ["Accept all", "Accept All", "Allow all", "I agree", "Got it", "Aceptar todo"];
|
|
34075
|
-
var CONSENT_HOSTS = [
|
|
34076
|
-
"transcend-cdn.com",
|
|
34077
|
-
"cookielaw.org",
|
|
34078
|
-
"onetrust.com",
|
|
34079
|
-
"cookiebot.com",
|
|
34080
|
-
"osano.com",
|
|
34081
|
-
"trustarc.com",
|
|
34082
|
-
"truste.com",
|
|
34083
|
-
"usercentrics.eu",
|
|
34084
|
-
"didomi.io",
|
|
34085
|
-
"privacy-center.org",
|
|
34086
|
-
"iubenda.com",
|
|
34087
|
-
"termly.io",
|
|
34088
|
-
"cookieyes.com",
|
|
34089
|
-
"sp-prod.net",
|
|
34090
|
-
"quantcast.com",
|
|
34091
|
-
"consensu.org",
|
|
34092
|
-
"ketch.com",
|
|
34093
|
-
"secureprivacy.ai",
|
|
34094
|
-
"civicuk.com"
|
|
34095
|
-
];
|
|
34096
|
-
async function blockConsentManagers(page) {
|
|
34097
|
-
await page.route("**/*", (route) => {
|
|
34098
|
-
let host = "";
|
|
34099
|
-
try {
|
|
34100
|
-
host = new URL(route.request().url()).host;
|
|
34101
|
-
} catch {
|
|
34102
|
-
return route.continue();
|
|
34103
|
-
}
|
|
34104
|
-
const isConsentVendor = CONSENT_HOSTS.some((vendor) => host === vendor || host.endsWith(`.${vendor}`));
|
|
34105
|
-
return isConsentVendor ? route.abort() : route.continue();
|
|
34106
|
-
});
|
|
34107
|
-
}
|
|
34108
|
-
var ignore = () => void 0;
|
|
34109
|
-
async function preparePage(page, url, timeoutMs) {
|
|
34110
|
-
const response = await page.goto(url, { waitUntil: "domcontentloaded", timeout: timeoutMs });
|
|
34111
|
-
const status = response?.status() ?? null;
|
|
34112
|
-
await page.waitForLoadState("networkidle", { timeout: 8e3 }).catch(ignore);
|
|
34113
|
-
await dismissConsent(page, 4e3);
|
|
34114
|
-
await scrollThroughPage(page);
|
|
34115
|
-
await dismissConsent(page, 1e3);
|
|
34116
|
-
await page.evaluate(async () => {
|
|
34117
|
-
await document.fonts.ready;
|
|
34118
|
-
});
|
|
34119
|
-
await freezeMotion(page);
|
|
34120
|
-
await unpinOverlays(page);
|
|
34121
|
-
const measured = await page.evaluate(() => ({
|
|
34122
|
-
finalUrl: location.href,
|
|
34123
|
-
title: document.title,
|
|
34124
|
-
documentHeight: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0),
|
|
34125
|
-
bodyText: (document.body?.innerText ?? "").slice(0, 2e3)
|
|
34126
|
-
}));
|
|
34127
|
-
return { ...measured, status };
|
|
34128
|
-
}
|
|
34129
|
-
async function settleConsent(page) {
|
|
34130
|
-
await dismissConsent(page, 4e3);
|
|
34131
|
-
await page.mouse.wheel(0, 400).catch(ignore);
|
|
34132
|
-
await page.waitForTimeout(1500);
|
|
34133
|
-
await dismissConsent(page, 2e3);
|
|
34134
|
-
await page.evaluate(() => window.scrollTo(0, 0)).catch(ignore);
|
|
34135
|
-
}
|
|
34136
|
-
async function dismissConsent(page, waitForBannerMs) {
|
|
34137
|
-
await page.locator(CONSENT_SELECTORS.join(", ")).first().waitFor({ state: "attached", timeout: waitForBannerMs }).catch(ignore);
|
|
34138
|
-
for (const selector of CONSENT_SELECTORS) {
|
|
34139
|
-
const found = page.locator(selector).first();
|
|
34140
|
-
if (!await found.count().catch(() => 0)) continue;
|
|
34141
|
-
await found.click({ timeout: 2e3, force: true }).catch(ignore);
|
|
34142
|
-
await page.waitForTimeout(400);
|
|
34143
|
-
return;
|
|
34144
|
-
}
|
|
34145
|
-
for (const text of CONSENT_TEXTS) {
|
|
34146
|
-
const button = page.getByRole("button", { name: text, exact: false }).first();
|
|
34147
|
-
if (!await button.count().catch(() => 0)) continue;
|
|
34148
|
-
if (!await button.isVisible().catch(() => false)) continue;
|
|
34149
|
-
await button.click({ timeout: 2e3, force: true }).catch(ignore);
|
|
34150
|
-
await page.waitForTimeout(400);
|
|
34151
|
-
return;
|
|
34152
|
-
}
|
|
34153
|
-
}
|
|
34154
|
-
async function scrollThroughPage(page) {
|
|
34155
|
-
await page.evaluate(async () => {
|
|
34156
|
-
const step = 600;
|
|
34157
|
-
const pause = () => new Promise((resolve5) => setTimeout(resolve5, 250));
|
|
34158
|
-
for (let i = 0; i < 40; i++) {
|
|
34159
|
-
window.scrollBy(0, step);
|
|
34160
|
-
await pause();
|
|
34161
|
-
const reachedBottom = window.scrollY + window.innerHeight >= document.documentElement.scrollHeight - 2;
|
|
34162
|
-
if (reachedBottom) break;
|
|
34163
|
-
}
|
|
34164
|
-
window.scrollTo(0, 0);
|
|
34165
|
-
await pause();
|
|
34166
|
-
});
|
|
34167
|
-
await page.waitForTimeout(500);
|
|
34168
|
-
}
|
|
34169
|
-
var MAX_HEADER_HEIGHT = 220;
|
|
34170
|
-
async function unpinOverlays(page) {
|
|
34171
|
-
await page.evaluate((maxHeaderHeight) => {
|
|
34172
|
-
window.scrollTo(0, 0);
|
|
34173
|
-
for (const element of Array.from(document.querySelectorAll("*"))) {
|
|
34174
|
-
const position = getComputedStyle(element).position;
|
|
34175
|
-
if (position === "sticky") {
|
|
34176
|
-
element.style.setProperty("position", "static", "important");
|
|
34177
|
-
continue;
|
|
34178
|
-
}
|
|
34179
|
-
if (position !== "fixed") continue;
|
|
34180
|
-
const rect = element.getBoundingClientRect();
|
|
34181
|
-
const isTopAnchoredHeader = rect.top <= 8 && rect.height > 0 && rect.height <= maxHeaderHeight;
|
|
34182
|
-
if (isTopAnchoredHeader) {
|
|
34183
|
-
element.style.setProperty("position", "absolute", "important");
|
|
34184
|
-
element.style.setProperty("bottom", "auto", "important");
|
|
34185
|
-
} else {
|
|
34186
|
-
element.style.setProperty("display", "none", "important");
|
|
34187
|
-
}
|
|
34188
|
-
}
|
|
34189
|
-
}, MAX_HEADER_HEIGHT);
|
|
34190
|
-
await page.waitForTimeout(250);
|
|
34191
|
-
}
|
|
34192
|
-
async function freezeMotion(page) {
|
|
34193
|
-
await page.evaluate(() => {
|
|
34194
|
-
const highestTimer = window.setTimeout(() => void 0, 0);
|
|
34195
|
-
for (let id = 1; id <= highestTimer; id++) window.clearInterval(id);
|
|
34196
|
-
const style = document.createElement("style");
|
|
34197
|
-
style.setAttribute("data-baker-freeze", "true");
|
|
34198
|
-
style.textContent = `*, *::before, *::after {
|
|
34199
|
-
animation-play-state: paused !important;
|
|
34200
|
-
animation-delay: 0s !important;
|
|
34201
|
-
transition: none !important;
|
|
34202
|
-
}`;
|
|
34203
|
-
document.head.appendChild(style);
|
|
34204
|
-
for (const video of Array.from(document.querySelectorAll("video"))) {
|
|
34205
|
-
video.pause();
|
|
34206
|
-
}
|
|
34207
|
-
});
|
|
34208
|
-
await page.waitForTimeout(250);
|
|
34209
|
-
}
|
|
34210
|
-
|
|
34211
|
-
// src/engine/landing-library/geometry.ts
|
|
34212
|
-
function adaptiveGapThreshold(bands) {
|
|
34213
|
-
const sorted = [...bands].sort((a, b) => a.top - b.top);
|
|
34214
|
-
const gaps = [];
|
|
34215
|
-
for (let i = 0; i < sorted.length - 1; i++) {
|
|
34216
|
-
const current = sorted[i];
|
|
34217
|
-
const next = sorted[i + 1];
|
|
34218
|
-
if (!current || !next) continue;
|
|
34219
|
-
if (next.top > current.bottom) {
|
|
34220
|
-
const gap = next.top - current.bottom;
|
|
34221
|
-
if (gap < 200) gaps.push(gap);
|
|
34222
|
-
}
|
|
34223
|
-
}
|
|
34224
|
-
gaps.sort((a, b) => a - b);
|
|
34225
|
-
const p75 = gaps[Math.floor(gaps.length * 0.75)];
|
|
34226
|
-
if (p75 === void 0) return 15;
|
|
34227
|
-
return Math.max(10, Math.min(50, p75));
|
|
34228
|
-
}
|
|
34229
|
-
function bandsCollide(a, b, margin, maxGap) {
|
|
34230
|
-
const shrunkA = { top: a.top + margin, bottom: a.bottom - margin };
|
|
34231
|
-
const shrunkB = { top: b.top + margin, bottom: b.bottom - margin };
|
|
34232
|
-
const overlaps = shrunkA.top <= shrunkB.bottom && shrunkA.bottom >= shrunkB.top || shrunkB.top <= shrunkA.bottom && shrunkB.bottom >= shrunkA.top;
|
|
34233
|
-
if (overlaps) return true;
|
|
34234
|
-
const gap = Math.min(Math.abs(shrunkA.bottom - shrunkB.top), Math.abs(shrunkB.bottom - shrunkA.top));
|
|
34235
|
-
return gap <= maxGap;
|
|
34236
|
-
}
|
|
34237
|
-
function shouldPromoteToParent(group) {
|
|
34238
|
-
const isRunt = group.childCount < 3 || group.height < 80;
|
|
34239
|
-
return isRunt && group.parentHeight < 1600;
|
|
34240
|
-
}
|
|
34241
|
-
|
|
34242
|
-
// src/engine/landing-library/segment.ts
|
|
34243
|
-
async function installPageRuntime(page) {
|
|
34244
|
-
await page.addInitScript({
|
|
34245
|
-
content: `
|
|
34246
|
-
window.__name = window.__name || function (target) { return target; };
|
|
34247
|
-
window.__bakerGeom = {
|
|
34248
|
-
adaptiveGapThreshold: ${adaptiveGapThreshold.toString()},
|
|
34249
|
-
bandsCollide: ${bandsCollide.toString()},
|
|
34250
|
-
shouldPromoteToParent: ${shouldPromoteToParent.toString()},
|
|
34251
|
-
};`
|
|
34252
|
-
});
|
|
34253
|
-
}
|
|
34254
|
-
async function segmentPage(page, options) {
|
|
34255
|
-
return await page.evaluate(inPageSegment, options);
|
|
34256
|
-
}
|
|
34257
|
-
var inPageSegment = (options) => {
|
|
34258
|
-
const geom = window.__bakerGeom;
|
|
34259
|
-
const scrollX = window.scrollX;
|
|
34260
|
-
const scrollY = window.scrollY;
|
|
34261
|
-
const docWidth = Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0);
|
|
34262
|
-
const docHeight = Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0);
|
|
34263
|
-
const rectOf = (element) => {
|
|
34264
|
-
const r = element.getBoundingClientRect();
|
|
34265
|
-
const top = Math.max(0, Math.round(r.top + scrollY));
|
|
34266
|
-
const left = Math.max(0, Math.round(r.left + scrollX));
|
|
34267
|
-
return {
|
|
34268
|
-
top,
|
|
34269
|
-
left,
|
|
34270
|
-
bottom: Math.min(docHeight, Math.round(r.bottom + scrollY)),
|
|
34271
|
-
right: Math.min(docWidth, Math.round(r.right + scrollX))
|
|
34272
|
-
};
|
|
34273
|
-
};
|
|
34274
|
-
const syntheticRect = (element) => {
|
|
34275
|
-
const children = Array.from(element.children);
|
|
34276
|
-
if (children.length === 0) return null;
|
|
34277
|
-
let top = Number.POSITIVE_INFINITY;
|
|
34278
|
-
let left = Number.POSITIVE_INFINITY;
|
|
34279
|
-
let bottom = Number.NEGATIVE_INFINITY;
|
|
34280
|
-
let right = Number.NEGATIVE_INFINITY;
|
|
34281
|
-
for (const child of children) {
|
|
34282
|
-
const r = rectOf(child);
|
|
34283
|
-
if (r.bottom - r.top <= 0 && r.right - r.left <= 0) continue;
|
|
34284
|
-
top = Math.min(top, r.top);
|
|
34285
|
-
left = Math.min(left, r.left);
|
|
34286
|
-
bottom = Math.max(bottom, r.bottom);
|
|
34287
|
-
right = Math.max(right, r.right);
|
|
34288
|
-
}
|
|
34289
|
-
if (!Number.isFinite(top) || !Number.isFinite(bottom)) return null;
|
|
34290
|
-
return { top, left, bottom, right };
|
|
34291
|
-
};
|
|
34292
|
-
const boxOf = (element) => {
|
|
34293
|
-
const style = getComputedStyle(element);
|
|
34294
|
-
const r = style.display === "contents" ? syntheticRect(element) : rectOf(element);
|
|
34295
|
-
if (!r) return null;
|
|
34296
|
-
return { ...r, height: r.bottom - r.top, width: r.right - r.left };
|
|
34297
|
-
};
|
|
34298
|
-
const hasHiddenAncestor = (element) => {
|
|
34299
|
-
let current = element;
|
|
34300
|
-
while (current && current !== document.documentElement) {
|
|
34301
|
-
const style = getComputedStyle(current);
|
|
34302
|
-
if (style.display === "none" || style.visibility === "hidden" || Number(style.opacity) === 0) return true;
|
|
34303
|
-
current = current.parentElement;
|
|
34304
|
-
}
|
|
34305
|
-
return false;
|
|
34306
|
-
};
|
|
34307
|
-
const isClippedByAncestor = (element, box) => {
|
|
34308
|
-
let parent = element.parentElement;
|
|
34309
|
-
while (parent && parent !== document.documentElement) {
|
|
34310
|
-
const style = getComputedStyle(parent);
|
|
34311
|
-
const clips = style.overflow === "hidden" || style.overflowX === "hidden" || style.overflowY === "hidden" || style.overflow === "clip";
|
|
34312
|
-
if (clips) {
|
|
34313
|
-
const p = rectOf(parent);
|
|
34314
|
-
const intersects = box.left < p.right && box.right > p.left && box.top < p.bottom && box.bottom > p.top;
|
|
34315
|
-
if (!intersects) return true;
|
|
34316
|
-
}
|
|
34317
|
-
parent = parent.parentElement;
|
|
34318
|
-
}
|
|
34319
|
-
return false;
|
|
34320
|
-
};
|
|
34321
|
-
const directText = (element) => {
|
|
34322
|
-
let text = "";
|
|
34323
|
-
for (const node of Array.from(element.childNodes)) {
|
|
34324
|
-
if (node.nodeType === 3) text += node.textContent ?? "";
|
|
34325
|
-
}
|
|
34326
|
-
return text.trim();
|
|
34327
|
-
};
|
|
34328
|
-
const NON_COPY_TAGS = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "NOSCRIPT", "TEMPLATE"]);
|
|
34329
|
-
const visibleText = (root) => {
|
|
34330
|
-
let text = "";
|
|
34331
|
-
const hiddenCache = /* @__PURE__ */ new Map();
|
|
34332
|
-
const isHidden = (element) => {
|
|
34333
|
-
const cached = hiddenCache.get(element);
|
|
34334
|
-
if (cached !== void 0) return cached;
|
|
34335
|
-
const style = getComputedStyle(element);
|
|
34336
|
-
const hidden = style.display === "none" || style.visibility === "hidden";
|
|
34337
|
-
hiddenCache.set(element, hidden);
|
|
34338
|
-
return hidden;
|
|
34339
|
-
};
|
|
34340
|
-
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
34341
|
-
acceptNode: (node) => {
|
|
34342
|
-
let parent = node.parentElement;
|
|
34343
|
-
while (parent) {
|
|
34344
|
-
if (NON_COPY_TAGS.has(parent.tagName) || isHidden(parent)) return NodeFilter.FILTER_REJECT;
|
|
34345
|
-
if (parent === root) break;
|
|
34346
|
-
parent = parent.parentElement;
|
|
34347
|
-
}
|
|
34348
|
-
return NodeFilter.FILTER_ACCEPT;
|
|
34349
|
-
}
|
|
34350
|
-
});
|
|
34351
|
-
while (walker.nextNode() && text.length < 400) {
|
|
34352
|
-
text += ` ${walker.currentNode.textContent ?? ""}`;
|
|
34353
|
-
}
|
|
34354
|
-
return text.replace(/\s+/g, " ").trim();
|
|
34355
|
-
};
|
|
34356
|
-
const GRAPHIC_TAGS = /* @__PURE__ */ new Set(["img", "svg", "video", "picture", "canvas", "iframe"]);
|
|
34357
|
-
const hasBackgroundImage = (element) => {
|
|
34358
|
-
const bg = getComputedStyle(element).backgroundImage;
|
|
34359
|
-
return Boolean(bg) && bg !== "none" && bg.includes("url(");
|
|
34360
|
-
};
|
|
34361
|
-
const isContentLeaf = (element) => {
|
|
34362
|
-
const tag = element.tagName.toLowerCase();
|
|
34363
|
-
if (GRAPHIC_TAGS.has(tag)) return true;
|
|
34364
|
-
if (directText(element).length > 0) return true;
|
|
34365
|
-
return hasBackgroundImage(element);
|
|
34366
|
-
};
|
|
34367
|
-
const selectorFor = (element) => {
|
|
34368
|
-
const parts = [];
|
|
34369
|
-
let current = element;
|
|
34370
|
-
while (current && current !== document.documentElement) {
|
|
34371
|
-
const tag = current.tagName.toLowerCase();
|
|
34372
|
-
if (tag === "body") {
|
|
34373
|
-
parts.unshift("body");
|
|
34374
|
-
break;
|
|
34375
|
-
}
|
|
34376
|
-
const parent = current.parentElement;
|
|
34377
|
-
if (!parent) {
|
|
34378
|
-
parts.unshift(tag);
|
|
34379
|
-
break;
|
|
34380
|
-
}
|
|
34381
|
-
const sameTag = Array.from(parent.children).filter((c) => c.tagName === current?.tagName);
|
|
34382
|
-
const position = sameTag.indexOf(current) + 1;
|
|
34383
|
-
parts.unshift(sameTag.length > 1 ? `${tag}:nth-of-type(${position})` : tag);
|
|
34384
|
-
current = parent;
|
|
34385
|
-
}
|
|
34386
|
-
return parts.join(" > ");
|
|
34387
|
-
};
|
|
34388
|
-
const leaves = [];
|
|
34389
|
-
const all = document.querySelectorAll("*");
|
|
34390
|
-
const limit = Math.min(all.length, options.maxElements);
|
|
34391
|
-
for (let i = 0; i < limit; i++) {
|
|
34392
|
-
const element = all[i];
|
|
34393
|
-
if (!element) continue;
|
|
34394
|
-
const tag = element.tagName.toLowerCase();
|
|
34395
|
-
if (tag === "script" || tag === "style" || tag === "noscript" || tag === "link" || tag === "head") continue;
|
|
34396
|
-
if (!isContentLeaf(element)) continue;
|
|
34397
|
-
const box = boxOf(element);
|
|
34398
|
-
if (!box || box.height <= 0 || box.width <= 0) continue;
|
|
34399
|
-
if (box.height > options.maxSectionHeight) continue;
|
|
34400
|
-
if (hasHiddenAncestor(element)) continue;
|
|
34401
|
-
if (isClippedByAncestor(element, box)) continue;
|
|
34402
|
-
leaves.push({
|
|
34403
|
-
element,
|
|
34404
|
-
top: box.top,
|
|
34405
|
-
bottom: box.bottom,
|
|
34406
|
-
left: box.left,
|
|
34407
|
-
right: box.right,
|
|
34408
|
-
height: box.height
|
|
34409
|
-
});
|
|
34410
|
-
}
|
|
34411
|
-
if (leaves.length === 0) return [];
|
|
34412
|
-
const gapThreshold = geom.adaptiveGapThreshold(leaves.map((l) => ({ top: l.top, bottom: l.bottom })));
|
|
34413
|
-
const commonAncestor = (elements) => {
|
|
34414
|
-
let ancestor = elements[0] ?? null;
|
|
34415
|
-
while (ancestor && !elements.every((e) => ancestor?.contains(e))) {
|
|
34416
|
-
ancestor = ancestor.parentElement;
|
|
34417
|
-
}
|
|
34418
|
-
return ancestor;
|
|
34419
|
-
};
|
|
34420
|
-
const abandon = (members) => members.map((m) => ({ ...m, sealed: true }));
|
|
34421
|
-
const findCoveringAncestor = (start, bounds) => {
|
|
34422
|
-
let element = start;
|
|
34423
|
-
while (element) {
|
|
34424
|
-
const box = boxOf(element);
|
|
34425
|
-
if (!box) return null;
|
|
34426
|
-
if (box.height > options.maxSectionHeight) return null;
|
|
34427
|
-
const covers = box.top <= bounds.top && box.bottom >= bounds.bottom && box.left <= bounds.left && box.right >= bounds.right;
|
|
34428
|
-
if (covers) return { element, box };
|
|
34429
|
-
if (!element.parentElement || element.parentElement === document.documentElement) return null;
|
|
34430
|
-
element = element.parentElement;
|
|
34431
|
-
}
|
|
34432
|
-
return null;
|
|
34433
|
-
};
|
|
34434
|
-
const mergeBucket = (members) => {
|
|
34435
|
-
const bounds = {
|
|
34436
|
-
top: Math.min(...members.map((m) => m.top)),
|
|
34437
|
-
bottom: Math.max(...members.map((m) => m.bottom)),
|
|
34438
|
-
left: Math.min(...members.map((m) => m.left)),
|
|
34439
|
-
right: Math.max(...members.map((m) => m.right))
|
|
34440
|
-
};
|
|
34441
|
-
const covering = findCoveringAncestor(commonAncestor(members.map((m) => m.root)), bounds);
|
|
34442
|
-
if (!covering) return abandon(members);
|
|
34443
|
-
return [
|
|
34444
|
-
{
|
|
34445
|
-
root: covering.element,
|
|
34446
|
-
top: covering.box.top,
|
|
34447
|
-
bottom: covering.box.bottom,
|
|
34448
|
-
left: covering.box.left,
|
|
34449
|
-
right: covering.box.right,
|
|
34450
|
-
height: covering.box.height,
|
|
34451
|
-
leaves: members.flatMap((m) => m.leaves),
|
|
34452
|
-
sealed: false
|
|
34453
|
-
}
|
|
34454
|
-
];
|
|
34455
|
-
};
|
|
34456
|
-
const clusterOnce = (groups2) => {
|
|
34457
|
-
const buckets = [];
|
|
34458
|
-
for (const group of groups2) {
|
|
34459
|
-
if (group.sealed) {
|
|
34460
|
-
buckets.push([group]);
|
|
34461
|
-
continue;
|
|
34462
|
-
}
|
|
34463
|
-
const target = buckets.find(
|
|
34464
|
-
(bucket) => bucket.some(
|
|
34465
|
-
(member) => !member.sealed && geom.bandsCollide(
|
|
34466
|
-
{ top: member.top, bottom: member.bottom },
|
|
34467
|
-
{ top: group.top, bottom: group.bottom },
|
|
34468
|
-
options.collisionMargin,
|
|
34469
|
-
gapThreshold
|
|
34470
|
-
)
|
|
34471
|
-
)
|
|
34472
|
-
);
|
|
34473
|
-
if (target) target.push(group);
|
|
34474
|
-
else buckets.push([group]);
|
|
34475
|
-
}
|
|
34476
|
-
if (buckets.length === groups2.length) return groups2;
|
|
34477
|
-
return buckets.flatMap((bucket) => bucket.length === 1 ? [bucket[0]] : mergeBucket(bucket));
|
|
34478
|
-
};
|
|
34479
|
-
let groups = leaves.map((leaf) => ({
|
|
34480
|
-
root: leaf.element,
|
|
34481
|
-
top: leaf.top,
|
|
34482
|
-
bottom: leaf.bottom,
|
|
34483
|
-
left: leaf.left,
|
|
34484
|
-
right: leaf.right,
|
|
34485
|
-
height: leaf.height,
|
|
34486
|
-
leaves: [leaf],
|
|
34487
|
-
sealed: false
|
|
34488
|
-
}));
|
|
34489
|
-
for (let pass = 0; pass < 40; pass++) {
|
|
34490
|
-
const next = clusterOnce(groups);
|
|
34491
|
-
if (next.length === groups.length) break;
|
|
34492
|
-
groups = next;
|
|
34493
|
-
}
|
|
34494
|
-
for (let pass = 0; pass < 10; pass++) {
|
|
34495
|
-
let promoted = false;
|
|
34496
|
-
groups = groups.map((group) => {
|
|
34497
|
-
const parent = group.root.parentElement;
|
|
34498
|
-
if (!parent || parent === document.documentElement || parent === document.body) return group;
|
|
34499
|
-
const parentBox = boxOf(parent);
|
|
34500
|
-
if (!parentBox) return group;
|
|
34501
|
-
if (!geom.shouldPromoteToParent({
|
|
34502
|
-
childCount: group.leaves.length,
|
|
34503
|
-
height: group.height,
|
|
34504
|
-
parentHeight: parentBox.height
|
|
34505
|
-
})) {
|
|
34506
|
-
return group;
|
|
34507
|
-
}
|
|
34508
|
-
promoted = true;
|
|
34509
|
-
return {
|
|
34510
|
-
...group,
|
|
34511
|
-
root: parent,
|
|
34512
|
-
top: parentBox.top,
|
|
34513
|
-
bottom: parentBox.bottom,
|
|
34514
|
-
left: parentBox.left,
|
|
34515
|
-
right: parentBox.right,
|
|
34516
|
-
height: parentBox.height
|
|
34517
|
-
};
|
|
34518
|
-
});
|
|
34519
|
-
if (!promoted) break;
|
|
34520
|
-
groups = clusterOnce(groups);
|
|
34521
|
-
}
|
|
34522
|
-
const sameBox = (a, b) => Math.abs(a.top - b.top) <= 4 && Math.abs(a.bottom - b.bottom) <= 4 && Math.abs(a.left - b.left) <= 4 && Math.abs(a.right - b.right) <= 4;
|
|
34523
|
-
const containsBox = (outer, inner) => outer.top - 4 <= inner.top && outer.bottom + 4 >= inner.bottom && outer.left - 4 <= inner.left && outer.right + 4 >= inner.right;
|
|
34524
|
-
const deduped = [];
|
|
34525
|
-
const area = (g) => (g.right - g.left) * g.height;
|
|
34526
|
-
for (const group of groups.slice().sort((a, b) => area(b) - area(a) || b.leaves.length - a.leaves.length)) {
|
|
34527
|
-
const duplicate = deduped.some(
|
|
34528
|
-
(kept) => kept.root === group.root || kept.root.contains(group.root) || sameBox(kept, group) || containsBox(kept, group)
|
|
34529
|
-
);
|
|
34530
|
-
if (!duplicate) deduped.push(group);
|
|
34531
|
-
}
|
|
34532
|
-
return deduped.filter((group) => group.height > 0 && group.right - group.left > 0).sort((a, b) => a.top - b.top).map((group, index) => ({
|
|
34533
|
-
index,
|
|
34534
|
-
selector: selectorFor(group.root),
|
|
34535
|
-
rect: {
|
|
34536
|
-
top: group.top,
|
|
34537
|
-
left: group.left,
|
|
34538
|
-
width: group.right - group.left,
|
|
34539
|
-
height: group.height
|
|
34540
|
-
},
|
|
34541
|
-
leafCount: group.leaves.length,
|
|
34542
|
-
textPreview: visibleText(group.root).slice(0, 200)
|
|
34543
|
-
}));
|
|
34544
|
-
};
|
|
34545
|
-
|
|
34546
|
-
// src/engine/landing-library/motionTake.ts
|
|
34547
|
-
var FRAME_TIMES_MS = [0, 120, 260, 450, 800, 1400];
|
|
34548
|
-
var FRAME_WIDTH = 460;
|
|
34549
|
-
var GRID_COLUMNS = 3;
|
|
34550
|
-
var LABEL_HEIGHT = 22;
|
|
34551
|
-
async function captureMotionTake(browser, url, selector, timeoutMs = 45e3) {
|
|
34552
|
-
const { context, page } = await newPage(browser, DESKTOP_VIEWPORT, { motion: true });
|
|
34553
|
-
try {
|
|
34554
|
-
await blockConsentManagers(page);
|
|
34555
|
-
await installPageRuntime(page);
|
|
34556
|
-
await page.goto(url, { waitUntil: "domcontentloaded", timeout: timeoutMs });
|
|
34557
|
-
await page.waitForLoadState("networkidle", { timeout: 8e3 }).catch(() => void 0);
|
|
34558
|
-
await settleConsent(page);
|
|
34559
|
-
const target = page.locator(selector).first();
|
|
34560
|
-
if (!await target.count().catch(() => 0)) return null;
|
|
34561
|
-
await page.evaluate((sectionSelector) => {
|
|
34562
|
-
const element = document.querySelector(sectionSelector);
|
|
34563
|
-
if (!element) return;
|
|
34564
|
-
const top = element.getBoundingClientRect().top + window.scrollY;
|
|
34565
|
-
window.scrollTo(0, Math.max(0, top - window.innerHeight - 200));
|
|
34566
|
-
}, selector);
|
|
34567
|
-
await page.waitForTimeout(600);
|
|
34568
|
-
await page.evaluate((sectionSelector) => {
|
|
34569
|
-
document.querySelector(sectionSelector)?.scrollIntoView({ block: "center" });
|
|
34570
|
-
}, selector);
|
|
34571
|
-
const frames = [];
|
|
34572
|
-
let previous = 0;
|
|
34573
|
-
for (const time of FRAME_TIMES_MS) {
|
|
34574
|
-
await page.waitForTimeout(Math.max(0, time - previous));
|
|
34575
|
-
previous = time;
|
|
34576
|
-
const shot = await page.screenshot({ type: "png", timeout: 1e4 }).catch(() => null);
|
|
34577
|
-
if (shot) frames.push(shot);
|
|
34578
|
-
}
|
|
34579
|
-
if (frames.length === 0) return null;
|
|
34580
|
-
return { filmstrip: await composeFilmstrip(frames), frameCount: frames.length };
|
|
34581
|
-
} catch {
|
|
34582
|
-
return null;
|
|
34583
|
-
} finally {
|
|
34584
|
-
await context.close();
|
|
34585
|
-
}
|
|
34586
|
-
}
|
|
34587
|
-
async function composeFilmstrip(frames) {
|
|
34588
|
-
const scaled = await Promise.all(frames.map((frame) => sharp4(frame).resize({ width: FRAME_WIDTH }).png().toBuffer()));
|
|
34589
|
-
const first = await sharp4(scaled[0]).metadata();
|
|
34590
|
-
const frameHeight = first.height ?? 300;
|
|
34591
|
-
const cellHeight = frameHeight + LABEL_HEIGHT;
|
|
34592
|
-
const rows = Math.ceil(scaled.length / GRID_COLUMNS);
|
|
34593
|
-
const width = FRAME_WIDTH * Math.min(GRID_COLUMNS, scaled.length);
|
|
34594
|
-
const composites = scaled.flatMap((frame, index) => {
|
|
34595
|
-
const column = index % GRID_COLUMNS;
|
|
34596
|
-
const row = Math.floor(index / GRID_COLUMNS);
|
|
34597
|
-
const label = Buffer.from(
|
|
34598
|
-
`<svg width="${FRAME_WIDTH}" height="${LABEL_HEIGHT}">
|
|
34599
|
-
<rect width="100%" height="100%" fill="#111827"/>
|
|
34600
|
-
<text x="8" y="15" font-family="monospace" font-size="12" fill="#f9fafb">+${FRAME_TIMES_MS[index] ?? 0}ms</text>
|
|
34601
|
-
</svg>`
|
|
34602
|
-
);
|
|
34603
|
-
return [
|
|
34604
|
-
{ input: label, left: column * FRAME_WIDTH, top: row * cellHeight },
|
|
34605
|
-
{ input: frame, left: column * FRAME_WIDTH, top: row * cellHeight + LABEL_HEIGHT }
|
|
34606
|
-
];
|
|
34607
|
-
});
|
|
34608
|
-
return await sharp4({
|
|
34609
|
-
create: {
|
|
34610
|
-
width,
|
|
34611
|
-
height: cellHeight * rows,
|
|
34612
|
-
channels: 3,
|
|
34613
|
-
background: { r: 17, g: 24, b: 39 }
|
|
34614
|
-
}
|
|
34615
|
-
}).composite(composites).png().toBuffer();
|
|
34616
|
-
}
|
|
34617
|
-
|
|
34618
|
-
// src/engine/landing-library/renderBundle.ts
|
|
34619
|
-
var SETTLE_ANIMATIONS_CSS = `*, *::before, *::after {
|
|
34620
|
-
animation-play-state: running !important;
|
|
34621
|
-
animation-delay: 0s !important;
|
|
34622
|
-
animation-duration: 1ms !important;
|
|
34623
|
-
animation-iteration-count: 1 !important;
|
|
34624
|
-
animation-fill-mode: forwards !important;
|
|
34625
|
-
transition: none !important;
|
|
34626
|
-
}`;
|
|
34627
|
-
async function renderBundleToPng(browser, html, viewportWidth, options = {}) {
|
|
34628
|
-
const { wholePage = false, timeoutMs = 2e4 } = options;
|
|
34629
|
-
const { context, page } = await newPage(browser, { width: viewportWidth, height: 900 });
|
|
34630
|
-
try {
|
|
34631
|
-
await page.setContent(html, { waitUntil: "load", timeout: timeoutMs });
|
|
34632
|
-
await page.addStyleTag({ content: SETTLE_ANIMATIONS_CSS });
|
|
34633
|
-
await page.evaluate(async () => {
|
|
34634
|
-
await document.fonts.ready;
|
|
34635
|
-
});
|
|
34636
|
-
await page.waitForTimeout(300);
|
|
34637
|
-
if (!wholePage) {
|
|
34638
|
-
for (const selector of [`[${SECTION_ROOT_ATTRIBUTE}]`, "body > *"]) {
|
|
34639
|
-
const target = page.locator(selector).first();
|
|
34640
|
-
if (!await target.count()) continue;
|
|
34641
|
-
const shot = await target.screenshot({ type: "png", timeout: timeoutMs }).catch(() => null);
|
|
34642
|
-
if (shot) return shot;
|
|
34643
|
-
}
|
|
34644
|
-
}
|
|
34645
|
-
return await page.screenshot({ type: "png", fullPage: true }).catch(() => null);
|
|
34646
|
-
} catch {
|
|
34647
|
-
return null;
|
|
34648
|
-
} finally {
|
|
34649
|
-
await context.close();
|
|
34650
|
-
}
|
|
34651
|
-
}
|
|
34652
|
-
|
|
34653
|
-
// src/engine/landing-library/report.ts
|
|
34654
|
-
import { writeFile as writeFile14 } from "fs/promises";
|
|
34655
|
-
import path30 from "path";
|
|
34656
|
-
async function writeCaptureReport(manifest, outDir) {
|
|
34657
|
-
const file = path30.join(outDir, "report.html");
|
|
34658
|
-
await writeFile14(file, renderReport(manifest));
|
|
34659
|
-
return file;
|
|
34660
|
-
}
|
|
34661
|
-
function escapeHtml3(value) {
|
|
34662
|
-
return value.replace(
|
|
34663
|
-
/[&<>"']/g,
|
|
34664
|
-
(character) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[character] ?? character
|
|
34665
|
-
);
|
|
34666
|
-
}
|
|
34667
|
-
function fidelityTone(fidelity) {
|
|
34668
|
-
if (fidelity === null) return "unknown";
|
|
34669
|
-
if (fidelity >= 0.95) return "good";
|
|
34670
|
-
if (fidelity >= 0.85) return "fair";
|
|
34671
|
-
return "poor";
|
|
34672
|
-
}
|
|
34673
|
-
function medianFidelity(sections) {
|
|
34674
|
-
const scored = sections.map((section) => section.fidelity).filter((value) => value !== null).sort((a, b) => a - b);
|
|
34675
|
-
return scored.length === 0 ? null : scored[Math.floor(scored.length / 2)] ?? null;
|
|
34676
|
-
}
|
|
34677
|
-
function formatFidelity(fidelity) {
|
|
34678
|
-
return fidelity === null ? "\u2014" : fidelity.toFixed(2);
|
|
34679
|
-
}
|
|
34680
|
-
function renderSection3(section) {
|
|
34681
|
-
const tone = fidelityTone(section.fidelity);
|
|
34682
|
-
const rendered = section.bundle ? section.bundle.replace(/section\.html$/, "section-rendered.png") : null;
|
|
34683
|
-
const shot = (label, src, note) => {
|
|
34684
|
-
if (!src) return `<figure class="shot empty"><figcaption>${label} \u2014 none</figcaption></figure>`;
|
|
34685
|
-
return `<figure class="shot">
|
|
34686
|
-
<figcaption>${label}${note ? ` <span class="note">${escapeHtml3(note)}</span>` : ""}</figcaption>
|
|
34687
|
-
<a href="${escapeHtml3(src)}" target="_blank" rel="noopener"><img src="${escapeHtml3(src)}" alt="" loading="lazy"></a>
|
|
34688
|
-
</figure>`;
|
|
34689
|
-
};
|
|
34690
|
-
return `<section class="card">
|
|
34691
|
-
<header>
|
|
34692
|
-
<h2><span class="index">${String(section.index).padStart(2, "0")}</span> ${section.rect.width}\xD7${section.rect.height}</h2>
|
|
34693
|
-
<span class="badge ${tone}">fidelity ${formatFidelity(section.fidelity)}</span>
|
|
34694
|
-
${section.fidelityNote ? `<span class="badge warn">${escapeHtml3(section.fidelityNote)}</span>` : ""}
|
|
34695
|
-
${section.motion.hasMotion ? `<span class="badge motion">${escapeHtml3(section.motion.summary)}</span>` : ""}
|
|
34696
|
-
</header>
|
|
34697
|
-
<p class="preview">${escapeHtml3(section.textPreview.slice(0, 220)) || "<em>no text</em>"}</p>
|
|
34698
|
-
<div class="shots">
|
|
34699
|
-
${shot("Live", section.desktopShot)}
|
|
34700
|
-
${shot("Reproduction", rendered, "rendered from the extracted bundle")}
|
|
34701
|
-
${shot("Mobile", section.mobileShot)}
|
|
34702
|
-
</div>
|
|
34703
|
-
${section.motionFilmstrip ? `<div class="filmstrip">${shot("Motion \u2014 six frames, left to right", section.motionFilmstrip)}</div>` : ""}
|
|
34704
|
-
<footer>
|
|
34705
|
-
<code>${escapeHtml3(section.selector)}</code>
|
|
34706
|
-
${section.bundle ? `<a href="${escapeHtml3(section.bundle)}" target="_blank" rel="noopener">open the standalone bundle \u2192</a>` : ""}
|
|
34707
|
-
</footer>
|
|
34708
|
-
</section>`;
|
|
34709
|
-
}
|
|
34710
|
-
function renderReport(manifest) {
|
|
34711
|
-
const median = medianFidelity(manifest.sections);
|
|
34712
|
-
const moving = manifest.sections.filter((section) => section.motionFilmstrip !== null).length;
|
|
34713
|
-
return `<!doctype html>
|
|
34714
|
-
<html lang="en">
|
|
34715
|
-
<head>
|
|
34716
|
-
<meta charset="utf-8">
|
|
34717
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
34718
|
-
<title>Capture report \u2014 ${escapeHtml3(manifest.title || manifest.url)}</title>
|
|
34719
|
-
<style>
|
|
34720
|
-
:root { color-scheme: light dark; --line: #e5e7eb; --muted: #6b7280; --bg: #fafafa; --card: #fff; }
|
|
34721
|
-
@media (prefers-color-scheme: dark) {
|
|
34722
|
-
:root { --line: #27272a; --muted: #a1a1aa; --bg: #09090b; --card: #131316; }
|
|
34723
|
-
}
|
|
34724
|
-
* { box-sizing: border-box; }
|
|
34725
|
-
body { margin: 0; padding: 24px; background: var(--bg);
|
|
34726
|
-
font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; }
|
|
34727
|
-
h1 { margin: 0 0 4px; font-size: 20px; }
|
|
34728
|
-
a { color: inherit; }
|
|
34729
|
-
.sub { color: var(--muted); margin: 0 0 20px; }
|
|
34730
|
-
.stats { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 24px; }
|
|
34731
|
-
.stat { border: 1px solid var(--line); border-radius: 10px; padding: 8px 12px; background: var(--card); }
|
|
34732
|
-
.stat b { display: block; font-size: 18px; }
|
|
34733
|
-
.stat span { color: var(--muted); font-size: 12px; }
|
|
34734
|
-
.card { border: 1px solid var(--line); border-radius: 12px; background: var(--card);
|
|
34735
|
-
padding: 16px; margin-bottom: 16px; }
|
|
34736
|
-
.card header { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
|
|
34737
|
-
.card h2 { font-size: 15px; margin: 0; font-weight: 600; }
|
|
34738
|
-
.index { display: inline-block; min-width: 26px; color: var(--muted); }
|
|
34739
|
-
.badge { font-size: 12px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--line); }
|
|
34740
|
-
.badge.good { background: #dcfce7; color: #166534; border-color: #bbf7d0; }
|
|
34741
|
-
.badge.fair { background: #fef3c7; color: #92400e; border-color: #fde68a; }
|
|
34742
|
-
.badge.poor { background: #fee2e2; color: #991b1b; border-color: #fecaca; }
|
|
34743
|
-
.badge.warn { background: #fee2e2; color: #991b1b; border-color: #fecaca; }
|
|
34744
|
-
.badge.motion { background: #ede9fe; color: #5b21b6; border-color: #ddd6fe; }
|
|
34745
|
-
.preview { color: var(--muted); margin: 0 0 12px; font-size: 13px; }
|
|
34746
|
-
.shots { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; align-items: start; }
|
|
34747
|
-
.filmstrip { margin-top: 12px; }
|
|
34748
|
-
figure { margin: 0; }
|
|
34749
|
-
figcaption { font-size: 12px; color: var(--muted); margin-bottom: 4px; }
|
|
34750
|
-
figcaption .note { opacity: .75; }
|
|
34751
|
-
.shot img { width: 100%; height: auto; border: 1px solid var(--line); border-radius: 8px;
|
|
34752
|
-
background: #fff; display: block; }
|
|
34753
|
-
.shot.empty { border: 1px dashed var(--line); border-radius: 8px; padding: 20px; text-align: center; }
|
|
34754
|
-
.card footer { display: flex; justify-content: space-between; gap: 12px; margin-top: 12px;
|
|
34755
|
-
font-size: 12px; color: var(--muted); }
|
|
34756
|
-
code { font: 12px ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-all; }
|
|
34757
|
-
</style>
|
|
34758
|
-
</head>
|
|
34759
|
-
<body>
|
|
34760
|
-
<h1>${escapeHtml3(manifest.title || "Capture report")}</h1>
|
|
34761
|
-
<p class="sub"><a href="${escapeHtml3(manifest.finalUrl)}" target="_blank" rel="noopener">${escapeHtml3(manifest.finalUrl)}</a> \xB7 captured ${escapeHtml3(manifest.capturedAt)}</p>
|
|
34762
|
-
|
|
34763
|
-
<div class="stats">
|
|
34764
|
-
<div class="stat"><b>${manifest.sections.length}</b><span>sections</span></div>
|
|
34765
|
-
<div class="stat"><b>${formatFidelity(median)}</b><span>median fidelity</span></div>
|
|
34766
|
-
<div class="stat"><b>${formatFidelity(manifest.page.fidelity)}</b><span>whole page</span></div>
|
|
34767
|
-
<div class="stat"><b>${moving}</b><span>filmed moving</span></div>
|
|
34768
|
-
<div class="stat"><b>${manifest.documentHeight}px</b><span>page height</span></div>
|
|
34769
|
-
</div>
|
|
34770
|
-
|
|
34771
|
-
<section class="card">
|
|
34772
|
-
<header>
|
|
34773
|
-
<h2>Whole page</h2>
|
|
34774
|
-
<span class="badge ${fidelityTone(manifest.page.fidelity)}">fidelity ${formatFidelity(manifest.page.fidelity)}</span>
|
|
34775
|
-
</header>
|
|
34776
|
-
<p class="preview">The same extractor rooted at <body> \u2014 one standalone file that should render like the original.</p>
|
|
34777
|
-
<div class="shots">
|
|
34778
|
-
<figure class="shot"><figcaption>Live</figcaption><a href="full-page.png" target="_blank" rel="noopener"><img src="full-page.png" alt="" loading="lazy"></a></figure>
|
|
34779
|
-
${manifest.page.bundle ? `<figure class="shot"><figcaption>Reproduction <span class="note">from page.html</span></figcaption><a href="page-rendered.png" target="_blank" rel="noopener"><img src="page-rendered.png" alt="" loading="lazy"></a></figure>` : `<figure class="shot empty"><figcaption>Reproduction \u2014 none</figcaption></figure>`}
|
|
34780
|
-
</div>
|
|
34781
|
-
</section>
|
|
34782
|
-
|
|
34783
|
-
${manifest.sections.map(renderSection3).join("\n")}
|
|
34784
|
-
</body>
|
|
34785
|
-
</html>
|
|
34786
|
-
`;
|
|
34787
|
-
}
|
|
34788
|
-
|
|
34789
|
-
// src/engine/landing-library/types.ts
|
|
34790
|
-
var DEFAULT_SEGMENT_OPTIONS = {
|
|
34791
|
-
maxSectionHeight: 2160,
|
|
34792
|
-
collisionMargin: 2,
|
|
34793
|
-
maxElements: 15e3
|
|
34794
|
-
};
|
|
34795
|
-
|
|
34796
|
-
// src/engine/landing-library/visualHash.ts
|
|
34797
|
-
import sharp5 from "sharp";
|
|
34798
|
-
var HASH_WIDTH = 9;
|
|
34799
|
-
var HASH_HEIGHT = 8;
|
|
34800
|
-
async function perceptualHash(image) {
|
|
34801
|
-
try {
|
|
34802
|
-
const raw = await sharp5(image).greyscale().resize(HASH_WIDTH, HASH_HEIGHT, { fit: "fill" }).raw().toBuffer();
|
|
34803
|
-
return bitsToHex(rowGradientBits(new Uint8Array(raw)));
|
|
34804
|
-
} catch {
|
|
34805
|
-
return null;
|
|
34806
|
-
}
|
|
34807
|
-
}
|
|
34808
|
-
function rowGradientBits(pixels) {
|
|
34809
|
-
const bits = [];
|
|
34810
|
-
for (let y = 0; y < HASH_HEIGHT; y++) {
|
|
34811
|
-
for (let x = 0; x < HASH_WIDTH - 1; x++) {
|
|
34812
|
-
const left = pixels[y * HASH_WIDTH + x] ?? 0;
|
|
34813
|
-
const right = pixels[y * HASH_WIDTH + x + 1] ?? 0;
|
|
34814
|
-
bits.push(left > right);
|
|
34815
|
-
}
|
|
34816
|
-
}
|
|
34817
|
-
return bits;
|
|
34818
|
-
}
|
|
34819
|
-
function bitsToHex(bits) {
|
|
34820
|
-
let hex = "";
|
|
34821
|
-
for (let index = 0; index < bits.length; index += 4) {
|
|
34822
|
-
let nibble = 0;
|
|
34823
|
-
for (let offset = 0; offset < 4; offset++) {
|
|
34824
|
-
if (bits[index + offset]) nibble |= 1 << 3 - offset;
|
|
34825
|
-
}
|
|
34826
|
-
hex += nibble.toString(16);
|
|
34827
|
-
}
|
|
34828
|
-
return hex;
|
|
34829
|
-
}
|
|
34830
|
-
|
|
34831
|
-
// src/engine/landing-library/run.ts
|
|
34832
|
-
async function reproducePage(args) {
|
|
34833
|
-
const { browser, page, outDir, pageUrl, livePageShot } = args;
|
|
34834
|
-
const built = await buildSectionBundle(page, "body", pageUrl).catch(() => null);
|
|
34835
|
-
if (!built) return { bundle: null, fidelity: null };
|
|
34836
|
-
await writeFile15(path31.join(outDir, "page.html"), built.html);
|
|
34837
|
-
const rendered = await renderBundleToPng(browser, built.html, DESKTOP_VIEWPORT.width, {
|
|
34838
|
-
wholePage: true,
|
|
34839
|
-
timeoutMs: 6e4
|
|
34840
|
-
});
|
|
34841
|
-
if (!rendered || !livePageShot) return { bundle: "page.html", fidelity: null };
|
|
34842
|
-
await writeFile15(path31.join(outDir, "page-rendered.png"), rendered);
|
|
34843
|
-
const { score, note } = await scoreFidelity(livePageShot, rendered);
|
|
34844
|
-
return { bundle: "page.html", fidelity: score, ...note ? { fidelityNote: note } : {} };
|
|
34845
|
-
}
|
|
34846
|
-
async function captureOneSection(args) {
|
|
34847
|
-
const { browser, page, candidate, sectionsDir, outDir, pageUrl, withCode } = args;
|
|
34848
|
-
const dir = path31.join(sectionsDir, String(candidate.index).padStart(2, "0"));
|
|
34849
|
-
await mkdir11(dir, { recursive: true });
|
|
34850
|
-
const desktop = await captureSection(page, candidate);
|
|
34851
|
-
if (desktop) await writeFile15(path31.join(dir, "desktop.png"), desktop);
|
|
34852
|
-
const visualHash = desktop ? await perceptualHash(desktop) : null;
|
|
34853
|
-
const motion = await collectMotion(page, candidate.selector);
|
|
34854
|
-
const built = withCode ? await buildSectionBundle(page, candidate.selector, pageUrl) : null;
|
|
34855
|
-
let fidelity = null;
|
|
34856
|
-
let fidelityNote;
|
|
34857
|
-
if (built) {
|
|
34858
|
-
await writeFile15(path31.join(dir, "section.html"), built.html);
|
|
34859
|
-
const rendered = await renderBundleToPng(browser, built.html, DESKTOP_VIEWPORT.width);
|
|
34860
|
-
if (rendered && desktop) {
|
|
34861
|
-
await writeFile15(path31.join(dir, "section-rendered.png"), rendered);
|
|
34862
|
-
const result = await scoreFidelity(desktop, rendered);
|
|
34863
|
-
fidelity = result.score;
|
|
34864
|
-
fidelityNote = result.note;
|
|
34865
|
-
}
|
|
34866
|
-
}
|
|
34867
|
-
return {
|
|
34868
|
-
...candidate,
|
|
34869
|
-
desktopShot: desktop ? path31.relative(outDir, path31.join(dir, "desktop.png")) : null,
|
|
34870
|
-
mobileShot: null,
|
|
34871
|
-
bundle: built ? path31.relative(outDir, path31.join(dir, "section.html")) : null,
|
|
34872
|
-
fidelity,
|
|
34873
|
-
...fidelityNote ? { fidelityNote } : {},
|
|
34874
|
-
...built ? { cssStats: built.stats } : {},
|
|
34875
|
-
motion,
|
|
34876
|
-
motionFilmstrip: null,
|
|
34877
|
-
visualHash
|
|
34878
|
-
};
|
|
34879
|
-
}
|
|
34880
|
-
async function captureMobileShots(args) {
|
|
34881
|
-
const { browser, sections, sectionsDir, outDir, pageUrl, timeoutMs } = args;
|
|
34882
|
-
const mobile = await newPage(browser, MOBILE_VIEWPORT);
|
|
34883
|
-
try {
|
|
34884
|
-
await blockConsentManagers(mobile.page);
|
|
34885
|
-
await installPageRuntime(mobile.page);
|
|
34886
|
-
await preparePage(mobile.page, pageUrl, timeoutMs);
|
|
34887
|
-
for (const section of sections) {
|
|
34888
|
-
const shot = await captureSectionOnMobile(mobile.page, section);
|
|
34889
|
-
if (!shot) continue;
|
|
34890
|
-
const file = path31.join(sectionsDir, String(section.index).padStart(2, "0"), "mobile.png");
|
|
34891
|
-
await writeFile15(file, shot);
|
|
34892
|
-
section.mobileShot = path31.relative(outDir, file);
|
|
34893
|
-
}
|
|
34894
|
-
} finally {
|
|
34895
|
-
await mobile.context.close();
|
|
34896
|
-
}
|
|
34897
|
-
}
|
|
34898
|
-
async function captureMotionTakes(args) {
|
|
34899
|
-
const { browser, sections, sectionsDir, outDir, pageUrl, log } = args;
|
|
34900
|
-
const moving = sections.filter((section) => isWorthFilming(section.motion));
|
|
34901
|
-
if (moving.length === 0) return;
|
|
34902
|
-
log(`filming ${moving.length} moving sections`);
|
|
34903
|
-
for (const section of moving) {
|
|
34904
|
-
const take = await captureMotionTake(browser, pageUrl, section.selector);
|
|
34905
|
-
if (!take) continue;
|
|
34906
|
-
const dir = path31.join(sectionsDir, String(section.index).padStart(2, "0"));
|
|
34907
|
-
const file = path31.join(dir, "motion-filmstrip.png");
|
|
34908
|
-
await writeFile15(file, take.filmstrip);
|
|
34909
|
-
section.motionFilmstrip = path31.relative(outDir, file);
|
|
34910
|
-
log(` [${section.index}] ${section.motion.summary}`);
|
|
34911
|
-
}
|
|
34912
|
-
}
|
|
34913
|
-
async function scrapeLanding(options) {
|
|
34914
|
-
const timeoutMs = options.timeoutMs ?? 45e3;
|
|
34915
|
-
const log = options.onProgress ?? (() => void 0);
|
|
34916
|
-
const sectionsDir = path31.join(options.outDir, "sections");
|
|
34917
|
-
const browser = await launchBrowser();
|
|
34918
|
-
try {
|
|
34919
|
-
const { context, page } = await newPage(browser, DESKTOP_VIEWPORT);
|
|
34920
|
-
await blockConsentManagers(page);
|
|
34921
|
-
await installPageRuntime(page);
|
|
34922
|
-
log(`loading ${options.url}`);
|
|
34923
|
-
const prepared = await preparePage(page, options.url, timeoutMs);
|
|
34924
|
-
const blocked = detectBlockedPage({
|
|
34925
|
-
status: prepared.status,
|
|
34926
|
-
title: prepared.title,
|
|
34927
|
-
bodyText: prepared.bodyText,
|
|
34928
|
-
html: await page.content().catch(() => "")
|
|
34929
|
-
});
|
|
34930
|
-
if (blocked) throw new BlockedPageError(blocked);
|
|
34931
|
-
await mkdir11(sectionsDir, { recursive: true });
|
|
34932
|
-
log("segmenting");
|
|
34933
|
-
const candidates = await segmentPage(page, DEFAULT_SEGMENT_OPTIONS);
|
|
34934
|
-
log(`found ${candidates.length} sections`);
|
|
34935
|
-
const sections = [];
|
|
34936
|
-
for (const candidate of candidates) {
|
|
34937
|
-
const section = await captureOneSection({
|
|
34938
|
-
browser,
|
|
34939
|
-
page,
|
|
34940
|
-
candidate,
|
|
34941
|
-
sectionsDir,
|
|
34942
|
-
outDir: options.outDir,
|
|
34943
|
-
pageUrl: prepared.finalUrl,
|
|
34944
|
-
withCode: options.code !== false
|
|
34945
|
-
});
|
|
34946
|
-
sections.push(section);
|
|
34947
|
-
log(
|
|
34948
|
-
` [${candidate.index}] ${candidate.rect.width}x${candidate.rect.height}${section.fidelity === null ? "" : ` fidelity=${section.fidelity.toFixed(2)}`} \u2014 ${candidate.textPreview.slice(0, 50)}`
|
|
34949
|
-
);
|
|
34950
|
-
}
|
|
34951
|
-
const fullPage = await page.screenshot({ type: "png", fullPage: true }).catch(() => null);
|
|
34952
|
-
if (fullPage) await writeFile15(path31.join(options.outDir, "full-page.png"), fullPage);
|
|
34953
|
-
const reproduction = options.code === false ? { bundle: null, fidelity: null } : await reproducePage({
|
|
34954
|
-
browser,
|
|
34955
|
-
page,
|
|
34956
|
-
outDir: options.outDir,
|
|
34957
|
-
pageUrl: prepared.finalUrl,
|
|
34958
|
-
livePageShot: fullPage
|
|
34959
|
-
});
|
|
34960
|
-
log(`page reproduction: ${reproduction.fidelity === null ? "unavailable" : reproduction.fidelity.toFixed(2)}`);
|
|
34961
|
-
await context.close();
|
|
34962
|
-
if (options.mobile !== false) {
|
|
34963
|
-
log("capturing mobile");
|
|
34964
|
-
await captureMobileShots({
|
|
34965
|
-
browser,
|
|
34966
|
-
sections,
|
|
34967
|
-
sectionsDir,
|
|
34968
|
-
outDir: options.outDir,
|
|
34969
|
-
pageUrl: prepared.finalUrl,
|
|
34970
|
-
timeoutMs
|
|
34971
|
-
});
|
|
34972
|
-
}
|
|
34973
|
-
if (options.motion !== false) {
|
|
34974
|
-
await captureMotionTakes({
|
|
34975
|
-
browser,
|
|
34976
|
-
sections,
|
|
34977
|
-
sectionsDir,
|
|
34978
|
-
outDir: options.outDir,
|
|
34979
|
-
pageUrl: prepared.finalUrl,
|
|
34980
|
-
log
|
|
34981
|
-
});
|
|
34982
|
-
}
|
|
34983
|
-
const manifest = {
|
|
34984
|
-
url: options.url,
|
|
34985
|
-
finalUrl: prepared.finalUrl,
|
|
34986
|
-
title: prepared.title,
|
|
34987
|
-
documentHeight: prepared.documentHeight,
|
|
34988
|
-
viewport: DESKTOP_VIEWPORT,
|
|
34989
|
-
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
34990
|
-
sections,
|
|
34991
|
-
page: reproduction
|
|
34992
|
-
};
|
|
34993
|
-
await writeFile15(path31.join(options.outDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
34994
|
-
`);
|
|
34995
|
-
if (options.report !== false) {
|
|
34996
|
-
const reportPath = await writeCaptureReport(manifest, options.outDir);
|
|
34997
|
-
log(`report: ${reportPath}`);
|
|
34998
|
-
}
|
|
34999
|
-
return manifest;
|
|
35000
|
-
} finally {
|
|
35001
|
-
await browser.close();
|
|
35002
|
-
}
|
|
35003
|
-
}
|
|
35004
|
-
|
|
35005
|
-
// src/commands/landing/inspiration/scrape.ts
|
|
35006
|
-
async function recordCapture(manifest, outDir) {
|
|
35007
|
-
const consultedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
35008
|
-
const references = [];
|
|
35009
|
-
for (const section of manifest.sections) {
|
|
35010
|
-
if (!section.bundle) continue;
|
|
35011
|
-
try {
|
|
35012
|
-
const markup = await readFile23(path32.join(outDir, section.bundle), "utf8");
|
|
35013
|
-
const copyStrings = capturedCopyStrings(markup);
|
|
35014
|
-
if (copyStrings.length === 0) continue;
|
|
35015
|
-
references.push({
|
|
35016
|
-
sectionId: `local:${manifest.finalUrl}#${section.index}`,
|
|
35017
|
-
sourceUrl: manifest.finalUrl,
|
|
35018
|
-
copyStrings,
|
|
35019
|
-
consultedAt
|
|
35020
|
-
});
|
|
35021
|
-
} catch {
|
|
35022
|
-
}
|
|
35023
|
-
}
|
|
35024
|
-
return await recordReferences(process.cwd(), references);
|
|
35025
|
-
}
|
|
35026
|
-
registerSchema({
|
|
35027
|
-
command: "landing.inspiration.scrape",
|
|
35028
|
-
description: "Start here when the user points at a specific page and wants it now: capture one landing page into a directory of section screenshots, standalone HTML bundles, a whole-page reproduction and motion filmstrips. Returns when the capture is done, unlike `add`. Read the screenshots. Also records every captured section for the originality check.",
|
|
35029
|
-
args: {
|
|
35030
|
-
url: { type: "string", description: "Page to capture", required: true },
|
|
35031
|
-
out: { type: "string", description: "Output directory", required: true },
|
|
35032
|
-
mobile: { type: "boolean", description: "Phone-viewport pass. `--no-mobile` to skip", required: false },
|
|
35033
|
-
code: {
|
|
35034
|
-
type: "boolean",
|
|
35035
|
-
description: "Standalone HTML+CSS bundles and fidelity scores. `--no-code` to segment and screenshot only",
|
|
35036
|
-
required: false
|
|
35037
|
-
},
|
|
35038
|
-
motion: {
|
|
35039
|
-
type: "boolean",
|
|
35040
|
-
description: "Film sections that move. `--no-motion` to skip \u2014 roughly halves runtime",
|
|
35041
|
-
required: false
|
|
35042
|
-
},
|
|
35043
|
-
report: { type: "boolean", description: "Write report.html. `--no-report` to skip", required: false }
|
|
35044
|
-
}
|
|
35045
|
-
});
|
|
35046
|
-
var scrapeCommand = defineCommand146({
|
|
35047
|
-
meta: {
|
|
35048
|
-
name: "scrape",
|
|
35049
|
-
description: "Capture a landing page to a directory, now. Example: baker landing inspiration scrape https://linear.app --out .baker/inspiration/linear.app"
|
|
35050
|
-
},
|
|
35051
|
-
args: {
|
|
35052
|
-
url: { type: "positional", description: "Page to capture", required: true },
|
|
35053
|
-
out: { type: "string", description: "Output directory", required: true },
|
|
35054
|
-
mobile: {
|
|
35055
|
-
type: "boolean",
|
|
35056
|
-
description: "Phone-viewport pass (--no-mobile to skip)",
|
|
35057
|
-
required: false,
|
|
35058
|
-
default: true
|
|
35059
|
-
},
|
|
35060
|
-
code: { type: "boolean", description: "Bundles + fidelity (--no-code to skip)", required: false, default: true },
|
|
35061
|
-
motion: {
|
|
35062
|
-
type: "boolean",
|
|
35063
|
-
description: "Film moving sections (--no-motion to skip)",
|
|
35064
|
-
required: false,
|
|
35065
|
-
default: true
|
|
35066
|
-
},
|
|
35067
|
-
report: { type: "boolean", description: "Write report.html (--no-report to skip)", required: false, default: true }
|
|
35068
|
-
},
|
|
35069
|
-
run: async ({ args }) => {
|
|
35070
|
-
try {
|
|
35071
|
-
const manifest = await scrapeLanding({
|
|
35072
|
-
url: args.url,
|
|
35073
|
-
outDir: args.out,
|
|
35074
|
-
mobile: args.mobile,
|
|
35075
|
-
code: args.code,
|
|
35076
|
-
motion: args.motion,
|
|
35077
|
-
report: args.report,
|
|
35078
|
-
// Progress goes to stderr so stdout stays a clean JSON envelope.
|
|
35079
|
-
onProgress: (message) => process.stderr.write(`${message}
|
|
35080
|
-
`)
|
|
35081
|
-
});
|
|
35082
|
-
const scored = manifest.sections.map((section) => section.fidelity).filter((value) => value !== null).sort((a, b) => a - b);
|
|
35083
|
-
const recorded = await recordCapture(manifest, args.out);
|
|
35084
|
-
writeJson({
|
|
35085
|
-
ok: true,
|
|
35086
|
-
data: {
|
|
35087
|
-
title: manifest.title,
|
|
35088
|
-
sections: manifest.sections.length,
|
|
35089
|
-
medianFidelity: scored.length > 0 ? scored[Math.floor(scored.length / 2)] : null,
|
|
35090
|
-
pageFidelity: manifest.page.fidelity,
|
|
35091
|
-
out: args.out,
|
|
35092
|
-
report: args.report ? `${args.out}/report.html` : null
|
|
35093
|
-
},
|
|
35094
|
-
hints: [
|
|
35095
|
-
INSPIRATION_HINTS.structureNotCopy,
|
|
35096
|
-
INSPIRATION_HINTS.adapt,
|
|
35097
|
-
recorded ? "These sections are recorded for the originality check \u2014 `baker landing critique` will block a publish that ships their copy." : "Could not record this capture, so the originality check cannot see it. Be especially careful not to reuse its copy."
|
|
35098
|
-
]
|
|
35099
|
-
});
|
|
35100
|
-
} catch (error) {
|
|
35101
|
-
if (error instanceof BlockedPageError) {
|
|
35102
|
-
writeJson({ ok: false, error: { code: error.code, message: error.message } });
|
|
35103
|
-
process.exit(1);
|
|
35104
|
-
}
|
|
35105
|
-
reportError(error);
|
|
35106
|
-
}
|
|
35107
|
-
}
|
|
35108
|
-
});
|
|
35109
|
-
|
|
35110
|
-
// src/commands/landing/inspiration/search.ts
|
|
35111
|
-
import { mkdir as mkdir12, writeFile as writeFile16 } from "fs/promises";
|
|
35112
|
-
import path33 from "path";
|
|
35113
|
-
import { defineCommand as defineCommand147 } from "citty";
|
|
35114
|
-
registerSchema({
|
|
35115
|
-
command: "landing.inspiration.search",
|
|
35116
|
-
description: "Search the shared library of real landing-page sections for reference before you design. Start here: baker landing inspiration search 'pricing with a monthly/annual toggle'. Returns the idea behind each section plus a downloaded screenshot you can Read. Defaults to this company's saved sections; pass --scope all for the whole library.",
|
|
35117
|
-
args: {
|
|
35118
|
-
query: { type: "string", description: "What you want to see, in plain English", required: false },
|
|
35119
|
-
type: {
|
|
35120
|
-
type: "string",
|
|
35121
|
-
description: "Comma list of section types: hero,pricing,faq,testimonials,\u2026",
|
|
35122
|
-
required: false
|
|
35123
|
-
},
|
|
35124
|
-
composition: {
|
|
35125
|
-
type: "string",
|
|
35126
|
-
description: "Comma list of composition patterns (references/composition.md)",
|
|
35127
|
-
required: false
|
|
35128
|
-
},
|
|
35129
|
-
register: {
|
|
35130
|
-
type: "string",
|
|
35131
|
-
description: "Comma list of visual registers: dev-tool-minimal,luxury-high-end,\u2026",
|
|
35132
|
-
required: false
|
|
35133
|
-
},
|
|
35134
|
-
interaction: { type: "string", description: "Comma list of interaction patterns", required: false },
|
|
35135
|
-
motion: {
|
|
35136
|
-
type: "string",
|
|
35137
|
-
description: "Comma list of motion kinds: scroll-reveal,marquee,parallax,\u2026",
|
|
35138
|
-
required: false
|
|
35139
|
-
},
|
|
35140
|
-
media: { type: "string", description: "Comma list of media kinds", required: false },
|
|
35141
|
-
device: { type: "string", description: "Comma list of content devices", required: false },
|
|
35142
|
-
theme: { type: "string", description: "light | dark | mixed", required: false },
|
|
35143
|
-
"max-rank": {
|
|
35144
|
-
type: "number",
|
|
35145
|
-
description: "Show>Tell rank ceiling 1-7; 3 means 'rank 3 or better'",
|
|
35146
|
-
required: false
|
|
35147
|
-
},
|
|
35148
|
-
"min-craft": { type: "number", description: "Craft floor 0-1", required: false },
|
|
35149
|
-
"min-fidelity": {
|
|
35150
|
-
type: "number",
|
|
35151
|
-
description: "Only sections whose markup reproduces this well (0-1)",
|
|
35152
|
-
required: false
|
|
35153
|
-
},
|
|
35154
|
-
domain: { type: "string", description: "Restrict to one site", required: false },
|
|
35155
|
-
"similar-to": {
|
|
35156
|
-
type: "string",
|
|
35157
|
-
description: "Section id \u2014 find sections that look like this one",
|
|
35158
|
-
required: false
|
|
35159
|
-
},
|
|
35160
|
-
scope: { type: "string", description: "favorites (default) | all", required: false },
|
|
35161
|
-
limit: { type: "number", description: "Max results (default 8)", required: false },
|
|
35162
|
-
"no-images": { type: "boolean", description: "Skip downloading screenshots", required: false }
|
|
35163
|
-
}
|
|
35164
|
-
});
|
|
35165
|
-
function buildSearchBody(args) {
|
|
35166
|
-
const body = {};
|
|
35167
|
-
const setList2 = (key, value) => {
|
|
35168
|
-
const list = splitList(value);
|
|
35169
|
-
if (list) Object.assign(body, { [key]: list });
|
|
35170
|
-
};
|
|
35171
|
-
if (args.query) body.query = String(args.query);
|
|
35172
|
-
if (args["similar-to"]) body.similarToSectionId = String(args["similar-to"]);
|
|
35173
|
-
setList2("sectionType", args.type);
|
|
35174
|
-
setList2("composition", args.composition);
|
|
35175
|
-
setList2("visualRegister", args.register);
|
|
35176
|
-
setList2("interaction", args.interaction);
|
|
35177
|
-
setList2("motion", args.motion);
|
|
35178
|
-
setList2("mediaKind", args.media);
|
|
35179
|
-
setList2("contentDevice", args.device);
|
|
35180
|
-
if (args.theme) body.theme = String(args.theme);
|
|
35181
|
-
if (args.domain) body.domain = String(args.domain);
|
|
35182
|
-
const maxRank = parseNumber(args["max-rank"]);
|
|
35183
|
-
if (maxRank !== void 0) body.maxShowTellRank = maxRank;
|
|
35184
|
-
const minCraft = parseNumber(args["min-craft"]);
|
|
35185
|
-
if (minCraft !== void 0) body.minCraftScore = minCraft;
|
|
35186
|
-
const minFidelity = parseNumber(args["min-fidelity"]);
|
|
35187
|
-
if (minFidelity !== void 0) body.minFidelity = minFidelity;
|
|
35188
|
-
const limit = parseNumber(args.limit);
|
|
35189
|
-
body.limit = limit ?? 8;
|
|
35190
|
-
body.scope = args.scope === "all" ? "all" : "favorites";
|
|
35191
|
-
return body;
|
|
35192
|
-
}
|
|
35193
|
-
async function downloadShots(results) {
|
|
35194
|
-
const dir = path33.join(process.cwd(), ".baker", "inspiration");
|
|
35195
|
-
await mkdir12(dir, { recursive: true });
|
|
35196
|
-
const saved = /* @__PURE__ */ new Map();
|
|
35197
|
-
await Promise.all(
|
|
35198
|
-
results.map(async (result) => {
|
|
35199
|
-
if (!result.desktopShotUrl) return;
|
|
35200
|
-
try {
|
|
35201
|
-
const response = await fetch(result.desktopShotUrl);
|
|
35202
|
-
if (!response.ok) return;
|
|
35203
|
-
const file = path33.join(dir, `${result.id}.png`);
|
|
35204
|
-
await writeFile16(file, Buffer.from(await response.arrayBuffer()));
|
|
35205
|
-
saved.set(result.id, path33.relative(process.cwd(), file));
|
|
35206
|
-
} catch {
|
|
35207
|
-
}
|
|
35208
|
-
})
|
|
35209
|
-
);
|
|
35210
|
-
return saved;
|
|
35211
|
-
}
|
|
35212
|
-
var searchCommand2 = defineCommand147({
|
|
35213
|
-
meta: {
|
|
35214
|
-
name: "search",
|
|
35215
|
-
description: "Search real landing-page sections for reference. Example: baker landing inspiration search 'dark developer hero with a terminal' --register dev-tool-minimal --scope all"
|
|
35216
|
-
},
|
|
35217
|
-
args: {
|
|
35218
|
-
query: { type: "positional", description: "What you want to see, in plain English", required: false },
|
|
35219
|
-
type: { type: "string", description: "Comma list of section types", required: false },
|
|
35220
|
-
composition: { type: "string", description: "Comma list of composition patterns", required: false },
|
|
35221
|
-
register: { type: "string", description: "Comma list of visual registers", required: false },
|
|
35222
|
-
interaction: { type: "string", description: "Comma list of interaction patterns", required: false },
|
|
35223
|
-
motion: { type: "string", description: "Comma list of motion kinds", required: false },
|
|
35224
|
-
media: { type: "string", description: "Comma list of media kinds", required: false },
|
|
35225
|
-
device: { type: "string", description: "Comma list of content devices", required: false },
|
|
35226
|
-
theme: { type: "string", description: "light | dark | mixed", required: false },
|
|
35227
|
-
"max-rank": { type: "string", description: "Show>Tell rank ceiling 1-7", required: false },
|
|
35228
|
-
"min-craft": { type: "string", description: "Craft floor 0-1", required: false },
|
|
35229
|
-
"min-fidelity": { type: "string", description: "Reproduction-fidelity floor 0-1", required: false },
|
|
35230
|
-
domain: { type: "string", description: "Restrict to one site", required: false },
|
|
35231
|
-
"similar-to": { type: "string", description: "Section id to find lookalikes of", required: false },
|
|
35232
|
-
scope: { type: "string", description: "favorites (default) | all", required: false, default: "favorites" },
|
|
35233
|
-
limit: { type: "string", description: "Max results (default 8)", required: false },
|
|
35234
|
-
"no-images": { type: "boolean", description: "Skip downloading screenshots", required: false, default: false },
|
|
35235
|
-
full: { type: "boolean", description: "Include every classification facet", required: false, default: false }
|
|
35236
|
-
},
|
|
35237
|
-
run: async ({ args }) => {
|
|
35238
|
-
try {
|
|
35239
|
-
const body = buildSearchBody(args);
|
|
35240
|
-
const data = await apiPost("/api/landing-inspiration/search", body);
|
|
35241
|
-
const results = Array.isArray(data?.results) ? data.results : [];
|
|
35242
|
-
const shots = args["no-images"] ? /* @__PURE__ */ new Map() : await downloadShots(results);
|
|
35243
|
-
const full = args.full;
|
|
35244
|
-
const rows = results.map((result) => ({
|
|
35245
|
-
id: result.id,
|
|
35246
|
-
section: result.sectionType,
|
|
35247
|
-
composition: result.composition,
|
|
35248
|
-
look: result.visualRegister,
|
|
35249
|
-
motion: result.motionSummary,
|
|
35250
|
-
why_it_works: result.whyItWorks,
|
|
35251
|
-
craft: Number(result.craftScore?.toFixed?.(2) ?? result.craftScore),
|
|
35252
|
-
fidelity: result.fidelity,
|
|
35253
|
-
domain: result.domain,
|
|
35254
|
-
// Only worth saying when it means something: >1 marks a section the site
|
|
35255
|
-
// reuses across its pages, which is a stronger signal than a one-off.
|
|
35256
|
-
...result.pageCount > 1 ? { used_on_pages: result.pageCount } : {},
|
|
35257
|
-
screenshot: shots.get(result.id) ?? null,
|
|
35258
|
-
...full ? {
|
|
35259
|
-
theme: result.theme,
|
|
35260
|
-
density: result.density,
|
|
35261
|
-
show_tell_rank: result.showTellRank,
|
|
35262
|
-
interactions: result.interactions,
|
|
35263
|
-
media: result.mediaKinds,
|
|
35264
|
-
devices: result.contentDevices,
|
|
35265
|
-
tags: result.tags,
|
|
35266
|
-
headline: result.headline,
|
|
35267
|
-
source_url: result.sourceUrl
|
|
35268
|
-
} : {}
|
|
35269
|
-
}));
|
|
35270
|
-
const hints = [INSPIRATION_HINTS.adapt];
|
|
35271
|
-
if (rows.length === 0) {
|
|
35272
|
-
hints.push(
|
|
35273
|
-
body.scope === "favorites" ? "No matches in this company's saved sections. Retry with --scope all to search the whole library, or add a page with `baker landing inspiration add <url>`." : "No matches. Loosen the filters, or add reference pages with `baker landing inspiration add <url>`."
|
|
35274
|
-
);
|
|
35275
|
-
} else if (data.moreInFullLibrary > 0) {
|
|
35276
|
-
hints.push(`${data.moreInFullLibrary} more match in the full library \u2014 re-run with --scope all.`);
|
|
35277
|
-
}
|
|
35278
|
-
if (shots.size > 0) hints.push(`Screenshots saved to .baker/inspiration/ \u2014 Read them before deciding.`);
|
|
35279
|
-
writeJson({
|
|
35280
|
-
ok: true,
|
|
35281
|
-
data: { results: rows, scope: data.scope, more_in_full_library: data.moreInFullLibrary, total: data.total },
|
|
35282
|
-
hints
|
|
35283
|
-
});
|
|
35284
|
-
} catch (error) {
|
|
35285
|
-
reportError(error);
|
|
35286
|
-
}
|
|
35287
|
-
}
|
|
35288
|
-
});
|
|
35289
|
-
|
|
35290
|
-
// src/commands/landing/inspiration/view.ts
|
|
35291
|
-
import { mkdir as mkdir13, writeFile as writeFile17 } from "fs/promises";
|
|
35292
|
-
import path34 from "path";
|
|
35293
|
-
import { defineCommand as defineCommand148 } from "citty";
|
|
35294
|
-
registerSchema({
|
|
35295
|
-
command: "landing.inspiration.view",
|
|
35296
|
-
description: "Everything known about one section: composition, motion, design tokens, the copy it uses, why it works, and what must change to make it yours. Downloads the desktop and mobile screenshots plus the motion filmstrip so you can look at them.",
|
|
35297
|
-
args: { id: { type: "string", description: "Section id from search", required: true } }
|
|
35298
|
-
});
|
|
35299
|
-
async function download(url, file) {
|
|
35300
|
-
if (!url) return null;
|
|
35301
|
-
try {
|
|
35302
|
-
const response = await fetch(url);
|
|
35303
|
-
if (!response.ok) return null;
|
|
35304
|
-
await mkdir13(path34.dirname(file), { recursive: true });
|
|
35305
|
-
await writeFile17(file, Buffer.from(await response.arrayBuffer()));
|
|
35306
|
-
return path34.relative(process.cwd(), file);
|
|
35307
|
-
} catch {
|
|
35308
|
-
return null;
|
|
35309
|
-
}
|
|
35310
|
-
}
|
|
35311
|
-
var viewCommand2 = defineCommand148({
|
|
35312
|
-
meta: {
|
|
35313
|
-
name: "view",
|
|
35314
|
-
description: "Full detail for one reference section. Example: baker landing inspiration view k57abc\u2026 \u2014 read the screenshots it saves before you build."
|
|
35315
|
-
},
|
|
35316
|
-
args: { id: { type: "positional", description: "Section id from search", required: true } },
|
|
35317
|
-
run: async ({ args }) => {
|
|
35318
|
-
try {
|
|
35319
|
-
const id = args.id;
|
|
35320
|
-
const data = await apiGet("/api/landing-inspiration/section", { id });
|
|
35321
|
-
const section = data.section;
|
|
35322
|
-
const dir = path34.join(process.cwd(), ".baker", "inspiration", id);
|
|
35323
|
-
const [desktop, mobile, filmstrip] = await Promise.all([
|
|
35324
|
-
download(section.desktopShotUrl, path34.join(dir, "desktop.png")),
|
|
35325
|
-
download(section.mobileShotUrl, path34.join(dir, "mobile.png")),
|
|
35326
|
-
download(section.motionFilmstripUrl, path34.join(dir, "motion-filmstrip.png"))
|
|
35327
|
-
]);
|
|
35328
|
-
const hints = [INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt];
|
|
35329
|
-
const fidelity = fidelityHint(section.fidelity);
|
|
35330
|
-
if (fidelity) hints.push(fidelity);
|
|
35331
|
-
if (filmstrip) {
|
|
35332
|
-
hints.push(
|
|
35333
|
-
"motion-filmstrip.png shows this section animating in, six frames left-to-right then top-to-bottom, with the elapsed time on each. Read it if you want the motion, not just the layout."
|
|
35334
|
-
);
|
|
35335
|
-
}
|
|
35336
|
-
writeJson({
|
|
35337
|
-
ok: true,
|
|
35338
|
-
data: {
|
|
35339
|
-
id: section.id,
|
|
35340
|
-
section: section.sectionType,
|
|
35341
|
-
composition: section.composition,
|
|
35342
|
-
look: section.visualRegister,
|
|
35343
|
-
theme: section.theme,
|
|
35344
|
-
density: section.density,
|
|
35345
|
-
show_tell_rank: section.showTellRank,
|
|
35346
|
-
interactions: section.interactions,
|
|
35347
|
-
media: section.mediaKinds,
|
|
35348
|
-
devices: section.contentDevices,
|
|
35349
|
-
tags: section.tags,
|
|
35350
|
-
motion: section.motionSummary,
|
|
35351
|
-
design_tokens: section.designTokens,
|
|
35352
|
-
size: section.boundingBox,
|
|
35353
|
-
copy: {
|
|
35354
|
-
headline: section.headline,
|
|
35355
|
-
subhead: section.subhead,
|
|
35356
|
-
ctas: section.ctaLabels,
|
|
35357
|
-
proof: section.proofSignals
|
|
35358
|
-
},
|
|
35359
|
-
why_it_works: section.whyItWorks,
|
|
35360
|
-
adaptation_notes: section.adaptationNotes,
|
|
35361
|
-
reproduction_notes: section.reproductionNotes,
|
|
35362
|
-
craft: section.craftScore,
|
|
35363
|
-
fidelity: section.fidelity,
|
|
35364
|
-
source_url: section.sourceUrl,
|
|
35365
|
-
screenshots: { desktop, mobile, motion_filmstrip: filmstrip }
|
|
35366
|
-
},
|
|
35367
|
-
hints
|
|
35368
|
-
});
|
|
35369
|
-
} catch (error) {
|
|
35370
|
-
reportError(error);
|
|
35371
|
-
}
|
|
35372
|
-
}
|
|
35373
|
-
});
|
|
35374
|
-
|
|
35375
|
-
// src/commands/landing/inspiration/index.ts
|
|
35376
|
-
var inspirationCommand = defineCommand149({
|
|
35377
|
-
meta: {
|
|
35378
|
-
name: "inspiration",
|
|
35379
|
-
description: `Reference library of real landing-page sections \u2014 look at how good pages actually solve a problem before you design one.
|
|
35380
|
-
|
|
35381
|
-
Start here: \`baker landing inspiration search "<what you want to see>"\` during research, BEFORE you write the Direction Contract.
|
|
35382
|
-
|
|
35383
|
-
This is inspiration, never a clipboard. Take the mechanism \u2014 what the eye hits first, what proof arrives before the ask, how the grid is split. The words are never yours to reuse: shipping a reference's headline is a Tier 0 message-match failure and \`baker landing critique\` will block the publish.
|
|
35384
|
-
|
|
35385
|
-
Subcommands:
|
|
35386
|
-
baker landing inspiration search "<query>" \u2014 search by look, section type, composition, register or motion; saves screenshots you can Read
|
|
35387
|
-
baker landing inspiration view <id> \u2014 one section in full: tokens, motion filmstrip, why it works, what to change
|
|
35388
|
-
baker landing inspiration code <id> \u2014 its standalone HTML+CSS, for structure only
|
|
35389
|
-
baker landing inspiration page <id> \u2014 a whole page as a sequence: how it orders its sections
|
|
35390
|
-
baker landing inspiration add <url> \u2014 add a page to the library and save it to this company (returns immediately)
|
|
35391
|
-
baker landing inspiration favorites \u2014 what this company has saved; the default search scope
|
|
35392
|
-
baker landing inspiration favorite <id> \u2014 save a section
|
|
35393
|
-
baker landing inspiration unfavorite <id> \u2014 unsave a section
|
|
35394
|
-
baker landing inspiration scrape <url> \u2014 internal/ops: run the capture locally
|
|
35395
|
-
|
|
35396
|
-
Examples:
|
|
35397
|
-
baker landing inspiration search "pricing with a monthly/annual toggle" --max-rank 3
|
|
35398
|
-
baker landing inspiration search "dark developer hero with a terminal" --register dev-tool-minimal --scope all
|
|
35399
|
-
baker landing inspiration search "testimonial wall with faces and company logos" --motion scroll-reveal
|
|
35400
|
-
baker landing inspiration add https://linear.app
|
|
35401
|
-
|
|
35402
|
-
Full guide: __tooling__/docs/tools/baker/landing.md`
|
|
35403
|
-
},
|
|
35404
|
-
subCommands: {
|
|
35405
|
-
search: searchCommand2,
|
|
35406
|
-
view: viewCommand2,
|
|
35407
|
-
code: codeCommand,
|
|
35408
|
-
page: pageCommand,
|
|
35409
|
-
add: addCommand,
|
|
35410
|
-
favorites: favoritesCommand,
|
|
35411
|
-
favorite: favoriteCommand,
|
|
35412
|
-
unfavorite: unfavoriteCommand,
|
|
35413
|
-
scrape: scrapeCommand
|
|
35414
|
-
}
|
|
35415
|
-
});
|
|
35416
|
-
|
|
35417
33006
|
// src/commands/landing/index.ts
|
|
35418
|
-
var landingCommand =
|
|
33007
|
+
var landingCommand = defineCommand142({
|
|
35419
33008
|
meta: {
|
|
35420
33009
|
name: "landing",
|
|
35421
33010
|
description: `Design-quality tools for landing pages (src/pages/<slug>/).
|
|
@@ -35423,17 +33012,15 @@ var landingCommand = defineCommand150({
|
|
|
35423
33012
|
Start here: \`baker landing critique <slug>\` after building or editing a landing.
|
|
35424
33013
|
|
|
35425
33014
|
Subcommands:
|
|
35426
|
-
baker landing inspiration \u2014 reference library of real landing-page sections; search it during research, BEFORE writing the Direction Contract. Inspiration only: take the mechanism, never the words.
|
|
35427
33015
|
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.`
|
|
35428
33016
|
},
|
|
35429
33017
|
subCommands: {
|
|
35430
|
-
inspiration: inspirationCommand,
|
|
35431
33018
|
critique: critiqueCommand2
|
|
35432
33019
|
}
|
|
35433
33020
|
});
|
|
35434
33021
|
|
|
35435
33022
|
// src/commands/mcp/index.ts
|
|
35436
|
-
import { defineCommand as
|
|
33023
|
+
import { defineCommand as defineCommand143 } from "citty";
|
|
35437
33024
|
|
|
35438
33025
|
// src/commands/mcp/platforms.ts
|
|
35439
33026
|
function readsKey(label) {
|
|
@@ -35502,7 +33089,7 @@ registerSchema({
|
|
|
35502
33089
|
description: "List everything this chat can reach: managed integrations (Attio, Slack, Gmail, Google Sheets, \u2026), custom MCP servers, and the platforms the company signed in to (HubSpot, Google Ads, GA4, Search Console, Tag Manager) which you read through their own `baker` commands. Start here when the user mentions an external tool or platform.",
|
|
35503
33090
|
args: {}
|
|
35504
33091
|
});
|
|
35505
|
-
var connectedCommand =
|
|
33092
|
+
var connectedCommand = defineCommand143({
|
|
35506
33093
|
meta: {
|
|
35507
33094
|
name: "connected",
|
|
35508
33095
|
description: `Everything this chat can reach \u2014 managed integrations, custom MCP servers, and connected platforms.
|
|
@@ -35561,7 +33148,7 @@ registerSchema({
|
|
|
35561
33148
|
description: "List the custom MCP servers this company's chats see (org + company + your own user scope).",
|
|
35562
33149
|
args: {}
|
|
35563
33150
|
});
|
|
35564
|
-
var listCommand13 =
|
|
33151
|
+
var listCommand13 = defineCommand143({
|
|
35565
33152
|
meta: { name: "list", description: "List custom MCP servers visible to this company's chats." },
|
|
35566
33153
|
run: async () => {
|
|
35567
33154
|
try {
|
|
@@ -35598,7 +33185,7 @@ registerSchema({
|
|
|
35598
33185
|
header: { type: "string", description: 'Auth header "Key: Value" (repeatable)', required: false }
|
|
35599
33186
|
}
|
|
35600
33187
|
});
|
|
35601
|
-
var
|
|
33188
|
+
var addCommand = defineCommand143({
|
|
35602
33189
|
meta: {
|
|
35603
33190
|
name: "add",
|
|
35604
33191
|
description: `Register a custom MCP server. Tools appear as mcp__<name>__* on the NEXT message.
|
|
@@ -35650,7 +33237,7 @@ registerSchema({
|
|
|
35650
33237
|
description: "Remove a company custom MCP server by name.",
|
|
35651
33238
|
args: { name: { type: "string", description: "Server name to remove", required: true } }
|
|
35652
33239
|
});
|
|
35653
|
-
var removeCommand4 =
|
|
33240
|
+
var removeCommand4 = defineCommand143({
|
|
35654
33241
|
meta: {
|
|
35655
33242
|
name: "remove",
|
|
35656
33243
|
description: `Remove a company custom MCP server by name.
|
|
@@ -35672,7 +33259,7 @@ Example:
|
|
|
35672
33259
|
}
|
|
35673
33260
|
}
|
|
35674
33261
|
});
|
|
35675
|
-
var mcpCommand =
|
|
33262
|
+
var mcpCommand = defineCommand143({
|
|
35676
33263
|
meta: {
|
|
35677
33264
|
name: "mcp",
|
|
35678
33265
|
description: `Third-party tools for this company \u2014 see what's connected, register custom HTTPS MCP endpoints.
|
|
@@ -35692,16 +33279,16 @@ Full guide: __tooling__/docs/tools/baker/mcp.md`
|
|
|
35692
33279
|
subCommands: {
|
|
35693
33280
|
connected: connectedCommand,
|
|
35694
33281
|
list: listCommand13,
|
|
35695
|
-
add:
|
|
33282
|
+
add: addCommand,
|
|
35696
33283
|
remove: removeCommand4
|
|
35697
33284
|
}
|
|
35698
33285
|
});
|
|
35699
33286
|
|
|
35700
33287
|
// src/commands/research/index.ts
|
|
35701
|
-
import { defineCommand as
|
|
33288
|
+
import { defineCommand as defineCommand154 } from "citty";
|
|
35702
33289
|
|
|
35703
33290
|
// src/commands/research/advertisers.ts
|
|
35704
|
-
import { defineCommand as
|
|
33291
|
+
import { defineCommand as defineCommand144 } from "citty";
|
|
35705
33292
|
|
|
35706
33293
|
// src/commands/research/output.ts
|
|
35707
33294
|
var RESEARCH_DATA_NOTE = "Estimates based on third-party SERP data \u2014 not exact figures. Use for directional insights, not precise measurement.";
|
|
@@ -35814,7 +33401,7 @@ var FIELDS3 = {
|
|
|
35814
33401
|
etv: "Estimated traffic value (USD)",
|
|
35815
33402
|
visibility: "SERP visibility score (0-1)"
|
|
35816
33403
|
};
|
|
35817
|
-
var advertisersCommand =
|
|
33404
|
+
var advertisersCommand = defineCommand144({
|
|
35818
33405
|
meta: {
|
|
35819
33406
|
name: "advertisers",
|
|
35820
33407
|
description: `Find domains competing for a keyword in Google SERPs.
|
|
@@ -35834,15 +33421,15 @@ Examples:
|
|
|
35834
33421
|
},
|
|
35835
33422
|
run: async ({ args }) => {
|
|
35836
33423
|
const keyword = args.keyword;
|
|
35837
|
-
const
|
|
33424
|
+
const location = args.location || void 0;
|
|
35838
33425
|
const language = args.language || void 0;
|
|
35839
33426
|
const limit = args.limit ? Number(args.limit) : void 0;
|
|
35840
33427
|
const skipCache = args["no-cache"] ? true : void 0;
|
|
35841
|
-
const queryContext = buildResearchQueryContext(
|
|
33428
|
+
const queryContext = buildResearchQueryContext(location, language);
|
|
35842
33429
|
try {
|
|
35843
33430
|
const data = await apiPost("/api/research/advertisers", {
|
|
35844
33431
|
keyword,
|
|
35845
|
-
location
|
|
33432
|
+
location,
|
|
35846
33433
|
language,
|
|
35847
33434
|
limit,
|
|
35848
33435
|
skipCache
|
|
@@ -35861,7 +33448,7 @@ Examples:
|
|
|
35861
33448
|
});
|
|
35862
33449
|
|
|
35863
33450
|
// src/commands/research/autocomplete.ts
|
|
35864
|
-
import { defineCommand as
|
|
33451
|
+
import { defineCommand as defineCommand145 } from "citty";
|
|
35865
33452
|
registerSchema({
|
|
35866
33453
|
command: "research.autocomplete",
|
|
35867
33454
|
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).",
|
|
@@ -35884,7 +33471,7 @@ registerSchema({
|
|
|
35884
33471
|
var FIELDS4 = {
|
|
35885
33472
|
suggestion: "Autocomplete suggestion from Google"
|
|
35886
33473
|
};
|
|
35887
|
-
var autocompleteCommand =
|
|
33474
|
+
var autocompleteCommand = defineCommand145({
|
|
35888
33475
|
meta: {
|
|
35889
33476
|
name: "autocomplete",
|
|
35890
33477
|
description: `Get Google Autocomplete suggestions for keyword expansion.
|
|
@@ -35903,15 +33490,15 @@ Examples:
|
|
|
35903
33490
|
},
|
|
35904
33491
|
run: async ({ args }) => {
|
|
35905
33492
|
const keyword = args.keyword;
|
|
35906
|
-
const
|
|
33493
|
+
const location = args.location || void 0;
|
|
35907
33494
|
const language = args.language || void 0;
|
|
35908
33495
|
const limit = args.limit ? Number(args.limit) : void 0;
|
|
35909
33496
|
const skipCache = args["no-cache"] ? true : void 0;
|
|
35910
|
-
const queryContext = buildResearchQueryContext(
|
|
33497
|
+
const queryContext = buildResearchQueryContext(location, language);
|
|
35911
33498
|
try {
|
|
35912
33499
|
const data = await apiPost("/api/research/autocomplete", {
|
|
35913
33500
|
keyword,
|
|
35914
|
-
location
|
|
33501
|
+
location,
|
|
35915
33502
|
language,
|
|
35916
33503
|
limit,
|
|
35917
33504
|
skipCache
|
|
@@ -35930,7 +33517,7 @@ Examples:
|
|
|
35930
33517
|
});
|
|
35931
33518
|
|
|
35932
33519
|
// src/commands/research/countries.ts
|
|
35933
|
-
import { defineCommand as
|
|
33520
|
+
import { defineCommand as defineCommand146 } from "citty";
|
|
35934
33521
|
registerSchema({
|
|
35935
33522
|
command: "research.countries",
|
|
35936
33523
|
description: "List all supported country codes for --location flag in research commands.",
|
|
@@ -35987,7 +33574,7 @@ var FIELDS5 = {
|
|
|
35987
33574
|
code: "Country code to pass as --location",
|
|
35988
33575
|
name: "Country name"
|
|
35989
33576
|
};
|
|
35990
|
-
var countriesCommand =
|
|
33577
|
+
var countriesCommand = defineCommand146({
|
|
35991
33578
|
meta: {
|
|
35992
33579
|
name: "countries",
|
|
35993
33580
|
description: "List all supported country codes for --location flag."
|
|
@@ -35998,7 +33585,7 @@ var countriesCommand = defineCommand154({
|
|
|
35998
33585
|
});
|
|
35999
33586
|
|
|
36000
33587
|
// src/commands/research/intent.ts
|
|
36001
|
-
import { defineCommand as
|
|
33588
|
+
import { defineCommand as defineCommand147 } from "citty";
|
|
36002
33589
|
registerSchema({
|
|
36003
33590
|
command: "research.intent",
|
|
36004
33591
|
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.",
|
|
@@ -36021,7 +33608,7 @@ var FIELDS6 = {
|
|
|
36021
33608
|
intent: "Primary Google Search intent: informational, navigational, commercial, transactional",
|
|
36022
33609
|
probability: "Confidence score 0.0-1.0"
|
|
36023
33610
|
};
|
|
36024
|
-
var intentCommand =
|
|
33611
|
+
var intentCommand = defineCommand147({
|
|
36025
33612
|
meta: {
|
|
36026
33613
|
name: "intent",
|
|
36027
33614
|
description: `Classify Google Search intent for keywords. Returns intent type and confidence.
|
|
@@ -36069,7 +33656,7 @@ Examples:
|
|
|
36069
33656
|
});
|
|
36070
33657
|
|
|
36071
33658
|
// src/commands/research/keyword-gap.ts
|
|
36072
|
-
import { defineCommand as
|
|
33659
|
+
import { defineCommand as defineCommand148 } from "citty";
|
|
36073
33660
|
registerSchema({
|
|
36074
33661
|
command: "research.keyword-gap",
|
|
36075
33662
|
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.",
|
|
@@ -36098,7 +33685,7 @@ var FIELDS7 = {
|
|
|
36098
33685
|
cpc: "Cost per click USD",
|
|
36099
33686
|
their_position: "Competitor's ranking position"
|
|
36100
33687
|
};
|
|
36101
|
-
var keywordGapCommand =
|
|
33688
|
+
var keywordGapCommand = defineCommand148({
|
|
36102
33689
|
meta: {
|
|
36103
33690
|
name: "keyword-gap",
|
|
36104
33691
|
description: `Find keywords a competitor has that you don't. Supports pagination via --offset.
|
|
@@ -36122,18 +33709,18 @@ Examples:
|
|
|
36122
33709
|
run: async ({ args }) => {
|
|
36123
33710
|
const competitor = args.competitor;
|
|
36124
33711
|
const ours = args.ours;
|
|
36125
|
-
const
|
|
33712
|
+
const location = args.location || void 0;
|
|
36126
33713
|
const language = args.language || void 0;
|
|
36127
33714
|
const type = args.type || void 0;
|
|
36128
33715
|
const limit = args.limit ? Number(args.limit) : void 0;
|
|
36129
33716
|
const offset = args.offset ? Number(args.offset) : void 0;
|
|
36130
33717
|
const skipCache = args["no-cache"] ? true : void 0;
|
|
36131
|
-
const queryContext = buildResearchQueryContext(
|
|
33718
|
+
const queryContext = buildResearchQueryContext(location, language);
|
|
36132
33719
|
try {
|
|
36133
33720
|
const result = await apiPost("/api/research/keyword-gap", {
|
|
36134
33721
|
competitor,
|
|
36135
33722
|
ours,
|
|
36136
|
-
location
|
|
33723
|
+
location,
|
|
36137
33724
|
language,
|
|
36138
33725
|
type,
|
|
36139
33726
|
limit,
|
|
@@ -36172,7 +33759,7 @@ Examples:
|
|
|
36172
33759
|
});
|
|
36173
33760
|
|
|
36174
33761
|
// src/commands/research/keywords-for-site.ts
|
|
36175
|
-
import { defineCommand as
|
|
33762
|
+
import { defineCommand as defineCommand149 } from "citty";
|
|
36176
33763
|
registerSchema({
|
|
36177
33764
|
command: "research.keywords-for-site",
|
|
36178
33765
|
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.",
|
|
@@ -36205,7 +33792,7 @@ var FIELDS8 = {
|
|
|
36205
33792
|
competition: "LOW, MEDIUM, or HIGH",
|
|
36206
33793
|
competition_index: "Competition score 0-100"
|
|
36207
33794
|
};
|
|
36208
|
-
var keywordsForSiteCommand =
|
|
33795
|
+
var keywordsForSiteCommand = defineCommand149({
|
|
36209
33796
|
meta: {
|
|
36210
33797
|
name: "keywords-for-site",
|
|
36211
33798
|
description: `Get keywords a competitor targets in Google. Use --type to filter paid/organic.
|
|
@@ -36227,17 +33814,17 @@ Examples:
|
|
|
36227
33814
|
},
|
|
36228
33815
|
run: async ({ args }) => {
|
|
36229
33816
|
const target = args.target;
|
|
36230
|
-
const
|
|
33817
|
+
const location = args.location || void 0;
|
|
36231
33818
|
const language = args.language || void 0;
|
|
36232
33819
|
const sort = args.sort || void 0;
|
|
36233
33820
|
const type = args.type || void 0;
|
|
36234
33821
|
const limit = args.limit ? Number(args.limit) : void 0;
|
|
36235
33822
|
const skipCache = args["no-cache"] ? true : void 0;
|
|
36236
|
-
const queryContext = buildResearchQueryContext(
|
|
33823
|
+
const queryContext = buildResearchQueryContext(location, language);
|
|
36237
33824
|
try {
|
|
36238
33825
|
const data = await apiPost("/api/research/keywords-for-site", {
|
|
36239
33826
|
target,
|
|
36240
|
-
location
|
|
33827
|
+
location,
|
|
36241
33828
|
language,
|
|
36242
33829
|
sort,
|
|
36243
33830
|
type,
|
|
@@ -36258,7 +33845,7 @@ Examples:
|
|
|
36258
33845
|
});
|
|
36259
33846
|
|
|
36260
33847
|
// src/commands/research/languages.ts
|
|
36261
|
-
import { defineCommand as
|
|
33848
|
+
import { defineCommand as defineCommand150 } from "citty";
|
|
36262
33849
|
registerSchema({
|
|
36263
33850
|
command: "research.languages",
|
|
36264
33851
|
description: "List all supported language codes for --language flag in research commands.",
|
|
@@ -36288,7 +33875,7 @@ var FIELDS9 = {
|
|
|
36288
33875
|
code: "Language code to pass as --language",
|
|
36289
33876
|
name: "Language name (also accepted by --language)"
|
|
36290
33877
|
};
|
|
36291
|
-
var languagesCommand2 =
|
|
33878
|
+
var languagesCommand2 = defineCommand150({
|
|
36292
33879
|
meta: {
|
|
36293
33880
|
name: "languages",
|
|
36294
33881
|
description: "List all supported language codes for --language flag."
|
|
@@ -36299,7 +33886,7 @@ var languagesCommand2 = defineCommand158({
|
|
|
36299
33886
|
});
|
|
36300
33887
|
|
|
36301
33888
|
// src/commands/research/lighthouse.ts
|
|
36302
|
-
import { defineCommand as
|
|
33889
|
+
import { defineCommand as defineCommand151 } from "citty";
|
|
36303
33890
|
registerSchema({
|
|
36304
33891
|
command: "research.lighthouse",
|
|
36305
33892
|
description: "Landing page performance audit. Returns metrics that affect Google Ads Quality Score and CPC.",
|
|
@@ -36318,7 +33905,7 @@ var FIELDS10 = {
|
|
|
36318
33905
|
speed_index_ms: "Speed Index in ms (good: < 3400)",
|
|
36319
33906
|
interactive_ms: "Time to Interactive in ms (good: < 3800)"
|
|
36320
33907
|
};
|
|
36321
|
-
var lighthouseCommand =
|
|
33908
|
+
var lighthouseCommand = defineCommand151({
|
|
36322
33909
|
meta: {
|
|
36323
33910
|
name: "lighthouse",
|
|
36324
33911
|
description: `Landing page performance audit. Metrics affecting Google Ads Quality Score.
|
|
@@ -36356,7 +33943,7 @@ Examples:
|
|
|
36356
33943
|
});
|
|
36357
33944
|
|
|
36358
33945
|
// src/commands/research/relevant-pages.ts
|
|
36359
|
-
import { defineCommand as
|
|
33946
|
+
import { defineCommand as defineCommand152 } from "citty";
|
|
36360
33947
|
registerSchema({
|
|
36361
33948
|
command: "research.relevant-pages",
|
|
36362
33949
|
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).",
|
|
@@ -36382,7 +33969,7 @@ var FIELDS11 = {
|
|
|
36382
33969
|
keywords: "Total organic keywords the page ranks for",
|
|
36383
33970
|
top_10: "Keywords in positions 1-10"
|
|
36384
33971
|
};
|
|
36385
|
-
var relevantPagesCommand =
|
|
33972
|
+
var relevantPagesCommand = defineCommand152({
|
|
36386
33973
|
meta: {
|
|
36387
33974
|
name: "relevant-pages",
|
|
36388
33975
|
description: `Get the top pages of a competitor domain with traffic data.
|
|
@@ -36401,15 +33988,15 @@ Examples:
|
|
|
36401
33988
|
},
|
|
36402
33989
|
run: async ({ args }) => {
|
|
36403
33990
|
const target = args.target;
|
|
36404
|
-
const
|
|
33991
|
+
const location = args.location || void 0;
|
|
36405
33992
|
const language = args.language || void 0;
|
|
36406
33993
|
const limit = args.limit ? Number(args.limit) : void 0;
|
|
36407
33994
|
const skipCache = args["no-cache"] ? true : void 0;
|
|
36408
|
-
const queryContext = buildResearchQueryContext(
|
|
33995
|
+
const queryContext = buildResearchQueryContext(location, language);
|
|
36409
33996
|
try {
|
|
36410
33997
|
const data = await apiPost("/api/research/relevant-pages", {
|
|
36411
33998
|
target,
|
|
36412
|
-
location
|
|
33999
|
+
location,
|
|
36413
34000
|
language,
|
|
36414
34001
|
limit,
|
|
36415
34002
|
skipCache
|
|
@@ -36428,7 +34015,7 @@ Examples:
|
|
|
36428
34015
|
});
|
|
36429
34016
|
|
|
36430
34017
|
// src/commands/research/web.ts
|
|
36431
|
-
import { defineCommand as
|
|
34018
|
+
import { defineCommand as defineCommand153 } from "citty";
|
|
36432
34019
|
registerSchema({
|
|
36433
34020
|
command: "research.web",
|
|
36434
34021
|
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).",
|
|
@@ -36479,7 +34066,7 @@ async function runDeepResearch(question) {
|
|
|
36479
34066
|
}
|
|
36480
34067
|
throw new Error("Deep research timed out");
|
|
36481
34068
|
}
|
|
36482
|
-
var webCommand =
|
|
34069
|
+
var webCommand = defineCommand153({
|
|
36483
34070
|
meta: {
|
|
36484
34071
|
name: "web",
|
|
36485
34072
|
description: `Search the web with AI to answer any open-ended marketing question. Uses live internet data via Google Search.
|
|
@@ -36539,7 +34126,7 @@ Examples:
|
|
|
36539
34126
|
});
|
|
36540
34127
|
|
|
36541
34128
|
// src/commands/research/index.ts
|
|
36542
|
-
var researchCommand =
|
|
34129
|
+
var researchCommand = defineCommand154({
|
|
36543
34130
|
meta: {
|
|
36544
34131
|
name: "research",
|
|
36545
34132
|
description: `Competitive intelligence and AI-powered research commands.
|
|
@@ -36580,10 +34167,10 @@ Full guide: __tooling__/docs/tools/baker/research.md`
|
|
|
36580
34167
|
});
|
|
36581
34168
|
|
|
36582
34169
|
// src/commands/scheduled-actions/index.ts
|
|
36583
|
-
import { defineCommand as
|
|
34170
|
+
import { defineCommand as defineCommand161 } from "citty";
|
|
36584
34171
|
|
|
36585
34172
|
// src/commands/scheduled-actions/create.ts
|
|
36586
|
-
import { defineCommand as
|
|
34173
|
+
import { defineCommand as defineCommand155 } from "citty";
|
|
36587
34174
|
|
|
36588
34175
|
// src/commands/scheduled-actions/shared.ts
|
|
36589
34176
|
var TEMP_SCHEDULED_ACTION_PREFIX = "temp_sched_";
|
|
@@ -36698,7 +34285,7 @@ registerSchema({
|
|
|
36698
34285
|
prompt: { type: "string", description: "Additional prompt instructions for the spawned agent", required: false }
|
|
36699
34286
|
}
|
|
36700
34287
|
});
|
|
36701
|
-
var createCommand2 =
|
|
34288
|
+
var createCommand2 = defineCommand155({
|
|
36702
34289
|
meta: {
|
|
36703
34290
|
name: "create",
|
|
36704
34291
|
description: 'Stage a scheduled action. Example: baker scheduled-actions create --name "Weekly report" --description "..." --cron "0 9 * * MON"'
|
|
@@ -36747,7 +34334,7 @@ var createCommand2 = defineCommand163({
|
|
|
36747
34334
|
});
|
|
36748
34335
|
|
|
36749
34336
|
// src/commands/scheduled-actions/delete.ts
|
|
36750
|
-
import { defineCommand as
|
|
34337
|
+
import { defineCommand as defineCommand156 } from "citty";
|
|
36751
34338
|
registerSchema({
|
|
36752
34339
|
command: "scheduled-actions.delete",
|
|
36753
34340
|
description: "Stage deletion of a published scheduled action or cancellation of a temp_sched_* draft creation.",
|
|
@@ -36755,7 +34342,7 @@ registerSchema({
|
|
|
36755
34342
|
id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
|
|
36756
34343
|
}
|
|
36757
34344
|
});
|
|
36758
|
-
var deleteCommand2 =
|
|
34345
|
+
var deleteCommand2 = defineCommand156({
|
|
36759
34346
|
meta: {
|
|
36760
34347
|
name: "delete",
|
|
36761
34348
|
description: "Stage scheduled action deletion. Example: baker scheduled-actions delete <id-or-temp_sched_id>"
|
|
@@ -36784,7 +34371,7 @@ var deleteCommand2 = defineCommand164({
|
|
|
36784
34371
|
});
|
|
36785
34372
|
|
|
36786
34373
|
// src/commands/scheduled-actions/get.ts
|
|
36787
|
-
import { defineCommand as
|
|
34374
|
+
import { defineCommand as defineCommand157 } from "citty";
|
|
36788
34375
|
registerSchema({
|
|
36789
34376
|
command: "scheduled-actions.get",
|
|
36790
34377
|
description: "Get a published scheduled action or a temp_sched_* draft-created scheduled action.",
|
|
@@ -36793,7 +34380,7 @@ registerSchema({
|
|
|
36793
34380
|
chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
|
|
36794
34381
|
}
|
|
36795
34382
|
});
|
|
36796
|
-
var getCommand3 =
|
|
34383
|
+
var getCommand3 = defineCommand157({
|
|
36797
34384
|
meta: {
|
|
36798
34385
|
name: "get",
|
|
36799
34386
|
description: "Get a scheduled action. Example: baker scheduled-actions get <id-or-temp_sched_id>"
|
|
@@ -36832,7 +34419,7 @@ var getCommand3 = defineCommand165({
|
|
|
36832
34419
|
});
|
|
36833
34420
|
|
|
36834
34421
|
// src/commands/scheduled-actions/list.ts
|
|
36835
|
-
import { defineCommand as
|
|
34422
|
+
import { defineCommand as defineCommand158 } from "citty";
|
|
36836
34423
|
registerSchema({
|
|
36837
34424
|
command: "scheduled-actions.list",
|
|
36838
34425
|
description: "List published scheduled actions. Includes draft state when BAKER_CHAT_ID is set, or --chat <id> to read an earlier chat's staged schedules instead.",
|
|
@@ -36840,7 +34427,7 @@ registerSchema({
|
|
|
36840
34427
|
chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
|
|
36841
34428
|
}
|
|
36842
34429
|
});
|
|
36843
|
-
var listCommand14 =
|
|
34430
|
+
var listCommand14 = defineCommand158({
|
|
36844
34431
|
meta: {
|
|
36845
34432
|
name: "list",
|
|
36846
34433
|
description: "List scheduled actions. Includes staged draft ops when BAKER_CHAT_ID is set, or --chat <id> to read an earlier chat's staged schedules instead."
|
|
@@ -36863,7 +34450,7 @@ var listCommand14 = defineCommand166({
|
|
|
36863
34450
|
});
|
|
36864
34451
|
|
|
36865
34452
|
// src/commands/scheduled-actions/trigger.ts
|
|
36866
|
-
import { defineCommand as
|
|
34453
|
+
import { defineCommand as defineCommand159 } from "citty";
|
|
36867
34454
|
registerSchema({
|
|
36868
34455
|
command: "scheduled-actions.trigger",
|
|
36869
34456
|
description: "Immediately trigger a published scheduled action. Does not require BAKER_CHAT_ID and rejects temp_sched_* IDs.",
|
|
@@ -36871,7 +34458,7 @@ registerSchema({
|
|
|
36871
34458
|
id: { type: "string", description: "Published scheduled action ID", required: true }
|
|
36872
34459
|
}
|
|
36873
34460
|
});
|
|
36874
|
-
var triggerCommand =
|
|
34461
|
+
var triggerCommand = defineCommand159({
|
|
36875
34462
|
meta: {
|
|
36876
34463
|
name: "trigger",
|
|
36877
34464
|
description: "Immediately trigger a published scheduled action. Example: baker scheduled-actions trigger <id>"
|
|
@@ -36908,7 +34495,7 @@ var triggerCommand = defineCommand167({
|
|
|
36908
34495
|
});
|
|
36909
34496
|
|
|
36910
34497
|
// src/commands/scheduled-actions/update.ts
|
|
36911
|
-
import { defineCommand as
|
|
34498
|
+
import { defineCommand as defineCommand160 } from "citty";
|
|
36912
34499
|
registerSchema({
|
|
36913
34500
|
command: "scheduled-actions.update",
|
|
36914
34501
|
description: "Stage an update to a published scheduled action or temp_sched_* draft-created scheduled action.",
|
|
@@ -36933,7 +34520,7 @@ registerSchema({
|
|
|
36933
34520
|
prompt: { type: "string", description: "Replacement additional spawned-agent instructions", required: false }
|
|
36934
34521
|
}
|
|
36935
34522
|
});
|
|
36936
|
-
var updateCommand2 =
|
|
34523
|
+
var updateCommand2 = defineCommand160({
|
|
36937
34524
|
meta: {
|
|
36938
34525
|
name: "update",
|
|
36939
34526
|
description: "Stage a scheduled action update. Example: baker scheduled-actions update <id> --enabled false"
|
|
@@ -37004,7 +34591,7 @@ var updateCommand2 = defineCommand168({
|
|
|
37004
34591
|
});
|
|
37005
34592
|
|
|
37006
34593
|
// src/commands/scheduled-actions/index.ts
|
|
37007
|
-
var scheduledActionsCommand =
|
|
34594
|
+
var scheduledActionsCommand = defineCommand161({
|
|
37008
34595
|
meta: {
|
|
37009
34596
|
name: "scheduled-actions",
|
|
37010
34597
|
description: `Manage Scheduled Actions. Subcommands: list, get, create, update, delete, trigger.
|
|
@@ -37031,14 +34618,14 @@ Full guide: __tooling__/docs/tools/baker/scheduled-actions.md`
|
|
|
37031
34618
|
});
|
|
37032
34619
|
|
|
37033
34620
|
// src/commands/schema.ts
|
|
37034
|
-
import { defineCommand as
|
|
34621
|
+
import { defineCommand as defineCommand162 } from "citty";
|
|
37035
34622
|
function narrowToFamily(commandName, available) {
|
|
37036
34623
|
const segments = commandName.split(".");
|
|
37037
34624
|
const prefix = segments[0] === "ads" && segments[1] ? `ads.${segments[1]}.` : `${segments[0]}.`;
|
|
37038
34625
|
const siblings = available.filter((name) => name.startsWith(prefix));
|
|
37039
34626
|
return siblings.length > 0 ? siblings : available;
|
|
37040
34627
|
}
|
|
37041
|
-
var schemaCommand =
|
|
34628
|
+
var schemaCommand = defineCommand162({
|
|
37042
34629
|
meta: {
|
|
37043
34630
|
name: "schema",
|
|
37044
34631
|
description: "Inspect command argument schemas (for AI agent introspection). Lists all commands if no argument given. Example: baker schema images.search"
|
|
@@ -37082,10 +34669,10 @@ var schemaCommand = defineCommand170({
|
|
|
37082
34669
|
});
|
|
37083
34670
|
|
|
37084
34671
|
// src/commands/studio/index.ts
|
|
37085
|
-
import { defineCommand as
|
|
34672
|
+
import { defineCommand as defineCommand171 } from "citty";
|
|
37086
34673
|
|
|
37087
34674
|
// src/commands/studio/animate.ts
|
|
37088
|
-
import { defineCommand as
|
|
34675
|
+
import { defineCommand as defineCommand163 } from "citty";
|
|
37089
34676
|
|
|
37090
34677
|
// src/commands/studio/batch.ts
|
|
37091
34678
|
function projectBatch(generation, full) {
|
|
@@ -37274,7 +34861,7 @@ function parseImageRefs(spec) {
|
|
|
37274
34861
|
}
|
|
37275
34862
|
var defaultDeps = {
|
|
37276
34863
|
ingest: (url) => apiPost("/api/images/ingest", { url, source: "uploaded" }),
|
|
37277
|
-
upload: (
|
|
34864
|
+
upload: (path28) => uploadLocalImage({ file: path28, contentType: detectImageContentType(path28), source: "uploaded" })
|
|
37278
34865
|
};
|
|
37279
34866
|
async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
|
|
37280
34867
|
const refs = parseImageRefs(spec);
|
|
@@ -37294,10 +34881,10 @@ async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
|
|
|
37294
34881
|
}
|
|
37295
34882
|
return { imageIds, added };
|
|
37296
34883
|
}
|
|
37297
|
-
function uploadFailure(
|
|
34884
|
+
function uploadFailure(path28) {
|
|
37298
34885
|
return (error) => {
|
|
37299
34886
|
if (error instanceof ApiError) throw error;
|
|
37300
|
-
throw new ApiError("VALIDATION_ERROR", `Could not read "${
|
|
34887
|
+
throw new ApiError("VALIDATION_ERROR", `Could not read "${path28}" as an image.`);
|
|
37301
34888
|
};
|
|
37302
34889
|
}
|
|
37303
34890
|
|
|
@@ -37306,23 +34893,33 @@ var MODEL_LIST = VIDEO_MODEL_IDS;
|
|
|
37306
34893
|
var DEFAULT_MAX_WAIT_MS = 6e5;
|
|
37307
34894
|
registerSchema({
|
|
37308
34895
|
command: "studio.animate",
|
|
37309
|
-
description: "
|
|
34896
|
+
description: "Render a clip. With --image the look is already fixed, so the prompt describes MOVEMENT \u2014 camera, subject, timing; with --from text there is no image and the prompt is the whole shot. --image takes library image ids, local file paths and/or image URLs (paths and URLs are added to the media library first). A rendered clip is NOT in the video library until `baker studio keep` approves it.",
|
|
37310
34897
|
args: {
|
|
37311
34898
|
prompt: {
|
|
37312
34899
|
type: "string",
|
|
37313
|
-
description: "What MOVES \u2014 camera, subject, timing. Kling reads 2,500 characters and Seedance 4,000; over the cap the request is refused rather than trimmed.",
|
|
34900
|
+
description: "What MOVES \u2014 camera, subject, timing. Kling reads 2,500 characters and Seedance 2.0 4,000; over the cap the request is refused rather than trimmed. Seedance 2.5 publishes no cap, which is why it is the one to reach for when the brief is a whole multi-shot sequence.",
|
|
37314
34901
|
required: true
|
|
37315
34902
|
},
|
|
37316
34903
|
image: {
|
|
37317
34904
|
type: "string",
|
|
37318
|
-
description: "Comma-separated source images: library image ids, local file paths, and/or image URLs. With the default --from frame this must be exactly ONE image (the opening frame) \u2014 several are refused rather than silently reduced to the first. With --from references, up to 9 on Seedance.",
|
|
37319
|
-
required:
|
|
34905
|
+
description: "Comma-separated source images: library image ids, local file paths, and/or image URLs. Required unless --from text. With the default --from frame this must be exactly ONE image (the opening frame) \u2014 several are refused rather than silently reduced to the first. With --from references, up to 9 on Seedance 2.0 and 30 on Seedance 2.5.",
|
|
34906
|
+
required: false
|
|
37320
34907
|
},
|
|
37321
34908
|
from: {
|
|
37322
34909
|
type: "string",
|
|
37323
|
-
description: "frame (default) animates a single image as the opening frame; references uses several
|
|
34910
|
+
description: "What the clip is built FROM. frame (default) animates a single image as the opening frame; references uses several assets as visual guidance; text renders the prompt with no image at all. frame and references are mutually exclusive at every provider, and reference mode is Seedance/Gemini Omni only \u2014 Veo and Kling are frame-only. Every model does text.",
|
|
37324
34911
|
required: false,
|
|
37325
|
-
enum: ["frame", "references"]
|
|
34912
|
+
enum: ["frame", "references", "text"]
|
|
34913
|
+
},
|
|
34914
|
+
"from-clip": {
|
|
34915
|
+
type: "string",
|
|
34916
|
+
description: "Comma-separated Studio takes to BUILD ON, each written batchId:slot (e.g. j57abc\u2026:0) \u2014 this is how you extend a clip, edit one, or carry its motion into a new shot. Seedance 2.5 only, up to 10 source clips in total, and requires --from references.",
|
|
34917
|
+
required: false
|
|
34918
|
+
},
|
|
34919
|
+
"from-video": {
|
|
34920
|
+
type: "string",
|
|
34921
|
+
description: "The same, but from the VIDEO LIBRARY: comma-separated video ids (see `baker videos library`) to build on. Use this for footage the client uploaded or synced rather than something the Studio rendered. Counts against the same 10 source clips as --from-clip. The first use of a given video spends a minute encoding a reusable copy and is refused with a 'try again shortly' message; every use after that is instant.",
|
|
34922
|
+
required: false
|
|
37326
34923
|
},
|
|
37327
34924
|
model: {
|
|
37328
34925
|
type: "string",
|
|
@@ -37340,7 +34937,7 @@ registerSchema({
|
|
|
37340
34937
|
// Left unset this SNAPS onto the model's set rather than failing: the lane
|
|
37341
34938
|
// default is 5s and Veo renders only 4/6/8, so an unstated length becomes 4
|
|
37342
34939
|
// there. An explicit length the model cannot render is refused by name.
|
|
37343
|
-
description: `Clip length in seconds \u2014 per-model. Unset it and the lane default (5s) snaps onto what the model renders. Across the roster: ${CLIP_DURATIONS.join(", ")}`,
|
|
34940
|
+
description: `Clip length in seconds \u2014 per-model. Unset it and the lane default (5s) snaps onto what the model renders. Past 15s only bytedance/seedance-2.5 will do it, out to 30. Across the roster: ${CLIP_DURATIONS.join(", ")}`,
|
|
37344
34941
|
required: false
|
|
37345
34942
|
},
|
|
37346
34943
|
quality: {
|
|
@@ -37393,6 +34990,11 @@ registerSchema({
|
|
|
37393
34990
|
description: "How closely the clip follows the prompt, 0-1 (Kling only; higher = closer to the prompt)",
|
|
37394
34991
|
required: false
|
|
37395
34992
|
},
|
|
34993
|
+
watermark: {
|
|
34994
|
+
type: "boolean",
|
|
34995
|
+
description: "Burn the provider's watermark into the picture. Seedance 2.5 only, and OFF by default because it cannot be removed after the render \u2014 pass it only if a client actually wants the mark.",
|
|
34996
|
+
required: false
|
|
34997
|
+
},
|
|
37396
34998
|
wait: {
|
|
37397
34999
|
type: "string",
|
|
37398
35000
|
description: "Pass false to get the batch id back immediately instead of waiting for the takes",
|
|
@@ -37419,8 +35021,31 @@ function clipKnobs(args) {
|
|
|
37419
35021
|
if (typeof args["enhance-prompt"] === "boolean") knobs.enhancePrompt = args["enhance-prompt"];
|
|
37420
35022
|
knobs.conditioningScale = num2(args["conditioning-scale"]);
|
|
37421
35023
|
knobs.cfgScale = num2(args["cfg-scale"]);
|
|
35024
|
+
if (args.watermark === true) knobs.watermark = true;
|
|
37422
35025
|
return knobs;
|
|
37423
35026
|
}
|
|
35027
|
+
function parseVideoIds(value) {
|
|
35028
|
+
if (typeof value !== "string") return void 0;
|
|
35029
|
+
const ids = value.split(",").map((part) => part.trim()).filter(Boolean);
|
|
35030
|
+
return ids.length > 0 ? ids : void 0;
|
|
35031
|
+
}
|
|
35032
|
+
function parseClipRefs(value) {
|
|
35033
|
+
if (typeof value !== "string" || value.trim() === "") return void 0;
|
|
35034
|
+
const refs = [];
|
|
35035
|
+
for (const entry of value.split(",").map((part) => part.trim())) {
|
|
35036
|
+
if (!entry) continue;
|
|
35037
|
+
const separator = entry.lastIndexOf(":");
|
|
35038
|
+
const generationId = separator === -1 ? "" : entry.slice(0, separator);
|
|
35039
|
+
const slot = Number(entry.slice(separator + 1));
|
|
35040
|
+
if (!generationId || !Number.isInteger(slot) || slot < 0) {
|
|
35041
|
+
throw new Error(
|
|
35042
|
+
`--from-clip takes batchId:slot pairs \u2014 "${entry}" is not one. Read a batch with \`baker studio get <id>\` to see its slots.`
|
|
35043
|
+
);
|
|
35044
|
+
}
|
|
35045
|
+
refs.push({ generationId, slot });
|
|
35046
|
+
}
|
|
35047
|
+
return refs.length > 0 ? refs : void 0;
|
|
35048
|
+
}
|
|
37424
35049
|
function clipImageLimitFor(args) {
|
|
37425
35050
|
const model = args.model ?? DEFAULT_VIDEO_GENERATE_MODEL;
|
|
37426
35051
|
const label = MODEL_REGISTRY.video_generate[model]?.label ?? model;
|
|
@@ -37443,9 +35068,18 @@ function clipImageLimitFor(args) {
|
|
|
37443
35068
|
refuse: (count) => `${label} takes up to ${max} reference images \u2014 you passed ${count}. Drop the weakest ones.`
|
|
37444
35069
|
};
|
|
37445
35070
|
}
|
|
35071
|
+
function clipModeFor(from) {
|
|
35072
|
+
if (from === "references") return "references";
|
|
35073
|
+
if (from === "text") return "text";
|
|
35074
|
+
return "first_frame";
|
|
35075
|
+
}
|
|
37446
35076
|
function buildAnimateBody(args, prompt, imageIds) {
|
|
37447
35077
|
const body = { prompt, imageIds, ...clipKnobs(args) };
|
|
37448
|
-
|
|
35078
|
+
const clipRefs = parseClipRefs(args["from-clip"]);
|
|
35079
|
+
if (clipRefs) body.clipRefs = clipRefs;
|
|
35080
|
+
const videoIds = parseVideoIds(args["from-video"]);
|
|
35081
|
+
if (videoIds) body.videoIds = videoIds;
|
|
35082
|
+
if (args.from) body.clipMode = clipModeFor(args.from);
|
|
37449
35083
|
if (args.model) body.model = args.model;
|
|
37450
35084
|
if (args["aspect-ratio"]) body.aspectRatio = args["aspect-ratio"];
|
|
37451
35085
|
body.duration = num2(args.duration);
|
|
@@ -37465,7 +35099,12 @@ function costHintsFor(body) {
|
|
|
37465
35099
|
// ceiling against an actual 5s — exactly double.
|
|
37466
35100
|
duration: body.duration ?? nearestClipDuration(model, DEFAULT_CLIP_DURATION_S),
|
|
37467
35101
|
resolution: body.resolution,
|
|
37468
|
-
generateAudio: body.generateAudio
|
|
35102
|
+
generateAudio: body.generateAudio,
|
|
35103
|
+
buildsOnSourceClip: (body.clipRefs?.length ?? 0) + (body.videoIds?.length ?? 0) > 0,
|
|
35104
|
+
// Reference images might carry a face, and a face sends the render to the
|
|
35105
|
+
// dearer provider. Scoped to reference mode because that is the only shape
|
|
35106
|
+
// that gets re-routed — a frame-started clip never does.
|
|
35107
|
+
mayRouteToFal: body.clipMode === "references" && body.imageIds.length > 0
|
|
37469
35108
|
});
|
|
37470
35109
|
const total = perTake * count;
|
|
37471
35110
|
const hints = [
|
|
@@ -37478,10 +35117,10 @@ function costHintsFor(body) {
|
|
|
37478
35117
|
}
|
|
37479
35118
|
return hints;
|
|
37480
35119
|
}
|
|
37481
|
-
var animateCommand =
|
|
35120
|
+
var animateCommand = defineCommand163({
|
|
37482
35121
|
meta: {
|
|
37483
35122
|
name: "animate",
|
|
37484
|
-
description: "
|
|
35123
|
+
description: "Render a clip. With an image the look is already fixed, so the prompt describes MOVEMENT \u2014 what the camera does, what the subject does, in what order. With --from text there is no image and the prompt is the whole shot.\n\nA rendered clip is NOT usable anywhere until you keep it: `baker studio keep <id> --slot N` is what puts it in the video library. Takes nobody keeps are never ingested, which is what makes a rejected batch cheap.\n\nFor anything longer than 15 seconds, or to build on footage that already exists, use --model bytedance/seedance-2.5: it renders 4-30s and is the only model that reads an existing clip or an existing soundtrack.\n\nExamples:\n baker studio animate 'slow push in, model turns to camera and smiles' --image j57abc123def456ghi789\n baker studio animate 'handheld drift right, steam rising from the cup' --image './out/hero.png' --duration 6 --quality 1080p\n baker studio animate 'product rotates once on a turntable' --image j57abc\u2026,j57def\u2026 --from references\n baker studio animate 'she keeps walking, camera stays with her, then she stops and looks up' --image j57abc\u2026 --from references --from-clip j57batch\u2026:0 --model bytedance/seedance-2.5 --duration 20\n baker studio animate 'hold on the product, then a slow push-in' --from references --from-video j57vid\u2026 --model bytedance/seedance-2.5\n baker studio animate 'slow drone pull-back over a solar farm at golden hour, no people' --from text --model bytedance/seedance-2.5 --duration 12"
|
|
37485
35124
|
},
|
|
37486
35125
|
args: {
|
|
37487
35126
|
prompt: { type: "positional", description: "What MOVES", required: false },
|
|
@@ -37490,7 +35129,17 @@ var animateCommand = defineCommand171({
|
|
|
37490
35129
|
description: "Comma-separated library image ids, local file paths, and/or image URLs",
|
|
37491
35130
|
required: false
|
|
37492
35131
|
},
|
|
37493
|
-
from: { type: "string", description: "frame (default) | references", required: false },
|
|
35132
|
+
from: { type: "string", description: "frame (default) | references | text", required: false },
|
|
35133
|
+
"from-clip": {
|
|
35134
|
+
type: "string",
|
|
35135
|
+
description: "Studio takes to build on, batchId:slot (Seedance 2.5, needs --from references)",
|
|
35136
|
+
required: false
|
|
35137
|
+
},
|
|
35138
|
+
"from-video": {
|
|
35139
|
+
type: "string",
|
|
35140
|
+
description: "Library video ids to build on (Seedance 2.5, needs --from references)",
|
|
35141
|
+
required: false
|
|
35142
|
+
},
|
|
37494
35143
|
model: {
|
|
37495
35144
|
type: "string",
|
|
37496
35145
|
description: `Model id (default ${DEFAULT_VIDEO_GENERATE_MODEL})`,
|
|
@@ -37523,6 +35172,11 @@ var animateCommand = defineCommand171({
|
|
|
37523
35172
|
},
|
|
37524
35173
|
"conditioning-scale": { type: "string", description: "Frame adherence", required: false },
|
|
37525
35174
|
"cfg-scale": { type: "string", description: "Prompt adherence 0-1", required: false },
|
|
35175
|
+
watermark: {
|
|
35176
|
+
type: "boolean",
|
|
35177
|
+
description: "Burn in the provider watermark (Seedance 2.5, off by default)",
|
|
35178
|
+
required: false
|
|
35179
|
+
},
|
|
37526
35180
|
wait: { type: "string", description: "false to return the batch id immediately", required: false },
|
|
37527
35181
|
"max-wait": { type: "string", description: "Max ms to wait (default 600000, 0 = no wait)", required: false },
|
|
37528
35182
|
full: { type: "boolean", description: "Include settings, references and attribution", required: false }
|
|
@@ -37534,17 +35188,18 @@ var animateCommand = defineCommand171({
|
|
|
37534
35188
|
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "A prompt is required" } });
|
|
37535
35189
|
process.exit(1);
|
|
37536
35190
|
}
|
|
37537
|
-
if (!args.image) {
|
|
35191
|
+
if (!args.image && args.from !== "text") {
|
|
37538
35192
|
writeJson({
|
|
37539
35193
|
ok: false,
|
|
37540
35194
|
error: {
|
|
37541
35195
|
code: "VALIDATION_ERROR",
|
|
37542
|
-
message: "--image is required: a clip animates a still, so it needs the image to start from"
|
|
35196
|
+
message: "--image is required unless you pass --from text: a clip normally animates a still, so it needs the image to start from",
|
|
35197
|
+
fix: "Pass --image <id|path|url>, or --from text to render from the prompt alone."
|
|
37543
35198
|
}
|
|
37544
35199
|
});
|
|
37545
35200
|
process.exit(1);
|
|
37546
35201
|
}
|
|
37547
|
-
const { imageIds } = await resolveLibraryImageIds(args.image, clipImageLimitFor(args));
|
|
35202
|
+
const { imageIds } = args.image ? await resolveLibraryImageIds(args.image, clipImageLimitFor(args)) : { imageIds: [] };
|
|
37548
35203
|
const body = buildAnimateBody(args, prompt, imageIds);
|
|
37549
35204
|
const costHints = costHintsFor(body);
|
|
37550
35205
|
const start = await apiPost("/api/studio/animate", body);
|
|
@@ -37581,7 +35236,7 @@ var animateCommand = defineCommand171({
|
|
|
37581
35236
|
});
|
|
37582
35237
|
|
|
37583
35238
|
// src/commands/studio/generate.ts
|
|
37584
|
-
import { defineCommand as
|
|
35239
|
+
import { defineCommand as defineCommand164 } from "citty";
|
|
37585
35240
|
var MODEL_LIST2 = IMAGE_MODEL_IDS;
|
|
37586
35241
|
var DEFAULT_MAX_WAIT_MS2 = 24e4;
|
|
37587
35242
|
registerSchema({
|
|
@@ -37711,7 +35366,7 @@ function buildGenerateBody(args, prompt) {
|
|
|
37711
35366
|
}
|
|
37712
35367
|
return body;
|
|
37713
35368
|
}
|
|
37714
|
-
var generateCommand =
|
|
35369
|
+
var generateCommand = defineCommand164({
|
|
37715
35370
|
meta: {
|
|
37716
35371
|
name: "generate",
|
|
37717
35372
|
description: "Start here to make an image. Renders 1-8 takes of one brief, ingests each into the media library as it lands, and shows the batch in the dashboard Studio next to the ones the client ran.\n\nModel choice: google/gemini-3.1-flash-image-preview (default \u2014 fast, best at editing a reference and at extreme ratios), google/gemini-3-pro-image-preview (highest fidelity, slower), openai/gpt-image-2 (photoreal and the cleanest in-image text \u2014 no --image-size, no 4:5 / 5:4), recraft/recraft-v4.1-pro-vector (vector/flat marks with palette control).\n\n--reference is the biggest quality lever there is: a real logo, product shot, Pinterest pin or sandbox screenshot beats any amount of adjectives.\n\nExamples:\n baker studio generate 'matte black bottle on wet marble, hard studio light, 35mm' --aspect-ratio 3:2 --count 3\n baker studio generate 'this bottle on a sunlit kitchen counter' --reference './src/brand/product.png,https://\u2026/kitchen.jpg'\n baker studio generate 'founder-style selfie, kitchen background, natural light' --skill ugc-selfie-hook\n baker studio generate 'flat geometric mascot, brand palette' --model recraft/recraft-v4.1-pro-vector --rgb-colors '[[10,10,10],[255,80,0]]'"
|
|
@@ -37789,7 +35444,7 @@ var generateCommand = defineCommand172({
|
|
|
37789
35444
|
});
|
|
37790
35445
|
|
|
37791
35446
|
// src/commands/studio/get.ts
|
|
37792
|
-
import { defineCommand as
|
|
35447
|
+
import { defineCommand as defineCommand165 } from "citty";
|
|
37793
35448
|
registerSchema({
|
|
37794
35449
|
command: "studio.get",
|
|
37795
35450
|
description: "Read one Studio batch: every take, where it lives, and why a take is missing. This is how you pick up a batch that was still rendering when the start command returned.",
|
|
@@ -37798,7 +35453,7 @@ registerSchema({
|
|
|
37798
35453
|
full: { type: "boolean", description: "Include settings, references and attribution", required: false }
|
|
37799
35454
|
}
|
|
37800
35455
|
});
|
|
37801
|
-
var getCommand4 =
|
|
35456
|
+
var getCommand4 = defineCommand165({
|
|
37802
35457
|
meta: {
|
|
37803
35458
|
name: "get",
|
|
37804
35459
|
description: "Read one Studio batch \u2014 the takes, their urls, whether each is in the library, and the reason for any that failed.\n\nExample: baker studio get j57abc123def456ghi789\nExample: baker studio get j57abc123def456ghi789 --full"
|
|
@@ -37827,7 +35482,7 @@ var getCommand4 = defineCommand173({
|
|
|
37827
35482
|
});
|
|
37828
35483
|
|
|
37829
35484
|
// src/commands/studio/improve.ts
|
|
37830
|
-
import { defineCommand as
|
|
35485
|
+
import { defineCommand as defineCommand166 } from "citty";
|
|
37831
35486
|
var DESCRIPTION = "Sharpen a rough brief into directed art direction \u2014 the same rewrite the client gets from the wand in the Studio prompt bar. Reach for it when you are relaying the CLIENT's own words and want them shaped without substituting your voice; when you are writing the art direction yourself, just write it, because you will do a better job than this does.";
|
|
37832
35487
|
registerSchema({
|
|
37833
35488
|
command: "studio.improve",
|
|
@@ -37852,7 +35507,7 @@ registerSchema({
|
|
|
37852
35507
|
}
|
|
37853
35508
|
}
|
|
37854
35509
|
});
|
|
37855
|
-
var improveCommand =
|
|
35510
|
+
var improveCommand = defineCommand166({
|
|
37856
35511
|
meta: {
|
|
37857
35512
|
name: "improve",
|
|
37858
35513
|
description: `${DESCRIPTION}
|
|
@@ -37896,7 +35551,7 @@ Examples:
|
|
|
37896
35551
|
});
|
|
37897
35552
|
|
|
37898
35553
|
// src/commands/studio/keep.ts
|
|
37899
|
-
import { defineCommand as
|
|
35554
|
+
import { defineCommand as defineCommand167 } from "citty";
|
|
37900
35555
|
registerSchema({
|
|
37901
35556
|
command: "studio.keep",
|
|
37902
35557
|
description: "Mark one take as the keeper. For an image this stars it, so the client reviewing the batch sees which one you used. For a CLIP it is the step that puts it in the video library \u2014 until then the clip cannot be used in a canvas, a landing, or an ad.",
|
|
@@ -37906,7 +35561,7 @@ registerSchema({
|
|
|
37906
35561
|
undo: { type: "boolean", description: "Un-star an image, or take a kept clip back out", required: false }
|
|
37907
35562
|
}
|
|
37908
35563
|
});
|
|
37909
|
-
var keepCommand =
|
|
35564
|
+
var keepCommand = defineCommand167({
|
|
37910
35565
|
meta: {
|
|
37911
35566
|
name: "keep",
|
|
37912
35567
|
description: "Mark one take as the keeper. An image gets starred (it was already in the library); a clip gets INGESTED into the video library, which is what makes it usable anywhere else.\n\nExample: baker studio keep j57abc123def456ghi789 --slot 2\nExample: baker studio keep j57abc123def456ghi789 --slot 2 --undo"
|
|
@@ -37957,7 +35612,7 @@ var keepCommand = defineCommand175({
|
|
|
37957
35612
|
});
|
|
37958
35613
|
|
|
37959
35614
|
// src/commands/studio/list.ts
|
|
37960
|
-
import { defineCommand as
|
|
35615
|
+
import { defineCommand as defineCommand168 } from "citty";
|
|
37961
35616
|
registerSchema({
|
|
37962
35617
|
command: "studio.list",
|
|
37963
35618
|
description: "Recent Studio batches for THIS conversation, newest first \u2014 what you have already generated, so you re-use a take instead of paying for it twice. `--all` widens it to everything the company generated, including what people ran themselves in the dashboard.",
|
|
@@ -37968,7 +35623,7 @@ registerSchema({
|
|
|
37968
35623
|
full: { type: "boolean", description: "Include settings, references and attribution", required: false }
|
|
37969
35624
|
}
|
|
37970
35625
|
});
|
|
37971
|
-
var listCommand15 =
|
|
35626
|
+
var listCommand15 = defineCommand168({
|
|
37972
35627
|
meta: {
|
|
37973
35628
|
name: "list",
|
|
37974
35629
|
description: "Recent Studio batches, newest first. Scoped to this conversation unless you pass --all.\n\nExample: baker studio list\nExample: baker studio list --kind video --limit 5\nExample: baker studio list --all # includes batches the client ran in the dashboard"
|
|
@@ -38002,7 +35657,7 @@ var listCommand15 = defineCommand176({
|
|
|
38002
35657
|
});
|
|
38003
35658
|
|
|
38004
35659
|
// src/commands/studio/models.ts
|
|
38005
|
-
import { defineCommand as
|
|
35660
|
+
import { defineCommand as defineCommand169 } from "citty";
|
|
38006
35661
|
var DESCRIPTION2 = "What each Studio model actually accepts: its shapes, resolutions, clip lengths, prompt character cap, how many reference images it takes, and which knobs it has. Read this before a batch you care about \u2014 the models disagree far more than they look like they do, and a setting the chosen model does not have is REFUSED, not ignored.";
|
|
38007
35662
|
registerSchema({
|
|
38008
35663
|
command: "studio.models",
|
|
@@ -38063,6 +35718,7 @@ function imageCard(model) {
|
|
|
38063
35718
|
};
|
|
38064
35719
|
}
|
|
38065
35720
|
function videoCard(model) {
|
|
35721
|
+
const sourceClips = maxInputSlot("video_generate", model, "video");
|
|
38066
35722
|
return {
|
|
38067
35723
|
id: model,
|
|
38068
35724
|
label: MODEL_REGISTRY.video_generate[model]?.label,
|
|
@@ -38074,6 +35730,11 @@ function videoCard(model) {
|
|
|
38074
35730
|
// Only meaningful in reference mode; a first-frame clip always takes exactly
|
|
38075
35731
|
// one image, whatever the model's reference slot allows.
|
|
38076
35732
|
referenceMode: supportsReferenceToVideo(model) ? `up to ${maxInputReferences("video_generate", model) ?? "several"} reference images` : "first frame only \u2014 this model cannot take reference images",
|
|
35733
|
+
// Stated on EVERY card, not just the one model that has it: "can this build
|
|
35734
|
+
// on a clip I already made" is the question that decides whether extend and
|
|
35735
|
+
// edit are possible at all, and a field that appears only when the answer is
|
|
35736
|
+
// yes reads as "unknown" everywhere else.
|
|
35737
|
+
buildsOnClips: sourceClips ? `up to ${sourceClips} source clips \u2014 extend, edit, or carry their motion (pass with --from-clip)` : "no \u2014 this model cannot build on an existing clip",
|
|
38077
35738
|
knobs: knobsFor("video_generate", model)
|
|
38078
35739
|
};
|
|
38079
35740
|
}
|
|
@@ -38083,7 +35744,7 @@ function buildModelCards(kind, model) {
|
|
|
38083
35744
|
const selected = model ? ids.filter((id) => id === model) : ids;
|
|
38084
35745
|
return selected.map((id) => build(id));
|
|
38085
35746
|
}
|
|
38086
|
-
var modelsCommand =
|
|
35747
|
+
var modelsCommand = defineCommand169({
|
|
38087
35748
|
meta: {
|
|
38088
35749
|
name: "models",
|
|
38089
35750
|
description: `${DESCRIPTION2}
|
|
@@ -38123,20 +35784,20 @@ Examples:
|
|
|
38123
35784
|
data: cards,
|
|
38124
35785
|
meta: { count: cards.length },
|
|
38125
35786
|
hints: [
|
|
38126
|
-
kind === "image" ? "Shapes snap to the nearest the model renders; every other setting is refused if the model does not have it." : "A clip starting from one exact frame uses ONE image. Only Seedance and Gemini Omni take several as references."
|
|
35787
|
+
kind === "image" ? "Shapes snap to the nearest the model renders; every other setting is refused if the model does not have it." : "A clip starting from one exact frame uses ONE image. Only Seedance and Gemini Omni take several as references, and only Seedance 2.5 goes past 15s or builds on an existing clip."
|
|
38127
35788
|
]
|
|
38128
35789
|
});
|
|
38129
35790
|
}
|
|
38130
35791
|
});
|
|
38131
35792
|
|
|
38132
35793
|
// src/commands/studio/skills.ts
|
|
38133
|
-
import { defineCommand as
|
|
35794
|
+
import { defineCommand as defineCommand170 } from "citty";
|
|
38134
35795
|
registerSchema({
|
|
38135
35796
|
command: "studio.skills",
|
|
38136
35797
|
description: "The craft directions `studio generate --skill <id>` accepts. Each one carries directed art direction plus the model, shape and take count it wants, so you pick a look by name instead of writing the boilerplate yourself.",
|
|
38137
35798
|
args: {}
|
|
38138
35799
|
});
|
|
38139
|
-
var skillsCommand =
|
|
35800
|
+
var skillsCommand = defineCommand170({
|
|
38140
35801
|
meta: {
|
|
38141
35802
|
name: "skills",
|
|
38142
35803
|
description: "List the craft directions available to `baker studio generate --skill <id>` \u2014 what each one is for, whether it wants a reference image, and the model/shape/count it defaults to.\n\nExample: baker studio skills"
|
|
@@ -38160,7 +35821,7 @@ var skillsCommand = defineCommand178({
|
|
|
38160
35821
|
});
|
|
38161
35822
|
|
|
38162
35823
|
// src/commands/studio/index.ts
|
|
38163
|
-
var studioCommand =
|
|
35824
|
+
var studioCommand = defineCommand171({
|
|
38164
35825
|
meta: {
|
|
38165
35826
|
name: "studio",
|
|
38166
35827
|
description: `Make new imagery and clips. Every batch is recorded and shows up in the dashboard Studio for the client to review, labelled with this conversation.
|
|
@@ -38203,10 +35864,10 @@ Full guide: __tooling__/docs/tools/baker/studio.md`
|
|
|
38203
35864
|
});
|
|
38204
35865
|
|
|
38205
35866
|
// src/commands/tag-manager/index.ts
|
|
38206
|
-
import { defineCommand as
|
|
35867
|
+
import { defineCommand as defineCommand175 } from "citty";
|
|
38207
35868
|
|
|
38208
35869
|
// src/commands/tag-manager/draft.ts
|
|
38209
|
-
import { defineCommand as
|
|
35870
|
+
import { defineCommand as defineCommand172 } from "citty";
|
|
38210
35871
|
|
|
38211
35872
|
// src/commands/tag-manager/shared.ts
|
|
38212
35873
|
import { readFileSync as readFileSync13 } from "fs";
|
|
@@ -38273,10 +35934,10 @@ async function stageOp4(op) {
|
|
|
38273
35934
|
handleError4(err);
|
|
38274
35935
|
}
|
|
38275
35936
|
}
|
|
38276
|
-
async function draftAction3(
|
|
35937
|
+
async function draftAction3(path28, body, chat) {
|
|
38277
35938
|
const chatId = resolveChatId(chat);
|
|
38278
35939
|
try {
|
|
38279
|
-
const data = await apiPost(
|
|
35940
|
+
const data = await apiPost(path28, { chatId, ...body });
|
|
38280
35941
|
writeJsonEnvelope({ ok: true, data });
|
|
38281
35942
|
return data;
|
|
38282
35943
|
} catch (err) {
|
|
@@ -38329,13 +35990,13 @@ registerSchema({
|
|
|
38329
35990
|
chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
|
|
38330
35991
|
}
|
|
38331
35992
|
});
|
|
38332
|
-
var draftCommand4 =
|
|
35993
|
+
var draftCommand4 = defineCommand172({
|
|
38333
35994
|
meta: {
|
|
38334
35995
|
name: "draft",
|
|
38335
35996
|
description: "List, show, amend, remove, or clear staged Tag Manager changes for this chat. `list` and `show` take --chat <id> to read an earlier chat's changes instead."
|
|
38336
35997
|
},
|
|
38337
35998
|
subCommands: {
|
|
38338
|
-
list:
|
|
35999
|
+
list: defineCommand172({
|
|
38339
36000
|
meta: {
|
|
38340
36001
|
name: "list",
|
|
38341
36002
|
description: "Review everything staged on this chat (--json for the raw envelope)"
|
|
@@ -38348,7 +36009,7 @@ var draftCommand4 = defineCommand180({
|
|
|
38348
36009
|
await draftList2(args.json === true, args.chat);
|
|
38349
36010
|
}
|
|
38350
36011
|
}),
|
|
38351
|
-
show:
|
|
36012
|
+
show: defineCommand172({
|
|
38352
36013
|
meta: {
|
|
38353
36014
|
name: "show",
|
|
38354
36015
|
description: "Print the full staged payload for one change \u2014 the receipt to verify it looks right before publish (never truncated)."
|
|
@@ -38365,7 +36026,7 @@ var draftCommand4 = defineCommand180({
|
|
|
38365
36026
|
);
|
|
38366
36027
|
}
|
|
38367
36028
|
}),
|
|
38368
|
-
amend:
|
|
36029
|
+
amend: defineCommand172({
|
|
38369
36030
|
meta: {
|
|
38370
36031
|
name: "amend",
|
|
38371
36032
|
description: "Update a staged change in place \u2014 merges a JSON patch into its payload (objects deep-merge, null deletes a key, arrays/scalars replace) and re-validates. Use this instead of remove + re-create."
|
|
@@ -38382,7 +36043,7 @@ var draftCommand4 = defineCommand180({
|
|
|
38382
36043
|
});
|
|
38383
36044
|
}
|
|
38384
36045
|
}),
|
|
38385
|
-
remove:
|
|
36046
|
+
remove: defineCommand172({
|
|
38386
36047
|
meta: { name: "remove", description: "Remove one staged change (cascades to anything depending on it)" },
|
|
38387
36048
|
args: { ref: { type: "positional", description: "Staged ref (gtm_temp_*) or target", required: false } },
|
|
38388
36049
|
run: async ({ args }) => {
|
|
@@ -38391,7 +36052,7 @@ var draftCommand4 = defineCommand180({
|
|
|
38391
36052
|
});
|
|
38392
36053
|
}
|
|
38393
36054
|
}),
|
|
38394
|
-
clear:
|
|
36055
|
+
clear: defineCommand172({
|
|
38395
36056
|
meta: { name: "clear", description: "Discard all Tag Manager changes staged on this chat" },
|
|
38396
36057
|
run: async () => {
|
|
38397
36058
|
await draftAction3("/api/tag-manager/draft/clear", {});
|
|
@@ -38401,7 +36062,7 @@ var draftCommand4 = defineCommand180({
|
|
|
38401
36062
|
});
|
|
38402
36063
|
|
|
38403
36064
|
// src/commands/tag-manager/read.ts
|
|
38404
|
-
import { defineCommand as
|
|
36065
|
+
import { defineCommand as defineCommand173 } from "citty";
|
|
38405
36066
|
registerSchema({
|
|
38406
36067
|
command: "tagManager.containers",
|
|
38407
36068
|
description: "List the Google Tag Manager containers this company's connection can reach. Every container the company connected is flagged `connected: true` \u2014 there can be several, and Baker may read and change all of them. Start here to confirm which containers you are managing.",
|
|
@@ -38442,7 +36103,7 @@ function containersHints(containers) {
|
|
|
38442
36103
|
}))
|
|
38443
36104
|
});
|
|
38444
36105
|
}
|
|
38445
|
-
var containersCommand =
|
|
36106
|
+
var containersCommand = defineCommand173({
|
|
38446
36107
|
meta: {
|
|
38447
36108
|
name: "containers",
|
|
38448
36109
|
description: `List Tag Manager containers reachable by this company's connection.
|
|
@@ -38459,7 +36120,7 @@ Start here:
|
|
|
38459
36120
|
}
|
|
38460
36121
|
}
|
|
38461
36122
|
});
|
|
38462
|
-
var readCommand =
|
|
36123
|
+
var readCommand = defineCommand173({
|
|
38463
36124
|
meta: {
|
|
38464
36125
|
name: "read",
|
|
38465
36126
|
description: `Read the current contents of the Tag Manager container \u2014 always do this before staging changes.
|
|
@@ -38501,7 +36162,7 @@ Examples:
|
|
|
38501
36162
|
});
|
|
38502
36163
|
|
|
38503
36164
|
// src/commands/tag-manager/write-commands.ts
|
|
38504
|
-
import { defineCommand as
|
|
36165
|
+
import { defineCommand as defineCommand174 } from "citty";
|
|
38505
36166
|
var CONTAINER_ARG_DESCRIPTION = "Numeric container id (optional only when one container is connected \u2014 run `baker tag-manager containers`)";
|
|
38506
36167
|
var ENTITIES = [
|
|
38507
36168
|
{
|
|
@@ -38557,10 +36218,10 @@ for (const { entity, noun, createHint } of ENTITIES) {
|
|
|
38557
36218
|
});
|
|
38558
36219
|
}
|
|
38559
36220
|
function entityCommand(entity, noun, example) {
|
|
38560
|
-
return
|
|
36221
|
+
return defineCommand174({
|
|
38561
36222
|
meta: { name: entity, description: `Stage ${noun} changes on this chat's Tag Manager draft` },
|
|
38562
36223
|
subCommands: {
|
|
38563
|
-
create:
|
|
36224
|
+
create: defineCommand174({
|
|
38564
36225
|
meta: {
|
|
38565
36226
|
name: "create",
|
|
38566
36227
|
description: `Stage a new ${noun}
|
|
@@ -38582,7 +36243,7 @@ Examples:
|
|
|
38582
36243
|
});
|
|
38583
36244
|
}
|
|
38584
36245
|
}),
|
|
38585
|
-
update:
|
|
36246
|
+
update: defineCommand174({
|
|
38586
36247
|
meta: {
|
|
38587
36248
|
name: "update",
|
|
38588
36249
|
description: `Stage an update to an existing ${noun} (pass its id or path)`
|
|
@@ -38602,7 +36263,7 @@ Examples:
|
|
|
38602
36263
|
});
|
|
38603
36264
|
}
|
|
38604
36265
|
}),
|
|
38605
|
-
delete:
|
|
36266
|
+
delete: defineCommand174({
|
|
38606
36267
|
meta: { name: "delete", description: `Stage the deletion of a ${noun} (pass its id or path)` },
|
|
38607
36268
|
args: {
|
|
38608
36269
|
id: { type: "positional", description: `${noun} id or path`, required: false },
|
|
@@ -38643,7 +36304,7 @@ function builtinTypes(args) {
|
|
|
38643
36304
|
}
|
|
38644
36305
|
return raw.split(",").map((entry) => entry.trim());
|
|
38645
36306
|
}
|
|
38646
|
-
var builtinCommand =
|
|
36307
|
+
var builtinCommand = defineCommand174({
|
|
38647
36308
|
meta: {
|
|
38648
36309
|
name: "builtin",
|
|
38649
36310
|
description: `Enable or disable built-in variables
|
|
@@ -38653,7 +36314,7 @@ Examples:
|
|
|
38653
36314
|
baker tag-manager builtin disable --types formId`
|
|
38654
36315
|
},
|
|
38655
36316
|
subCommands: {
|
|
38656
|
-
enable:
|
|
36317
|
+
enable: defineCommand174({
|
|
38657
36318
|
meta: { name: "enable", description: "Stage enabling built-in variables" },
|
|
38658
36319
|
args: {
|
|
38659
36320
|
types: { type: "string", description: "Comma-separated types", required: false },
|
|
@@ -38667,7 +36328,7 @@ Examples:
|
|
|
38667
36328
|
});
|
|
38668
36329
|
}
|
|
38669
36330
|
}),
|
|
38670
|
-
disable:
|
|
36331
|
+
disable: defineCommand174({
|
|
38671
36332
|
meta: { name: "disable", description: "Stage disabling built-in variables" },
|
|
38672
36333
|
args: {
|
|
38673
36334
|
types: { type: "string", description: "Comma-separated types", required: false },
|
|
@@ -38685,7 +36346,7 @@ Examples:
|
|
|
38685
36346
|
});
|
|
38686
36347
|
|
|
38687
36348
|
// src/commands/tag-manager/index.ts
|
|
38688
|
-
var tagManagerCommand =
|
|
36349
|
+
var tagManagerCommand = defineCommand175({
|
|
38689
36350
|
meta: {
|
|
38690
36351
|
name: "tag-manager",
|
|
38691
36352
|
description: `Read and change what lives inside the client's Google Tag Manager container \u2014 tags, triggers, variables, folders and built-in variables.
|
|
@@ -38722,7 +36383,7 @@ Full guide: __tooling__/docs/tools/baker/tag-manager.md`
|
|
|
38722
36383
|
});
|
|
38723
36384
|
|
|
38724
36385
|
// src/commands/tags/index.ts
|
|
38725
|
-
import { defineCommand as
|
|
36386
|
+
import { defineCommand as defineCommand176 } from "citty";
|
|
38726
36387
|
|
|
38727
36388
|
// src/commands/tags/shared.ts
|
|
38728
36389
|
function failApi3(err) {
|
|
@@ -38791,7 +36452,7 @@ async function listTags(json) {
|
|
|
38791
36452
|
var listArgs9 = {
|
|
38792
36453
|
json: { type: "boolean", description: "Print the raw JSON envelope instead of the readable list" }
|
|
38793
36454
|
};
|
|
38794
|
-
var listCommand16 =
|
|
36455
|
+
var listCommand16 = defineCommand176({
|
|
38795
36456
|
meta: {
|
|
38796
36457
|
name: "list",
|
|
38797
36458
|
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"
|
|
@@ -38810,7 +36471,7 @@ async function listDraft3(chat) {
|
|
|
38810
36471
|
failApi3(err);
|
|
38811
36472
|
}
|
|
38812
36473
|
}
|
|
38813
|
-
var draftCommand5 =
|
|
36474
|
+
var draftCommand5 = defineCommand176({
|
|
38814
36475
|
meta: {
|
|
38815
36476
|
name: "draft",
|
|
38816
36477
|
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). Takes --chat <id> to read an earlier chat's staged changes instead."
|
|
@@ -38820,7 +36481,7 @@ var draftCommand5 = defineCommand184({
|
|
|
38820
36481
|
await listDraft3(args.chat);
|
|
38821
36482
|
}
|
|
38822
36483
|
});
|
|
38823
|
-
var tagsCommand3 =
|
|
36484
|
+
var tagsCommand3 = defineCommand176({
|
|
38824
36485
|
meta: {
|
|
38825
36486
|
name: "tags",
|
|
38826
36487
|
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.
|
|
@@ -38849,10 +36510,10 @@ Full guide: __tooling__/docs/tools/baker/tags.md`
|
|
|
38849
36510
|
});
|
|
38850
36511
|
|
|
38851
36512
|
// src/commands/testimonials/index.ts
|
|
38852
|
-
import { defineCommand as
|
|
36513
|
+
import { defineCommand as defineCommand180 } from "citty";
|
|
38853
36514
|
|
|
38854
36515
|
// src/commands/testimonials/get.ts
|
|
38855
|
-
import { defineCommand as
|
|
36516
|
+
import { defineCommand as defineCommand177 } from "citty";
|
|
38856
36517
|
registerSchema({
|
|
38857
36518
|
command: "testimonials.get",
|
|
38858
36519
|
description: "Get a single testimonial by ID",
|
|
@@ -38860,7 +36521,7 @@ registerSchema({
|
|
|
38860
36521
|
id: { type: "string", description: "Testimonial ID", required: true }
|
|
38861
36522
|
}
|
|
38862
36523
|
});
|
|
38863
|
-
var getCommand5 =
|
|
36524
|
+
var getCommand5 = defineCommand177({
|
|
38864
36525
|
meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
|
|
38865
36526
|
args: {
|
|
38866
36527
|
id: { type: "positional", description: "Testimonial ID", required: false },
|
|
@@ -38897,7 +36558,7 @@ var getCommand5 = defineCommand185({
|
|
|
38897
36558
|
});
|
|
38898
36559
|
|
|
38899
36560
|
// src/commands/testimonials/list.ts
|
|
38900
|
-
import { defineCommand as
|
|
36561
|
+
import { defineCommand as defineCommand178 } from "citty";
|
|
38901
36562
|
registerSchema({
|
|
38902
36563
|
command: "testimonials.list",
|
|
38903
36564
|
description: "List testimonials with optional filters.",
|
|
@@ -38927,7 +36588,7 @@ registerSchema({
|
|
|
38927
36588
|
limit: { type: "number", description: "Max results (default 50)", required: false, default: 50 }
|
|
38928
36589
|
}
|
|
38929
36590
|
});
|
|
38930
|
-
var listCommand17 =
|
|
36591
|
+
var listCommand17 = defineCommand178({
|
|
38931
36592
|
meta: {
|
|
38932
36593
|
name: "list",
|
|
38933
36594
|
description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
|
|
@@ -38976,7 +36637,7 @@ var listCommand17 = defineCommand186({
|
|
|
38976
36637
|
});
|
|
38977
36638
|
|
|
38978
36639
|
// src/commands/testimonials/search.ts
|
|
38979
|
-
import { defineCommand as
|
|
36640
|
+
import { defineCommand as defineCommand179 } from "citty";
|
|
38980
36641
|
function languageBiasHint(results, requestedLanguage) {
|
|
38981
36642
|
if (requestedLanguage) {
|
|
38982
36643
|
return null;
|
|
@@ -39054,7 +36715,7 @@ function buildSearchRequest(query, args) {
|
|
|
39054
36715
|
}
|
|
39055
36716
|
return body;
|
|
39056
36717
|
}
|
|
39057
|
-
var
|
|
36718
|
+
var searchCommand2 = defineCommand179({
|
|
39058
36719
|
meta: {
|
|
39059
36720
|
name: "search",
|
|
39060
36721
|
description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
|
|
@@ -39110,7 +36771,7 @@ var searchCommand3 = defineCommand187({
|
|
|
39110
36771
|
var tagsCommand4 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
|
|
39111
36772
|
|
|
39112
36773
|
// src/commands/testimonials/index.ts
|
|
39113
|
-
var testimonialsCommand =
|
|
36774
|
+
var testimonialsCommand = defineCommand180({
|
|
39114
36775
|
meta: {
|
|
39115
36776
|
name: "testimonials",
|
|
39116
36777
|
description: `Find and browse testimonials in Baker. Subcommands: search, get, list, tags.
|
|
@@ -39125,17 +36786,17 @@ Full guide: __tooling__/docs/tools/baker/testimonials.md`
|
|
|
39125
36786
|
},
|
|
39126
36787
|
subCommands: {
|
|
39127
36788
|
get: getCommand5,
|
|
39128
|
-
search:
|
|
36789
|
+
search: searchCommand2,
|
|
39129
36790
|
list: listCommand17,
|
|
39130
36791
|
tags: tagsCommand4
|
|
39131
36792
|
}
|
|
39132
36793
|
});
|
|
39133
36794
|
|
|
39134
36795
|
// src/commands/videos/index.ts
|
|
39135
|
-
import { defineCommand as
|
|
36796
|
+
import { defineCommand as defineCommand185 } from "citty";
|
|
39136
36797
|
|
|
39137
36798
|
// src/commands/videos/delete.ts
|
|
39138
|
-
import { defineCommand as
|
|
36799
|
+
import { defineCommand as defineCommand181 } from "citty";
|
|
39139
36800
|
registerSchema({
|
|
39140
36801
|
command: "videos.delete",
|
|
39141
36802
|
description: "Delete a video by ID",
|
|
@@ -39149,7 +36810,7 @@ registerSchema({
|
|
|
39149
36810
|
}
|
|
39150
36811
|
}
|
|
39151
36812
|
});
|
|
39152
|
-
var deleteCommand3 =
|
|
36813
|
+
var deleteCommand3 = defineCommand181({
|
|
39153
36814
|
meta: {
|
|
39154
36815
|
name: "delete",
|
|
39155
36816
|
description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
|
|
@@ -39190,7 +36851,7 @@ var deleteCommand3 = defineCommand189({
|
|
|
39190
36851
|
});
|
|
39191
36852
|
|
|
39192
36853
|
// src/commands/videos/get.ts
|
|
39193
|
-
import { defineCommand as
|
|
36854
|
+
import { defineCommand as defineCommand182 } from "citty";
|
|
39194
36855
|
registerSchema({
|
|
39195
36856
|
command: "videos.get",
|
|
39196
36857
|
description: "Get a single video by ID",
|
|
@@ -39198,7 +36859,7 @@ registerSchema({
|
|
|
39198
36859
|
id: { type: "string", description: "Video ID", required: true }
|
|
39199
36860
|
}
|
|
39200
36861
|
});
|
|
39201
|
-
var getCommand6 =
|
|
36862
|
+
var getCommand6 = defineCommand182({
|
|
39202
36863
|
meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
|
|
39203
36864
|
args: {
|
|
39204
36865
|
id: { type: "positional", description: "Video ID", required: false },
|
|
@@ -39235,7 +36896,7 @@ var getCommand6 = defineCommand190({
|
|
|
39235
36896
|
});
|
|
39236
36897
|
|
|
39237
36898
|
// src/commands/videos/search.ts
|
|
39238
|
-
import { defineCommand as
|
|
36899
|
+
import { defineCommand as defineCommand183 } from "citty";
|
|
39239
36900
|
registerSchema({
|
|
39240
36901
|
command: "videos.search",
|
|
39241
36902
|
description: "Search videos by text query. Only returns ready videos.",
|
|
@@ -39245,7 +36906,7 @@ registerSchema({
|
|
|
39245
36906
|
tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
|
|
39246
36907
|
}
|
|
39247
36908
|
});
|
|
39248
|
-
var
|
|
36909
|
+
var searchCommand3 = defineCommand183({
|
|
39249
36910
|
meta: {
|
|
39250
36911
|
name: "search",
|
|
39251
36912
|
description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
|
|
@@ -39295,9 +36956,9 @@ var searchCommand4 = defineCommand191({
|
|
|
39295
36956
|
var tagsCommand5 = makeTagsCommand("videos", "video", "/api/videos/tags");
|
|
39296
36957
|
|
|
39297
36958
|
// src/commands/videos/upload.ts
|
|
39298
|
-
import { readFile as
|
|
36959
|
+
import { readFile as readFile22, stat as stat7 } from "fs/promises";
|
|
39299
36960
|
import { extname as extname3 } from "path";
|
|
39300
|
-
import { defineCommand as
|
|
36961
|
+
import { defineCommand as defineCommand184 } from "citty";
|
|
39301
36962
|
var MIME_MAP = {
|
|
39302
36963
|
".mp4": "video/mp4",
|
|
39303
36964
|
".mov": "video/quicktime",
|
|
@@ -39331,7 +36992,7 @@ function detectContentType(filePath) {
|
|
|
39331
36992
|
}
|
|
39332
36993
|
return mime;
|
|
39333
36994
|
}
|
|
39334
|
-
var uploadCommand2 =
|
|
36995
|
+
var uploadCommand2 = defineCommand184({
|
|
39335
36996
|
meta: {
|
|
39336
36997
|
name: "upload",
|
|
39337
36998
|
description: "Upload a video file to Baker via Mux direct upload. Auto-detects content type. Example: baker videos upload ./demo.mp4"
|
|
@@ -39360,7 +37021,7 @@ var uploadCommand2 = defineCommand192({
|
|
|
39360
37021
|
return;
|
|
39361
37022
|
}
|
|
39362
37023
|
const { uploadUrl, videoId } = await apiPost("/api/videos/upload", {});
|
|
39363
|
-
const fileBuffer = await
|
|
37024
|
+
const fileBuffer = await readFile22(filePath);
|
|
39364
37025
|
const uploadResponse = await fetch(uploadUrl, {
|
|
39365
37026
|
method: "PUT",
|
|
39366
37027
|
headers: { "Content-Type": contentType },
|
|
@@ -39385,7 +37046,7 @@ var uploadCommand2 = defineCommand192({
|
|
|
39385
37046
|
});
|
|
39386
37047
|
|
|
39387
37048
|
// src/commands/videos/index.ts
|
|
39388
|
-
var videosCommand =
|
|
37049
|
+
var videosCommand = defineCommand185({
|
|
39389
37050
|
meta: {
|
|
39390
37051
|
name: "videos",
|
|
39391
37052
|
description: `Find and manage videos in Baker. Subcommands: search, get, upload, delete, tags.
|
|
@@ -39401,7 +37062,7 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
|
|
|
39401
37062
|
},
|
|
39402
37063
|
subCommands: {
|
|
39403
37064
|
get: getCommand6,
|
|
39404
|
-
search:
|
|
37065
|
+
search: searchCommand3,
|
|
39405
37066
|
upload: uploadCommand2,
|
|
39406
37067
|
delete: deleteCommand3,
|
|
39407
37068
|
tags: tagsCommand5
|
|
@@ -39409,19 +37070,19 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
|
|
|
39409
37070
|
});
|
|
39410
37071
|
|
|
39411
37072
|
// src/commands/winning-ads/index.ts
|
|
39412
|
-
import { defineCommand as
|
|
37073
|
+
import { defineCommand as defineCommand198 } from "citty";
|
|
39413
37074
|
|
|
39414
37075
|
// src/commands/winning-ads/advertisers.ts
|
|
39415
|
-
import { defineCommand as
|
|
37076
|
+
import { defineCommand as defineCommand186 } from "citty";
|
|
39416
37077
|
|
|
39417
37078
|
// src/commands/winning-ads/shared.ts
|
|
39418
|
-
function
|
|
37079
|
+
function splitList(value) {
|
|
39419
37080
|
if (!value) {
|
|
39420
37081
|
return [];
|
|
39421
37082
|
}
|
|
39422
37083
|
return value.split(",").map((v) => v.trim()).filter(Boolean);
|
|
39423
37084
|
}
|
|
39424
|
-
function
|
|
37085
|
+
function reportError(err) {
|
|
39425
37086
|
if (err instanceof ApiError) {
|
|
39426
37087
|
writeJson({ ok: false, error: { code: err.code, message: err.message } });
|
|
39427
37088
|
process.exit(1);
|
|
@@ -39465,7 +37126,7 @@ function advertiserNormalizer(record, full) {
|
|
|
39465
37126
|
last_synced_at: record.last_synced_at ?? null
|
|
39466
37127
|
};
|
|
39467
37128
|
}
|
|
39468
|
-
var advertisersCommand2 =
|
|
37129
|
+
var advertisersCommand2 = defineCommand186({
|
|
39469
37130
|
meta: {
|
|
39470
37131
|
name: "advertisers",
|
|
39471
37132
|
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'
|
|
@@ -39517,13 +37178,13 @@ var advertisersCommand2 = defineCommand194({
|
|
|
39517
37178
|
advertiserNormalizer
|
|
39518
37179
|
);
|
|
39519
37180
|
} catch (err) {
|
|
39520
|
-
|
|
37181
|
+
reportError(err);
|
|
39521
37182
|
}
|
|
39522
37183
|
}
|
|
39523
37184
|
});
|
|
39524
37185
|
|
|
39525
37186
|
// src/commands/winning-ads/brief.ts
|
|
39526
|
-
import { defineCommand as
|
|
37187
|
+
import { defineCommand as defineCommand187 } from "citty";
|
|
39527
37188
|
registerSchema({
|
|
39528
37189
|
command: "winning-ads.brief",
|
|
39529
37190
|
description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
|
|
@@ -39569,7 +37230,7 @@ function parseDna(raw) {
|
|
|
39569
37230
|
}
|
|
39570
37231
|
return parsed;
|
|
39571
37232
|
}
|
|
39572
|
-
var briefCommand =
|
|
37233
|
+
var briefCommand = defineCommand187({
|
|
39573
37234
|
meta: {
|
|
39574
37235
|
name: "brief",
|
|
39575
37236
|
description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
|
|
@@ -39599,13 +37260,13 @@ var briefCommand = defineCommand195({
|
|
|
39599
37260
|
const data = await apiPost("/api/ad-library/brief", body);
|
|
39600
37261
|
writeJson({ ok: true, data });
|
|
39601
37262
|
} catch (err) {
|
|
39602
|
-
|
|
37263
|
+
reportError(err);
|
|
39603
37264
|
}
|
|
39604
37265
|
}
|
|
39605
37266
|
});
|
|
39606
37267
|
|
|
39607
37268
|
// src/commands/winning-ads/content.ts
|
|
39608
|
-
import { defineCommand as
|
|
37269
|
+
import { defineCommand as defineCommand188 } from "citty";
|
|
39609
37270
|
registerSchema({
|
|
39610
37271
|
command: "winning-ads.content",
|
|
39611
37272
|
description: "Read what's INSIDE one winning ad: the spoken transcript, the on-screen text, and the ad copy. Use this after `search`/`winners`/`feed` return a shortlist \u2014 pass an ad_id to understand a reference before reproducing it. Add --full for speech, pacing, and soundtrack detail. Video ads carry the transcript/on-screen text; static ads carry only the copy.",
|
|
@@ -39618,7 +37279,7 @@ registerSchema({
|
|
|
39618
37279
|
}
|
|
39619
37280
|
}
|
|
39620
37281
|
});
|
|
39621
|
-
var contentCommand =
|
|
37282
|
+
var contentCommand = defineCommand188({
|
|
39622
37283
|
meta: {
|
|
39623
37284
|
name: "content",
|
|
39624
37285
|
description: "Read the transcript + on-screen text + copy of one winning ad. Example: baker winning-ads content adg_123 --platform meta --full --output md"
|
|
@@ -39661,16 +37322,16 @@ var contentCommand = defineCommand196({
|
|
|
39661
37322
|
adContentNormalizer
|
|
39662
37323
|
);
|
|
39663
37324
|
} catch (err) {
|
|
39664
|
-
|
|
37325
|
+
reportError(err);
|
|
39665
37326
|
}
|
|
39666
37327
|
}
|
|
39667
37328
|
});
|
|
39668
37329
|
|
|
39669
37330
|
// src/commands/winning-ads/feed.ts
|
|
39670
|
-
import { defineCommand as
|
|
37331
|
+
import { defineCommand as defineCommand189 } from "citty";
|
|
39671
37332
|
function buildFeedParams(input) {
|
|
39672
37333
|
const params = {};
|
|
39673
|
-
const advertiser =
|
|
37334
|
+
const advertiser = splitList(input.advertiser);
|
|
39674
37335
|
if (advertiser.length > 0) {
|
|
39675
37336
|
params.advertiser = advertiser.join(",");
|
|
39676
37337
|
}
|
|
@@ -39683,11 +37344,11 @@ function buildFeedParams(input) {
|
|
|
39683
37344
|
if (input.limit !== void 0 && input.limit !== "") {
|
|
39684
37345
|
params.limit = input.limit;
|
|
39685
37346
|
}
|
|
39686
|
-
const winnerCategory =
|
|
37347
|
+
const winnerCategory = splitList(input.winnerCategory);
|
|
39687
37348
|
if (winnerCategory.length > 0) {
|
|
39688
37349
|
params.winner_category = winnerCategory.join(",");
|
|
39689
37350
|
}
|
|
39690
|
-
const format =
|
|
37351
|
+
const format = splitList(input.format);
|
|
39691
37352
|
if (format.length > 0) {
|
|
39692
37353
|
params.format = format.join(",");
|
|
39693
37354
|
}
|
|
@@ -39719,7 +37380,7 @@ registerSchema({
|
|
|
39719
37380
|
format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
|
|
39720
37381
|
}
|
|
39721
37382
|
});
|
|
39722
|
-
var feedCommand =
|
|
37383
|
+
var feedCommand = defineCommand189({
|
|
39723
37384
|
meta: {
|
|
39724
37385
|
name: "feed",
|
|
39725
37386
|
description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
|
|
@@ -39798,13 +37459,13 @@ var feedCommand = defineCommand197({
|
|
|
39798
37459
|
`);
|
|
39799
37460
|
}
|
|
39800
37461
|
} catch (err) {
|
|
39801
|
-
|
|
37462
|
+
reportError(err);
|
|
39802
37463
|
}
|
|
39803
37464
|
}
|
|
39804
37465
|
});
|
|
39805
37466
|
|
|
39806
37467
|
// src/commands/winning-ads/follow.ts
|
|
39807
|
-
import { defineCommand as
|
|
37468
|
+
import { defineCommand as defineCommand190 } from "citty";
|
|
39808
37469
|
var PLATFORMS = ["meta", "linkedin"];
|
|
39809
37470
|
registerSchema({
|
|
39810
37471
|
command: "winning-ads.follow",
|
|
@@ -39819,7 +37480,7 @@ registerSchema({
|
|
|
39819
37480
|
label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
|
|
39820
37481
|
}
|
|
39821
37482
|
});
|
|
39822
|
-
var followCommand =
|
|
37483
|
+
var followCommand = defineCommand190({
|
|
39823
37484
|
meta: {
|
|
39824
37485
|
name: "follow",
|
|
39825
37486
|
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'
|
|
@@ -39860,19 +37521,19 @@ var followCommand = defineCommand198({
|
|
|
39860
37521
|
}
|
|
39861
37522
|
writeJson({ ok: true, data, hints });
|
|
39862
37523
|
} catch (err) {
|
|
39863
|
-
|
|
37524
|
+
reportError(err);
|
|
39864
37525
|
}
|
|
39865
37526
|
}
|
|
39866
37527
|
});
|
|
39867
37528
|
|
|
39868
37529
|
// src/commands/winning-ads/follow-competitors.ts
|
|
39869
|
-
import { defineCommand as
|
|
37530
|
+
import { defineCommand as defineCommand191 } from "citty";
|
|
39870
37531
|
var PLATFORMS2 = ["meta", "linkedin"];
|
|
39871
37532
|
var BATCH_TIMEOUT_MS = 3e5;
|
|
39872
37533
|
function buildFollowBatchBody(input) {
|
|
39873
37534
|
const seen = /* @__PURE__ */ new Set();
|
|
39874
37535
|
const inputs = [];
|
|
39875
|
-
for (const domain of
|
|
37536
|
+
for (const domain of splitList(input.domains)) {
|
|
39876
37537
|
const key = domain.toLowerCase();
|
|
39877
37538
|
if (seen.has(key)) {
|
|
39878
37539
|
continue;
|
|
@@ -39899,7 +37560,7 @@ registerSchema({
|
|
|
39899
37560
|
}
|
|
39900
37561
|
}
|
|
39901
37562
|
});
|
|
39902
|
-
var followCompetitorsCommand =
|
|
37563
|
+
var followCompetitorsCommand = defineCommand191({
|
|
39903
37564
|
meta: {
|
|
39904
37565
|
name: "follow-competitors",
|
|
39905
37566
|
description: 'Follow many brands at once by domain \u2014 add every competitor in one call. Example: baker winning-ads follow-competitors "deel.com,notion.so,hubspot.com"'
|
|
@@ -39968,13 +37629,13 @@ var followCompetitorsCommand = defineCommand199({
|
|
|
39968
37629
|
}
|
|
39969
37630
|
writeJson({ ok: true, data, hints: hints.length > 0 ? hints : void 0 });
|
|
39970
37631
|
} catch (err) {
|
|
39971
|
-
|
|
37632
|
+
reportError(err);
|
|
39972
37633
|
}
|
|
39973
37634
|
}
|
|
39974
37635
|
});
|
|
39975
37636
|
|
|
39976
37637
|
// src/commands/winning-ads/following.ts
|
|
39977
|
-
import { defineCommand as
|
|
37638
|
+
import { defineCommand as defineCommand192 } from "citty";
|
|
39978
37639
|
registerSchema({
|
|
39979
37640
|
command: "winning-ads.following",
|
|
39980
37641
|
description: "List the brands you follow in your ad-dna library, with each one's status (ready vs still adding) and cached ad counts.",
|
|
@@ -40007,7 +37668,7 @@ function followingNormalizer(record, full) {
|
|
|
40007
37668
|
platforms: Array.isArray(record.platforms) ? record.platforms : []
|
|
40008
37669
|
};
|
|
40009
37670
|
}
|
|
40010
|
-
var followingCommand =
|
|
37671
|
+
var followingCommand = defineCommand192({
|
|
40011
37672
|
meta: {
|
|
40012
37673
|
name: "following",
|
|
40013
37674
|
description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
|
|
@@ -40036,13 +37697,13 @@ var followingCommand = defineCommand200({
|
|
|
40036
37697
|
followingNormalizer
|
|
40037
37698
|
);
|
|
40038
37699
|
} catch (err) {
|
|
40039
|
-
|
|
37700
|
+
reportError(err);
|
|
40040
37701
|
}
|
|
40041
37702
|
}
|
|
40042
37703
|
});
|
|
40043
37704
|
|
|
40044
37705
|
// src/commands/winning-ads/patterns.ts
|
|
40045
|
-
import { defineCommand as
|
|
37706
|
+
import { defineCommand as defineCommand193 } from "citty";
|
|
40046
37707
|
registerSchema({
|
|
40047
37708
|
command: "winning-ads.patterns",
|
|
40048
37709
|
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.",
|
|
@@ -40057,8 +37718,8 @@ registerSchema({
|
|
|
40057
37718
|
}
|
|
40058
37719
|
});
|
|
40059
37720
|
function buildPatternsBody(args) {
|
|
40060
|
-
const winners =
|
|
40061
|
-
const duds =
|
|
37721
|
+
const winners = splitList(args.winners);
|
|
37722
|
+
const duds = splitList(args.duds);
|
|
40062
37723
|
if (!winners.length || !duds.length) {
|
|
40063
37724
|
throw new Error("Provide at least one ad id for both --winners and --duds");
|
|
40064
37725
|
}
|
|
@@ -40081,7 +37742,7 @@ function discriminatorRow(record) {
|
|
|
40081
37742
|
top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
|
|
40082
37743
|
};
|
|
40083
37744
|
}
|
|
40084
|
-
var patternsCommand =
|
|
37745
|
+
var patternsCommand = defineCommand193({
|
|
40085
37746
|
meta: {
|
|
40086
37747
|
name: "patterns",
|
|
40087
37748
|
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"
|
|
@@ -40131,13 +37792,13 @@ var patternsCommand = defineCommand201({
|
|
|
40131
37792
|
(record) => discriminatorRow(record)
|
|
40132
37793
|
);
|
|
40133
37794
|
} catch (err) {
|
|
40134
|
-
|
|
37795
|
+
reportError(err);
|
|
40135
37796
|
}
|
|
40136
37797
|
}
|
|
40137
37798
|
});
|
|
40138
37799
|
|
|
40139
37800
|
// src/commands/winning-ads/search.ts
|
|
40140
|
-
import { defineCommand as
|
|
37801
|
+
import { defineCommand as defineCommand194 } from "citty";
|
|
40141
37802
|
registerSchema({
|
|
40142
37803
|
command: "winning-ads.search",
|
|
40143
37804
|
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.",
|
|
@@ -40207,7 +37868,7 @@ function setNumber(body, key, value) {
|
|
|
40207
37868
|
}
|
|
40208
37869
|
}
|
|
40209
37870
|
function setList(target, key, value) {
|
|
40210
|
-
const list =
|
|
37871
|
+
const list = splitList(value);
|
|
40211
37872
|
if (list.length) {
|
|
40212
37873
|
target[key] = list;
|
|
40213
37874
|
}
|
|
@@ -40217,7 +37878,7 @@ function setString(target, key, value) {
|
|
|
40217
37878
|
target[key] = value;
|
|
40218
37879
|
}
|
|
40219
37880
|
}
|
|
40220
|
-
function
|
|
37881
|
+
function buildSearchBody(args) {
|
|
40221
37882
|
const body = {};
|
|
40222
37883
|
if (args.query) {
|
|
40223
37884
|
body.free_text_query = args.query;
|
|
@@ -40245,7 +37906,7 @@ function buildSearchBody2(args) {
|
|
|
40245
37906
|
}
|
|
40246
37907
|
return body;
|
|
40247
37908
|
}
|
|
40248
|
-
var
|
|
37909
|
+
var searchCommand4 = defineCommand194({
|
|
40249
37910
|
meta: {
|
|
40250
37911
|
name: "search",
|
|
40251
37912
|
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"
|
|
@@ -40321,7 +37982,7 @@ var searchCommand5 = defineCommand202({
|
|
|
40321
37982
|
firstSeenBefore: args["first-seen-before"],
|
|
40322
37983
|
hookArchetype: args["hook-archetype"]
|
|
40323
37984
|
};
|
|
40324
|
-
const body =
|
|
37985
|
+
const body = buildSearchBody(searchArgs);
|
|
40325
37986
|
if (!("free_text_query" in body) && !("ref_ad_id" in body) && !("hard_filters" in body)) {
|
|
40326
37987
|
writeJson({
|
|
40327
37988
|
ok: false,
|
|
@@ -40354,13 +38015,13 @@ var searchCommand5 = defineCommand202({
|
|
|
40354
38015
|
winningAdNormalizer
|
|
40355
38016
|
);
|
|
40356
38017
|
} catch (err) {
|
|
40357
|
-
|
|
38018
|
+
reportError(err);
|
|
40358
38019
|
}
|
|
40359
38020
|
}
|
|
40360
38021
|
});
|
|
40361
38022
|
|
|
40362
38023
|
// src/commands/winning-ads/seeds.ts
|
|
40363
|
-
import { defineCommand as
|
|
38024
|
+
import { defineCommand as defineCommand195 } from "citty";
|
|
40364
38025
|
function leanRow(r) {
|
|
40365
38026
|
return {
|
|
40366
38027
|
key: r.key,
|
|
@@ -40388,7 +38049,7 @@ function makeSeedCommand(opts) {
|
|
|
40388
38049
|
limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
|
|
40389
38050
|
}
|
|
40390
38051
|
});
|
|
40391
|
-
return
|
|
38052
|
+
return defineCommand195({
|
|
40392
38053
|
meta: { name: opts.name, description: opts.description },
|
|
40393
38054
|
args: {
|
|
40394
38055
|
platform: { type: "string", description: "Single platform to segment on", required: false },
|
|
@@ -40415,7 +38076,7 @@ function makeSeedCommand(opts) {
|
|
|
40415
38076
|
}
|
|
40416
38077
|
writeOutput({ ok: true, data: projected }, output);
|
|
40417
38078
|
} catch (err) {
|
|
40418
|
-
|
|
38079
|
+
reportError(err);
|
|
40419
38080
|
}
|
|
40420
38081
|
}
|
|
40421
38082
|
});
|
|
@@ -40437,7 +38098,7 @@ var formatsCommand = makeSeedCommand({
|
|
|
40437
38098
|
});
|
|
40438
38099
|
|
|
40439
38100
|
// src/commands/winning-ads/unfollow.ts
|
|
40440
|
-
import { defineCommand as
|
|
38101
|
+
import { defineCommand as defineCommand196 } from "citty";
|
|
40441
38102
|
registerSchema({
|
|
40442
38103
|
command: "winning-ads.unfollow",
|
|
40443
38104
|
description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
|
|
@@ -40445,7 +38106,7 @@ registerSchema({
|
|
|
40445
38106
|
advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
|
|
40446
38107
|
}
|
|
40447
38108
|
});
|
|
40448
|
-
var unfollowCommand =
|
|
38109
|
+
var unfollowCommand = defineCommand196({
|
|
40449
38110
|
meta: {
|
|
40450
38111
|
name: "unfollow",
|
|
40451
38112
|
description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
|
|
@@ -40460,13 +38121,13 @@ var unfollowCommand = defineCommand204({
|
|
|
40460
38121
|
});
|
|
40461
38122
|
writeJson({ ok: true, data });
|
|
40462
38123
|
} catch (err) {
|
|
40463
|
-
|
|
38124
|
+
reportError(err);
|
|
40464
38125
|
}
|
|
40465
38126
|
}
|
|
40466
38127
|
});
|
|
40467
38128
|
|
|
40468
38129
|
// src/commands/winning-ads/winners.ts
|
|
40469
|
-
import { defineCommand as
|
|
38130
|
+
import { defineCommand as defineCommand197 } from "citty";
|
|
40470
38131
|
registerSchema({
|
|
40471
38132
|
command: "winning-ads.winners",
|
|
40472
38133
|
description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
|
|
@@ -40476,7 +38137,7 @@ registerSchema({
|
|
|
40476
38137
|
platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
|
|
40477
38138
|
}
|
|
40478
38139
|
});
|
|
40479
|
-
var winnersCommand =
|
|
38140
|
+
var winnersCommand = defineCommand197({
|
|
40480
38141
|
meta: {
|
|
40481
38142
|
name: "winners",
|
|
40482
38143
|
description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
|
|
@@ -40520,13 +38181,13 @@ var winnersCommand = defineCommand205({
|
|
|
40520
38181
|
winningAdNormalizer
|
|
40521
38182
|
);
|
|
40522
38183
|
} catch (err) {
|
|
40523
|
-
|
|
38184
|
+
reportError(err);
|
|
40524
38185
|
}
|
|
40525
38186
|
}
|
|
40526
38187
|
});
|
|
40527
38188
|
|
|
40528
38189
|
// src/commands/winning-ads/index.ts
|
|
40529
|
-
var winningAdsCommand =
|
|
38190
|
+
var winningAdsCommand = defineCommand198({
|
|
40530
38191
|
meta: {
|
|
40531
38192
|
name: "winning-ads",
|
|
40532
38193
|
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.
|
|
@@ -40564,7 +38225,7 @@ Examples:
|
|
|
40564
38225
|
Full guide: __tooling__/docs/tools/baker/winning-ads.md`
|
|
40565
38226
|
},
|
|
40566
38227
|
subCommands: {
|
|
40567
|
-
search:
|
|
38228
|
+
search: searchCommand4,
|
|
40568
38229
|
advertisers: advertisersCommand2,
|
|
40569
38230
|
follow: followCommand,
|
|
40570
38231
|
"follow-competitors": followCompetitorsCommand,
|
|
@@ -40598,7 +38259,7 @@ function getCliVersion() {
|
|
|
40598
38259
|
}
|
|
40599
38260
|
|
|
40600
38261
|
// src/cli.ts
|
|
40601
|
-
var main =
|
|
38262
|
+
var main = defineCommand199({
|
|
40602
38263
|
meta: {
|
|
40603
38264
|
name: "baker",
|
|
40604
38265
|
version: getCliVersion(),
|