@koda-sl/baker-cli 0.185.0 → 0.186.0-dev.6d88981c0
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 +29 -1
- package/dist/{chunk-3TXZKSAR.js → chunk-PJ5KH27L.js} +4 -4
- package/dist/chunk-PJ5KH27L.js.map +1 -0
- package/dist/cli.js +2726 -280
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +2 -1
- package/dist/chunk-3TXZKSAR.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
toModelSafeImage,
|
|
47
47
|
ulid,
|
|
48
48
|
validateCanvasDeep
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-PJ5KH27L.js";
|
|
50
50
|
import {
|
|
51
51
|
csvOrJson,
|
|
52
52
|
daysAgoIso,
|
|
@@ -89,7 +89,7 @@ import {
|
|
|
89
89
|
} from "./chunk-YL3HDEIJ.js";
|
|
90
90
|
|
|
91
91
|
// src/cli.ts
|
|
92
|
-
import { defineCommand as
|
|
92
|
+
import { defineCommand as defineCommand207, runMain } from "citty";
|
|
93
93
|
|
|
94
94
|
// src/commands/actions/index.ts
|
|
95
95
|
import { defineCommand as defineCommand18 } from "citty";
|
|
@@ -3760,7 +3760,9 @@ var tagInputResultSchema = z13.discriminatedUnion("status", [
|
|
|
3760
3760
|
}),
|
|
3761
3761
|
z13.object({
|
|
3762
3762
|
status: z13.literal("declined"),
|
|
3763
|
-
reason: z13.string().optional()
|
|
3763
|
+
reason: z13.string().optional(),
|
|
3764
|
+
/** Set when the platform could not apply an approved change, rather than the user skipping it. */
|
|
3765
|
+
failed: z13.boolean().optional()
|
|
3764
3766
|
})
|
|
3765
3767
|
]);
|
|
3766
3768
|
var tagChangeToolResultSchema = z13.object({
|
|
@@ -7529,19 +7531,19 @@ Examples:
|
|
|
7529
7531
|
},
|
|
7530
7532
|
run: async ({ args }) => {
|
|
7531
7533
|
const customerId = await resolveCustomerId(args);
|
|
7532
|
-
const
|
|
7534
|
+
const window2 = resolveChangesWindow({
|
|
7533
7535
|
days: args.days ? Number(args.days) : void 0,
|
|
7534
7536
|
scope: args.scope
|
|
7535
7537
|
});
|
|
7536
|
-
if (!
|
|
7537
|
-
writeJsonEnvelope({ ok: false, error: { ...
|
|
7538
|
+
if (!window2.ok) {
|
|
7539
|
+
writeJsonEnvelope({ ok: false, error: { ...window2.error, retryable: false } });
|
|
7538
7540
|
process.exit(1);
|
|
7539
7541
|
return;
|
|
7540
7542
|
}
|
|
7541
7543
|
const body = {
|
|
7542
7544
|
customerId,
|
|
7543
|
-
days:
|
|
7544
|
-
scope:
|
|
7545
|
+
days: window2.days,
|
|
7546
|
+
scope: window2.scope,
|
|
7545
7547
|
limit: args.limit ? Number(args.limit) : 50
|
|
7546
7548
|
};
|
|
7547
7549
|
const managerId = getManagerIdForCustomer(customerId);
|
|
@@ -7558,7 +7560,7 @@ Examples:
|
|
|
7558
7560
|
const first = data[0];
|
|
7559
7561
|
const fields = first ? Object.keys(first) : [];
|
|
7560
7562
|
const fieldDescs = getFieldDescriptions(fields);
|
|
7561
|
-
writeJsonEnvelope({ ok: true, data, fields: fieldDescs, hints:
|
|
7563
|
+
writeJsonEnvelope({ ok: true, data, fields: fieldDescs, hints: window2.hints });
|
|
7562
7564
|
} catch (err) {
|
|
7563
7565
|
if (err instanceof ApiError) {
|
|
7564
7566
|
writeAdsJson(parseApiError(err.message, "", customerId, err.code));
|
|
@@ -7915,8 +7917,8 @@ function countStaccato(text) {
|
|
|
7915
7917
|
let first = "";
|
|
7916
7918
|
let runStart = 0;
|
|
7917
7919
|
for (const [i, s] of sentences.entries()) {
|
|
7918
|
-
const
|
|
7919
|
-
if (
|
|
7920
|
+
const words3 = s.split(/\s+/).filter(Boolean).length;
|
|
7921
|
+
if (words3 > 0 && words3 <= STACCATO_MAX_WORDS) {
|
|
7920
7922
|
if (run === 0) runStart = i;
|
|
7921
7923
|
run++;
|
|
7922
7924
|
if (run === STACCATO_RUN) {
|
|
@@ -7934,10 +7936,10 @@ function countTitleCaseHeadings(text) {
|
|
|
7934
7936
|
let first = "";
|
|
7935
7937
|
for (const m of text.matchAll(/^\s{0,3}#{1,6}\s+(.+)$/gm)) {
|
|
7936
7938
|
const heading = (m[1] ?? "").trim();
|
|
7937
|
-
const
|
|
7938
|
-
if (
|
|
7939
|
-
const capitalized =
|
|
7940
|
-
if (capitalized /
|
|
7939
|
+
const words3 = heading.split(/\s+/).filter((w) => new RegExp("\\p{L}", "u").test(w));
|
|
7940
|
+
if (words3.length < 4) continue;
|
|
7941
|
+
const capitalized = words3.filter((w) => new RegExp("^\\p{Lu}", "u").test(w)).length;
|
|
7942
|
+
if (capitalized / words3.length < 0.8) continue;
|
|
7941
7943
|
count++;
|
|
7942
7944
|
if (!first) first = heading;
|
|
7943
7945
|
}
|
|
@@ -8266,11 +8268,11 @@ function rawTextEntries(value) {
|
|
|
8266
8268
|
const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
8267
8269
|
return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
8268
8270
|
}
|
|
8269
|
-
function rawFileEntries(
|
|
8270
|
-
if (typeof
|
|
8271
|
+
function rawFileEntries(path35) {
|
|
8272
|
+
if (typeof path35 !== "string" || path35.length === 0) {
|
|
8271
8273
|
return [];
|
|
8272
8274
|
}
|
|
8273
|
-
return readFileSync2(
|
|
8275
|
+
return readFileSync2(path35, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
8274
8276
|
}
|
|
8275
8277
|
function keywordEntries(args) {
|
|
8276
8278
|
const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
|
|
@@ -8293,19 +8295,19 @@ function keywordEntries(args) {
|
|
|
8293
8295
|
}
|
|
8294
8296
|
return entries;
|
|
8295
8297
|
}
|
|
8296
|
-
function loadJsonFileArg(
|
|
8297
|
-
if (typeof
|
|
8298
|
+
function loadJsonFileArg(path35) {
|
|
8299
|
+
if (typeof path35 !== "string" || path35.length === 0) {
|
|
8298
8300
|
return {};
|
|
8299
8301
|
}
|
|
8300
8302
|
try {
|
|
8301
|
-
const parsed = JSON.parse(readFileSync2(
|
|
8303
|
+
const parsed = JSON.parse(readFileSync2(path35, "utf8"));
|
|
8302
8304
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8303
|
-
failWriteValidation(`${
|
|
8305
|
+
failWriteValidation(`${path35} must contain a JSON object`);
|
|
8304
8306
|
}
|
|
8305
8307
|
return parsed;
|
|
8306
8308
|
} catch (err) {
|
|
8307
8309
|
if (err instanceof SyntaxError) {
|
|
8308
|
-
failWriteValidation(`${
|
|
8310
|
+
failWriteValidation(`${path35} is not valid JSON: ${err.message}`);
|
|
8309
8311
|
}
|
|
8310
8312
|
throw err;
|
|
8311
8313
|
}
|
|
@@ -8435,10 +8437,10 @@ async function stageUpdate(kind, customerId, target, payload, hints) {
|
|
|
8435
8437
|
async function stageTarget(kind, customerId, target, hints) {
|
|
8436
8438
|
await stageGoogleOp({ kind, customerId, target }, hints);
|
|
8437
8439
|
}
|
|
8438
|
-
async function draftAction(
|
|
8440
|
+
async function draftAction(path35, body, chat) {
|
|
8439
8441
|
try {
|
|
8440
8442
|
const chatId = resolveChatId(chat);
|
|
8441
|
-
const response = await apiPost(
|
|
8443
|
+
const response = await apiPost(path35, { chatId, ...body });
|
|
8442
8444
|
writeJsonEnvelope(response);
|
|
8443
8445
|
} catch (err) {
|
|
8444
8446
|
handleGoogleError(err);
|
|
@@ -11106,19 +11108,19 @@ function failWriteValidation2(message) {
|
|
|
11106
11108
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
11107
11109
|
process.exit(1);
|
|
11108
11110
|
}
|
|
11109
|
-
function loadJsonFileArg2(
|
|
11110
|
-
if (typeof
|
|
11111
|
+
function loadJsonFileArg2(path35) {
|
|
11112
|
+
if (typeof path35 !== "string" || path35.length === 0) {
|
|
11111
11113
|
return {};
|
|
11112
11114
|
}
|
|
11113
11115
|
try {
|
|
11114
|
-
const parsed = JSON.parse(readFileSync4(
|
|
11116
|
+
const parsed = JSON.parse(readFileSync4(path35, "utf8"));
|
|
11115
11117
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
11116
|
-
failWriteValidation2(`${
|
|
11118
|
+
failWriteValidation2(`${path35} must contain a JSON object`);
|
|
11117
11119
|
}
|
|
11118
11120
|
return parsed;
|
|
11119
11121
|
} catch (err) {
|
|
11120
11122
|
if (err instanceof SyntaxError) {
|
|
11121
|
-
failWriteValidation2(`${
|
|
11123
|
+
failWriteValidation2(`${path35} is not valid JSON: ${err.message}`);
|
|
11122
11124
|
}
|
|
11123
11125
|
throw err;
|
|
11124
11126
|
}
|
|
@@ -11203,15 +11205,15 @@ function parseLocaleFlag(value) {
|
|
|
11203
11205
|
}
|
|
11204
11206
|
return { language: match[1], country: match[2].toUpperCase() };
|
|
11205
11207
|
}
|
|
11206
|
-
function loadTargetingFileArg(
|
|
11207
|
-
if (typeof
|
|
11208
|
+
function loadTargetingFileArg(path35) {
|
|
11209
|
+
if (typeof path35 !== "string" || path35.length === 0) {
|
|
11208
11210
|
return void 0;
|
|
11209
11211
|
}
|
|
11210
|
-
const parsed = loadJsonFileArg2(
|
|
11212
|
+
const parsed = loadJsonFileArg2(path35);
|
|
11211
11213
|
const criteria = parsed.targetingCriteria ?? parsed;
|
|
11212
11214
|
if (!criteria.include) {
|
|
11213
11215
|
failWriteValidation2(
|
|
11214
|
-
`${
|
|
11216
|
+
`${path35} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
|
|
11215
11217
|
);
|
|
11216
11218
|
}
|
|
11217
11219
|
return criteria;
|
|
@@ -11246,14 +11248,14 @@ function parseCsvLine(line) {
|
|
|
11246
11248
|
cells.push(current);
|
|
11247
11249
|
return cells.map((cell) => cell.trim());
|
|
11248
11250
|
}
|
|
11249
|
-
function parseListFileArg(
|
|
11250
|
-
if (typeof
|
|
11251
|
+
function parseListFileArg(path35, maxRows) {
|
|
11252
|
+
if (typeof path35 !== "string" || path35.length === 0) {
|
|
11251
11253
|
return void 0;
|
|
11252
11254
|
}
|
|
11253
|
-
const raw = readFileSync4(
|
|
11255
|
+
const raw = readFileSync4(path35, "utf8");
|
|
11254
11256
|
const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
11255
11257
|
if (lines.length < 2) {
|
|
11256
|
-
failWriteValidation2(`${
|
|
11258
|
+
failWriteValidation2(`${path35} needs a header row and at least one data row`);
|
|
11257
11259
|
}
|
|
11258
11260
|
const columns = parseCsvLine(lines[0]).map((column) => column.trim());
|
|
11259
11261
|
const rows = [];
|
|
@@ -11272,7 +11274,7 @@ function parseListFileArg(path28, maxRows) {
|
|
|
11272
11274
|
}
|
|
11273
11275
|
}
|
|
11274
11276
|
if (rows.length > maxRows) {
|
|
11275
|
-
failWriteValidation2(`${
|
|
11277
|
+
failWriteValidation2(`${path35} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
|
|
11276
11278
|
}
|
|
11277
11279
|
return { columns, rows };
|
|
11278
11280
|
}
|
|
@@ -11368,11 +11370,11 @@ function readPositionals(args) {
|
|
|
11368
11370
|
function splitIdList(raw) {
|
|
11369
11371
|
return raw.split(",").map((id) => id.trim()).filter(Boolean);
|
|
11370
11372
|
}
|
|
11371
|
-
function idsFileEntries(
|
|
11372
|
-
if (typeof
|
|
11373
|
+
function idsFileEntries(path35) {
|
|
11374
|
+
if (typeof path35 !== "string" || path35.length === 0) {
|
|
11373
11375
|
return [];
|
|
11374
11376
|
}
|
|
11375
|
-
return readFileSync4(
|
|
11377
|
+
return readFileSync4(path35, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
|
|
11376
11378
|
}
|
|
11377
11379
|
function requireTargets(args, entity) {
|
|
11378
11380
|
const positionals = readPositionals(args);
|
|
@@ -13995,9 +13997,9 @@ function compactRow(row) {
|
|
|
13995
13997
|
...destination.postUrn ? { postUrn: destination.postUrn } : {}
|
|
13996
13998
|
};
|
|
13997
13999
|
}
|
|
13998
|
-
function readPath(row,
|
|
14000
|
+
function readPath(row, path35) {
|
|
13999
14001
|
let current = row;
|
|
14000
|
-
for (const segment of
|
|
14002
|
+
for (const segment of path35.split(".")) {
|
|
14001
14003
|
const record = asRecord2(current);
|
|
14002
14004
|
if (!record) return void 0;
|
|
14003
14005
|
current = record[segment];
|
|
@@ -14007,10 +14009,10 @@ function readPath(row, path28) {
|
|
|
14007
14009
|
function projectFields(rows, paths) {
|
|
14008
14010
|
return rows.map((row) => {
|
|
14009
14011
|
const projected = {};
|
|
14010
|
-
for (const
|
|
14011
|
-
const value = readPath(row,
|
|
14012
|
+
for (const path35 of paths) {
|
|
14013
|
+
const value = readPath(row, path35);
|
|
14012
14014
|
if (value !== void 0) {
|
|
14013
|
-
projected[
|
|
14015
|
+
projected[path35] = value;
|
|
14014
14016
|
}
|
|
14015
14017
|
}
|
|
14016
14018
|
return projected;
|
|
@@ -15217,11 +15219,11 @@ var updateStatusSchema = z19.enum(UPDATE_STATUSES);
|
|
|
15217
15219
|
function currencyMinimums2(currencyCode) {
|
|
15218
15220
|
return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
|
|
15219
15221
|
}
|
|
15220
|
-
function validateDailyBudgetFloor(money, ctx,
|
|
15222
|
+
function validateDailyBudgetFloor(money, ctx, path35) {
|
|
15221
15223
|
if (money?.currencyCode) {
|
|
15222
15224
|
const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
|
|
15223
15225
|
if (Number(money.amount) < min) {
|
|
15224
|
-
ctx.addIssue({ code: "custom", path:
|
|
15226
|
+
ctx.addIssue({ code: "custom", path: path35, message: `below the ${min} ${money.currencyCode} daily minimum` });
|
|
15225
15227
|
}
|
|
15226
15228
|
}
|
|
15227
15229
|
}
|
|
@@ -15864,19 +15866,19 @@ function failWriteValidation3(message) {
|
|
|
15864
15866
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
15865
15867
|
process.exit(1);
|
|
15866
15868
|
}
|
|
15867
|
-
function loadJsonFileArg3(
|
|
15868
|
-
if (typeof
|
|
15869
|
+
function loadJsonFileArg3(path35) {
|
|
15870
|
+
if (typeof path35 !== "string" || path35.length === 0) {
|
|
15869
15871
|
return {};
|
|
15870
15872
|
}
|
|
15871
15873
|
try {
|
|
15872
|
-
const parsed = JSON.parse(readFileSync8(
|
|
15874
|
+
const parsed = JSON.parse(readFileSync8(path35, "utf8"));
|
|
15873
15875
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15874
|
-
failWriteValidation3(`${
|
|
15876
|
+
failWriteValidation3(`${path35} must contain a JSON object`);
|
|
15875
15877
|
}
|
|
15876
15878
|
return parsed;
|
|
15877
15879
|
} catch (err) {
|
|
15878
15880
|
if (err instanceof SyntaxError) {
|
|
15879
|
-
failWriteValidation3(`${
|
|
15881
|
+
failWriteValidation3(`${path35} is not valid JSON: ${err.message}`);
|
|
15880
15882
|
}
|
|
15881
15883
|
throw err;
|
|
15882
15884
|
}
|
|
@@ -19938,11 +19940,11 @@ function estSpeechS(text) {
|
|
|
19938
19940
|
var OBSERVED_WPS_MIN = 1;
|
|
19939
19941
|
var OBSERVED_WPS_MAX = 6;
|
|
19940
19942
|
function estSpeechWindowS(text, startS, endS) {
|
|
19941
|
-
const
|
|
19942
|
-
const
|
|
19943
|
-
if (
|
|
19944
|
-
const wps =
|
|
19945
|
-
if (wps >= OBSERVED_WPS_MIN && wps <= OBSERVED_WPS_MAX) return
|
|
19943
|
+
const words3 = wordCount(text);
|
|
19944
|
+
const window2 = (endS ?? 0) - (startS ?? 0);
|
|
19945
|
+
if (words3 > 0 && window2 > 0.3) {
|
|
19946
|
+
const wps = words3 / window2;
|
|
19947
|
+
if (wps >= OBSERVED_WPS_MIN && wps <= OBSERVED_WPS_MAX) return window2;
|
|
19946
19948
|
}
|
|
19947
19949
|
return estSpeechS(text);
|
|
19948
19950
|
}
|
|
@@ -20797,10 +20799,10 @@ function scrubFloatSentences(text, floatDescs) {
|
|
|
20797
20799
|
if (floatDescs.length === 0 || !text) return text;
|
|
20798
20800
|
const tokenSets = floatDescs.map((d) => new Set(floatTokens(d)));
|
|
20799
20801
|
const kept = text.split(/(?<=[.!?])\s+/).filter((sentence) => {
|
|
20800
|
-
const
|
|
20802
|
+
const words3 = new Set(floatTokens(sentence));
|
|
20801
20803
|
return !tokenSets.some((ts) => {
|
|
20802
20804
|
let hits = 0;
|
|
20803
|
-
for (const w of
|
|
20805
|
+
for (const w of words3) if (ts.has(w)) hits++;
|
|
20804
20806
|
return hits >= 2;
|
|
20805
20807
|
});
|
|
20806
20808
|
}).join(" ").trim();
|
|
@@ -22991,15 +22993,15 @@ function collectClipAdvisories(scene, i, out) {
|
|
|
22991
22993
|
const round22 = (n) => Math.round(n * 100) / 100;
|
|
22992
22994
|
const original = scene.duration_s ?? 5;
|
|
22993
22995
|
if (original > 15) out.clamped.push({ scene: i, original_s: original, clip_s: snapToSeedance(original) });
|
|
22994
|
-
const
|
|
22995
|
-
if (
|
|
22996
|
-
out.oversize.push({ scene: i, scene_s: round22(
|
|
22996
|
+
const window2 = sceneDurationS(scene);
|
|
22997
|
+
if (window2 > SEEDANCE_SAFE_MAX_S)
|
|
22998
|
+
out.oversize.push({ scene: i, scene_s: round22(window2), clip_s: ceilToSeedance(window2) });
|
|
22997
22999
|
const speech = (scene.dialogue ?? []).reduce(
|
|
22998
23000
|
(s, line) => s + (line.line ? estSpeechWindowS(line.line, line.start_s, line.end_s) : 0),
|
|
22999
23001
|
0
|
|
23000
23002
|
);
|
|
23001
|
-
if (speech >
|
|
23002
|
-
out.overstuffed.push({ scene: i, scene_s: round22(
|
|
23003
|
+
if (speech > window2 * OVERSTUFF_RATIO)
|
|
23004
|
+
out.overstuffed.push({ scene: i, scene_s: round22(window2), est_speech_s: round22(speech) });
|
|
23003
23005
|
}
|
|
23004
23006
|
function videoReport(input, elementsInput) {
|
|
23005
23007
|
const blueprint = VideoBlueprint.parse(input);
|
|
@@ -26672,12 +26674,12 @@ function listFlowSlugs() {
|
|
|
26672
26674
|
return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_") && entry.name !== ".gitkeep").map((entry) => entry.name).sort();
|
|
26673
26675
|
}
|
|
26674
26676
|
function readFlowTree(slug) {
|
|
26675
|
-
const
|
|
26676
|
-
if (!existsSync4(
|
|
26677
|
+
const path35 = join3(flowsDir(), slug, "_data.json");
|
|
26678
|
+
if (!existsSync4(path35)) {
|
|
26677
26679
|
failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
|
|
26678
26680
|
}
|
|
26679
26681
|
try {
|
|
26680
|
-
return JSON.parse(readFileSync9(
|
|
26682
|
+
return JSON.parse(readFileSync9(path35, "utf-8"));
|
|
26681
26683
|
} catch (error) {
|
|
26682
26684
|
failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
|
|
26683
26685
|
}
|
|
@@ -27090,10 +27092,10 @@ async function stageOps(ops) {
|
|
|
27090
27092
|
handleError(err);
|
|
27091
27093
|
}
|
|
27092
27094
|
}
|
|
27093
|
-
async function draftAction2(
|
|
27095
|
+
async function draftAction2(path35, body, chat) {
|
|
27094
27096
|
const chatId = resolveChatId(chat);
|
|
27095
27097
|
try {
|
|
27096
|
-
const data = await apiPost(
|
|
27098
|
+
const data = await apiPost(path35, { chatId, ...body });
|
|
27097
27099
|
writeJsonEnvelope({ ok: true, data });
|
|
27098
27100
|
return data;
|
|
27099
27101
|
} catch (err) {
|
|
@@ -29117,9 +29119,9 @@ async function readImageBuffer(pathOrUrl) {
|
|
|
29117
29119
|
}
|
|
29118
29120
|
return readFile19(pathOrUrl);
|
|
29119
29121
|
}
|
|
29120
|
-
async function isDirectory(
|
|
29122
|
+
async function isDirectory(path35) {
|
|
29121
29123
|
try {
|
|
29122
|
-
const s = await stat4(
|
|
29124
|
+
const s = await stat4(path35);
|
|
29123
29125
|
return s.isDirectory();
|
|
29124
29126
|
} catch {
|
|
29125
29127
|
return false;
|
|
@@ -31582,11 +31584,11 @@ Full guide: __tooling__/docs/tools/baker/images.md`
|
|
|
31582
31584
|
});
|
|
31583
31585
|
|
|
31584
31586
|
// src/commands/landing/index.ts
|
|
31585
|
-
import { defineCommand as
|
|
31587
|
+
import { defineCommand as defineCommand150 } from "citty";
|
|
31586
31588
|
|
|
31587
31589
|
// src/commands/landing/critique.ts
|
|
31588
31590
|
import { readdir as readdir8, stat as stat6 } from "fs/promises";
|
|
31589
|
-
import
|
|
31591
|
+
import path28 from "path";
|
|
31590
31592
|
import { defineCommand as defineCommand141 } from "citty";
|
|
31591
31593
|
|
|
31592
31594
|
// src/engine/landing/lib/brand-tokens.ts
|
|
@@ -31995,6 +31997,11 @@ var RULE_META = {
|
|
|
31995
31997
|
severity: "block",
|
|
31996
31998
|
note: "Gradient text is a top AI tell. Emphasis comes from weight or size, not a clipped gradient fill."
|
|
31997
31999
|
},
|
|
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
|
+
},
|
|
31998
32005
|
"broken-image": {
|
|
31999
32006
|
family: "integrity",
|
|
32000
32007
|
severity: "block",
|
|
@@ -32175,6 +32182,64 @@ var SEVERITY_WEIGHT = {
|
|
|
32175
32182
|
advisory: 0.05
|
|
32176
32183
|
};
|
|
32177
32184
|
|
|
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
|
+
|
|
32178
32243
|
// src/engine/landing/lib/rules.ts
|
|
32179
32244
|
var cap2 = (m, i) => m[i] ?? "";
|
|
32180
32245
|
var num = (m, i) => Number(m[i] ?? 0);
|
|
@@ -32579,8 +32644,8 @@ var ANALYZERS = [
|
|
|
32579
32644
|
const lines = text.split("\n");
|
|
32580
32645
|
for (const m of text.matchAll(/(?:-webkit-)?background-clip\s*:\s*text/gi)) {
|
|
32581
32646
|
const line = lineOf(text, m.index ?? 0);
|
|
32582
|
-
const
|
|
32583
|
-
if (/gradient\(/i.test(
|
|
32647
|
+
const window2 = lines.slice(Math.max(0, line - 7), Math.min(lines.length, line + 6)).join("\n");
|
|
32648
|
+
if (/gradient\(/i.test(window2)) {
|
|
32584
32649
|
out.push({ id: "gradient-text", snippet: "background-clip: text + gradient", file, line });
|
|
32585
32650
|
}
|
|
32586
32651
|
}
|
|
@@ -32702,7 +32767,16 @@ function dedupe(findings) {
|
|
|
32702
32767
|
}
|
|
32703
32768
|
|
|
32704
32769
|
// src/engine/landing/lib/critique.ts
|
|
32705
|
-
var FAMILIES = [
|
|
32770
|
+
var FAMILIES = [
|
|
32771
|
+
"typography",
|
|
32772
|
+
"color",
|
|
32773
|
+
"borders_depth",
|
|
32774
|
+
"motion",
|
|
32775
|
+
"spacing",
|
|
32776
|
+
"copy",
|
|
32777
|
+
"integrity",
|
|
32778
|
+
"originality"
|
|
32779
|
+
];
|
|
32706
32780
|
function round4(n) {
|
|
32707
32781
|
return Math.round(n * 100) / 100;
|
|
32708
32782
|
}
|
|
@@ -32721,6 +32795,7 @@ function critiqueLanding(input) {
|
|
|
32721
32795
|
const raws = [];
|
|
32722
32796
|
for (const source of sources) raws.push(...detectSource(source));
|
|
32723
32797
|
raws.push(...detectPage(sources));
|
|
32798
|
+
raws.push(...detectOriginality(sources, input.references ?? []));
|
|
32724
32799
|
for (const raw of raws) {
|
|
32725
32800
|
const meta = RULE_META[raw.id];
|
|
32726
32801
|
if (!meta) continue;
|
|
@@ -32758,41 +32833,82 @@ function describeCounts(findings) {
|
|
|
32758
32833
|
return [b ? `${b} block` : "", w ? `${w} warn` : "", a ? `${a} advisory` : ""].filter(Boolean).join(", ");
|
|
32759
32834
|
}
|
|
32760
32835
|
|
|
32761
|
-
// src/
|
|
32762
|
-
import { mkdir as mkdir8,
|
|
32836
|
+
// src/engine/landing/lib/referenceStore.ts
|
|
32837
|
+
import { mkdir as mkdir8, readFile as readFile21, writeFile as writeFile11 } from "fs/promises";
|
|
32763
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
|
+
// src/commands/landing/snapshot.ts
|
|
32878
|
+
import { mkdir as mkdir9, rename as rename2, writeFile as writeFile12 } from "fs/promises";
|
|
32879
|
+
import path26 from "path";
|
|
32764
32880
|
var CRITIC_VERSION = "2";
|
|
32765
32881
|
function critiqueCacheDir(projectRoot) {
|
|
32766
|
-
return
|
|
32882
|
+
return path26.join(projectRoot, ".cache", "landing-critique");
|
|
32767
32883
|
}
|
|
32768
32884
|
function snapshotPath(projectRoot, slug) {
|
|
32769
|
-
return
|
|
32885
|
+
return path26.join(critiqueCacheDir(projectRoot), `${slug}.json`);
|
|
32770
32886
|
}
|
|
32771
32887
|
async function writeCritiqueSnapshot(projectRoot, snapshot) {
|
|
32772
|
-
await
|
|
32888
|
+
await mkdir9(critiqueCacheDir(projectRoot), { recursive: true });
|
|
32773
32889
|
const dest = snapshotPath(projectRoot, snapshot.slug);
|
|
32774
32890
|
const tmp = `${dest}.tmp`;
|
|
32775
|
-
await
|
|
32891
|
+
await writeFile12(tmp, `${JSON.stringify(snapshot, null, 2)}
|
|
32776
32892
|
`, "utf8");
|
|
32777
32893
|
await rename2(tmp, dest);
|
|
32778
32894
|
}
|
|
32779
32895
|
|
|
32780
32896
|
// src/commands/landing/source-version.ts
|
|
32781
|
-
import { readdir as readdir7, readFile as
|
|
32782
|
-
import
|
|
32897
|
+
import { readdir as readdir7, readFile as readFile22, stat as stat5 } from "fs/promises";
|
|
32898
|
+
import path27 from "path";
|
|
32783
32899
|
async function landingSourceRelPaths(landingDir) {
|
|
32784
32900
|
const rel = [];
|
|
32785
|
-
if (await isFile(
|
|
32786
|
-
const componentsDir =
|
|
32901
|
+
if (await isFile(path27.join(landingDir, "index.astro"))) rel.push("index.astro");
|
|
32902
|
+
const componentsDir = path27.join(landingDir, "_components");
|
|
32787
32903
|
for (const abs of await walkAstro(componentsDir)) {
|
|
32788
|
-
rel.push(
|
|
32904
|
+
rel.push(path27.relative(landingDir, abs).split(path27.sep).join("/"));
|
|
32789
32905
|
}
|
|
32790
32906
|
return rel.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
32791
32907
|
}
|
|
32792
32908
|
async function readLandingSources(landingDir) {
|
|
32793
32909
|
const rel = await landingSourceRelPaths(landingDir);
|
|
32794
32910
|
const out = [];
|
|
32795
|
-
for (const r of rel) out.push({ path: r, text: await
|
|
32911
|
+
for (const r of rel) out.push({ path: r, text: await readFile22(path27.join(landingDir, r), "utf8") });
|
|
32796
32912
|
return out;
|
|
32797
32913
|
}
|
|
32798
32914
|
async function computeLandingSourceSha(landingDir) {
|
|
@@ -32801,7 +32917,7 @@ async function computeLandingSourceSha(landingDir) {
|
|
|
32801
32917
|
for (const r of rel) {
|
|
32802
32918
|
let bytes;
|
|
32803
32919
|
try {
|
|
32804
|
-
bytes = await
|
|
32920
|
+
bytes = await readFile22(path27.join(landingDir, r));
|
|
32805
32921
|
} catch {
|
|
32806
32922
|
bytes = Buffer.alloc(0);
|
|
32807
32923
|
}
|
|
@@ -32825,7 +32941,7 @@ async function walkAstro(dir) {
|
|
|
32825
32941
|
}
|
|
32826
32942
|
const out = [];
|
|
32827
32943
|
for (const entry of entries) {
|
|
32828
|
-
const abs =
|
|
32944
|
+
const abs = path27.join(dir, entry.name);
|
|
32829
32945
|
if (entry.isDirectory()) out.push(...await walkAstro(abs));
|
|
32830
32946
|
else if (entry.isFile() && entry.name.endsWith(".astro")) out.push(abs);
|
|
32831
32947
|
}
|
|
@@ -32886,14 +33002,14 @@ var critiqueCommand2 = defineCommand141({
|
|
|
32886
33002
|
{ availableSlugs: await listLandingSlugs(projectRoot) }
|
|
32887
33003
|
);
|
|
32888
33004
|
}
|
|
32889
|
-
if (!await isDir(
|
|
33005
|
+
if (!await isDir(path28.resolve(projectRoot, "src", "pages", slug))) {
|
|
32890
33006
|
fail5("NOT_FOUND", `No landing at src/pages/${slug}/`, {
|
|
32891
33007
|
availableSlugs: await listLandingSlugs(projectRoot)
|
|
32892
33008
|
});
|
|
32893
33009
|
}
|
|
32894
33010
|
}
|
|
32895
|
-
const brand = await loadBrandTokens(projectRoot);
|
|
32896
|
-
const results = await Promise.all(slugs.map((slug) => critiqueOne(projectRoot, slug, brand)));
|
|
33011
|
+
const [brand, references] = await Promise.all([loadBrandTokens(projectRoot), readReferences(projectRoot)]);
|
|
33012
|
+
const results = await Promise.all(slugs.map((slug) => critiqueOne(projectRoot, slug, brand, references)));
|
|
32897
33013
|
const landings = results.map(({ slug, report }) => ({
|
|
32898
33014
|
slug,
|
|
32899
33015
|
overall: report.overall,
|
|
@@ -32924,10 +33040,10 @@ var critiqueCommand2 = defineCommand141({
|
|
|
32924
33040
|
);
|
|
32925
33041
|
}
|
|
32926
33042
|
});
|
|
32927
|
-
async function critiqueOne(projectRoot, slug, brand) {
|
|
32928
|
-
const landingDir =
|
|
33043
|
+
async function critiqueOne(projectRoot, slug, brand, references) {
|
|
33044
|
+
const landingDir = path28.resolve(projectRoot, "src", "pages", slug);
|
|
32929
33045
|
const [sources, sourceSha] = await Promise.all([readLandingSources(landingDir), computeLandingSourceSha(landingDir)]);
|
|
32930
|
-
const report = critiqueLanding({ slug, sources, brand });
|
|
33046
|
+
const report = critiqueLanding({ slug, sources, brand, references });
|
|
32931
33047
|
let snapshotFailed = false;
|
|
32932
33048
|
try {
|
|
32933
33049
|
await writeCritiqueSnapshot(projectRoot, {
|
|
@@ -32945,7 +33061,7 @@ async function critiqueOne(projectRoot, slug, brand) {
|
|
|
32945
33061
|
}
|
|
32946
33062
|
async function listLandingSlugs(projectRoot) {
|
|
32947
33063
|
try {
|
|
32948
|
-
const entries = await readdir8(
|
|
33064
|
+
const entries = await readdir8(path28.join(projectRoot, "src", "pages"), { withFileTypes: true });
|
|
32949
33065
|
return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).map((e) => e.name).sort();
|
|
32950
33066
|
} catch {
|
|
32951
33067
|
return [];
|
|
@@ -32970,8 +33086,2336 @@ async function isDir(p) {
|
|
|
32970
33086
|
}
|
|
32971
33087
|
}
|
|
32972
33088
|
|
|
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
|
+
|
|
32973
35417
|
// src/commands/landing/index.ts
|
|
32974
|
-
var landingCommand =
|
|
35418
|
+
var landingCommand = defineCommand150({
|
|
32975
35419
|
meta: {
|
|
32976
35420
|
name: "landing",
|
|
32977
35421
|
description: `Design-quality tools for landing pages (src/pages/<slug>/).
|
|
@@ -32979,15 +35423,17 @@ var landingCommand = defineCommand142({
|
|
|
32979
35423
|
Start here: \`baker landing critique <slug>\` after building or editing a landing.
|
|
32980
35424
|
|
|
32981
35425
|
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.
|
|
32982
35427
|
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.`
|
|
32983
35428
|
},
|
|
32984
35429
|
subCommands: {
|
|
35430
|
+
inspiration: inspirationCommand,
|
|
32985
35431
|
critique: critiqueCommand2
|
|
32986
35432
|
}
|
|
32987
35433
|
});
|
|
32988
35434
|
|
|
32989
35435
|
// src/commands/mcp/index.ts
|
|
32990
|
-
import { defineCommand as
|
|
35436
|
+
import { defineCommand as defineCommand151 } from "citty";
|
|
32991
35437
|
|
|
32992
35438
|
// src/commands/mcp/platforms.ts
|
|
32993
35439
|
function readsKey(label) {
|
|
@@ -33056,7 +35502,7 @@ registerSchema({
|
|
|
33056
35502
|
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.",
|
|
33057
35503
|
args: {}
|
|
33058
35504
|
});
|
|
33059
|
-
var connectedCommand =
|
|
35505
|
+
var connectedCommand = defineCommand151({
|
|
33060
35506
|
meta: {
|
|
33061
35507
|
name: "connected",
|
|
33062
35508
|
description: `Everything this chat can reach \u2014 managed integrations, custom MCP servers, and connected platforms.
|
|
@@ -33115,7 +35561,7 @@ registerSchema({
|
|
|
33115
35561
|
description: "List the custom MCP servers this company's chats see (org + company + your own user scope).",
|
|
33116
35562
|
args: {}
|
|
33117
35563
|
});
|
|
33118
|
-
var listCommand13 =
|
|
35564
|
+
var listCommand13 = defineCommand151({
|
|
33119
35565
|
meta: { name: "list", description: "List custom MCP servers visible to this company's chats." },
|
|
33120
35566
|
run: async () => {
|
|
33121
35567
|
try {
|
|
@@ -33152,7 +35598,7 @@ registerSchema({
|
|
|
33152
35598
|
header: { type: "string", description: 'Auth header "Key: Value" (repeatable)', required: false }
|
|
33153
35599
|
}
|
|
33154
35600
|
});
|
|
33155
|
-
var
|
|
35601
|
+
var addCommand2 = defineCommand151({
|
|
33156
35602
|
meta: {
|
|
33157
35603
|
name: "add",
|
|
33158
35604
|
description: `Register a custom MCP server. Tools appear as mcp__<name>__* on the NEXT message.
|
|
@@ -33204,7 +35650,7 @@ registerSchema({
|
|
|
33204
35650
|
description: "Remove a company custom MCP server by name.",
|
|
33205
35651
|
args: { name: { type: "string", description: "Server name to remove", required: true } }
|
|
33206
35652
|
});
|
|
33207
|
-
var removeCommand4 =
|
|
35653
|
+
var removeCommand4 = defineCommand151({
|
|
33208
35654
|
meta: {
|
|
33209
35655
|
name: "remove",
|
|
33210
35656
|
description: `Remove a company custom MCP server by name.
|
|
@@ -33226,7 +35672,7 @@ Example:
|
|
|
33226
35672
|
}
|
|
33227
35673
|
}
|
|
33228
35674
|
});
|
|
33229
|
-
var mcpCommand =
|
|
35675
|
+
var mcpCommand = defineCommand151({
|
|
33230
35676
|
meta: {
|
|
33231
35677
|
name: "mcp",
|
|
33232
35678
|
description: `Third-party tools for this company \u2014 see what's connected, register custom HTTPS MCP endpoints.
|
|
@@ -33246,16 +35692,16 @@ Full guide: __tooling__/docs/tools/baker/mcp.md`
|
|
|
33246
35692
|
subCommands: {
|
|
33247
35693
|
connected: connectedCommand,
|
|
33248
35694
|
list: listCommand13,
|
|
33249
|
-
add:
|
|
35695
|
+
add: addCommand2,
|
|
33250
35696
|
remove: removeCommand4
|
|
33251
35697
|
}
|
|
33252
35698
|
});
|
|
33253
35699
|
|
|
33254
35700
|
// src/commands/research/index.ts
|
|
33255
|
-
import { defineCommand as
|
|
35701
|
+
import { defineCommand as defineCommand162 } from "citty";
|
|
33256
35702
|
|
|
33257
35703
|
// src/commands/research/advertisers.ts
|
|
33258
|
-
import { defineCommand as
|
|
35704
|
+
import { defineCommand as defineCommand152 } from "citty";
|
|
33259
35705
|
|
|
33260
35706
|
// src/commands/research/output.ts
|
|
33261
35707
|
var RESEARCH_DATA_NOTE = "Estimates based on third-party SERP data \u2014 not exact figures. Use for directional insights, not precise measurement.";
|
|
@@ -33368,7 +35814,7 @@ var FIELDS3 = {
|
|
|
33368
35814
|
etv: "Estimated traffic value (USD)",
|
|
33369
35815
|
visibility: "SERP visibility score (0-1)"
|
|
33370
35816
|
};
|
|
33371
|
-
var advertisersCommand =
|
|
35817
|
+
var advertisersCommand = defineCommand152({
|
|
33372
35818
|
meta: {
|
|
33373
35819
|
name: "advertisers",
|
|
33374
35820
|
description: `Find domains competing for a keyword in Google SERPs.
|
|
@@ -33388,15 +35834,15 @@ Examples:
|
|
|
33388
35834
|
},
|
|
33389
35835
|
run: async ({ args }) => {
|
|
33390
35836
|
const keyword = args.keyword;
|
|
33391
|
-
const
|
|
35837
|
+
const location2 = args.location || void 0;
|
|
33392
35838
|
const language = args.language || void 0;
|
|
33393
35839
|
const limit = args.limit ? Number(args.limit) : void 0;
|
|
33394
35840
|
const skipCache = args["no-cache"] ? true : void 0;
|
|
33395
|
-
const queryContext = buildResearchQueryContext(
|
|
35841
|
+
const queryContext = buildResearchQueryContext(location2, language);
|
|
33396
35842
|
try {
|
|
33397
35843
|
const data = await apiPost("/api/research/advertisers", {
|
|
33398
35844
|
keyword,
|
|
33399
|
-
location,
|
|
35845
|
+
location: location2,
|
|
33400
35846
|
language,
|
|
33401
35847
|
limit,
|
|
33402
35848
|
skipCache
|
|
@@ -33415,7 +35861,7 @@ Examples:
|
|
|
33415
35861
|
});
|
|
33416
35862
|
|
|
33417
35863
|
// src/commands/research/autocomplete.ts
|
|
33418
|
-
import { defineCommand as
|
|
35864
|
+
import { defineCommand as defineCommand153 } from "citty";
|
|
33419
35865
|
registerSchema({
|
|
33420
35866
|
command: "research.autocomplete",
|
|
33421
35867
|
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).",
|
|
@@ -33438,7 +35884,7 @@ registerSchema({
|
|
|
33438
35884
|
var FIELDS4 = {
|
|
33439
35885
|
suggestion: "Autocomplete suggestion from Google"
|
|
33440
35886
|
};
|
|
33441
|
-
var autocompleteCommand =
|
|
35887
|
+
var autocompleteCommand = defineCommand153({
|
|
33442
35888
|
meta: {
|
|
33443
35889
|
name: "autocomplete",
|
|
33444
35890
|
description: `Get Google Autocomplete suggestions for keyword expansion.
|
|
@@ -33457,15 +35903,15 @@ Examples:
|
|
|
33457
35903
|
},
|
|
33458
35904
|
run: async ({ args }) => {
|
|
33459
35905
|
const keyword = args.keyword;
|
|
33460
|
-
const
|
|
35906
|
+
const location2 = args.location || void 0;
|
|
33461
35907
|
const language = args.language || void 0;
|
|
33462
35908
|
const limit = args.limit ? Number(args.limit) : void 0;
|
|
33463
35909
|
const skipCache = args["no-cache"] ? true : void 0;
|
|
33464
|
-
const queryContext = buildResearchQueryContext(
|
|
35910
|
+
const queryContext = buildResearchQueryContext(location2, language);
|
|
33465
35911
|
try {
|
|
33466
35912
|
const data = await apiPost("/api/research/autocomplete", {
|
|
33467
35913
|
keyword,
|
|
33468
|
-
location,
|
|
35914
|
+
location: location2,
|
|
33469
35915
|
language,
|
|
33470
35916
|
limit,
|
|
33471
35917
|
skipCache
|
|
@@ -33484,7 +35930,7 @@ Examples:
|
|
|
33484
35930
|
});
|
|
33485
35931
|
|
|
33486
35932
|
// src/commands/research/countries.ts
|
|
33487
|
-
import { defineCommand as
|
|
35933
|
+
import { defineCommand as defineCommand154 } from "citty";
|
|
33488
35934
|
registerSchema({
|
|
33489
35935
|
command: "research.countries",
|
|
33490
35936
|
description: "List all supported country codes for --location flag in research commands.",
|
|
@@ -33541,7 +35987,7 @@ var FIELDS5 = {
|
|
|
33541
35987
|
code: "Country code to pass as --location",
|
|
33542
35988
|
name: "Country name"
|
|
33543
35989
|
};
|
|
33544
|
-
var countriesCommand =
|
|
35990
|
+
var countriesCommand = defineCommand154({
|
|
33545
35991
|
meta: {
|
|
33546
35992
|
name: "countries",
|
|
33547
35993
|
description: "List all supported country codes for --location flag."
|
|
@@ -33552,7 +35998,7 @@ var countriesCommand = defineCommand146({
|
|
|
33552
35998
|
});
|
|
33553
35999
|
|
|
33554
36000
|
// src/commands/research/intent.ts
|
|
33555
|
-
import { defineCommand as
|
|
36001
|
+
import { defineCommand as defineCommand155 } from "citty";
|
|
33556
36002
|
registerSchema({
|
|
33557
36003
|
command: "research.intent",
|
|
33558
36004
|
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.",
|
|
@@ -33575,7 +36021,7 @@ var FIELDS6 = {
|
|
|
33575
36021
|
intent: "Primary Google Search intent: informational, navigational, commercial, transactional",
|
|
33576
36022
|
probability: "Confidence score 0.0-1.0"
|
|
33577
36023
|
};
|
|
33578
|
-
var intentCommand =
|
|
36024
|
+
var intentCommand = defineCommand155({
|
|
33579
36025
|
meta: {
|
|
33580
36026
|
name: "intent",
|
|
33581
36027
|
description: `Classify Google Search intent for keywords. Returns intent type and confidence.
|
|
@@ -33623,7 +36069,7 @@ Examples:
|
|
|
33623
36069
|
});
|
|
33624
36070
|
|
|
33625
36071
|
// src/commands/research/keyword-gap.ts
|
|
33626
|
-
import { defineCommand as
|
|
36072
|
+
import { defineCommand as defineCommand156 } from "citty";
|
|
33627
36073
|
registerSchema({
|
|
33628
36074
|
command: "research.keyword-gap",
|
|
33629
36075
|
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.",
|
|
@@ -33652,7 +36098,7 @@ var FIELDS7 = {
|
|
|
33652
36098
|
cpc: "Cost per click USD",
|
|
33653
36099
|
their_position: "Competitor's ranking position"
|
|
33654
36100
|
};
|
|
33655
|
-
var keywordGapCommand =
|
|
36101
|
+
var keywordGapCommand = defineCommand156({
|
|
33656
36102
|
meta: {
|
|
33657
36103
|
name: "keyword-gap",
|
|
33658
36104
|
description: `Find keywords a competitor has that you don't. Supports pagination via --offset.
|
|
@@ -33676,18 +36122,18 @@ Examples:
|
|
|
33676
36122
|
run: async ({ args }) => {
|
|
33677
36123
|
const competitor = args.competitor;
|
|
33678
36124
|
const ours = args.ours;
|
|
33679
|
-
const
|
|
36125
|
+
const location2 = args.location || void 0;
|
|
33680
36126
|
const language = args.language || void 0;
|
|
33681
36127
|
const type = args.type || void 0;
|
|
33682
36128
|
const limit = args.limit ? Number(args.limit) : void 0;
|
|
33683
36129
|
const offset = args.offset ? Number(args.offset) : void 0;
|
|
33684
36130
|
const skipCache = args["no-cache"] ? true : void 0;
|
|
33685
|
-
const queryContext = buildResearchQueryContext(
|
|
36131
|
+
const queryContext = buildResearchQueryContext(location2, language);
|
|
33686
36132
|
try {
|
|
33687
36133
|
const result = await apiPost("/api/research/keyword-gap", {
|
|
33688
36134
|
competitor,
|
|
33689
36135
|
ours,
|
|
33690
|
-
location,
|
|
36136
|
+
location: location2,
|
|
33691
36137
|
language,
|
|
33692
36138
|
type,
|
|
33693
36139
|
limit,
|
|
@@ -33726,7 +36172,7 @@ Examples:
|
|
|
33726
36172
|
});
|
|
33727
36173
|
|
|
33728
36174
|
// src/commands/research/keywords-for-site.ts
|
|
33729
|
-
import { defineCommand as
|
|
36175
|
+
import { defineCommand as defineCommand157 } from "citty";
|
|
33730
36176
|
registerSchema({
|
|
33731
36177
|
command: "research.keywords-for-site",
|
|
33732
36178
|
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.",
|
|
@@ -33759,7 +36205,7 @@ var FIELDS8 = {
|
|
|
33759
36205
|
competition: "LOW, MEDIUM, or HIGH",
|
|
33760
36206
|
competition_index: "Competition score 0-100"
|
|
33761
36207
|
};
|
|
33762
|
-
var keywordsForSiteCommand =
|
|
36208
|
+
var keywordsForSiteCommand = defineCommand157({
|
|
33763
36209
|
meta: {
|
|
33764
36210
|
name: "keywords-for-site",
|
|
33765
36211
|
description: `Get keywords a competitor targets in Google. Use --type to filter paid/organic.
|
|
@@ -33781,17 +36227,17 @@ Examples:
|
|
|
33781
36227
|
},
|
|
33782
36228
|
run: async ({ args }) => {
|
|
33783
36229
|
const target = args.target;
|
|
33784
|
-
const
|
|
36230
|
+
const location2 = args.location || void 0;
|
|
33785
36231
|
const language = args.language || void 0;
|
|
33786
36232
|
const sort = args.sort || void 0;
|
|
33787
36233
|
const type = args.type || void 0;
|
|
33788
36234
|
const limit = args.limit ? Number(args.limit) : void 0;
|
|
33789
36235
|
const skipCache = args["no-cache"] ? true : void 0;
|
|
33790
|
-
const queryContext = buildResearchQueryContext(
|
|
36236
|
+
const queryContext = buildResearchQueryContext(location2, language);
|
|
33791
36237
|
try {
|
|
33792
36238
|
const data = await apiPost("/api/research/keywords-for-site", {
|
|
33793
36239
|
target,
|
|
33794
|
-
location,
|
|
36240
|
+
location: location2,
|
|
33795
36241
|
language,
|
|
33796
36242
|
sort,
|
|
33797
36243
|
type,
|
|
@@ -33812,7 +36258,7 @@ Examples:
|
|
|
33812
36258
|
});
|
|
33813
36259
|
|
|
33814
36260
|
// src/commands/research/languages.ts
|
|
33815
|
-
import { defineCommand as
|
|
36261
|
+
import { defineCommand as defineCommand158 } from "citty";
|
|
33816
36262
|
registerSchema({
|
|
33817
36263
|
command: "research.languages",
|
|
33818
36264
|
description: "List all supported language codes for --language flag in research commands.",
|
|
@@ -33842,7 +36288,7 @@ var FIELDS9 = {
|
|
|
33842
36288
|
code: "Language code to pass as --language",
|
|
33843
36289
|
name: "Language name (also accepted by --language)"
|
|
33844
36290
|
};
|
|
33845
|
-
var languagesCommand2 =
|
|
36291
|
+
var languagesCommand2 = defineCommand158({
|
|
33846
36292
|
meta: {
|
|
33847
36293
|
name: "languages",
|
|
33848
36294
|
description: "List all supported language codes for --language flag."
|
|
@@ -33853,7 +36299,7 @@ var languagesCommand2 = defineCommand150({
|
|
|
33853
36299
|
});
|
|
33854
36300
|
|
|
33855
36301
|
// src/commands/research/lighthouse.ts
|
|
33856
|
-
import { defineCommand as
|
|
36302
|
+
import { defineCommand as defineCommand159 } from "citty";
|
|
33857
36303
|
registerSchema({
|
|
33858
36304
|
command: "research.lighthouse",
|
|
33859
36305
|
description: "Landing page performance audit. Returns metrics that affect Google Ads Quality Score and CPC.",
|
|
@@ -33872,7 +36318,7 @@ var FIELDS10 = {
|
|
|
33872
36318
|
speed_index_ms: "Speed Index in ms (good: < 3400)",
|
|
33873
36319
|
interactive_ms: "Time to Interactive in ms (good: < 3800)"
|
|
33874
36320
|
};
|
|
33875
|
-
var lighthouseCommand =
|
|
36321
|
+
var lighthouseCommand = defineCommand159({
|
|
33876
36322
|
meta: {
|
|
33877
36323
|
name: "lighthouse",
|
|
33878
36324
|
description: `Landing page performance audit. Metrics affecting Google Ads Quality Score.
|
|
@@ -33910,7 +36356,7 @@ Examples:
|
|
|
33910
36356
|
});
|
|
33911
36357
|
|
|
33912
36358
|
// src/commands/research/relevant-pages.ts
|
|
33913
|
-
import { defineCommand as
|
|
36359
|
+
import { defineCommand as defineCommand160 } from "citty";
|
|
33914
36360
|
registerSchema({
|
|
33915
36361
|
command: "research.relevant-pages",
|
|
33916
36362
|
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).",
|
|
@@ -33936,7 +36382,7 @@ var FIELDS11 = {
|
|
|
33936
36382
|
keywords: "Total organic keywords the page ranks for",
|
|
33937
36383
|
top_10: "Keywords in positions 1-10"
|
|
33938
36384
|
};
|
|
33939
|
-
var relevantPagesCommand =
|
|
36385
|
+
var relevantPagesCommand = defineCommand160({
|
|
33940
36386
|
meta: {
|
|
33941
36387
|
name: "relevant-pages",
|
|
33942
36388
|
description: `Get the top pages of a competitor domain with traffic data.
|
|
@@ -33955,15 +36401,15 @@ Examples:
|
|
|
33955
36401
|
},
|
|
33956
36402
|
run: async ({ args }) => {
|
|
33957
36403
|
const target = args.target;
|
|
33958
|
-
const
|
|
36404
|
+
const location2 = args.location || void 0;
|
|
33959
36405
|
const language = args.language || void 0;
|
|
33960
36406
|
const limit = args.limit ? Number(args.limit) : void 0;
|
|
33961
36407
|
const skipCache = args["no-cache"] ? true : void 0;
|
|
33962
|
-
const queryContext = buildResearchQueryContext(
|
|
36408
|
+
const queryContext = buildResearchQueryContext(location2, language);
|
|
33963
36409
|
try {
|
|
33964
36410
|
const data = await apiPost("/api/research/relevant-pages", {
|
|
33965
36411
|
target,
|
|
33966
|
-
location,
|
|
36412
|
+
location: location2,
|
|
33967
36413
|
language,
|
|
33968
36414
|
limit,
|
|
33969
36415
|
skipCache
|
|
@@ -33982,7 +36428,7 @@ Examples:
|
|
|
33982
36428
|
});
|
|
33983
36429
|
|
|
33984
36430
|
// src/commands/research/web.ts
|
|
33985
|
-
import { defineCommand as
|
|
36431
|
+
import { defineCommand as defineCommand161 } from "citty";
|
|
33986
36432
|
registerSchema({
|
|
33987
36433
|
command: "research.web",
|
|
33988
36434
|
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).",
|
|
@@ -34033,7 +36479,7 @@ async function runDeepResearch(question) {
|
|
|
34033
36479
|
}
|
|
34034
36480
|
throw new Error("Deep research timed out");
|
|
34035
36481
|
}
|
|
34036
|
-
var webCommand =
|
|
36482
|
+
var webCommand = defineCommand161({
|
|
34037
36483
|
meta: {
|
|
34038
36484
|
name: "web",
|
|
34039
36485
|
description: `Search the web with AI to answer any open-ended marketing question. Uses live internet data via Google Search.
|
|
@@ -34093,7 +36539,7 @@ Examples:
|
|
|
34093
36539
|
});
|
|
34094
36540
|
|
|
34095
36541
|
// src/commands/research/index.ts
|
|
34096
|
-
var researchCommand =
|
|
36542
|
+
var researchCommand = defineCommand162({
|
|
34097
36543
|
meta: {
|
|
34098
36544
|
name: "research",
|
|
34099
36545
|
description: `Competitive intelligence and AI-powered research commands.
|
|
@@ -34134,10 +36580,10 @@ Full guide: __tooling__/docs/tools/baker/research.md`
|
|
|
34134
36580
|
});
|
|
34135
36581
|
|
|
34136
36582
|
// src/commands/scheduled-actions/index.ts
|
|
34137
|
-
import { defineCommand as
|
|
36583
|
+
import { defineCommand as defineCommand169 } from "citty";
|
|
34138
36584
|
|
|
34139
36585
|
// src/commands/scheduled-actions/create.ts
|
|
34140
|
-
import { defineCommand as
|
|
36586
|
+
import { defineCommand as defineCommand163 } from "citty";
|
|
34141
36587
|
|
|
34142
36588
|
// src/commands/scheduled-actions/shared.ts
|
|
34143
36589
|
var TEMP_SCHEDULED_ACTION_PREFIX = "temp_sched_";
|
|
@@ -34252,7 +36698,7 @@ registerSchema({
|
|
|
34252
36698
|
prompt: { type: "string", description: "Additional prompt instructions for the spawned agent", required: false }
|
|
34253
36699
|
}
|
|
34254
36700
|
});
|
|
34255
|
-
var createCommand2 =
|
|
36701
|
+
var createCommand2 = defineCommand163({
|
|
34256
36702
|
meta: {
|
|
34257
36703
|
name: "create",
|
|
34258
36704
|
description: 'Stage a scheduled action. Example: baker scheduled-actions create --name "Weekly report" --description "..." --cron "0 9 * * MON"'
|
|
@@ -34301,7 +36747,7 @@ var createCommand2 = defineCommand155({
|
|
|
34301
36747
|
});
|
|
34302
36748
|
|
|
34303
36749
|
// src/commands/scheduled-actions/delete.ts
|
|
34304
|
-
import { defineCommand as
|
|
36750
|
+
import { defineCommand as defineCommand164 } from "citty";
|
|
34305
36751
|
registerSchema({
|
|
34306
36752
|
command: "scheduled-actions.delete",
|
|
34307
36753
|
description: "Stage deletion of a published scheduled action or cancellation of a temp_sched_* draft creation.",
|
|
@@ -34309,7 +36755,7 @@ registerSchema({
|
|
|
34309
36755
|
id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
|
|
34310
36756
|
}
|
|
34311
36757
|
});
|
|
34312
|
-
var deleteCommand2 =
|
|
36758
|
+
var deleteCommand2 = defineCommand164({
|
|
34313
36759
|
meta: {
|
|
34314
36760
|
name: "delete",
|
|
34315
36761
|
description: "Stage scheduled action deletion. Example: baker scheduled-actions delete <id-or-temp_sched_id>"
|
|
@@ -34338,7 +36784,7 @@ var deleteCommand2 = defineCommand156({
|
|
|
34338
36784
|
});
|
|
34339
36785
|
|
|
34340
36786
|
// src/commands/scheduled-actions/get.ts
|
|
34341
|
-
import { defineCommand as
|
|
36787
|
+
import { defineCommand as defineCommand165 } from "citty";
|
|
34342
36788
|
registerSchema({
|
|
34343
36789
|
command: "scheduled-actions.get",
|
|
34344
36790
|
description: "Get a published scheduled action or a temp_sched_* draft-created scheduled action.",
|
|
@@ -34347,7 +36793,7 @@ registerSchema({
|
|
|
34347
36793
|
chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
|
|
34348
36794
|
}
|
|
34349
36795
|
});
|
|
34350
|
-
var getCommand3 =
|
|
36796
|
+
var getCommand3 = defineCommand165({
|
|
34351
36797
|
meta: {
|
|
34352
36798
|
name: "get",
|
|
34353
36799
|
description: "Get a scheduled action. Example: baker scheduled-actions get <id-or-temp_sched_id>"
|
|
@@ -34386,7 +36832,7 @@ var getCommand3 = defineCommand157({
|
|
|
34386
36832
|
});
|
|
34387
36833
|
|
|
34388
36834
|
// src/commands/scheduled-actions/list.ts
|
|
34389
|
-
import { defineCommand as
|
|
36835
|
+
import { defineCommand as defineCommand166 } from "citty";
|
|
34390
36836
|
registerSchema({
|
|
34391
36837
|
command: "scheduled-actions.list",
|
|
34392
36838
|
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.",
|
|
@@ -34394,7 +36840,7 @@ registerSchema({
|
|
|
34394
36840
|
chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
|
|
34395
36841
|
}
|
|
34396
36842
|
});
|
|
34397
|
-
var listCommand14 =
|
|
36843
|
+
var listCommand14 = defineCommand166({
|
|
34398
36844
|
meta: {
|
|
34399
36845
|
name: "list",
|
|
34400
36846
|
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."
|
|
@@ -34417,7 +36863,7 @@ var listCommand14 = defineCommand158({
|
|
|
34417
36863
|
});
|
|
34418
36864
|
|
|
34419
36865
|
// src/commands/scheduled-actions/trigger.ts
|
|
34420
|
-
import { defineCommand as
|
|
36866
|
+
import { defineCommand as defineCommand167 } from "citty";
|
|
34421
36867
|
registerSchema({
|
|
34422
36868
|
command: "scheduled-actions.trigger",
|
|
34423
36869
|
description: "Immediately trigger a published scheduled action. Does not require BAKER_CHAT_ID and rejects temp_sched_* IDs.",
|
|
@@ -34425,7 +36871,7 @@ registerSchema({
|
|
|
34425
36871
|
id: { type: "string", description: "Published scheduled action ID", required: true }
|
|
34426
36872
|
}
|
|
34427
36873
|
});
|
|
34428
|
-
var triggerCommand =
|
|
36874
|
+
var triggerCommand = defineCommand167({
|
|
34429
36875
|
meta: {
|
|
34430
36876
|
name: "trigger",
|
|
34431
36877
|
description: "Immediately trigger a published scheduled action. Example: baker scheduled-actions trigger <id>"
|
|
@@ -34462,7 +36908,7 @@ var triggerCommand = defineCommand159({
|
|
|
34462
36908
|
});
|
|
34463
36909
|
|
|
34464
36910
|
// src/commands/scheduled-actions/update.ts
|
|
34465
|
-
import { defineCommand as
|
|
36911
|
+
import { defineCommand as defineCommand168 } from "citty";
|
|
34466
36912
|
registerSchema({
|
|
34467
36913
|
command: "scheduled-actions.update",
|
|
34468
36914
|
description: "Stage an update to a published scheduled action or temp_sched_* draft-created scheduled action.",
|
|
@@ -34487,7 +36933,7 @@ registerSchema({
|
|
|
34487
36933
|
prompt: { type: "string", description: "Replacement additional spawned-agent instructions", required: false }
|
|
34488
36934
|
}
|
|
34489
36935
|
});
|
|
34490
|
-
var updateCommand2 =
|
|
36936
|
+
var updateCommand2 = defineCommand168({
|
|
34491
36937
|
meta: {
|
|
34492
36938
|
name: "update",
|
|
34493
36939
|
description: "Stage a scheduled action update. Example: baker scheduled-actions update <id> --enabled false"
|
|
@@ -34558,7 +37004,7 @@ var updateCommand2 = defineCommand160({
|
|
|
34558
37004
|
});
|
|
34559
37005
|
|
|
34560
37006
|
// src/commands/scheduled-actions/index.ts
|
|
34561
|
-
var scheduledActionsCommand =
|
|
37007
|
+
var scheduledActionsCommand = defineCommand169({
|
|
34562
37008
|
meta: {
|
|
34563
37009
|
name: "scheduled-actions",
|
|
34564
37010
|
description: `Manage Scheduled Actions. Subcommands: list, get, create, update, delete, trigger.
|
|
@@ -34585,14 +37031,14 @@ Full guide: __tooling__/docs/tools/baker/scheduled-actions.md`
|
|
|
34585
37031
|
});
|
|
34586
37032
|
|
|
34587
37033
|
// src/commands/schema.ts
|
|
34588
|
-
import { defineCommand as
|
|
37034
|
+
import { defineCommand as defineCommand170 } from "citty";
|
|
34589
37035
|
function narrowToFamily(commandName, available) {
|
|
34590
37036
|
const segments = commandName.split(".");
|
|
34591
37037
|
const prefix = segments[0] === "ads" && segments[1] ? `ads.${segments[1]}.` : `${segments[0]}.`;
|
|
34592
37038
|
const siblings = available.filter((name) => name.startsWith(prefix));
|
|
34593
37039
|
return siblings.length > 0 ? siblings : available;
|
|
34594
37040
|
}
|
|
34595
|
-
var schemaCommand =
|
|
37041
|
+
var schemaCommand = defineCommand170({
|
|
34596
37042
|
meta: {
|
|
34597
37043
|
name: "schema",
|
|
34598
37044
|
description: "Inspect command argument schemas (for AI agent introspection). Lists all commands if no argument given. Example: baker schema images.search"
|
|
@@ -34636,10 +37082,10 @@ var schemaCommand = defineCommand162({
|
|
|
34636
37082
|
});
|
|
34637
37083
|
|
|
34638
37084
|
// src/commands/studio/index.ts
|
|
34639
|
-
import { defineCommand as
|
|
37085
|
+
import { defineCommand as defineCommand179 } from "citty";
|
|
34640
37086
|
|
|
34641
37087
|
// src/commands/studio/animate.ts
|
|
34642
|
-
import { defineCommand as
|
|
37088
|
+
import { defineCommand as defineCommand171 } from "citty";
|
|
34643
37089
|
|
|
34644
37090
|
// src/commands/studio/batch.ts
|
|
34645
37091
|
function projectBatch(generation, full) {
|
|
@@ -34828,7 +37274,7 @@ function parseImageRefs(spec) {
|
|
|
34828
37274
|
}
|
|
34829
37275
|
var defaultDeps = {
|
|
34830
37276
|
ingest: (url) => apiPost("/api/images/ingest", { url, source: "uploaded" }),
|
|
34831
|
-
upload: (
|
|
37277
|
+
upload: (path35) => uploadLocalImage({ file: path35, contentType: detectImageContentType(path35), source: "uploaded" })
|
|
34832
37278
|
};
|
|
34833
37279
|
async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
|
|
34834
37280
|
const refs = parseImageRefs(spec);
|
|
@@ -34848,10 +37294,10 @@ async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
|
|
|
34848
37294
|
}
|
|
34849
37295
|
return { imageIds, added };
|
|
34850
37296
|
}
|
|
34851
|
-
function uploadFailure(
|
|
37297
|
+
function uploadFailure(path35) {
|
|
34852
37298
|
return (error) => {
|
|
34853
37299
|
if (error instanceof ApiError) throw error;
|
|
34854
|
-
throw new ApiError("VALIDATION_ERROR", `Could not read "${
|
|
37300
|
+
throw new ApiError("VALIDATION_ERROR", `Could not read "${path35}" as an image.`);
|
|
34855
37301
|
};
|
|
34856
37302
|
}
|
|
34857
37303
|
|
|
@@ -35032,7 +37478,7 @@ function costHintsFor(body) {
|
|
|
35032
37478
|
}
|
|
35033
37479
|
return hints;
|
|
35034
37480
|
}
|
|
35035
|
-
var animateCommand =
|
|
37481
|
+
var animateCommand = defineCommand171({
|
|
35036
37482
|
meta: {
|
|
35037
37483
|
name: "animate",
|
|
35038
37484
|
description: "Turn a still into a clip. The image fixes the look, so the prompt describes MOVEMENT \u2014 what the camera does, what the subject does, in what order.\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\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"
|
|
@@ -35135,7 +37581,7 @@ var animateCommand = defineCommand163({
|
|
|
35135
37581
|
});
|
|
35136
37582
|
|
|
35137
37583
|
// src/commands/studio/generate.ts
|
|
35138
|
-
import { defineCommand as
|
|
37584
|
+
import { defineCommand as defineCommand172 } from "citty";
|
|
35139
37585
|
var MODEL_LIST2 = IMAGE_MODEL_IDS;
|
|
35140
37586
|
var DEFAULT_MAX_WAIT_MS2 = 24e4;
|
|
35141
37587
|
registerSchema({
|
|
@@ -35265,7 +37711,7 @@ function buildGenerateBody(args, prompt) {
|
|
|
35265
37711
|
}
|
|
35266
37712
|
return body;
|
|
35267
37713
|
}
|
|
35268
|
-
var generateCommand =
|
|
37714
|
+
var generateCommand = defineCommand172({
|
|
35269
37715
|
meta: {
|
|
35270
37716
|
name: "generate",
|
|
35271
37717
|
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]]'"
|
|
@@ -35343,7 +37789,7 @@ var generateCommand = defineCommand164({
|
|
|
35343
37789
|
});
|
|
35344
37790
|
|
|
35345
37791
|
// src/commands/studio/get.ts
|
|
35346
|
-
import { defineCommand as
|
|
37792
|
+
import { defineCommand as defineCommand173 } from "citty";
|
|
35347
37793
|
registerSchema({
|
|
35348
37794
|
command: "studio.get",
|
|
35349
37795
|
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.",
|
|
@@ -35352,7 +37798,7 @@ registerSchema({
|
|
|
35352
37798
|
full: { type: "boolean", description: "Include settings, references and attribution", required: false }
|
|
35353
37799
|
}
|
|
35354
37800
|
});
|
|
35355
|
-
var getCommand4 =
|
|
37801
|
+
var getCommand4 = defineCommand173({
|
|
35356
37802
|
meta: {
|
|
35357
37803
|
name: "get",
|
|
35358
37804
|
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"
|
|
@@ -35381,7 +37827,7 @@ var getCommand4 = defineCommand165({
|
|
|
35381
37827
|
});
|
|
35382
37828
|
|
|
35383
37829
|
// src/commands/studio/improve.ts
|
|
35384
|
-
import { defineCommand as
|
|
37830
|
+
import { defineCommand as defineCommand174 } from "citty";
|
|
35385
37831
|
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.";
|
|
35386
37832
|
registerSchema({
|
|
35387
37833
|
command: "studio.improve",
|
|
@@ -35406,7 +37852,7 @@ registerSchema({
|
|
|
35406
37852
|
}
|
|
35407
37853
|
}
|
|
35408
37854
|
});
|
|
35409
|
-
var improveCommand =
|
|
37855
|
+
var improveCommand = defineCommand174({
|
|
35410
37856
|
meta: {
|
|
35411
37857
|
name: "improve",
|
|
35412
37858
|
description: `${DESCRIPTION}
|
|
@@ -35450,7 +37896,7 @@ Examples:
|
|
|
35450
37896
|
});
|
|
35451
37897
|
|
|
35452
37898
|
// src/commands/studio/keep.ts
|
|
35453
|
-
import { defineCommand as
|
|
37899
|
+
import { defineCommand as defineCommand175 } from "citty";
|
|
35454
37900
|
registerSchema({
|
|
35455
37901
|
command: "studio.keep",
|
|
35456
37902
|
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.",
|
|
@@ -35460,7 +37906,7 @@ registerSchema({
|
|
|
35460
37906
|
undo: { type: "boolean", description: "Un-star an image, or take a kept clip back out", required: false }
|
|
35461
37907
|
}
|
|
35462
37908
|
});
|
|
35463
|
-
var keepCommand =
|
|
37909
|
+
var keepCommand = defineCommand175({
|
|
35464
37910
|
meta: {
|
|
35465
37911
|
name: "keep",
|
|
35466
37912
|
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"
|
|
@@ -35511,7 +37957,7 @@ var keepCommand = defineCommand167({
|
|
|
35511
37957
|
});
|
|
35512
37958
|
|
|
35513
37959
|
// src/commands/studio/list.ts
|
|
35514
|
-
import { defineCommand as
|
|
37960
|
+
import { defineCommand as defineCommand176 } from "citty";
|
|
35515
37961
|
registerSchema({
|
|
35516
37962
|
command: "studio.list",
|
|
35517
37963
|
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.",
|
|
@@ -35522,7 +37968,7 @@ registerSchema({
|
|
|
35522
37968
|
full: { type: "boolean", description: "Include settings, references and attribution", required: false }
|
|
35523
37969
|
}
|
|
35524
37970
|
});
|
|
35525
|
-
var listCommand15 =
|
|
37971
|
+
var listCommand15 = defineCommand176({
|
|
35526
37972
|
meta: {
|
|
35527
37973
|
name: "list",
|
|
35528
37974
|
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"
|
|
@@ -35556,7 +38002,7 @@ var listCommand15 = defineCommand168({
|
|
|
35556
38002
|
});
|
|
35557
38003
|
|
|
35558
38004
|
// src/commands/studio/models.ts
|
|
35559
|
-
import { defineCommand as
|
|
38005
|
+
import { defineCommand as defineCommand177 } from "citty";
|
|
35560
38006
|
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.";
|
|
35561
38007
|
registerSchema({
|
|
35562
38008
|
command: "studio.models",
|
|
@@ -35637,7 +38083,7 @@ function buildModelCards(kind, model) {
|
|
|
35637
38083
|
const selected = model ? ids.filter((id) => id === model) : ids;
|
|
35638
38084
|
return selected.map((id) => build(id));
|
|
35639
38085
|
}
|
|
35640
|
-
var modelsCommand =
|
|
38086
|
+
var modelsCommand = defineCommand177({
|
|
35641
38087
|
meta: {
|
|
35642
38088
|
name: "models",
|
|
35643
38089
|
description: `${DESCRIPTION2}
|
|
@@ -35684,13 +38130,13 @@ Examples:
|
|
|
35684
38130
|
});
|
|
35685
38131
|
|
|
35686
38132
|
// src/commands/studio/skills.ts
|
|
35687
|
-
import { defineCommand as
|
|
38133
|
+
import { defineCommand as defineCommand178 } from "citty";
|
|
35688
38134
|
registerSchema({
|
|
35689
38135
|
command: "studio.skills",
|
|
35690
38136
|
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.",
|
|
35691
38137
|
args: {}
|
|
35692
38138
|
});
|
|
35693
|
-
var skillsCommand =
|
|
38139
|
+
var skillsCommand = defineCommand178({
|
|
35694
38140
|
meta: {
|
|
35695
38141
|
name: "skills",
|
|
35696
38142
|
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"
|
|
@@ -35714,7 +38160,7 @@ var skillsCommand = defineCommand170({
|
|
|
35714
38160
|
});
|
|
35715
38161
|
|
|
35716
38162
|
// src/commands/studio/index.ts
|
|
35717
|
-
var studioCommand =
|
|
38163
|
+
var studioCommand = defineCommand179({
|
|
35718
38164
|
meta: {
|
|
35719
38165
|
name: "studio",
|
|
35720
38166
|
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.
|
|
@@ -35757,10 +38203,10 @@ Full guide: __tooling__/docs/tools/baker/studio.md`
|
|
|
35757
38203
|
});
|
|
35758
38204
|
|
|
35759
38205
|
// src/commands/tag-manager/index.ts
|
|
35760
|
-
import { defineCommand as
|
|
38206
|
+
import { defineCommand as defineCommand183 } from "citty";
|
|
35761
38207
|
|
|
35762
38208
|
// src/commands/tag-manager/draft.ts
|
|
35763
|
-
import { defineCommand as
|
|
38209
|
+
import { defineCommand as defineCommand180 } from "citty";
|
|
35764
38210
|
|
|
35765
38211
|
// src/commands/tag-manager/shared.ts
|
|
35766
38212
|
import { readFileSync as readFileSync13 } from "fs";
|
|
@@ -35827,10 +38273,10 @@ async function stageOp4(op) {
|
|
|
35827
38273
|
handleError4(err);
|
|
35828
38274
|
}
|
|
35829
38275
|
}
|
|
35830
|
-
async function draftAction3(
|
|
38276
|
+
async function draftAction3(path35, body, chat) {
|
|
35831
38277
|
const chatId = resolveChatId(chat);
|
|
35832
38278
|
try {
|
|
35833
|
-
const data = await apiPost(
|
|
38279
|
+
const data = await apiPost(path35, { chatId, ...body });
|
|
35834
38280
|
writeJsonEnvelope({ ok: true, data });
|
|
35835
38281
|
return data;
|
|
35836
38282
|
} catch (err) {
|
|
@@ -35883,13 +38329,13 @@ registerSchema({
|
|
|
35883
38329
|
chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
|
|
35884
38330
|
}
|
|
35885
38331
|
});
|
|
35886
|
-
var draftCommand4 =
|
|
38332
|
+
var draftCommand4 = defineCommand180({
|
|
35887
38333
|
meta: {
|
|
35888
38334
|
name: "draft",
|
|
35889
38335
|
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."
|
|
35890
38336
|
},
|
|
35891
38337
|
subCommands: {
|
|
35892
|
-
list:
|
|
38338
|
+
list: defineCommand180({
|
|
35893
38339
|
meta: {
|
|
35894
38340
|
name: "list",
|
|
35895
38341
|
description: "Review everything staged on this chat (--json for the raw envelope)"
|
|
@@ -35902,7 +38348,7 @@ var draftCommand4 = defineCommand172({
|
|
|
35902
38348
|
await draftList2(args.json === true, args.chat);
|
|
35903
38349
|
}
|
|
35904
38350
|
}),
|
|
35905
|
-
show:
|
|
38351
|
+
show: defineCommand180({
|
|
35906
38352
|
meta: {
|
|
35907
38353
|
name: "show",
|
|
35908
38354
|
description: "Print the full staged payload for one change \u2014 the receipt to verify it looks right before publish (never truncated)."
|
|
@@ -35919,7 +38365,7 @@ var draftCommand4 = defineCommand172({
|
|
|
35919
38365
|
);
|
|
35920
38366
|
}
|
|
35921
38367
|
}),
|
|
35922
|
-
amend:
|
|
38368
|
+
amend: defineCommand180({
|
|
35923
38369
|
meta: {
|
|
35924
38370
|
name: "amend",
|
|
35925
38371
|
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."
|
|
@@ -35936,7 +38382,7 @@ var draftCommand4 = defineCommand172({
|
|
|
35936
38382
|
});
|
|
35937
38383
|
}
|
|
35938
38384
|
}),
|
|
35939
|
-
remove:
|
|
38385
|
+
remove: defineCommand180({
|
|
35940
38386
|
meta: { name: "remove", description: "Remove one staged change (cascades to anything depending on it)" },
|
|
35941
38387
|
args: { ref: { type: "positional", description: "Staged ref (gtm_temp_*) or target", required: false } },
|
|
35942
38388
|
run: async ({ args }) => {
|
|
@@ -35945,7 +38391,7 @@ var draftCommand4 = defineCommand172({
|
|
|
35945
38391
|
});
|
|
35946
38392
|
}
|
|
35947
38393
|
}),
|
|
35948
|
-
clear:
|
|
38394
|
+
clear: defineCommand180({
|
|
35949
38395
|
meta: { name: "clear", description: "Discard all Tag Manager changes staged on this chat" },
|
|
35950
38396
|
run: async () => {
|
|
35951
38397
|
await draftAction3("/api/tag-manager/draft/clear", {});
|
|
@@ -35955,7 +38401,7 @@ var draftCommand4 = defineCommand172({
|
|
|
35955
38401
|
});
|
|
35956
38402
|
|
|
35957
38403
|
// src/commands/tag-manager/read.ts
|
|
35958
|
-
import { defineCommand as
|
|
38404
|
+
import { defineCommand as defineCommand181 } from "citty";
|
|
35959
38405
|
registerSchema({
|
|
35960
38406
|
command: "tagManager.containers",
|
|
35961
38407
|
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.",
|
|
@@ -35996,7 +38442,7 @@ function containersHints(containers) {
|
|
|
35996
38442
|
}))
|
|
35997
38443
|
});
|
|
35998
38444
|
}
|
|
35999
|
-
var containersCommand =
|
|
38445
|
+
var containersCommand = defineCommand181({
|
|
36000
38446
|
meta: {
|
|
36001
38447
|
name: "containers",
|
|
36002
38448
|
description: `List Tag Manager containers reachable by this company's connection.
|
|
@@ -36013,7 +38459,7 @@ Start here:
|
|
|
36013
38459
|
}
|
|
36014
38460
|
}
|
|
36015
38461
|
});
|
|
36016
|
-
var readCommand =
|
|
38462
|
+
var readCommand = defineCommand181({
|
|
36017
38463
|
meta: {
|
|
36018
38464
|
name: "read",
|
|
36019
38465
|
description: `Read the current contents of the Tag Manager container \u2014 always do this before staging changes.
|
|
@@ -36055,7 +38501,7 @@ Examples:
|
|
|
36055
38501
|
});
|
|
36056
38502
|
|
|
36057
38503
|
// src/commands/tag-manager/write-commands.ts
|
|
36058
|
-
import { defineCommand as
|
|
38504
|
+
import { defineCommand as defineCommand182 } from "citty";
|
|
36059
38505
|
var CONTAINER_ARG_DESCRIPTION = "Numeric container id (optional only when one container is connected \u2014 run `baker tag-manager containers`)";
|
|
36060
38506
|
var ENTITIES = [
|
|
36061
38507
|
{
|
|
@@ -36111,10 +38557,10 @@ for (const { entity, noun, createHint } of ENTITIES) {
|
|
|
36111
38557
|
});
|
|
36112
38558
|
}
|
|
36113
38559
|
function entityCommand(entity, noun, example) {
|
|
36114
|
-
return
|
|
38560
|
+
return defineCommand182({
|
|
36115
38561
|
meta: { name: entity, description: `Stage ${noun} changes on this chat's Tag Manager draft` },
|
|
36116
38562
|
subCommands: {
|
|
36117
|
-
create:
|
|
38563
|
+
create: defineCommand182({
|
|
36118
38564
|
meta: {
|
|
36119
38565
|
name: "create",
|
|
36120
38566
|
description: `Stage a new ${noun}
|
|
@@ -36136,7 +38582,7 @@ Examples:
|
|
|
36136
38582
|
});
|
|
36137
38583
|
}
|
|
36138
38584
|
}),
|
|
36139
|
-
update:
|
|
38585
|
+
update: defineCommand182({
|
|
36140
38586
|
meta: {
|
|
36141
38587
|
name: "update",
|
|
36142
38588
|
description: `Stage an update to an existing ${noun} (pass its id or path)`
|
|
@@ -36156,7 +38602,7 @@ Examples:
|
|
|
36156
38602
|
});
|
|
36157
38603
|
}
|
|
36158
38604
|
}),
|
|
36159
|
-
delete:
|
|
38605
|
+
delete: defineCommand182({
|
|
36160
38606
|
meta: { name: "delete", description: `Stage the deletion of a ${noun} (pass its id or path)` },
|
|
36161
38607
|
args: {
|
|
36162
38608
|
id: { type: "positional", description: `${noun} id or path`, required: false },
|
|
@@ -36197,7 +38643,7 @@ function builtinTypes(args) {
|
|
|
36197
38643
|
}
|
|
36198
38644
|
return raw.split(",").map((entry) => entry.trim());
|
|
36199
38645
|
}
|
|
36200
|
-
var builtinCommand =
|
|
38646
|
+
var builtinCommand = defineCommand182({
|
|
36201
38647
|
meta: {
|
|
36202
38648
|
name: "builtin",
|
|
36203
38649
|
description: `Enable or disable built-in variables
|
|
@@ -36207,7 +38653,7 @@ Examples:
|
|
|
36207
38653
|
baker tag-manager builtin disable --types formId`
|
|
36208
38654
|
},
|
|
36209
38655
|
subCommands: {
|
|
36210
|
-
enable:
|
|
38656
|
+
enable: defineCommand182({
|
|
36211
38657
|
meta: { name: "enable", description: "Stage enabling built-in variables" },
|
|
36212
38658
|
args: {
|
|
36213
38659
|
types: { type: "string", description: "Comma-separated types", required: false },
|
|
@@ -36221,7 +38667,7 @@ Examples:
|
|
|
36221
38667
|
});
|
|
36222
38668
|
}
|
|
36223
38669
|
}),
|
|
36224
|
-
disable:
|
|
38670
|
+
disable: defineCommand182({
|
|
36225
38671
|
meta: { name: "disable", description: "Stage disabling built-in variables" },
|
|
36226
38672
|
args: {
|
|
36227
38673
|
types: { type: "string", description: "Comma-separated types", required: false },
|
|
@@ -36239,7 +38685,7 @@ Examples:
|
|
|
36239
38685
|
});
|
|
36240
38686
|
|
|
36241
38687
|
// src/commands/tag-manager/index.ts
|
|
36242
|
-
var tagManagerCommand =
|
|
38688
|
+
var tagManagerCommand = defineCommand183({
|
|
36243
38689
|
meta: {
|
|
36244
38690
|
name: "tag-manager",
|
|
36245
38691
|
description: `Read and change what lives inside the client's Google Tag Manager container \u2014 tags, triggers, variables, folders and built-in variables.
|
|
@@ -36276,7 +38722,7 @@ Full guide: __tooling__/docs/tools/baker/tag-manager.md`
|
|
|
36276
38722
|
});
|
|
36277
38723
|
|
|
36278
38724
|
// src/commands/tags/index.ts
|
|
36279
|
-
import { defineCommand as
|
|
38725
|
+
import { defineCommand as defineCommand184 } from "citty";
|
|
36280
38726
|
|
|
36281
38727
|
// src/commands/tags/shared.ts
|
|
36282
38728
|
function failApi3(err) {
|
|
@@ -36345,7 +38791,7 @@ async function listTags(json) {
|
|
|
36345
38791
|
var listArgs9 = {
|
|
36346
38792
|
json: { type: "boolean", description: "Print the raw JSON envelope instead of the readable list" }
|
|
36347
38793
|
};
|
|
36348
|
-
var listCommand16 =
|
|
38794
|
+
var listCommand16 = defineCommand184({
|
|
36349
38795
|
meta: {
|
|
36350
38796
|
name: "list",
|
|
36351
38797
|
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"
|
|
@@ -36364,7 +38810,7 @@ async function listDraft3(chat) {
|
|
|
36364
38810
|
failApi3(err);
|
|
36365
38811
|
}
|
|
36366
38812
|
}
|
|
36367
|
-
var draftCommand5 =
|
|
38813
|
+
var draftCommand5 = defineCommand184({
|
|
36368
38814
|
meta: {
|
|
36369
38815
|
name: "draft",
|
|
36370
38816
|
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."
|
|
@@ -36374,7 +38820,7 @@ var draftCommand5 = defineCommand176({
|
|
|
36374
38820
|
await listDraft3(args.chat);
|
|
36375
38821
|
}
|
|
36376
38822
|
});
|
|
36377
|
-
var tagsCommand3 =
|
|
38823
|
+
var tagsCommand3 = defineCommand184({
|
|
36378
38824
|
meta: {
|
|
36379
38825
|
name: "tags",
|
|
36380
38826
|
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.
|
|
@@ -36403,10 +38849,10 @@ Full guide: __tooling__/docs/tools/baker/tags.md`
|
|
|
36403
38849
|
});
|
|
36404
38850
|
|
|
36405
38851
|
// src/commands/testimonials/index.ts
|
|
36406
|
-
import { defineCommand as
|
|
38852
|
+
import { defineCommand as defineCommand188 } from "citty";
|
|
36407
38853
|
|
|
36408
38854
|
// src/commands/testimonials/get.ts
|
|
36409
|
-
import { defineCommand as
|
|
38855
|
+
import { defineCommand as defineCommand185 } from "citty";
|
|
36410
38856
|
registerSchema({
|
|
36411
38857
|
command: "testimonials.get",
|
|
36412
38858
|
description: "Get a single testimonial by ID",
|
|
@@ -36414,7 +38860,7 @@ registerSchema({
|
|
|
36414
38860
|
id: { type: "string", description: "Testimonial ID", required: true }
|
|
36415
38861
|
}
|
|
36416
38862
|
});
|
|
36417
|
-
var getCommand5 =
|
|
38863
|
+
var getCommand5 = defineCommand185({
|
|
36418
38864
|
meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
|
|
36419
38865
|
args: {
|
|
36420
38866
|
id: { type: "positional", description: "Testimonial ID", required: false },
|
|
@@ -36451,7 +38897,7 @@ var getCommand5 = defineCommand177({
|
|
|
36451
38897
|
});
|
|
36452
38898
|
|
|
36453
38899
|
// src/commands/testimonials/list.ts
|
|
36454
|
-
import { defineCommand as
|
|
38900
|
+
import { defineCommand as defineCommand186 } from "citty";
|
|
36455
38901
|
registerSchema({
|
|
36456
38902
|
command: "testimonials.list",
|
|
36457
38903
|
description: "List testimonials with optional filters.",
|
|
@@ -36481,7 +38927,7 @@ registerSchema({
|
|
|
36481
38927
|
limit: { type: "number", description: "Max results (default 50)", required: false, default: 50 }
|
|
36482
38928
|
}
|
|
36483
38929
|
});
|
|
36484
|
-
var listCommand17 =
|
|
38930
|
+
var listCommand17 = defineCommand186({
|
|
36485
38931
|
meta: {
|
|
36486
38932
|
name: "list",
|
|
36487
38933
|
description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
|
|
@@ -36530,7 +38976,7 @@ var listCommand17 = defineCommand178({
|
|
|
36530
38976
|
});
|
|
36531
38977
|
|
|
36532
38978
|
// src/commands/testimonials/search.ts
|
|
36533
|
-
import { defineCommand as
|
|
38979
|
+
import { defineCommand as defineCommand187 } from "citty";
|
|
36534
38980
|
function languageBiasHint(results, requestedLanguage) {
|
|
36535
38981
|
if (requestedLanguage) {
|
|
36536
38982
|
return null;
|
|
@@ -36608,7 +39054,7 @@ function buildSearchRequest(query, args) {
|
|
|
36608
39054
|
}
|
|
36609
39055
|
return body;
|
|
36610
39056
|
}
|
|
36611
|
-
var
|
|
39057
|
+
var searchCommand3 = defineCommand187({
|
|
36612
39058
|
meta: {
|
|
36613
39059
|
name: "search",
|
|
36614
39060
|
description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
|
|
@@ -36664,7 +39110,7 @@ var searchCommand2 = defineCommand179({
|
|
|
36664
39110
|
var tagsCommand4 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
|
|
36665
39111
|
|
|
36666
39112
|
// src/commands/testimonials/index.ts
|
|
36667
|
-
var testimonialsCommand =
|
|
39113
|
+
var testimonialsCommand = defineCommand188({
|
|
36668
39114
|
meta: {
|
|
36669
39115
|
name: "testimonials",
|
|
36670
39116
|
description: `Find and browse testimonials in Baker. Subcommands: search, get, list, tags.
|
|
@@ -36679,17 +39125,17 @@ Full guide: __tooling__/docs/tools/baker/testimonials.md`
|
|
|
36679
39125
|
},
|
|
36680
39126
|
subCommands: {
|
|
36681
39127
|
get: getCommand5,
|
|
36682
|
-
search:
|
|
39128
|
+
search: searchCommand3,
|
|
36683
39129
|
list: listCommand17,
|
|
36684
39130
|
tags: tagsCommand4
|
|
36685
39131
|
}
|
|
36686
39132
|
});
|
|
36687
39133
|
|
|
36688
39134
|
// src/commands/videos/index.ts
|
|
36689
|
-
import { defineCommand as
|
|
39135
|
+
import { defineCommand as defineCommand193 } from "citty";
|
|
36690
39136
|
|
|
36691
39137
|
// src/commands/videos/delete.ts
|
|
36692
|
-
import { defineCommand as
|
|
39138
|
+
import { defineCommand as defineCommand189 } from "citty";
|
|
36693
39139
|
registerSchema({
|
|
36694
39140
|
command: "videos.delete",
|
|
36695
39141
|
description: "Delete a video by ID",
|
|
@@ -36703,7 +39149,7 @@ registerSchema({
|
|
|
36703
39149
|
}
|
|
36704
39150
|
}
|
|
36705
39151
|
});
|
|
36706
|
-
var deleteCommand3 =
|
|
39152
|
+
var deleteCommand3 = defineCommand189({
|
|
36707
39153
|
meta: {
|
|
36708
39154
|
name: "delete",
|
|
36709
39155
|
description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
|
|
@@ -36744,7 +39190,7 @@ var deleteCommand3 = defineCommand181({
|
|
|
36744
39190
|
});
|
|
36745
39191
|
|
|
36746
39192
|
// src/commands/videos/get.ts
|
|
36747
|
-
import { defineCommand as
|
|
39193
|
+
import { defineCommand as defineCommand190 } from "citty";
|
|
36748
39194
|
registerSchema({
|
|
36749
39195
|
command: "videos.get",
|
|
36750
39196
|
description: "Get a single video by ID",
|
|
@@ -36752,7 +39198,7 @@ registerSchema({
|
|
|
36752
39198
|
id: { type: "string", description: "Video ID", required: true }
|
|
36753
39199
|
}
|
|
36754
39200
|
});
|
|
36755
|
-
var getCommand6 =
|
|
39201
|
+
var getCommand6 = defineCommand190({
|
|
36756
39202
|
meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
|
|
36757
39203
|
args: {
|
|
36758
39204
|
id: { type: "positional", description: "Video ID", required: false },
|
|
@@ -36789,7 +39235,7 @@ var getCommand6 = defineCommand182({
|
|
|
36789
39235
|
});
|
|
36790
39236
|
|
|
36791
39237
|
// src/commands/videos/search.ts
|
|
36792
|
-
import { defineCommand as
|
|
39238
|
+
import { defineCommand as defineCommand191 } from "citty";
|
|
36793
39239
|
registerSchema({
|
|
36794
39240
|
command: "videos.search",
|
|
36795
39241
|
description: "Search videos by text query. Only returns ready videos.",
|
|
@@ -36799,7 +39245,7 @@ registerSchema({
|
|
|
36799
39245
|
tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
|
|
36800
39246
|
}
|
|
36801
39247
|
});
|
|
36802
|
-
var
|
|
39248
|
+
var searchCommand4 = defineCommand191({
|
|
36803
39249
|
meta: {
|
|
36804
39250
|
name: "search",
|
|
36805
39251
|
description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
|
|
@@ -36849,9 +39295,9 @@ var searchCommand3 = defineCommand183({
|
|
|
36849
39295
|
var tagsCommand5 = makeTagsCommand("videos", "video", "/api/videos/tags");
|
|
36850
39296
|
|
|
36851
39297
|
// src/commands/videos/upload.ts
|
|
36852
|
-
import { readFile as
|
|
39298
|
+
import { readFile as readFile24, stat as stat7 } from "fs/promises";
|
|
36853
39299
|
import { extname as extname3 } from "path";
|
|
36854
|
-
import { defineCommand as
|
|
39300
|
+
import { defineCommand as defineCommand192 } from "citty";
|
|
36855
39301
|
var MIME_MAP = {
|
|
36856
39302
|
".mp4": "video/mp4",
|
|
36857
39303
|
".mov": "video/quicktime",
|
|
@@ -36885,7 +39331,7 @@ function detectContentType(filePath) {
|
|
|
36885
39331
|
}
|
|
36886
39332
|
return mime;
|
|
36887
39333
|
}
|
|
36888
|
-
var uploadCommand2 =
|
|
39334
|
+
var uploadCommand2 = defineCommand192({
|
|
36889
39335
|
meta: {
|
|
36890
39336
|
name: "upload",
|
|
36891
39337
|
description: "Upload a video file to Baker via Mux direct upload. Auto-detects content type. Example: baker videos upload ./demo.mp4"
|
|
@@ -36914,7 +39360,7 @@ var uploadCommand2 = defineCommand184({
|
|
|
36914
39360
|
return;
|
|
36915
39361
|
}
|
|
36916
39362
|
const { uploadUrl, videoId } = await apiPost("/api/videos/upload", {});
|
|
36917
|
-
const fileBuffer = await
|
|
39363
|
+
const fileBuffer = await readFile24(filePath);
|
|
36918
39364
|
const uploadResponse = await fetch(uploadUrl, {
|
|
36919
39365
|
method: "PUT",
|
|
36920
39366
|
headers: { "Content-Type": contentType },
|
|
@@ -36939,7 +39385,7 @@ var uploadCommand2 = defineCommand184({
|
|
|
36939
39385
|
});
|
|
36940
39386
|
|
|
36941
39387
|
// src/commands/videos/index.ts
|
|
36942
|
-
var videosCommand =
|
|
39388
|
+
var videosCommand = defineCommand193({
|
|
36943
39389
|
meta: {
|
|
36944
39390
|
name: "videos",
|
|
36945
39391
|
description: `Find and manage videos in Baker. Subcommands: search, get, upload, delete, tags.
|
|
@@ -36955,7 +39401,7 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
|
|
|
36955
39401
|
},
|
|
36956
39402
|
subCommands: {
|
|
36957
39403
|
get: getCommand6,
|
|
36958
|
-
search:
|
|
39404
|
+
search: searchCommand4,
|
|
36959
39405
|
upload: uploadCommand2,
|
|
36960
39406
|
delete: deleteCommand3,
|
|
36961
39407
|
tags: tagsCommand5
|
|
@@ -36963,19 +39409,19 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
|
|
|
36963
39409
|
});
|
|
36964
39410
|
|
|
36965
39411
|
// src/commands/winning-ads/index.ts
|
|
36966
|
-
import { defineCommand as
|
|
39412
|
+
import { defineCommand as defineCommand206 } from "citty";
|
|
36967
39413
|
|
|
36968
39414
|
// src/commands/winning-ads/advertisers.ts
|
|
36969
|
-
import { defineCommand as
|
|
39415
|
+
import { defineCommand as defineCommand194 } from "citty";
|
|
36970
39416
|
|
|
36971
39417
|
// src/commands/winning-ads/shared.ts
|
|
36972
|
-
function
|
|
39418
|
+
function splitList2(value) {
|
|
36973
39419
|
if (!value) {
|
|
36974
39420
|
return [];
|
|
36975
39421
|
}
|
|
36976
39422
|
return value.split(",").map((v) => v.trim()).filter(Boolean);
|
|
36977
39423
|
}
|
|
36978
|
-
function
|
|
39424
|
+
function reportError2(err) {
|
|
36979
39425
|
if (err instanceof ApiError) {
|
|
36980
39426
|
writeJson({ ok: false, error: { code: err.code, message: err.message } });
|
|
36981
39427
|
process.exit(1);
|
|
@@ -37019,7 +39465,7 @@ function advertiserNormalizer(record, full) {
|
|
|
37019
39465
|
last_synced_at: record.last_synced_at ?? null
|
|
37020
39466
|
};
|
|
37021
39467
|
}
|
|
37022
|
-
var advertisersCommand2 =
|
|
39468
|
+
var advertisersCommand2 = defineCommand194({
|
|
37023
39469
|
meta: {
|
|
37024
39470
|
name: "advertisers",
|
|
37025
39471
|
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'
|
|
@@ -37071,13 +39517,13 @@ var advertisersCommand2 = defineCommand186({
|
|
|
37071
39517
|
advertiserNormalizer
|
|
37072
39518
|
);
|
|
37073
39519
|
} catch (err) {
|
|
37074
|
-
|
|
39520
|
+
reportError2(err);
|
|
37075
39521
|
}
|
|
37076
39522
|
}
|
|
37077
39523
|
});
|
|
37078
39524
|
|
|
37079
39525
|
// src/commands/winning-ads/brief.ts
|
|
37080
|
-
import { defineCommand as
|
|
39526
|
+
import { defineCommand as defineCommand195 } from "citty";
|
|
37081
39527
|
registerSchema({
|
|
37082
39528
|
command: "winning-ads.brief",
|
|
37083
39529
|
description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
|
|
@@ -37123,7 +39569,7 @@ function parseDna(raw) {
|
|
|
37123
39569
|
}
|
|
37124
39570
|
return parsed;
|
|
37125
39571
|
}
|
|
37126
|
-
var briefCommand =
|
|
39572
|
+
var briefCommand = defineCommand195({
|
|
37127
39573
|
meta: {
|
|
37128
39574
|
name: "brief",
|
|
37129
39575
|
description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
|
|
@@ -37153,13 +39599,13 @@ var briefCommand = defineCommand187({
|
|
|
37153
39599
|
const data = await apiPost("/api/ad-library/brief", body);
|
|
37154
39600
|
writeJson({ ok: true, data });
|
|
37155
39601
|
} catch (err) {
|
|
37156
|
-
|
|
39602
|
+
reportError2(err);
|
|
37157
39603
|
}
|
|
37158
39604
|
}
|
|
37159
39605
|
});
|
|
37160
39606
|
|
|
37161
39607
|
// src/commands/winning-ads/content.ts
|
|
37162
|
-
import { defineCommand as
|
|
39608
|
+
import { defineCommand as defineCommand196 } from "citty";
|
|
37163
39609
|
registerSchema({
|
|
37164
39610
|
command: "winning-ads.content",
|
|
37165
39611
|
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.",
|
|
@@ -37172,7 +39618,7 @@ registerSchema({
|
|
|
37172
39618
|
}
|
|
37173
39619
|
}
|
|
37174
39620
|
});
|
|
37175
|
-
var contentCommand =
|
|
39621
|
+
var contentCommand = defineCommand196({
|
|
37176
39622
|
meta: {
|
|
37177
39623
|
name: "content",
|
|
37178
39624
|
description: "Read the transcript + on-screen text + copy of one winning ad. Example: baker winning-ads content adg_123 --platform meta --full --output md"
|
|
@@ -37215,16 +39661,16 @@ var contentCommand = defineCommand188({
|
|
|
37215
39661
|
adContentNormalizer
|
|
37216
39662
|
);
|
|
37217
39663
|
} catch (err) {
|
|
37218
|
-
|
|
39664
|
+
reportError2(err);
|
|
37219
39665
|
}
|
|
37220
39666
|
}
|
|
37221
39667
|
});
|
|
37222
39668
|
|
|
37223
39669
|
// src/commands/winning-ads/feed.ts
|
|
37224
|
-
import { defineCommand as
|
|
39670
|
+
import { defineCommand as defineCommand197 } from "citty";
|
|
37225
39671
|
function buildFeedParams(input) {
|
|
37226
39672
|
const params = {};
|
|
37227
|
-
const advertiser =
|
|
39673
|
+
const advertiser = splitList2(input.advertiser);
|
|
37228
39674
|
if (advertiser.length > 0) {
|
|
37229
39675
|
params.advertiser = advertiser.join(",");
|
|
37230
39676
|
}
|
|
@@ -37237,11 +39683,11 @@ function buildFeedParams(input) {
|
|
|
37237
39683
|
if (input.limit !== void 0 && input.limit !== "") {
|
|
37238
39684
|
params.limit = input.limit;
|
|
37239
39685
|
}
|
|
37240
|
-
const winnerCategory =
|
|
39686
|
+
const winnerCategory = splitList2(input.winnerCategory);
|
|
37241
39687
|
if (winnerCategory.length > 0) {
|
|
37242
39688
|
params.winner_category = winnerCategory.join(",");
|
|
37243
39689
|
}
|
|
37244
|
-
const format =
|
|
39690
|
+
const format = splitList2(input.format);
|
|
37245
39691
|
if (format.length > 0) {
|
|
37246
39692
|
params.format = format.join(",");
|
|
37247
39693
|
}
|
|
@@ -37273,7 +39719,7 @@ registerSchema({
|
|
|
37273
39719
|
format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
|
|
37274
39720
|
}
|
|
37275
39721
|
});
|
|
37276
|
-
var feedCommand =
|
|
39722
|
+
var feedCommand = defineCommand197({
|
|
37277
39723
|
meta: {
|
|
37278
39724
|
name: "feed",
|
|
37279
39725
|
description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
|
|
@@ -37352,13 +39798,13 @@ var feedCommand = defineCommand189({
|
|
|
37352
39798
|
`);
|
|
37353
39799
|
}
|
|
37354
39800
|
} catch (err) {
|
|
37355
|
-
|
|
39801
|
+
reportError2(err);
|
|
37356
39802
|
}
|
|
37357
39803
|
}
|
|
37358
39804
|
});
|
|
37359
39805
|
|
|
37360
39806
|
// src/commands/winning-ads/follow.ts
|
|
37361
|
-
import { defineCommand as
|
|
39807
|
+
import { defineCommand as defineCommand198 } from "citty";
|
|
37362
39808
|
var PLATFORMS = ["meta", "linkedin"];
|
|
37363
39809
|
registerSchema({
|
|
37364
39810
|
command: "winning-ads.follow",
|
|
@@ -37373,7 +39819,7 @@ registerSchema({
|
|
|
37373
39819
|
label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
|
|
37374
39820
|
}
|
|
37375
39821
|
});
|
|
37376
|
-
var followCommand =
|
|
39822
|
+
var followCommand = defineCommand198({
|
|
37377
39823
|
meta: {
|
|
37378
39824
|
name: "follow",
|
|
37379
39825
|
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'
|
|
@@ -37414,19 +39860,19 @@ var followCommand = defineCommand190({
|
|
|
37414
39860
|
}
|
|
37415
39861
|
writeJson({ ok: true, data, hints });
|
|
37416
39862
|
} catch (err) {
|
|
37417
|
-
|
|
39863
|
+
reportError2(err);
|
|
37418
39864
|
}
|
|
37419
39865
|
}
|
|
37420
39866
|
});
|
|
37421
39867
|
|
|
37422
39868
|
// src/commands/winning-ads/follow-competitors.ts
|
|
37423
|
-
import { defineCommand as
|
|
39869
|
+
import { defineCommand as defineCommand199 } from "citty";
|
|
37424
39870
|
var PLATFORMS2 = ["meta", "linkedin"];
|
|
37425
39871
|
var BATCH_TIMEOUT_MS = 3e5;
|
|
37426
39872
|
function buildFollowBatchBody(input) {
|
|
37427
39873
|
const seen = /* @__PURE__ */ new Set();
|
|
37428
39874
|
const inputs = [];
|
|
37429
|
-
for (const domain of
|
|
39875
|
+
for (const domain of splitList2(input.domains)) {
|
|
37430
39876
|
const key = domain.toLowerCase();
|
|
37431
39877
|
if (seen.has(key)) {
|
|
37432
39878
|
continue;
|
|
@@ -37453,7 +39899,7 @@ registerSchema({
|
|
|
37453
39899
|
}
|
|
37454
39900
|
}
|
|
37455
39901
|
});
|
|
37456
|
-
var followCompetitorsCommand =
|
|
39902
|
+
var followCompetitorsCommand = defineCommand199({
|
|
37457
39903
|
meta: {
|
|
37458
39904
|
name: "follow-competitors",
|
|
37459
39905
|
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"'
|
|
@@ -37522,13 +39968,13 @@ var followCompetitorsCommand = defineCommand191({
|
|
|
37522
39968
|
}
|
|
37523
39969
|
writeJson({ ok: true, data, hints: hints.length > 0 ? hints : void 0 });
|
|
37524
39970
|
} catch (err) {
|
|
37525
|
-
|
|
39971
|
+
reportError2(err);
|
|
37526
39972
|
}
|
|
37527
39973
|
}
|
|
37528
39974
|
});
|
|
37529
39975
|
|
|
37530
39976
|
// src/commands/winning-ads/following.ts
|
|
37531
|
-
import { defineCommand as
|
|
39977
|
+
import { defineCommand as defineCommand200 } from "citty";
|
|
37532
39978
|
registerSchema({
|
|
37533
39979
|
command: "winning-ads.following",
|
|
37534
39980
|
description: "List the brands you follow in your ad-dna library, with each one's status (ready vs still adding) and cached ad counts.",
|
|
@@ -37561,7 +40007,7 @@ function followingNormalizer(record, full) {
|
|
|
37561
40007
|
platforms: Array.isArray(record.platforms) ? record.platforms : []
|
|
37562
40008
|
};
|
|
37563
40009
|
}
|
|
37564
|
-
var followingCommand =
|
|
40010
|
+
var followingCommand = defineCommand200({
|
|
37565
40011
|
meta: {
|
|
37566
40012
|
name: "following",
|
|
37567
40013
|
description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
|
|
@@ -37590,13 +40036,13 @@ var followingCommand = defineCommand192({
|
|
|
37590
40036
|
followingNormalizer
|
|
37591
40037
|
);
|
|
37592
40038
|
} catch (err) {
|
|
37593
|
-
|
|
40039
|
+
reportError2(err);
|
|
37594
40040
|
}
|
|
37595
40041
|
}
|
|
37596
40042
|
});
|
|
37597
40043
|
|
|
37598
40044
|
// src/commands/winning-ads/patterns.ts
|
|
37599
|
-
import { defineCommand as
|
|
40045
|
+
import { defineCommand as defineCommand201 } from "citty";
|
|
37600
40046
|
registerSchema({
|
|
37601
40047
|
command: "winning-ads.patterns",
|
|
37602
40048
|
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.",
|
|
@@ -37611,8 +40057,8 @@ registerSchema({
|
|
|
37611
40057
|
}
|
|
37612
40058
|
});
|
|
37613
40059
|
function buildPatternsBody(args) {
|
|
37614
|
-
const winners =
|
|
37615
|
-
const duds =
|
|
40060
|
+
const winners = splitList2(args.winners);
|
|
40061
|
+
const duds = splitList2(args.duds);
|
|
37616
40062
|
if (!winners.length || !duds.length) {
|
|
37617
40063
|
throw new Error("Provide at least one ad id for both --winners and --duds");
|
|
37618
40064
|
}
|
|
@@ -37635,7 +40081,7 @@ function discriminatorRow(record) {
|
|
|
37635
40081
|
top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
|
|
37636
40082
|
};
|
|
37637
40083
|
}
|
|
37638
|
-
var patternsCommand =
|
|
40084
|
+
var patternsCommand = defineCommand201({
|
|
37639
40085
|
meta: {
|
|
37640
40086
|
name: "patterns",
|
|
37641
40087
|
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"
|
|
@@ -37685,13 +40131,13 @@ var patternsCommand = defineCommand193({
|
|
|
37685
40131
|
(record) => discriminatorRow(record)
|
|
37686
40132
|
);
|
|
37687
40133
|
} catch (err) {
|
|
37688
|
-
|
|
40134
|
+
reportError2(err);
|
|
37689
40135
|
}
|
|
37690
40136
|
}
|
|
37691
40137
|
});
|
|
37692
40138
|
|
|
37693
40139
|
// src/commands/winning-ads/search.ts
|
|
37694
|
-
import { defineCommand as
|
|
40140
|
+
import { defineCommand as defineCommand202 } from "citty";
|
|
37695
40141
|
registerSchema({
|
|
37696
40142
|
command: "winning-ads.search",
|
|
37697
40143
|
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.",
|
|
@@ -37761,7 +40207,7 @@ function setNumber(body, key, value) {
|
|
|
37761
40207
|
}
|
|
37762
40208
|
}
|
|
37763
40209
|
function setList(target, key, value) {
|
|
37764
|
-
const list =
|
|
40210
|
+
const list = splitList2(value);
|
|
37765
40211
|
if (list.length) {
|
|
37766
40212
|
target[key] = list;
|
|
37767
40213
|
}
|
|
@@ -37771,7 +40217,7 @@ function setString(target, key, value) {
|
|
|
37771
40217
|
target[key] = value;
|
|
37772
40218
|
}
|
|
37773
40219
|
}
|
|
37774
|
-
function
|
|
40220
|
+
function buildSearchBody2(args) {
|
|
37775
40221
|
const body = {};
|
|
37776
40222
|
if (args.query) {
|
|
37777
40223
|
body.free_text_query = args.query;
|
|
@@ -37799,7 +40245,7 @@ function buildSearchBody(args) {
|
|
|
37799
40245
|
}
|
|
37800
40246
|
return body;
|
|
37801
40247
|
}
|
|
37802
|
-
var
|
|
40248
|
+
var searchCommand5 = defineCommand202({
|
|
37803
40249
|
meta: {
|
|
37804
40250
|
name: "search",
|
|
37805
40251
|
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"
|
|
@@ -37875,7 +40321,7 @@ var searchCommand4 = defineCommand194({
|
|
|
37875
40321
|
firstSeenBefore: args["first-seen-before"],
|
|
37876
40322
|
hookArchetype: args["hook-archetype"]
|
|
37877
40323
|
};
|
|
37878
|
-
const body =
|
|
40324
|
+
const body = buildSearchBody2(searchArgs);
|
|
37879
40325
|
if (!("free_text_query" in body) && !("ref_ad_id" in body) && !("hard_filters" in body)) {
|
|
37880
40326
|
writeJson({
|
|
37881
40327
|
ok: false,
|
|
@@ -37908,13 +40354,13 @@ var searchCommand4 = defineCommand194({
|
|
|
37908
40354
|
winningAdNormalizer
|
|
37909
40355
|
);
|
|
37910
40356
|
} catch (err) {
|
|
37911
|
-
|
|
40357
|
+
reportError2(err);
|
|
37912
40358
|
}
|
|
37913
40359
|
}
|
|
37914
40360
|
});
|
|
37915
40361
|
|
|
37916
40362
|
// src/commands/winning-ads/seeds.ts
|
|
37917
|
-
import { defineCommand as
|
|
40363
|
+
import { defineCommand as defineCommand203 } from "citty";
|
|
37918
40364
|
function leanRow(r) {
|
|
37919
40365
|
return {
|
|
37920
40366
|
key: r.key,
|
|
@@ -37942,7 +40388,7 @@ function makeSeedCommand(opts) {
|
|
|
37942
40388
|
limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
|
|
37943
40389
|
}
|
|
37944
40390
|
});
|
|
37945
|
-
return
|
|
40391
|
+
return defineCommand203({
|
|
37946
40392
|
meta: { name: opts.name, description: opts.description },
|
|
37947
40393
|
args: {
|
|
37948
40394
|
platform: { type: "string", description: "Single platform to segment on", required: false },
|
|
@@ -37969,7 +40415,7 @@ function makeSeedCommand(opts) {
|
|
|
37969
40415
|
}
|
|
37970
40416
|
writeOutput({ ok: true, data: projected }, output);
|
|
37971
40417
|
} catch (err) {
|
|
37972
|
-
|
|
40418
|
+
reportError2(err);
|
|
37973
40419
|
}
|
|
37974
40420
|
}
|
|
37975
40421
|
});
|
|
@@ -37991,7 +40437,7 @@ var formatsCommand = makeSeedCommand({
|
|
|
37991
40437
|
});
|
|
37992
40438
|
|
|
37993
40439
|
// src/commands/winning-ads/unfollow.ts
|
|
37994
|
-
import { defineCommand as
|
|
40440
|
+
import { defineCommand as defineCommand204 } from "citty";
|
|
37995
40441
|
registerSchema({
|
|
37996
40442
|
command: "winning-ads.unfollow",
|
|
37997
40443
|
description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
|
|
@@ -37999,7 +40445,7 @@ registerSchema({
|
|
|
37999
40445
|
advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
|
|
38000
40446
|
}
|
|
38001
40447
|
});
|
|
38002
|
-
var unfollowCommand =
|
|
40448
|
+
var unfollowCommand = defineCommand204({
|
|
38003
40449
|
meta: {
|
|
38004
40450
|
name: "unfollow",
|
|
38005
40451
|
description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
|
|
@@ -38014,13 +40460,13 @@ var unfollowCommand = defineCommand196({
|
|
|
38014
40460
|
});
|
|
38015
40461
|
writeJson({ ok: true, data });
|
|
38016
40462
|
} catch (err) {
|
|
38017
|
-
|
|
40463
|
+
reportError2(err);
|
|
38018
40464
|
}
|
|
38019
40465
|
}
|
|
38020
40466
|
});
|
|
38021
40467
|
|
|
38022
40468
|
// src/commands/winning-ads/winners.ts
|
|
38023
|
-
import { defineCommand as
|
|
40469
|
+
import { defineCommand as defineCommand205 } from "citty";
|
|
38024
40470
|
registerSchema({
|
|
38025
40471
|
command: "winning-ads.winners",
|
|
38026
40472
|
description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
|
|
@@ -38030,7 +40476,7 @@ registerSchema({
|
|
|
38030
40476
|
platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
|
|
38031
40477
|
}
|
|
38032
40478
|
});
|
|
38033
|
-
var winnersCommand =
|
|
40479
|
+
var winnersCommand = defineCommand205({
|
|
38034
40480
|
meta: {
|
|
38035
40481
|
name: "winners",
|
|
38036
40482
|
description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
|
|
@@ -38074,13 +40520,13 @@ var winnersCommand = defineCommand197({
|
|
|
38074
40520
|
winningAdNormalizer
|
|
38075
40521
|
);
|
|
38076
40522
|
} catch (err) {
|
|
38077
|
-
|
|
40523
|
+
reportError2(err);
|
|
38078
40524
|
}
|
|
38079
40525
|
}
|
|
38080
40526
|
});
|
|
38081
40527
|
|
|
38082
40528
|
// src/commands/winning-ads/index.ts
|
|
38083
|
-
var winningAdsCommand =
|
|
40529
|
+
var winningAdsCommand = defineCommand206({
|
|
38084
40530
|
meta: {
|
|
38085
40531
|
name: "winning-ads",
|
|
38086
40532
|
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.
|
|
@@ -38118,7 +40564,7 @@ Examples:
|
|
|
38118
40564
|
Full guide: __tooling__/docs/tools/baker/winning-ads.md`
|
|
38119
40565
|
},
|
|
38120
40566
|
subCommands: {
|
|
38121
|
-
search:
|
|
40567
|
+
search: searchCommand5,
|
|
38122
40568
|
advertisers: advertisersCommand2,
|
|
38123
40569
|
follow: followCommand,
|
|
38124
40570
|
"follow-competitors": followCompetitorsCommand,
|
|
@@ -38152,7 +40598,7 @@ function getCliVersion() {
|
|
|
38152
40598
|
}
|
|
38153
40599
|
|
|
38154
40600
|
// src/cli.ts
|
|
38155
|
-
var main =
|
|
40601
|
+
var main = defineCommand207({
|
|
38156
40602
|
meta: {
|
|
38157
40603
|
name: "baker",
|
|
38158
40604
|
version: getCliVersion(),
|