@koda-sl/baker-cli 0.141.0 → 0.142.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/cli.js +1364 -189
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -73,7 +73,7 @@ import {
|
|
|
73
73
|
} from "./chunk-RK67WL4O.js";
|
|
74
74
|
|
|
75
75
|
// src/cli.ts
|
|
76
|
-
import { defineCommand as
|
|
76
|
+
import { defineCommand as defineCommand177, runMain } from "citty";
|
|
77
77
|
|
|
78
78
|
// src/commands/actions/index.ts
|
|
79
79
|
import { defineCommand as defineCommand18 } from "citty";
|
|
@@ -5485,11 +5485,11 @@ function microsFlag(value, flag) {
|
|
|
5485
5485
|
if (value === void 0 || value === null || value === "") {
|
|
5486
5486
|
return void 0;
|
|
5487
5487
|
}
|
|
5488
|
-
const
|
|
5489
|
-
if (Number.isNaN(
|
|
5488
|
+
const num2 = Number(value);
|
|
5489
|
+
if (Number.isNaN(num2) || num2 <= 0) {
|
|
5490
5490
|
failWriteValidation(`${flag} must be a positive amount (major currency units, e.g. 50 or 50.00)`);
|
|
5491
5491
|
}
|
|
5492
|
-
return toMicros(
|
|
5492
|
+
return toMicros(num2);
|
|
5493
5493
|
}
|
|
5494
5494
|
function listFlag(value) {
|
|
5495
5495
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -5517,11 +5517,11 @@ function rawTextEntries(value) {
|
|
|
5517
5517
|
const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
5518
5518
|
return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
5519
5519
|
}
|
|
5520
|
-
function rawFileEntries(
|
|
5521
|
-
if (typeof
|
|
5520
|
+
function rawFileEntries(path27) {
|
|
5521
|
+
if (typeof path27 !== "string" || path27.length === 0) {
|
|
5522
5522
|
return [];
|
|
5523
5523
|
}
|
|
5524
|
-
return readFileSync2(
|
|
5524
|
+
return readFileSync2(path27, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
5525
5525
|
}
|
|
5526
5526
|
function keywordEntries(args) {
|
|
5527
5527
|
const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
|
|
@@ -5544,19 +5544,19 @@ function keywordEntries(args) {
|
|
|
5544
5544
|
}
|
|
5545
5545
|
return entries;
|
|
5546
5546
|
}
|
|
5547
|
-
function loadJsonFileArg(
|
|
5548
|
-
if (typeof
|
|
5547
|
+
function loadJsonFileArg(path27) {
|
|
5548
|
+
if (typeof path27 !== "string" || path27.length === 0) {
|
|
5549
5549
|
return {};
|
|
5550
5550
|
}
|
|
5551
5551
|
try {
|
|
5552
|
-
const parsed = JSON.parse(readFileSync2(
|
|
5552
|
+
const parsed = JSON.parse(readFileSync2(path27, "utf8"));
|
|
5553
5553
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
5554
|
-
failWriteValidation(`${
|
|
5554
|
+
failWriteValidation(`${path27} must contain a JSON object`);
|
|
5555
5555
|
}
|
|
5556
5556
|
return parsed;
|
|
5557
5557
|
} catch (err) {
|
|
5558
5558
|
if (err instanceof SyntaxError) {
|
|
5559
|
-
failWriteValidation(`${
|
|
5559
|
+
failWriteValidation(`${path27} is not valid JSON: ${err.message}`);
|
|
5560
5560
|
}
|
|
5561
5561
|
throw err;
|
|
5562
5562
|
}
|
|
@@ -5667,10 +5667,10 @@ async function stageUpdate(kind, customerId, target, payload) {
|
|
|
5667
5667
|
async function stageTarget(kind, customerId, target) {
|
|
5668
5668
|
await stageGoogleOp({ kind, customerId, target });
|
|
5669
5669
|
}
|
|
5670
|
-
async function draftAction(
|
|
5670
|
+
async function draftAction(path27, body) {
|
|
5671
5671
|
try {
|
|
5672
5672
|
const chatId = requireChatId();
|
|
5673
|
-
const response = await apiPost(
|
|
5673
|
+
const response = await apiPost(path27, { chatId, ...body });
|
|
5674
5674
|
writeJsonEnvelope(response);
|
|
5675
5675
|
} catch (err) {
|
|
5676
5676
|
handleGoogleError(err);
|
|
@@ -9234,19 +9234,19 @@ function failWriteValidation2(message) {
|
|
|
9234
9234
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
9235
9235
|
process.exit(1);
|
|
9236
9236
|
}
|
|
9237
|
-
function loadJsonFileArg2(
|
|
9238
|
-
if (typeof
|
|
9237
|
+
function loadJsonFileArg2(path27) {
|
|
9238
|
+
if (typeof path27 !== "string" || path27.length === 0) {
|
|
9239
9239
|
return {};
|
|
9240
9240
|
}
|
|
9241
9241
|
try {
|
|
9242
|
-
const parsed = JSON.parse(readFileSync6(
|
|
9242
|
+
const parsed = JSON.parse(readFileSync6(path27, "utf8"));
|
|
9243
9243
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9244
|
-
failWriteValidation2(`${
|
|
9244
|
+
failWriteValidation2(`${path27} must contain a JSON object`);
|
|
9245
9245
|
}
|
|
9246
9246
|
return parsed;
|
|
9247
9247
|
} catch (err) {
|
|
9248
9248
|
if (err instanceof SyntaxError) {
|
|
9249
|
-
failWriteValidation2(`${
|
|
9249
|
+
failWriteValidation2(`${path27} is not valid JSON: ${err.message}`);
|
|
9250
9250
|
}
|
|
9251
9251
|
throw err;
|
|
9252
9252
|
}
|
|
@@ -9331,15 +9331,15 @@ function parseLocaleFlag(value) {
|
|
|
9331
9331
|
}
|
|
9332
9332
|
return { language: match[1], country: match[2].toUpperCase() };
|
|
9333
9333
|
}
|
|
9334
|
-
function loadTargetingFileArg(
|
|
9335
|
-
if (typeof
|
|
9334
|
+
function loadTargetingFileArg(path27) {
|
|
9335
|
+
if (typeof path27 !== "string" || path27.length === 0) {
|
|
9336
9336
|
return void 0;
|
|
9337
9337
|
}
|
|
9338
|
-
const parsed = loadJsonFileArg2(
|
|
9338
|
+
const parsed = loadJsonFileArg2(path27);
|
|
9339
9339
|
const criteria = parsed.targetingCriteria ?? parsed;
|
|
9340
9340
|
if (!criteria.include) {
|
|
9341
9341
|
failWriteValidation2(
|
|
9342
|
-
`${
|
|
9342
|
+
`${path27} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
|
|
9343
9343
|
);
|
|
9344
9344
|
}
|
|
9345
9345
|
return criteria;
|
|
@@ -9374,14 +9374,14 @@ function parseCsvLine(line) {
|
|
|
9374
9374
|
cells.push(current);
|
|
9375
9375
|
return cells.map((cell) => cell.trim());
|
|
9376
9376
|
}
|
|
9377
|
-
function parseListFileArg(
|
|
9378
|
-
if (typeof
|
|
9377
|
+
function parseListFileArg(path27, maxRows) {
|
|
9378
|
+
if (typeof path27 !== "string" || path27.length === 0) {
|
|
9379
9379
|
return void 0;
|
|
9380
9380
|
}
|
|
9381
|
-
const raw = readFileSync6(
|
|
9381
|
+
const raw = readFileSync6(path27, "utf8");
|
|
9382
9382
|
const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
9383
9383
|
if (lines.length < 2) {
|
|
9384
|
-
failWriteValidation2(`${
|
|
9384
|
+
failWriteValidation2(`${path27} needs a header row and at least one data row`);
|
|
9385
9385
|
}
|
|
9386
9386
|
const columns = parseCsvLine(lines[0]).map((column) => column.trim());
|
|
9387
9387
|
const rows = [];
|
|
@@ -9400,7 +9400,7 @@ function parseListFileArg(path23, maxRows) {
|
|
|
9400
9400
|
}
|
|
9401
9401
|
}
|
|
9402
9402
|
if (rows.length > maxRows) {
|
|
9403
|
-
failWriteValidation2(`${
|
|
9403
|
+
failWriteValidation2(`${path27} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
|
|
9404
9404
|
}
|
|
9405
9405
|
return { columns, rows };
|
|
9406
9406
|
}
|
|
@@ -11500,11 +11500,11 @@ var updateStatusSchema = z14.enum(UPDATE_STATUSES);
|
|
|
11500
11500
|
function currencyMinimums2(currencyCode) {
|
|
11501
11501
|
return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
|
|
11502
11502
|
}
|
|
11503
|
-
function validateDailyBudgetFloor(money, ctx,
|
|
11503
|
+
function validateDailyBudgetFloor(money, ctx, path27) {
|
|
11504
11504
|
if (money?.currencyCode) {
|
|
11505
11505
|
const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
|
|
11506
11506
|
if (Number(money.amount) < min) {
|
|
11507
|
-
ctx.addIssue({ code: "custom", path:
|
|
11507
|
+
ctx.addIssue({ code: "custom", path: path27, message: `below the ${min} ${money.currencyCode} daily minimum` });
|
|
11508
11508
|
}
|
|
11509
11509
|
}
|
|
11510
11510
|
}
|
|
@@ -12136,19 +12136,19 @@ function failWriteValidation3(message) {
|
|
|
12136
12136
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
12137
12137
|
process.exit(1);
|
|
12138
12138
|
}
|
|
12139
|
-
function loadJsonFileArg3(
|
|
12140
|
-
if (typeof
|
|
12139
|
+
function loadJsonFileArg3(path27) {
|
|
12140
|
+
if (typeof path27 !== "string" || path27.length === 0) {
|
|
12141
12141
|
return {};
|
|
12142
12142
|
}
|
|
12143
12143
|
try {
|
|
12144
|
-
const parsed = JSON.parse(readFileSync8(
|
|
12144
|
+
const parsed = JSON.parse(readFileSync8(path27, "utf8"));
|
|
12145
12145
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
12146
|
-
failWriteValidation3(`${
|
|
12146
|
+
failWriteValidation3(`${path27} must contain a JSON object`);
|
|
12147
12147
|
}
|
|
12148
12148
|
return parsed;
|
|
12149
12149
|
} catch (err) {
|
|
12150
12150
|
if (err instanceof SyntaxError) {
|
|
12151
|
-
failWriteValidation3(`${
|
|
12151
|
+
failWriteValidation3(`${path27} is not valid JSON: ${err.message}`);
|
|
12152
12152
|
}
|
|
12153
12153
|
throw err;
|
|
12154
12154
|
}
|
|
@@ -16323,13 +16323,13 @@ function referenceRank(type) {
|
|
|
16323
16323
|
function orderSlotsByIdentity(slots) {
|
|
16324
16324
|
return [...slots].sort((a, b) => referenceRank(a.type) - referenceRank(b.type));
|
|
16325
16325
|
}
|
|
16326
|
-
function buildFramePrompt(edge, sceneIndex, framePrompt,
|
|
16326
|
+
function buildFramePrompt(edge, sceneIndex, framePrompt, present2, hasAnchor, mode, sceneStyle, imageModel) {
|
|
16327
16327
|
const profile = imageModel ? imageProfileFor(imageModel) : void 0;
|
|
16328
16328
|
const exclusionsLast = profile?.constraintPlacement === "last";
|
|
16329
16329
|
const photoreal = profile ? profile.photorealCue : true;
|
|
16330
16330
|
const EDGE = edge.toUpperCase();
|
|
16331
16331
|
const legend = [
|
|
16332
|
-
...
|
|
16332
|
+
...present2.map((s) => `- ${s.label} \u2014 ${roleForSlot(s)}`),
|
|
16333
16333
|
...hasAnchor ? [
|
|
16334
16334
|
"- ORIGINAL_FRAME \u2014 use ONLY for composition: framing, camera angle, shot size, subject placement, pose, and proportions. IGNORE its text, logo, brand name, colors, AND the identity of every person/animal/object in it \u2014 those come from the labeled reference images above, never from this frame. It is a DIFFERENT brand's footage with DIFFERENT actors, here ONLY to anchor where things sit and how the shot is framed (e.g. a profile/side angle stays a profile/side angle), never who they are or what palette to use."
|
|
16335
16335
|
] : []
|
|
@@ -16387,7 +16387,7 @@ function buildFramePrompt(edge, sceneIndex, framePrompt, present, hasAnchor, mod
|
|
|
16387
16387
|
// RECAST is the whole point of a transform: the dropped el_* images define who/
|
|
16388
16388
|
// what is on screen, NOT the source footage and NOT the prose. Without this, the
|
|
16389
16389
|
// model reproduces the original ad's people (a proven failure mode).
|
|
16390
|
-
...
|
|
16390
|
+
...present2.length > 0 ? [
|
|
16391
16391
|
"IDENTITY & AESTHETIC \u2014 RECAST (this is a transform, not a copy):",
|
|
16392
16392
|
"Identity comes from the reference image, never from the source footage or this prose. Render every",
|
|
16393
16393
|
"person, animal, product, and set to MATCH its labeled reference image above \u2014 that image is the ONLY",
|
|
@@ -16442,17 +16442,17 @@ function ingestFrameRef(url, edge, ctx, nodes) {
|
|
|
16442
16442
|
ctx.ingestCache?.set(url, ref);
|
|
16443
16443
|
return ref;
|
|
16444
16444
|
}
|
|
16445
|
-
function buildFrameRef(edge, url, framePrompt,
|
|
16445
|
+
function buildFrameRef(edge, url, framePrompt, present2, ctx, nodes) {
|
|
16446
16446
|
const tag = ctx.tag ?? "";
|
|
16447
16447
|
if (ctx.reuse && url) return ingestFrameRef(url, edge, ctx, nodes);
|
|
16448
|
-
const castSlots =
|
|
16448
|
+
const castSlots = present2.filter((s) => {
|
|
16449
16449
|
const t = s.type.toLowerCase();
|
|
16450
16450
|
return t === "person" || t === "animal";
|
|
16451
16451
|
});
|
|
16452
16452
|
const useOriginalAnchor = Boolean(url) && castSlots.length === 0;
|
|
16453
16453
|
const hasOriginal = useOriginalAnchor;
|
|
16454
16454
|
const originalRef = useOriginalAnchor && url ? ingestFrameRef(url, edge, ctx, nodes) : void 0;
|
|
16455
|
-
const presentOrdered = orderSlotsByIdentity(
|
|
16455
|
+
const presentOrdered = orderSlotsByIdentity(present2);
|
|
16456
16456
|
const reference = [...presentOrdered.map((s) => s.ref), ...originalRef ? [originalRef] : []];
|
|
16457
16457
|
const imageProfile = imageProfileFor(ctx.imageModel);
|
|
16458
16458
|
const genParams = {
|
|
@@ -16588,13 +16588,13 @@ function spokenTextParts(scene, nativeLine, loc, profile) {
|
|
|
16588
16588
|
if (transcript) parts.push(`Transcript: ${loc(transcript)}`);
|
|
16589
16589
|
return parts;
|
|
16590
16590
|
}
|
|
16591
|
-
function buildSeedancePrompt(scene, sceneIndex,
|
|
16591
|
+
function buildSeedancePrompt(scene, sceneIndex, present2, mode, audio, nativeLine, nativeLang, uiRouted, videoModel) {
|
|
16592
16592
|
const profile = (videoModel ? clipProfileFor(videoModel) : void 0) ?? SEEDANCE_PROFILE;
|
|
16593
16593
|
const loc = (s) => nativeLine ? localizeNumeralsForNative(s, nativeLang) : s;
|
|
16594
16594
|
const routed = uiRouted ?? (compositeRegionsOf(scene) !== null && isUiOnlyComposite(compositeRegionsOf(scene) ?? []));
|
|
16595
16595
|
const plate = routed ? compositePlateRegion(scene) ?? {} : null;
|
|
16596
16596
|
const parts = visualBriefParts(scene, sceneIndex, plate);
|
|
16597
|
-
const refs = plate ?
|
|
16597
|
+
const refs = plate ? present2.filter((s) => !UI_SURFACE_RE.test(s.description ?? "")) : present2;
|
|
16598
16598
|
if (refs.length > 0) {
|
|
16599
16599
|
parts.push(
|
|
16600
16600
|
`Keep these consistent with their references: ${refs.map((s) => `${s.label} (${s.description ?? s.type})`).join("; ")}`
|
|
@@ -16633,15 +16633,15 @@ function sceneOutTransition(scene, isLast) {
|
|
|
16633
16633
|
const xfade = type ? XFADE_BY_TYPE[type] : void 0;
|
|
16634
16634
|
return xfade ? { xfade, dur: TRANSITION_DEFAULT_S } : null;
|
|
16635
16635
|
}
|
|
16636
|
-
function sceneShootMode(scene,
|
|
16636
|
+
function sceneShootMode(scene, present2, nativeTurn, cameraOn, casts) {
|
|
16637
16637
|
const talking = Boolean(nativeTurn) || cameraOn && (scene.dialogue ?? []).some(
|
|
16638
16638
|
(d) => d.line?.trim() && isOnCameraSpeaker(d.speaker ?? "voiceover", casts, cameraOn)
|
|
16639
16639
|
);
|
|
16640
16640
|
return deriveShootMode({
|
|
16641
16641
|
explicit: scene.shoot_mode,
|
|
16642
16642
|
talking,
|
|
16643
|
-
hasPerson:
|
|
16644
|
-
hasProduct:
|
|
16643
|
+
hasPerson: present2.some((s) => s.type.toLowerCase() === "person" || s.type.toLowerCase() === "animal"),
|
|
16644
|
+
hasProduct: present2.some((s) => s.type.toLowerCase() === "product")
|
|
16645
16645
|
});
|
|
16646
16646
|
}
|
|
16647
16647
|
function emitSceneNativeAudio(i, nativeTurn, ambientBroll, lengths, clock, nodes, voTracks, nativeSegments, clipRef) {
|
|
@@ -16685,12 +16685,12 @@ function sameSpeakerTrackEntries(segments) {
|
|
|
16685
16685
|
return sorted.map((s, k) => {
|
|
16686
16686
|
const next = sorted[k + 1];
|
|
16687
16687
|
const naturalLen = s.end_s - s.start_s;
|
|
16688
|
-
const
|
|
16689
|
-
const capped =
|
|
16688
|
+
const cap3 = next ? Math.min(s.end_s, next.start_s) - s.start_s : naturalLen;
|
|
16689
|
+
const capped = cap3 < naturalLen - 1e-3;
|
|
16690
16690
|
return {
|
|
16691
16691
|
slot: `seg${k}`,
|
|
16692
16692
|
start_s: s.start_s,
|
|
16693
|
-
...capped ? { duration_s: Math.round(Math.max(0.1,
|
|
16693
|
+
...capped ? { duration_s: Math.round(Math.max(0.1, cap3) * 1e3) / 1e3 } : {}
|
|
16694
16694
|
};
|
|
16695
16695
|
});
|
|
16696
16696
|
}
|
|
@@ -16729,7 +16729,7 @@ function buildPerSpeakerVoiceConversion(segments, totalMs, nodes) {
|
|
|
16729
16729
|
}
|
|
16730
16730
|
return tracks;
|
|
16731
16731
|
}
|
|
16732
|
-
function emitSceneClip(i, scene,
|
|
16732
|
+
function emitSceneClip(i, scene, present2, mode, nativeTurn, ambientBroll, frames, lengths, out, opts, nodes, tag = "", defer = false) {
|
|
16733
16733
|
const profile = clipProfileFor(opts.videoModel) ?? SEEDANCE_PROFILE;
|
|
16734
16734
|
const clipParams = {
|
|
16735
16735
|
model: opts.videoModel,
|
|
@@ -16739,7 +16739,7 @@ function emitSceneClip(i, scene, present, mode, nativeTurn, ambientBroll, frames
|
|
|
16739
16739
|
prompt: buildSeedancePrompt(
|
|
16740
16740
|
scene,
|
|
16741
16741
|
i,
|
|
16742
|
-
|
|
16742
|
+
present2,
|
|
16743
16743
|
mode,
|
|
16744
16744
|
Boolean(nativeTurn) || ambientBroll,
|
|
16745
16745
|
nativeTurn?.text,
|
|
@@ -16838,9 +16838,9 @@ function uiRoutedRuns(input) {
|
|
|
16838
16838
|
flushRun();
|
|
16839
16839
|
return runs;
|
|
16840
16840
|
}
|
|
16841
|
-
function sceneIsFullScreenUi(scene,
|
|
16841
|
+
function sceneIsFullScreenUi(scene, present2) {
|
|
16842
16842
|
if (scene.narrative_role?.trim() === "cta") return false;
|
|
16843
|
-
const hasCast =
|
|
16843
|
+
const hasCast = present2.some((s) => {
|
|
16844
16844
|
const t = s.type.toLowerCase();
|
|
16845
16845
|
return t === "person" || t === "animal";
|
|
16846
16846
|
});
|
|
@@ -16875,14 +16875,14 @@ function presenterIndexOf(regions, hasNative) {
|
|
|
16875
16875
|
if (flagged >= 0) return flagged;
|
|
16876
16876
|
return hasNative ? 0 : -1;
|
|
16877
16877
|
}
|
|
16878
|
-
function slotsForRegion(
|
|
16879
|
-
return
|
|
16878
|
+
function slotsForRegion(present2, isPresenter) {
|
|
16879
|
+
return present2.filter((s) => {
|
|
16880
16880
|
const t = s.type.toLowerCase();
|
|
16881
16881
|
const person = t === "person" || t === "animal";
|
|
16882
16882
|
return isPresenter ? person : !person;
|
|
16883
16883
|
});
|
|
16884
16884
|
}
|
|
16885
|
-
function buildCompositeScene(layout, regions, comp, scene, i,
|
|
16885
|
+
function buildCompositeScene(layout, regions, comp, scene, i, present2, mode, nativeTurn, lengths, out, opts, nodes) {
|
|
16886
16886
|
const dims = canvasDims(opts.outAr);
|
|
16887
16887
|
const presIdx = presenterIndexOf(regions, Boolean(nativeTurn));
|
|
16888
16888
|
const regionRefs = [];
|
|
@@ -16890,7 +16890,7 @@ function buildCompositeScene(layout, regions, comp, scene, i, present, mode, nat
|
|
|
16890
16890
|
regions.forEach((region, r) => {
|
|
16891
16891
|
const isPresenter = r === presIdx;
|
|
16892
16892
|
const tag = `_r${r}`;
|
|
16893
|
-
const regionSlots = slotsForRegion(
|
|
16893
|
+
const regionSlots = slotsForRegion(present2, isPresenter);
|
|
16894
16894
|
const ctx = {
|
|
16895
16895
|
sceneIndex: i,
|
|
16896
16896
|
genAr: opts.genAr,
|
|
@@ -16990,14 +16990,14 @@ function sceneTiming(scene, isLast, nativeTurn, durationSet = SEEDANCE_DURATIONS
|
|
|
16990
16990
|
const genDur = ceilToDurations(durationSet, Math.max(trimTarget, speech));
|
|
16991
16991
|
return { dur, out, trimTarget, genDur, speech };
|
|
16992
16992
|
}
|
|
16993
|
-
function emitCompositeScene(composite, scene, i,
|
|
16993
|
+
function emitCompositeScene(composite, scene, i, present2, mode, nativeTurn, lengths, out, opts, clock, nodes, voTracks, nativeSegments, clips) {
|
|
16994
16994
|
const built = buildCompositeScene(
|
|
16995
16995
|
composite.layout,
|
|
16996
16996
|
composite.regions,
|
|
16997
16997
|
composite.comp,
|
|
16998
16998
|
scene,
|
|
16999
16999
|
i,
|
|
17000
|
-
|
|
17000
|
+
present2,
|
|
17001
17001
|
mode,
|
|
17002
17002
|
nativeTurn,
|
|
17003
17003
|
{ dur: lengths.dur, trimTarget: lengths.trimTarget, genDur: lengths.genDur },
|
|
@@ -17102,9 +17102,9 @@ function emitGraphicScene(i, scene, lengths, out, surfaceIngests, nodes, clips)
|
|
|
17102
17102
|
clips.push({ ref: `$ref:${refId}.asset`, scene_s: lengths.dur, out, still: { fit: "pad" } });
|
|
17103
17103
|
}
|
|
17104
17104
|
var BRAND_CARD_RE = /\b(?:solid|plain|flat|brand|logo|wordmark|end[- ]?card|cta card|title card|colou?r background|background colou?r)\b/i;
|
|
17105
|
-
function sceneIsBrandCard(scene,
|
|
17105
|
+
function sceneIsBrandCard(scene, present2, isCta) {
|
|
17106
17106
|
if (!isCta) return false;
|
|
17107
|
-
const hasCast =
|
|
17107
|
+
const hasCast = present2.some((s) => {
|
|
17108
17108
|
const t = s.type.toLowerCase();
|
|
17109
17109
|
return t === "person" || t === "animal";
|
|
17110
17110
|
});
|
|
@@ -17440,7 +17440,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
17440
17440
|
const anchor = phrase.shownScenes[0];
|
|
17441
17441
|
const anchorScene = env.blueprint.scenes[anchor];
|
|
17442
17442
|
if (!anchorScene) return;
|
|
17443
|
-
const
|
|
17443
|
+
const present2 = slotsForScene(env.slots, anchor);
|
|
17444
17444
|
const nativeTurn = {
|
|
17445
17445
|
sceneIndex: anchor,
|
|
17446
17446
|
speaker: phrase.speaker,
|
|
@@ -17450,7 +17450,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
17450
17450
|
voiceNode,
|
|
17451
17451
|
native: true
|
|
17452
17452
|
};
|
|
17453
|
-
const mode = sceneShootMode(anchorScene,
|
|
17453
|
+
const mode = sceneShootMode(anchorScene, present2, nativeTurn, env.cameraOn, env.casts);
|
|
17454
17454
|
const ctx = {
|
|
17455
17455
|
sceneIndex: anchor,
|
|
17456
17456
|
genAr: env.genAr,
|
|
@@ -17493,7 +17493,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
17493
17493
|
prompt: buildSeedancePrompt(
|
|
17494
17494
|
anchorScene,
|
|
17495
17495
|
anchor,
|
|
17496
|
-
|
|
17496
|
+
present2,
|
|
17497
17497
|
mode,
|
|
17498
17498
|
true,
|
|
17499
17499
|
phrase.text,
|
|
@@ -17610,7 +17610,7 @@ function emitPhraseTts(phrase, voiceNode, idx, used, clock, nodes, out, language
|
|
|
17610
17610
|
});
|
|
17611
17611
|
}
|
|
17612
17612
|
function emitCompositeInTimeline(composite, scene, i, isLast, env, canonical, ensureVoiceNode, usedVoIds, nodes, out) {
|
|
17613
|
-
const
|
|
17613
|
+
const present2 = slotsForScene(env.slots, i);
|
|
17614
17614
|
const onCam = (scene.dialogue ?? []).filter(
|
|
17615
17615
|
(l) => Boolean(l.line?.trim()) && isOnCameraSpeaker(l.speaker ?? "voiceover", env.casts, env.cameraOn)
|
|
17616
17616
|
);
|
|
@@ -17631,13 +17631,13 @@ function emitCompositeInTimeline(composite, scene, i, isLast, env, canonical, en
|
|
|
17631
17631
|
speech_words: wordCount(text)
|
|
17632
17632
|
});
|
|
17633
17633
|
}
|
|
17634
|
-
const mode = sceneShootMode(scene,
|
|
17634
|
+
const mode = sceneShootMode(scene, present2, nativeTurn, env.cameraOn, env.casts);
|
|
17635
17635
|
const lengths = sceneTiming(scene, isLast, nativeTurn, clipProfileFor(env.opts.videoModel)?.durationSet);
|
|
17636
17636
|
emitCompositeScene(
|
|
17637
17637
|
composite,
|
|
17638
17638
|
scene,
|
|
17639
17639
|
i,
|
|
17640
|
-
|
|
17640
|
+
present2,
|
|
17641
17641
|
mode,
|
|
17642
17642
|
nativeTurn,
|
|
17643
17643
|
lengths,
|
|
@@ -17732,8 +17732,8 @@ function brollKeyframes(scene, i, env, ctx, lengths, prevEndFrame, nodes) {
|
|
|
17732
17732
|
return { first, last, sharesPrevFrame };
|
|
17733
17733
|
}
|
|
17734
17734
|
function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
17735
|
-
const
|
|
17736
|
-
const mode = sceneShootMode(scene,
|
|
17735
|
+
const present2 = slotsForScene(env.slots, i);
|
|
17736
|
+
const mode = sceneShootMode(scene, present2, void 0, env.cameraOn, env.casts);
|
|
17737
17737
|
const ambientBroll = Boolean(env.opts.ambient) && mode !== "ugc_selfie";
|
|
17738
17738
|
const lengths = sceneTiming(scene, isLast, void 0, clipProfileFor(env.opts.videoModel)?.durationSet);
|
|
17739
17739
|
const ctx = {
|
|
@@ -17750,13 +17750,13 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
|
17750
17750
|
out.routes.set(i, "graphic_plate");
|
|
17751
17751
|
return void 0;
|
|
17752
17752
|
}
|
|
17753
|
-
if (!env.reuse && sceneIsFullScreenUi(scene,
|
|
17753
|
+
if (!env.reuse && sceneIsFullScreenUi(scene, present2)) {
|
|
17754
17754
|
emitScreenScene(i, scene, lengths, lengths.out, env.surfaceIngests, nodes, out.clips);
|
|
17755
17755
|
out.routes.set(i, "screen_still");
|
|
17756
17756
|
return void 0;
|
|
17757
17757
|
}
|
|
17758
17758
|
const isCta = scene.narrative_role?.trim() === "cta" || isLast;
|
|
17759
|
-
if (!env.reuse && sceneIsBrandCard(scene,
|
|
17759
|
+
if (!env.reuse && sceneIsBrandCard(scene, present2, isCta)) {
|
|
17760
17760
|
emitBrandCardScene(i, lengths, lengths.out, env.outAr, brandPlateColor(env.blueprint), nodes, out.clips);
|
|
17761
17761
|
out.routes.set(i, "brand_card");
|
|
17762
17762
|
return void 0;
|
|
@@ -17771,7 +17771,7 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
|
17771
17771
|
const clip = emitSceneClip(
|
|
17772
17772
|
i,
|
|
17773
17773
|
scene,
|
|
17774
|
-
|
|
17774
|
+
present2,
|
|
17775
17775
|
mode,
|
|
17776
17776
|
void 0,
|
|
17777
17777
|
ambientBroll,
|
|
@@ -18533,7 +18533,7 @@ function sceneSpokenText(scene) {
|
|
|
18533
18533
|
return (scene.dialogue ?? []).map((d) => d.line?.trim()).filter((l) => Boolean(l)).join(" ") || null;
|
|
18534
18534
|
}
|
|
18535
18535
|
function buildMotionBoard(blueprint) {
|
|
18536
|
-
const
|
|
18536
|
+
const round5 = (n) => Math.round(n * 100) / 100;
|
|
18537
18537
|
let cursor = 0;
|
|
18538
18538
|
return blueprint.scenes.map((scene, i) => {
|
|
18539
18539
|
const start_s = scene.start_s ?? cursor;
|
|
@@ -18545,15 +18545,15 @@ function buildMotionBoard(blueprint) {
|
|
|
18545
18545
|
const graphics = [
|
|
18546
18546
|
...(overlays.success ? overlays.data : []).filter((ov) => ov.text?.trim()).map((ov) => ({
|
|
18547
18547
|
kind: "text",
|
|
18548
|
-
at_s:
|
|
18549
|
-
dur_s:
|
|
18548
|
+
at_s: round5(ov.appears_at_s ?? start_s),
|
|
18549
|
+
dur_s: round5(ov.duration_s ?? 2.5),
|
|
18550
18550
|
position: ov.position ?? "bottom_center",
|
|
18551
18551
|
text: ov.text?.trim()
|
|
18552
18552
|
})),
|
|
18553
18553
|
...(floats.success ? floats.data : []).map((fe) => ({
|
|
18554
18554
|
kind: "graphic",
|
|
18555
|
-
at_s:
|
|
18556
|
-
dur_s:
|
|
18555
|
+
at_s: round5(fe.appears_at_s ?? start_s),
|
|
18556
|
+
dur_s: round5(fe.duration_s ?? 2.5),
|
|
18557
18557
|
position: fe.position ?? "bottom_center",
|
|
18558
18558
|
label: fe.brand_name || fe.what_it_represents || fe.description || fe.kind || "element"
|
|
18559
18559
|
}))
|
|
@@ -18561,7 +18561,7 @@ function buildMotionBoard(blueprint) {
|
|
|
18561
18561
|
return {
|
|
18562
18562
|
scene: i,
|
|
18563
18563
|
role: resolveSceneRole(scene, i, blueprint.scenes.length),
|
|
18564
|
-
window_s: [
|
|
18564
|
+
window_s: [round5(start_s), round5(end_s)],
|
|
18565
18565
|
// A continuation b-roll scene shares the previous scene's end frame as its start
|
|
18566
18566
|
// (no own `s<i>_start` node), so point the storyboard at that shared keyframe.
|
|
18567
18567
|
storyboard_frames: [scene.continues_previous && i > 0 ? `s${i - 1}_end` : `s${i}_start`],
|
|
@@ -19226,10 +19226,10 @@ function runDirsToPrune(entries, keep, currentRunId) {
|
|
|
19226
19226
|
return runs.slice(0, Math.max(0, runs.length - keep));
|
|
19227
19227
|
}
|
|
19228
19228
|
async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
|
|
19229
|
-
const { readdir:
|
|
19229
|
+
const { readdir: readdir9 } = await import("fs/promises");
|
|
19230
19230
|
let entries;
|
|
19231
19231
|
try {
|
|
19232
|
-
entries = await
|
|
19232
|
+
entries = await readdir9(outputsDir);
|
|
19233
19233
|
} catch {
|
|
19234
19234
|
return;
|
|
19235
19235
|
}
|
|
@@ -21001,10 +21001,10 @@ function resolveShippedCanvasDir(name, startDir, exists = existsSync3, maxDepth
|
|
|
21001
21001
|
import { appendFile, readFile as readFile13 } from "fs/promises";
|
|
21002
21002
|
import path19 from "path";
|
|
21003
21003
|
function missingGitignoreEntries(existing, entries) {
|
|
21004
|
-
const
|
|
21004
|
+
const present2 = new Set(
|
|
21005
21005
|
existing.split("\n").map((l) => l.trim().replace(/\/+$/, "")).filter((l) => l.length > 0 && !l.startsWith("#"))
|
|
21006
21006
|
);
|
|
21007
|
-
return entries.filter((e) => !
|
|
21007
|
+
return entries.filter((e) => !present2.has(e.trim().replace(/\/+$/, "")));
|
|
21008
21008
|
}
|
|
21009
21009
|
async function ensureGitignore(dir, entries) {
|
|
21010
21010
|
const file = path19.join(dir, ".gitignore");
|
|
@@ -22279,12 +22279,12 @@ function listFlowSlugs() {
|
|
|
22279
22279
|
return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_") && entry.name !== ".gitkeep").map((entry) => entry.name).sort();
|
|
22280
22280
|
}
|
|
22281
22281
|
function readFlowTree(slug) {
|
|
22282
|
-
const
|
|
22283
|
-
if (!existsSync4(
|
|
22282
|
+
const path27 = join3(flowsDir(), slug, "_data.json");
|
|
22283
|
+
if (!existsSync4(path27)) {
|
|
22284
22284
|
failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
|
|
22285
22285
|
}
|
|
22286
22286
|
try {
|
|
22287
|
-
return JSON.parse(readFileSync9(
|
|
22287
|
+
return JSON.parse(readFileSync9(path27, "utf-8"));
|
|
22288
22288
|
} catch (error) {
|
|
22289
22289
|
failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
|
|
22290
22290
|
}
|
|
@@ -23444,9 +23444,9 @@ async function readImageBuffer(pathOrUrl) {
|
|
|
23444
23444
|
}
|
|
23445
23445
|
return readFile18(pathOrUrl);
|
|
23446
23446
|
}
|
|
23447
|
-
async function isDirectory(
|
|
23447
|
+
async function isDirectory(path27) {
|
|
23448
23448
|
try {
|
|
23449
|
-
const s = await stat4(
|
|
23449
|
+
const s = await stat4(path27);
|
|
23450
23450
|
return s.isDirectory();
|
|
23451
23451
|
} catch {
|
|
23452
23452
|
return false;
|
|
@@ -25146,7 +25146,7 @@ function emitError3(err) {
|
|
|
25146
25146
|
process.exit(1);
|
|
25147
25147
|
}
|
|
25148
25148
|
function coerceRawArgs(args) {
|
|
25149
|
-
const
|
|
25149
|
+
const num2 = (v) => {
|
|
25150
25150
|
if (typeof v === "number") return v;
|
|
25151
25151
|
if (typeof v === "string" && v.length > 0) {
|
|
25152
25152
|
const n = Number(v);
|
|
@@ -25161,8 +25161,8 @@ function coerceRawArgs(args) {
|
|
|
25161
25161
|
color: str(args.color),
|
|
25162
25162
|
"remove-bg": bool(args["remove-bg"]),
|
|
25163
25163
|
"shrink-to-content": bool(args["shrink-to-content"]),
|
|
25164
|
-
height:
|
|
25165
|
-
width:
|
|
25164
|
+
height: num2(args.height),
|
|
25165
|
+
width: num2(args.width),
|
|
25166
25166
|
size: str(args.size),
|
|
25167
25167
|
fit: str(args.fit),
|
|
25168
25168
|
output: str(args.output),
|
|
@@ -26010,8 +26010,1182 @@ Full guide: __tooling__/docs/tools/baker/images.md`
|
|
|
26010
26010
|
}
|
|
26011
26011
|
});
|
|
26012
26012
|
|
|
26013
|
-
// src/commands/
|
|
26013
|
+
// src/commands/landing/index.ts
|
|
26014
|
+
import { defineCommand as defineCommand134 } from "citty";
|
|
26015
|
+
|
|
26016
|
+
// src/commands/landing/critique.ts
|
|
26017
|
+
import { readdir as readdir8, stat as stat6 } from "fs/promises";
|
|
26018
|
+
import path26 from "path";
|
|
26014
26019
|
import { defineCommand as defineCommand133 } from "citty";
|
|
26020
|
+
|
|
26021
|
+
// src/engine/landing/lib/brand-tokens.ts
|
|
26022
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
26023
|
+
import path23 from "path";
|
|
26024
|
+
|
|
26025
|
+
// src/engine/landing/lib/color.ts
|
|
26026
|
+
var NEUTRAL_COLOR_KEYWORDS = /* @__PURE__ */ new Set([
|
|
26027
|
+
"transparent",
|
|
26028
|
+
"currentcolor",
|
|
26029
|
+
"black",
|
|
26030
|
+
"white",
|
|
26031
|
+
"gray",
|
|
26032
|
+
"grey",
|
|
26033
|
+
"silver",
|
|
26034
|
+
"dimgray",
|
|
26035
|
+
"dimgrey",
|
|
26036
|
+
"darkgray",
|
|
26037
|
+
"darkgrey",
|
|
26038
|
+
"lightgray",
|
|
26039
|
+
"lightgrey",
|
|
26040
|
+
"gainsboro",
|
|
26041
|
+
"whitesmoke"
|
|
26042
|
+
]);
|
|
26043
|
+
function hexChannels(color) {
|
|
26044
|
+
const long = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})(?:[0-9a-f]{2})?$/i);
|
|
26045
|
+
if (long)
|
|
26046
|
+
return [
|
|
26047
|
+
Number.parseInt(long[1] ?? "0", 16),
|
|
26048
|
+
Number.parseInt(long[2] ?? "0", 16),
|
|
26049
|
+
Number.parseInt(long[3] ?? "0", 16)
|
|
26050
|
+
];
|
|
26051
|
+
const short = color.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])(?:[0-9a-f])?$/i);
|
|
26052
|
+
if (short) {
|
|
26053
|
+
const dup = (s) => Number.parseInt(`${s ?? "0"}${s ?? "0"}`, 16);
|
|
26054
|
+
return [dup(short[1]), dup(short[2]), dup(short[3])];
|
|
26055
|
+
}
|
|
26056
|
+
return null;
|
|
26057
|
+
}
|
|
26058
|
+
function parseColor2(raw) {
|
|
26059
|
+
const c = String(raw || "").trim().toLowerCase();
|
|
26060
|
+
if (!c || c === "transparent") return null;
|
|
26061
|
+
const rgb = c.match(/rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:[\s,/]+([\d.]+%?))?\s*\)/i);
|
|
26062
|
+
if (rgb) {
|
|
26063
|
+
const alpha = rgb[4];
|
|
26064
|
+
const a = alpha === void 0 ? 1 : alpha.endsWith("%") ? Number.parseFloat(alpha) / 100 : Number.parseFloat(alpha);
|
|
26065
|
+
return { r: Number(rgb[1]), g: Number(rgb[2]), b: Number(rgb[3]), a };
|
|
26066
|
+
}
|
|
26067
|
+
const oklch = parseOklch(c);
|
|
26068
|
+
if (oklch) return oklch;
|
|
26069
|
+
const hex = hexChannels(c);
|
|
26070
|
+
if (hex) return { r: hex[0], g: hex[1], b: hex[2], a: 1 };
|
|
26071
|
+
return null;
|
|
26072
|
+
}
|
|
26073
|
+
function parseOklch(c) {
|
|
26074
|
+
const m = c.match(/oklch\(\s*([\d.]+%?)\s+([\d.]+%?)\s+([\d.]+)(?:deg)?(?:\s*\/\s*([\d.]+%?))?\s*\)/i);
|
|
26075
|
+
if (!m) return null;
|
|
26076
|
+
const pct = (s, scale) => s.endsWith("%") ? Number.parseFloat(s) / 100 * scale : Number.parseFloat(s);
|
|
26077
|
+
const alpha = m[4];
|
|
26078
|
+
const a = alpha === void 0 ? 1 : pct(alpha, 1);
|
|
26079
|
+
return { ...oklchToRgb(pct(m[1] ?? "0", 1), pct(m[2] ?? "0", 0.4), Number.parseFloat(m[3] ?? "0")), a };
|
|
26080
|
+
}
|
|
26081
|
+
function oklchToRgb(L, C, H) {
|
|
26082
|
+
const hRad = H * Math.PI / 180;
|
|
26083
|
+
const a = C * Math.cos(hRad);
|
|
26084
|
+
const b = C * Math.sin(hRad);
|
|
26085
|
+
const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;
|
|
26086
|
+
const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;
|
|
26087
|
+
const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
|
|
26088
|
+
const lin = [
|
|
26089
|
+
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
26090
|
+
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
26091
|
+
-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
|
|
26092
|
+
].map((v) => {
|
|
26093
|
+
const g = v <= 31308e-7 ? 12.92 * v : 1.055 * Math.sign(v) * Math.abs(v) ** (1 / 2.4) - 0.055;
|
|
26094
|
+
return Math.round(Math.max(0, Math.min(1, g)) * 255);
|
|
26095
|
+
});
|
|
26096
|
+
return { r: lin[0] ?? 0, g: lin[1] ?? 0, b: lin[2] ?? 0 };
|
|
26097
|
+
}
|
|
26098
|
+
function isNeutralAuthoredColor(rawColor) {
|
|
26099
|
+
const c = String(rawColor || "").trim().toLowerCase();
|
|
26100
|
+
if (!c) return false;
|
|
26101
|
+
if (NEUTRAL_COLOR_KEYWORDS.has(c)) return true;
|
|
26102
|
+
if (/^rgba?\(/i.test(c)) {
|
|
26103
|
+
const channels2 = c.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i);
|
|
26104
|
+
if (channels2) {
|
|
26105
|
+
const values = [1, 2, 3].map((i) => Number(channels2[i]));
|
|
26106
|
+
return Math.max(...values) - Math.min(...values) < 30;
|
|
26107
|
+
}
|
|
26108
|
+
return false;
|
|
26109
|
+
}
|
|
26110
|
+
const oklch = c.match(/oklch\(\s*[\d.]+%?\s+([\d.-]+)/i);
|
|
26111
|
+
if (oklch) return Number.parseFloat(oklch[1] ?? "0") < 0.02;
|
|
26112
|
+
const lch = c.match(/lch\(\s*[\d.]+%?\s+([\d.-]+)/i);
|
|
26113
|
+
if (lch) return Number.parseFloat(lch[1] ?? "0") < 3;
|
|
26114
|
+
const hsl = c.match(/hsla?\(\s*[\d.-]+\s*,?\s*([\d.]+)%/i);
|
|
26115
|
+
if (hsl) return Number.parseFloat(hsl[1] ?? "0") < 10;
|
|
26116
|
+
const channels = hexChannels(c);
|
|
26117
|
+
if (channels) return Math.max(...channels) - Math.min(...channels) < 30;
|
|
26118
|
+
return false;
|
|
26119
|
+
}
|
|
26120
|
+
function hasChroma(c, threshold = 30) {
|
|
26121
|
+
if (!c) return false;
|
|
26122
|
+
return Math.max(c.r, c.g, c.b) - Math.min(c.r, c.g, c.b) >= threshold;
|
|
26123
|
+
}
|
|
26124
|
+
function getHue(c) {
|
|
26125
|
+
if (!c) return 0;
|
|
26126
|
+
const r = c.r / 255;
|
|
26127
|
+
const g = c.g / 255;
|
|
26128
|
+
const b = c.b / 255;
|
|
26129
|
+
const max = Math.max(r, g, b);
|
|
26130
|
+
const min = Math.min(r, g, b);
|
|
26131
|
+
if (max === min) return 0;
|
|
26132
|
+
const d = max - min;
|
|
26133
|
+
let h;
|
|
26134
|
+
if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
26135
|
+
else if (max === g) h = ((b - r) / d + 2) / 6;
|
|
26136
|
+
else h = ((r - g) / d + 4) / 6;
|
|
26137
|
+
return Math.round(h * 360);
|
|
26138
|
+
}
|
|
26139
|
+
function isAiPurpleHue(hue) {
|
|
26140
|
+
return hue >= 225 && hue <= 310;
|
|
26141
|
+
}
|
|
26142
|
+
function isCreamColor(c) {
|
|
26143
|
+
if (Math.min(c.r, c.g, c.b) < 209) return false;
|
|
26144
|
+
if (!(c.r >= c.g && c.g >= c.b)) return false;
|
|
26145
|
+
const warmth = c.r - c.b;
|
|
26146
|
+
return warmth >= 6 && warmth <= 48;
|
|
26147
|
+
}
|
|
26148
|
+
|
|
26149
|
+
// src/engine/landing/lib/brand-tokens.ts
|
|
26150
|
+
var EMPTY = { fonts: /* @__PURE__ */ new Set(), colors: [], hasTokens: false };
|
|
26151
|
+
function parseBrandTokens(globalCss, brandMd) {
|
|
26152
|
+
const fonts = /* @__PURE__ */ new Set();
|
|
26153
|
+
const colors = [];
|
|
26154
|
+
parseGlobalCss(globalCss, fonts, colors);
|
|
26155
|
+
parseBrandMd(brandMd, fonts, colors);
|
|
26156
|
+
return { fonts, colors, hasTokens: fonts.size > 0 || colors.length > 0 };
|
|
26157
|
+
}
|
|
26158
|
+
function parseGlobalCss(rawCss, fonts, colors) {
|
|
26159
|
+
const globalCss = rawCss.replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
26160
|
+
for (const m of globalCss.matchAll(/--font-[\w-]*\s*:\s*([^;]+);/gi)) {
|
|
26161
|
+
const first = (m[1] ?? "").split(",")[0]?.trim().replace(/^['"]|['"]$/g, "").toLowerCase();
|
|
26162
|
+
if (first) fonts.add(first);
|
|
26163
|
+
}
|
|
26164
|
+
for (const m of globalCss.matchAll(
|
|
26165
|
+
/--color-[\w-]*\s*:\s*(#[0-9a-f]{3,8}\b|rgba?\([^)]+\)|oklch\([^)]+\)|hsla?\([^)]+\))/gi
|
|
26166
|
+
)) {
|
|
26167
|
+
const c = parseColor2(m[1] ?? "");
|
|
26168
|
+
if (c) colors.push(c);
|
|
26169
|
+
}
|
|
26170
|
+
}
|
|
26171
|
+
function parseBrandMd(brandMd, fonts, colors) {
|
|
26172
|
+
const isProhibition = (line) => /\b(?:never|don'?t|do not|avoid|not|instead of|ban(?:ned)?)\b/i.test(line);
|
|
26173
|
+
for (const line of brandMd.split("\n")) {
|
|
26174
|
+
if (isProhibition(line)) continue;
|
|
26175
|
+
for (const m of line.matchAll(/#[0-9a-f]{6}\b/gi)) {
|
|
26176
|
+
const c = parseColor2(m[0]);
|
|
26177
|
+
if (c) colors.push(c);
|
|
26178
|
+
}
|
|
26179
|
+
if (!/\b(?:font|typeface|family)\b/i.test(line)) continue;
|
|
26180
|
+
const named = line.match(/["'`]([A-Za-z][\w ]{1,30})["'`]/g);
|
|
26181
|
+
if (named) for (const n of named) fonts.add(n.slice(1, -1).trim().toLowerCase());
|
|
26182
|
+
}
|
|
26183
|
+
}
|
|
26184
|
+
async function loadBrandTokens(projectRoot) {
|
|
26185
|
+
const globalCss = await safeRead(path23.join(projectRoot, "src", "styles", "global.css"));
|
|
26186
|
+
const brandMd = await safeRead(path23.join(projectRoot, "src", "brand", "BRAND.md"));
|
|
26187
|
+
if (!globalCss && !brandMd) return EMPTY;
|
|
26188
|
+
return parseBrandTokens(globalCss, brandMd);
|
|
26189
|
+
}
|
|
26190
|
+
async function safeRead(file) {
|
|
26191
|
+
try {
|
|
26192
|
+
return await readFile20(file, "utf8");
|
|
26193
|
+
} catch {
|
|
26194
|
+
return "";
|
|
26195
|
+
}
|
|
26196
|
+
}
|
|
26197
|
+
function brandCommitsFont(brand, snippet) {
|
|
26198
|
+
if (brand.fonts.size === 0) return false;
|
|
26199
|
+
const lower = snippet.toLowerCase();
|
|
26200
|
+
for (const f of brand.fonts) if (f.length >= 3 && lower.includes(f)) return true;
|
|
26201
|
+
return false;
|
|
26202
|
+
}
|
|
26203
|
+
function brandCommitsAiHue(brand) {
|
|
26204
|
+
return brand.colors.some((c) => hasChroma(c, 40) && isAiPurpleHue(getHue(c)));
|
|
26205
|
+
}
|
|
26206
|
+
function brandCommitsCream(brand) {
|
|
26207
|
+
return brand.colors.some((c) => isCreamColor(c));
|
|
26208
|
+
}
|
|
26209
|
+
|
|
26210
|
+
// src/engine/landing/lib/constants.ts
|
|
26211
|
+
var OVERUSED_FONTS = /* @__PURE__ */ new Set([
|
|
26212
|
+
// Older monoculture (still ubiquitous):
|
|
26213
|
+
"inter",
|
|
26214
|
+
"roboto",
|
|
26215
|
+
"open sans",
|
|
26216
|
+
"lato",
|
|
26217
|
+
"montserrat",
|
|
26218
|
+
"arial",
|
|
26219
|
+
"helvetica",
|
|
26220
|
+
// Newer monoculture (the Anthropic-skill / Vercel / GitHub default wave):
|
|
26221
|
+
"fraunces",
|
|
26222
|
+
"instrument sans",
|
|
26223
|
+
"instrument serif",
|
|
26224
|
+
"geist",
|
|
26225
|
+
"geist sans",
|
|
26226
|
+
"geist mono",
|
|
26227
|
+
"mona sans",
|
|
26228
|
+
"plus jakarta sans",
|
|
26229
|
+
"space grotesk",
|
|
26230
|
+
"space mono",
|
|
26231
|
+
"recoleta",
|
|
26232
|
+
"dm sans",
|
|
26233
|
+
"dm serif display",
|
|
26234
|
+
"dm serif text",
|
|
26235
|
+
"outfit",
|
|
26236
|
+
"syne",
|
|
26237
|
+
"cormorant",
|
|
26238
|
+
"playfair display",
|
|
26239
|
+
"lora",
|
|
26240
|
+
"crimson",
|
|
26241
|
+
"newsreader",
|
|
26242
|
+
"ibm plex",
|
|
26243
|
+
"ibm plex sans",
|
|
26244
|
+
"ibm plex serif"
|
|
26245
|
+
]);
|
|
26246
|
+
function isOverusedFont(family) {
|
|
26247
|
+
if (OVERUSED_FONTS.has(family)) return true;
|
|
26248
|
+
for (const entry of OVERUSED_FONTS) if (family.startsWith(`${entry} `)) return true;
|
|
26249
|
+
return false;
|
|
26250
|
+
}
|
|
26251
|
+
var GENERIC_FONTS = /* @__PURE__ */ new Set([
|
|
26252
|
+
"serif",
|
|
26253
|
+
"sans-serif",
|
|
26254
|
+
"monospace",
|
|
26255
|
+
"cursive",
|
|
26256
|
+
"fantasy",
|
|
26257
|
+
"system-ui",
|
|
26258
|
+
"ui-serif",
|
|
26259
|
+
"ui-sans-serif",
|
|
26260
|
+
"ui-monospace",
|
|
26261
|
+
"ui-rounded",
|
|
26262
|
+
"-apple-system",
|
|
26263
|
+
"blinkmacsystemfont",
|
|
26264
|
+
"segoe ui",
|
|
26265
|
+
"inherit",
|
|
26266
|
+
"initial",
|
|
26267
|
+
"unset",
|
|
26268
|
+
"revert"
|
|
26269
|
+
]);
|
|
26270
|
+
var EM_DASH_FLOOR = 8;
|
|
26271
|
+
var EM_DASH_CHARS_PER_DASH = 500;
|
|
26272
|
+
var TAILWIND_TEXT_PX = {
|
|
26273
|
+
"text-xs": 12,
|
|
26274
|
+
"text-sm": 14,
|
|
26275
|
+
"text-base": 16,
|
|
26276
|
+
"text-lg": 18,
|
|
26277
|
+
"text-xl": 20,
|
|
26278
|
+
"text-2xl": 24,
|
|
26279
|
+
"text-3xl": 30,
|
|
26280
|
+
"text-4xl": 36,
|
|
26281
|
+
"text-5xl": 48,
|
|
26282
|
+
"text-6xl": 60,
|
|
26283
|
+
"text-7xl": 72,
|
|
26284
|
+
"text-8xl": 96,
|
|
26285
|
+
"text-9xl": 128
|
|
26286
|
+
};
|
|
26287
|
+
var BUZZWORDS = [
|
|
26288
|
+
"streamline your",
|
|
26289
|
+
"empower your",
|
|
26290
|
+
"supercharge your",
|
|
26291
|
+
"unleash your",
|
|
26292
|
+
"unleash the power",
|
|
26293
|
+
"leverage the power",
|
|
26294
|
+
"built for the modern",
|
|
26295
|
+
"trusted by leading",
|
|
26296
|
+
"trusted by the world",
|
|
26297
|
+
"best-in-class",
|
|
26298
|
+
"industry-leading",
|
|
26299
|
+
"world-class",
|
|
26300
|
+
"enterprise-grade",
|
|
26301
|
+
"next-generation",
|
|
26302
|
+
"cutting-edge",
|
|
26303
|
+
"transform your business",
|
|
26304
|
+
"revolutionize",
|
|
26305
|
+
"game-changer",
|
|
26306
|
+
"game changing",
|
|
26307
|
+
"mission-critical",
|
|
26308
|
+
"best of breed",
|
|
26309
|
+
"future-proof",
|
|
26310
|
+
"future proof",
|
|
26311
|
+
"seamless experience",
|
|
26312
|
+
"seamlessly integrate",
|
|
26313
|
+
"drive engagement",
|
|
26314
|
+
"drive growth",
|
|
26315
|
+
"drive results",
|
|
26316
|
+
"harness the power"
|
|
26317
|
+
];
|
|
26318
|
+
var AI_PURPLE_HEXES = /* @__PURE__ */ new Set([
|
|
26319
|
+
"#7c3aed",
|
|
26320
|
+
"#8b5cf6",
|
|
26321
|
+
"#a855f7",
|
|
26322
|
+
"#9333ea",
|
|
26323
|
+
"#7e22ce",
|
|
26324
|
+
"#6d28d9",
|
|
26325
|
+
"#6366f1",
|
|
26326
|
+
"#764ba2",
|
|
26327
|
+
"#667eea"
|
|
26328
|
+
]);
|
|
26329
|
+
var CREAM_TAILWIND_BG = /* @__PURE__ */ new Set([
|
|
26330
|
+
"bg-amber-50",
|
|
26331
|
+
"bg-amber-100",
|
|
26332
|
+
"bg-orange-50",
|
|
26333
|
+
"bg-orange-100",
|
|
26334
|
+
"bg-yellow-50",
|
|
26335
|
+
"bg-stone-50",
|
|
26336
|
+
"bg-stone-100",
|
|
26337
|
+
"bg-stone-200"
|
|
26338
|
+
]);
|
|
26339
|
+
var RULE_META = {
|
|
26340
|
+
"gradient-text": {
|
|
26341
|
+
family: "color",
|
|
26342
|
+
severity: "block",
|
|
26343
|
+
note: "Gradient text is a top AI tell. Emphasis comes from weight or size, not a clipped gradient fill."
|
|
26344
|
+
},
|
|
26345
|
+
"broken-image": {
|
|
26346
|
+
family: "integrity",
|
|
26347
|
+
severity: "block",
|
|
26348
|
+
note: "An <img> with an empty or placeholder src ships a broken image. Source a real asset via `baker images`."
|
|
26349
|
+
},
|
|
26350
|
+
"side-tab": {
|
|
26351
|
+
family: "borders_depth",
|
|
26352
|
+
severity: "warn",
|
|
26353
|
+
note: "A thick colored border on one edge is the 'side-tab' AI tell. Use full borders, a background, or spacing instead."
|
|
26354
|
+
},
|
|
26355
|
+
"border-accent-on-rounded": {
|
|
26356
|
+
family: "borders_depth",
|
|
26357
|
+
severity: "warn",
|
|
26358
|
+
note: "A colored top/bottom accent bar on a rounded card is an AI tell. Let the card's own surface carry the accent."
|
|
26359
|
+
},
|
|
26360
|
+
"overused-font": {
|
|
26361
|
+
family: "typography",
|
|
26362
|
+
severity: "warn",
|
|
26363
|
+
brandAware: "font",
|
|
26364
|
+
note: "This face is a training-data default. Pick a face with a point of view \u2014 unless BRAND.md commits it."
|
|
26365
|
+
},
|
|
26366
|
+
"single-font": {
|
|
26367
|
+
family: "typography",
|
|
26368
|
+
severity: "advisory",
|
|
26369
|
+
note: "Only one face across the page flattens hierarchy. A display/body pair usually reads richer."
|
|
26370
|
+
},
|
|
26371
|
+
"flat-type-hierarchy": {
|
|
26372
|
+
family: "typography",
|
|
26373
|
+
severity: "warn",
|
|
26374
|
+
note: "Type steps are too close (<2\xD7 span). Widen the scale so the primary, secondary, and body are obvious at a glance."
|
|
26375
|
+
},
|
|
26376
|
+
"ai-color-palette": {
|
|
26377
|
+
family: "color",
|
|
26378
|
+
severity: "warn",
|
|
26379
|
+
brandAware: "color",
|
|
26380
|
+
note: "Purple/indigo-on-heading or purple\u2192cyan gradients are the AI palette cluster. Commit to the brand's own hues."
|
|
26381
|
+
},
|
|
26382
|
+
"cream-palette": {
|
|
26383
|
+
family: "color",
|
|
26384
|
+
severity: "warn",
|
|
26385
|
+
brandAware: "color",
|
|
26386
|
+
note: "Cream/parchment ground is the 'measured rendition' AI default. Rework from the brand's saturated materials."
|
|
26387
|
+
},
|
|
26388
|
+
"gray-on-color": {
|
|
26389
|
+
family: "color",
|
|
26390
|
+
severity: "warn",
|
|
26391
|
+
note: "Gray text on a colored surface reads muddy. Tint secondary text from the surface hue, never neutral gray."
|
|
26392
|
+
},
|
|
26393
|
+
"bounce-easing": {
|
|
26394
|
+
family: "motion",
|
|
26395
|
+
severity: "warn",
|
|
26396
|
+
note: "Bounce/elastic easing feels dated. Use natural deceleration (e.g. cubic-bezier(0.16, 1, 0.3, 1))."
|
|
26397
|
+
},
|
|
26398
|
+
"layout-transition": {
|
|
26399
|
+
family: "motion",
|
|
26400
|
+
severity: "advisory",
|
|
26401
|
+
note: "Transitioning width/height/padding/margin janks. Animate transform/opacity, or clip-path/max-height with care."
|
|
26402
|
+
},
|
|
26403
|
+
"monotonous-spacing": {
|
|
26404
|
+
family: "spacing",
|
|
26405
|
+
severity: "warn",
|
|
26406
|
+
note: "One spacing value dominates. Create rhythm \u2014 tight groups, generous separation, more space above a heading than below."
|
|
26407
|
+
},
|
|
26408
|
+
"marketing-buzzword": {
|
|
26409
|
+
family: "copy",
|
|
26410
|
+
severity: "warn",
|
|
26411
|
+
note: "SaaS filler ('supercharge your\u2026') is generic. Name names, use numbers, say the specific thing only this product proves."
|
|
26412
|
+
},
|
|
26413
|
+
"aphoristic-cadence": {
|
|
26414
|
+
family: "copy",
|
|
26415
|
+
severity: "warn",
|
|
26416
|
+
note: "Manufactured-contrast cadence ('Not an X. A Y.') is an AI copy tell. Vary sentence shape; make a real claim."
|
|
26417
|
+
},
|
|
26418
|
+
"em-dash-overuse": {
|
|
26419
|
+
family: "copy",
|
|
26420
|
+
severity: "warn",
|
|
26421
|
+
note: "Dense em-dashes are an AI cadence tell (and brand copy bans them). Recast with commas, colons, or full stops."
|
|
26422
|
+
},
|
|
26423
|
+
"dark-glow": {
|
|
26424
|
+
family: "borders_depth",
|
|
26425
|
+
severity: "warn",
|
|
26426
|
+
note: "A zero-offset colored glow is decoration, not depth. Shadows carry an offset and a soft blur."
|
|
26427
|
+
},
|
|
26428
|
+
marquee: {
|
|
26429
|
+
family: "motion",
|
|
26430
|
+
severity: "warn",
|
|
26431
|
+
note: "Auto-scrolling marquees are a dated tell. Let the visitor control the pace, or drop the motion."
|
|
26432
|
+
}
|
|
26433
|
+
};
|
|
26434
|
+
var SEVERITY_WEIGHT = {
|
|
26435
|
+
block: 0.5,
|
|
26436
|
+
warn: 0.2,
|
|
26437
|
+
advisory: 0.05
|
|
26438
|
+
};
|
|
26439
|
+
|
|
26440
|
+
// src/engine/landing/lib/rules.ts
|
|
26441
|
+
var cap2 = (m, i) => m[i] ?? "";
|
|
26442
|
+
var num = (m, i) => Number(m[i] ?? 0);
|
|
26443
|
+
var hasRounded = (line) => /\brounded(?:-\w+)?\b/.test(line) || /border-radius/i.test(line);
|
|
26444
|
+
var isSafeElement = (line) => /<(?:blockquote|nav[\s>]|pre[\s>]|code[\s>]|a\s|input[\s>]|span[\s>])/i.test(line);
|
|
26445
|
+
var hasNeutralTailwindBorder = (line) => /\bborder-(?:gray|slate|zinc|neutral|stone|white|black|transparent|current|inherit)\b|\bborder-(?:gray|slate|zinc|neutral|stone)-\d+\b/.test(
|
|
26446
|
+
line
|
|
26447
|
+
);
|
|
26448
|
+
function isNeutralBorderColor(str) {
|
|
26449
|
+
const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
|
|
26450
|
+
if (!m) return false;
|
|
26451
|
+
return isNeutralAuthoredColor(m[1] ?? "");
|
|
26452
|
+
}
|
|
26453
|
+
var LINE_MATCHERS = [
|
|
26454
|
+
// ── Side-tab: a thick colored border on one edge ──────────────────────────
|
|
26455
|
+
{
|
|
26456
|
+
id: "side-tab",
|
|
26457
|
+
regex: /\bborder-[lrse]-(\d+)\b/g,
|
|
26458
|
+
test: (m, line) => {
|
|
26459
|
+
if (isSafeElement(line) || hasNeutralTailwindBorder(line)) return false;
|
|
26460
|
+
return hasRounded(line) ? num(m, 1) >= 2 : num(m, 1) >= 4;
|
|
26461
|
+
},
|
|
26462
|
+
fmt: (m) => cap2(m, 0)
|
|
26463
|
+
},
|
|
26464
|
+
{
|
|
26465
|
+
id: "side-tab",
|
|
26466
|
+
regex: /border-(?:left|right)\s*:\s*(\d+)px\s+solid[^;]*/gi,
|
|
26467
|
+
test: (m, line) => {
|
|
26468
|
+
if (isSafeElement(line)) return false;
|
|
26469
|
+
if (isNeutralBorderColor(cap2(m, 0))) return false;
|
|
26470
|
+
return hasRounded(line) ? num(m, 1) >= 2 : num(m, 1) >= 3;
|
|
26471
|
+
},
|
|
26472
|
+
fmt: (m) => cap2(m, 0).replace(/\s*;?\s*$/, "")
|
|
26473
|
+
},
|
|
26474
|
+
{
|
|
26475
|
+
id: "side-tab",
|
|
26476
|
+
regex: /border-(?:left|right)-width\s*:\s*(\d+)px/gi,
|
|
26477
|
+
test: (m, line) => !isSafeElement(line) && num(m, 1) >= 3,
|
|
26478
|
+
fmt: (m) => cap2(m, 0)
|
|
26479
|
+
},
|
|
26480
|
+
{
|
|
26481
|
+
id: "side-tab",
|
|
26482
|
+
regex: /border-inline-(?:start|end)(?:-width)?\s*:\s*(\d+)px/gi,
|
|
26483
|
+
test: (m, line) => !isSafeElement(line) && num(m, 1) >= 3,
|
|
26484
|
+
fmt: (m) => cap2(m, 0)
|
|
26485
|
+
},
|
|
26486
|
+
// ── Border accent on a rounded card (top/bottom colored bar) ──────────────
|
|
26487
|
+
{
|
|
26488
|
+
id: "border-accent-on-rounded",
|
|
26489
|
+
regex: /\bborder-[tb]-(\d+)\b/g,
|
|
26490
|
+
test: (m, line) => hasRounded(line) && num(m, 1) >= 1 && !hasNeutralTailwindBorder(line),
|
|
26491
|
+
fmt: (m) => cap2(m, 0)
|
|
26492
|
+
},
|
|
26493
|
+
{
|
|
26494
|
+
id: "border-accent-on-rounded",
|
|
26495
|
+
regex: /border-(?:top|bottom)\s*:\s*(\d+)px\s+solid/gi,
|
|
26496
|
+
test: (m, line) => num(m, 1) >= 3 && hasRounded(line),
|
|
26497
|
+
fmt: (m) => cap2(m, 0)
|
|
26498
|
+
},
|
|
26499
|
+
// ── Gradient text (Tailwind class form; authored-CSS form is the window
|
|
26500
|
+
// analyzer below, so a multi-line rule block is still caught) ──────────────
|
|
26501
|
+
{
|
|
26502
|
+
id: "gradient-text",
|
|
26503
|
+
regex: /\bbg-clip-text\b/g,
|
|
26504
|
+
test: (_m, line) => (
|
|
26505
|
+
// v3 `bg-gradient-to-*`, v4 `bg-linear-to-*` / `bg-radial*` / `bg-conic*`,
|
|
26506
|
+
// and arbitrary-value `bg-[linear-gradient(…)]` spellings.
|
|
26507
|
+
/\bbg-gradient-to-|\bbg-linear-to-|\bbg-(?:radial|conic)\b|\bbg-\[(?:linear|radial|conic)-gradient/i.test(line)
|
|
26508
|
+
),
|
|
26509
|
+
fmt: () => "bg-clip-text + gradient"
|
|
26510
|
+
},
|
|
26511
|
+
// ── Gray text on a colored background (Tailwind). Shade ≥ 300 only: a pale
|
|
26512
|
+
// tint panel (bg-blue-50 info box) is a normal light ground, not "on color". ──
|
|
26513
|
+
{
|
|
26514
|
+
id: "gray-on-color",
|
|
26515
|
+
regex: /\btext-(?:gray|slate|zinc|neutral|stone)-(\d+)\b/g,
|
|
26516
|
+
test: (_m, line) => {
|
|
26517
|
+
const bg = line.match(
|
|
26518
|
+
/\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(\d+)\b/
|
|
26519
|
+
);
|
|
26520
|
+
return bg !== null && Number(bg[1] ?? 0) >= 300;
|
|
26521
|
+
},
|
|
26522
|
+
fmt: (m, line) => {
|
|
26523
|
+
const bg = line.match(
|
|
26524
|
+
/\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/
|
|
26525
|
+
);
|
|
26526
|
+
return `${cap2(m, 0)} on ${bg?.[0] || "?"}`;
|
|
26527
|
+
}
|
|
26528
|
+
},
|
|
26529
|
+
// ── AI color palette (Tailwind purple/indigo + banned hero hexes) ─────────
|
|
26530
|
+
{
|
|
26531
|
+
id: "ai-color-palette",
|
|
26532
|
+
regex: /\btext-(?:purple|violet|indigo)-(\d+)\b/g,
|
|
26533
|
+
test: (_m, line) => /\btext-(?:[2-9]xl)\b|<h[1-3]/i.test(line),
|
|
26534
|
+
fmt: (m) => `${cap2(m, 0)} on heading`
|
|
26535
|
+
},
|
|
26536
|
+
{
|
|
26537
|
+
id: "ai-color-palette",
|
|
26538
|
+
regex: /\bfrom-(?:purple|violet|indigo)-(\d+)\b/g,
|
|
26539
|
+
test: (_m, line) => /\bto-(?:purple|violet|indigo|blue|cyan|pink|fuchsia)-\d+\b/.test(line),
|
|
26540
|
+
fmt: (m) => `${cap2(m, 0)} gradient`
|
|
26541
|
+
},
|
|
26542
|
+
{
|
|
26543
|
+
id: "ai-color-palette",
|
|
26544
|
+
regex: /#[0-9a-f]{6}\b/gi,
|
|
26545
|
+
test: (m) => AI_PURPLE_HEXES.has(cap2(m, 0).toLowerCase()),
|
|
26546
|
+
fmt: (m) => `${cap2(m, 0)} (AI-cluster purple)`
|
|
26547
|
+
},
|
|
26548
|
+
// ── Cream/parchment ground (Tailwind + authored background hex) ───────────
|
|
26549
|
+
{
|
|
26550
|
+
id: "cream-palette",
|
|
26551
|
+
regex: /\bbg-(?:amber|orange|yellow|stone)-(?:50|100|200)\b/g,
|
|
26552
|
+
test: (m) => CREAM_TAILWIND_BG.has(cap2(m, 0)),
|
|
26553
|
+
fmt: (m) => `${cap2(m, 0)} (cream ground)`
|
|
26554
|
+
},
|
|
26555
|
+
{
|
|
26556
|
+
id: "cream-palette",
|
|
26557
|
+
regex: /background(?:-color)?\s*:\s*(#[0-9a-f]{6}\b|rgba?\([^)]+\))/gi,
|
|
26558
|
+
test: (m) => {
|
|
26559
|
+
const c = parseColor2(cap2(m, 1));
|
|
26560
|
+
return c !== null && isCreamColor(c);
|
|
26561
|
+
},
|
|
26562
|
+
fmt: (m) => `${cap2(m, 1)} (cream ground)`
|
|
26563
|
+
},
|
|
26564
|
+
// ── Bounce / elastic easing ───────────────────────────────────────────────
|
|
26565
|
+
{
|
|
26566
|
+
id: "bounce-easing",
|
|
26567
|
+
regex: /\banimate-bounce\b/g,
|
|
26568
|
+
test: () => true,
|
|
26569
|
+
fmt: () => "animate-bounce (Tailwind)"
|
|
26570
|
+
},
|
|
26571
|
+
{
|
|
26572
|
+
id: "bounce-easing",
|
|
26573
|
+
regex: /animation(?:-name)?\s*:\s*([^;{}]*(?:bounce|elastic|wobble|jiggle|spring)[^;{}]*)/gi,
|
|
26574
|
+
test: () => true,
|
|
26575
|
+
fmt: (m) => `animation: ${cap2(m, 1).trim()}`
|
|
26576
|
+
},
|
|
26577
|
+
{
|
|
26578
|
+
id: "bounce-easing",
|
|
26579
|
+
regex: /cubic-bezier\(\s*([\d.-]+)\s*,\s*([\d.-]+)\s*,\s*([\d.-]+)\s*,\s*([\d.-]+)\s*\)/g,
|
|
26580
|
+
test: (m) => {
|
|
26581
|
+
const y1 = Number.parseFloat(cap2(m, 2));
|
|
26582
|
+
const y2 = Number.parseFloat(cap2(m, 4));
|
|
26583
|
+
return y1 < -0.1 || y1 > 1.1 || y2 < -0.1 || y2 > 1.1;
|
|
26584
|
+
},
|
|
26585
|
+
fmt: (m) => `cubic-bezier(${cap2(m, 1)}, ${cap2(m, 2)}, ${cap2(m, 3)}, ${cap2(m, 4)})`
|
|
26586
|
+
},
|
|
26587
|
+
// ── Transitioning layout properties (jank) ────────────────────────────────
|
|
26588
|
+
{
|
|
26589
|
+
id: "layout-transition",
|
|
26590
|
+
// The lookahead skips Astro's view-transition directives (transition:name=,
|
|
26591
|
+
// transition:animate=, transition:persist) — markup idiom, not CSS.
|
|
26592
|
+
regex: /transition(?:-property)?\s*:\s*(?!name\s*=|animate\s*=|persist\b)([^;{}]+)/gi,
|
|
26593
|
+
test: (m) => {
|
|
26594
|
+
const val = cap2(m, 1).toLowerCase();
|
|
26595
|
+
if (/\ball\b/.test(val)) return false;
|
|
26596
|
+
return /\b(?:(?:max|min)-)?(?:width|height)\b|\bpadding\b|\bmargin\b/.test(val);
|
|
26597
|
+
},
|
|
26598
|
+
fmt: (m) => {
|
|
26599
|
+
const found = cap2(m, 1).match(
|
|
26600
|
+
/\b(?:(?:max|min)-)?(?:width|height)\b|\bpadding(?:-(?:top|right|bottom|left))?\b|\bmargin(?:-(?:top|right|bottom|left))?\b/gi
|
|
26601
|
+
);
|
|
26602
|
+
return `transition: ${found ? found.join(", ") : cap2(m, 1).trim()}`;
|
|
26603
|
+
}
|
|
26604
|
+
},
|
|
26605
|
+
// ── Broken image ──────────────────────────────────────────────────────────
|
|
26606
|
+
{
|
|
26607
|
+
id: "broken-image",
|
|
26608
|
+
regex: /<img\b[^>]*?\bsrc\s*=\s*(?:""|''|"\s+"|'\s+'|"#"|'#')/gi,
|
|
26609
|
+
test: () => true,
|
|
26610
|
+
fmt: (m) => cap2(m, 0).slice(0, 100)
|
|
26611
|
+
},
|
|
26612
|
+
{
|
|
26613
|
+
id: "broken-image",
|
|
26614
|
+
regex: /<img\b(?:(?!\bsrc\s*=)[^>])*>/gi,
|
|
26615
|
+
test: (m) => !/\bsrc\s*=/i.test(cap2(m, 0)),
|
|
26616
|
+
fmt: (m) => cap2(m, 0).slice(0, 100)
|
|
26617
|
+
}
|
|
26618
|
+
];
|
|
26619
|
+
function stripHtmlToText(html) {
|
|
26620
|
+
return html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ");
|
|
26621
|
+
}
|
|
26622
|
+
function blankAstroFrontmatter(text) {
|
|
26623
|
+
if (!text.startsWith("---")) return text;
|
|
26624
|
+
const lines = text.split("\n");
|
|
26625
|
+
if (!/^---\s*$/.test(lines[0] ?? "")) return text;
|
|
26626
|
+
for (let i = 1; i < lines.length; i++) {
|
|
26627
|
+
if (/^---\s*$/.test(lines[i] ?? "")) {
|
|
26628
|
+
for (let j = 0; j <= i; j++) lines[j] = "";
|
|
26629
|
+
return lines.join("\n");
|
|
26630
|
+
}
|
|
26631
|
+
}
|
|
26632
|
+
return text;
|
|
26633
|
+
}
|
|
26634
|
+
var ANALYZERS = [
|
|
26635
|
+
// Overused primary font (font-family declarations + Google Fonts URLs). Fires
|
|
26636
|
+
// at ≥1 and carries a line number → per file.
|
|
26637
|
+
{
|
|
26638
|
+
scope: "file",
|
|
26639
|
+
run: (text, file) => {
|
|
26640
|
+
const out = [];
|
|
26641
|
+
const seen = /* @__PURE__ */ new Set();
|
|
26642
|
+
for (const m of text.matchAll(/font-family\s*:\s*([^;}{]+)/gi)) {
|
|
26643
|
+
const first = firstFamily(cap2(m, 1));
|
|
26644
|
+
if (first && !GENERIC_FONTS.has(first) && isOverusedFont(first) && !seen.has(first)) {
|
|
26645
|
+
seen.add(first);
|
|
26646
|
+
out.push({ id: "overused-font", snippet: `font-family: ${first}`, file, line: lineOf(text, m.index ?? 0) });
|
|
26647
|
+
}
|
|
26648
|
+
}
|
|
26649
|
+
for (const m of text.matchAll(/fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi)) {
|
|
26650
|
+
for (const fam of googleFontFamilies(cap2(m, 0))) {
|
|
26651
|
+
if (isOverusedFont(fam) && !seen.has(fam)) {
|
|
26652
|
+
seen.add(fam);
|
|
26653
|
+
out.push({ id: "overused-font", snippet: `Google Fonts: ${fam}`, file, line: lineOf(text, m.index ?? 0) });
|
|
26654
|
+
}
|
|
26655
|
+
}
|
|
26656
|
+
}
|
|
26657
|
+
return out;
|
|
26658
|
+
}
|
|
26659
|
+
},
|
|
26660
|
+
// Single font across a substantial page — needs the whole page's font set.
|
|
26661
|
+
// Counts only the FIRST family of each stack (like overused-font): fallbacks
|
|
26662
|
+
// aren't choices, so `'Inter', 'Helvetica Neue'` everywhere is still one font.
|
|
26663
|
+
{
|
|
26664
|
+
scope: "page",
|
|
26665
|
+
run: (text, file) => {
|
|
26666
|
+
const fonts = /* @__PURE__ */ new Set();
|
|
26667
|
+
for (const m of text.matchAll(/font-family\s*:\s*([^;}{]+)/gi)) {
|
|
26668
|
+
const first = firstFamily(cap2(m, 1));
|
|
26669
|
+
if (first && !GENERIC_FONTS.has(first)) fonts.add(first);
|
|
26670
|
+
}
|
|
26671
|
+
for (const f of allGoogleFontFamilies(text)) fonts.add(f);
|
|
26672
|
+
if (fonts.size !== 1 || text.split("\n").length < 20) return [];
|
|
26673
|
+
const only = [...fonts][0] ?? "";
|
|
26674
|
+
return [{ id: "single-font", snippet: `only font used across the page is ${only}`, file }];
|
|
26675
|
+
}
|
|
26676
|
+
},
|
|
26677
|
+
// Flat type hierarchy (all sizes span <2×) — needs every size on the page.
|
|
26678
|
+
{
|
|
26679
|
+
scope: "page",
|
|
26680
|
+
run: (text, file) => {
|
|
26681
|
+
const sizes = /* @__PURE__ */ new Set();
|
|
26682
|
+
const REM = 16;
|
|
26683
|
+
for (const m of text.matchAll(/font-size\s*:\s*([\d.]+)(px|rem|em)\b/gi)) {
|
|
26684
|
+
const px = cap2(m, 2) === "px" ? num(m, 1) : num(m, 1) * REM;
|
|
26685
|
+
if (px > 0 && px < 200) sizes.add(Math.round(px * 10) / 10);
|
|
26686
|
+
}
|
|
26687
|
+
for (const [cls, px] of Object.entries(TAILWIND_TEXT_PX)) {
|
|
26688
|
+
if (new RegExp(`\\b${cls}\\b`).test(text)) sizes.add(px);
|
|
26689
|
+
}
|
|
26690
|
+
if (sizes.size < 3) return [];
|
|
26691
|
+
const sorted = [...sizes].sort((a, b) => a - b);
|
|
26692
|
+
const min = sorted[0] ?? 1;
|
|
26693
|
+
const max = sorted[sorted.length - 1] ?? 1;
|
|
26694
|
+
const ratio = max / min;
|
|
26695
|
+
if (ratio >= 2) return [];
|
|
26696
|
+
return [
|
|
26697
|
+
{
|
|
26698
|
+
id: "flat-type-hierarchy",
|
|
26699
|
+
snippet: `sizes ${sorted.map((s) => `${s}px`).join(", ")} (ratio ${ratio.toFixed(1)}:1)`,
|
|
26700
|
+
file
|
|
26701
|
+
}
|
|
26702
|
+
];
|
|
26703
|
+
}
|
|
26704
|
+
},
|
|
26705
|
+
// Monotonous spacing (one value dominates) — needs every spacing value.
|
|
26706
|
+
{
|
|
26707
|
+
scope: "page",
|
|
26708
|
+
run: (text, file) => {
|
|
26709
|
+
const rounded = collectSpacingPx(text).map((v) => Math.round(v / 4) * 4);
|
|
26710
|
+
if (rounded.length < 10) return [];
|
|
26711
|
+
const counts = {};
|
|
26712
|
+
for (const v of rounded) counts[v] = (counts[v] || 0) + 1;
|
|
26713
|
+
const maxCount = Math.max(...Object.values(counts));
|
|
26714
|
+
const pct = maxCount / rounded.length;
|
|
26715
|
+
const unique = [...new Set(rounded)].filter((v) => v > 0);
|
|
26716
|
+
if (pct <= 0.6 || unique.length > 3) return [];
|
|
26717
|
+
const dominant = Object.entries(counts).sort((a, b) => b[1] - a[1])[0]?.[0] ?? "?";
|
|
26718
|
+
return [
|
|
26719
|
+
{
|
|
26720
|
+
id: "monotonous-spacing",
|
|
26721
|
+
snippet: `~${dominant}px used ${maxCount}/${rounded.length} times (${Math.round(pct * 100)}%)`,
|
|
26722
|
+
file
|
|
26723
|
+
}
|
|
26724
|
+
];
|
|
26725
|
+
}
|
|
26726
|
+
},
|
|
26727
|
+
// Em-dash overuse (density-gated) — density is measured over the page's copy.
|
|
26728
|
+
{
|
|
26729
|
+
scope: "page",
|
|
26730
|
+
run: (text, file) => {
|
|
26731
|
+
const body = stripHtmlToText(text).replace(/—|�*8212;|�*2014;/gi, "\u2014");
|
|
26732
|
+
const count = (body.match(/[—]|(?<!-)--(?!-)(?=\S)/g) ?? []).length;
|
|
26733
|
+
if (count < EM_DASH_FLOOR) return [];
|
|
26734
|
+
if (body.length > count * EM_DASH_CHARS_PER_DASH) return [];
|
|
26735
|
+
return [{ id: "em-dash-overuse", snippet: `${count} em-dashes in body copy`, file }];
|
|
26736
|
+
}
|
|
26737
|
+
},
|
|
26738
|
+
// Marketing buzzwords. Fires at ≥1 → per file (keeps the offending file named).
|
|
26739
|
+
{
|
|
26740
|
+
scope: "file",
|
|
26741
|
+
run: (text, file) => {
|
|
26742
|
+
const body = stripHtmlToText(text);
|
|
26743
|
+
const lower = body.toLowerCase();
|
|
26744
|
+
let count = 0;
|
|
26745
|
+
let firstSample = "";
|
|
26746
|
+
for (const phrase of BUZZWORDS) {
|
|
26747
|
+
let from = 0;
|
|
26748
|
+
for (; ; ) {
|
|
26749
|
+
const idx = lower.indexOf(phrase, from);
|
|
26750
|
+
if (idx === -1) break;
|
|
26751
|
+
count++;
|
|
26752
|
+
if (!firstSample) {
|
|
26753
|
+
firstSample = body.slice(Math.max(0, idx - 12), Math.min(body.length, idx + phrase.length + 12)).trim();
|
|
26754
|
+
}
|
|
26755
|
+
from = idx + phrase.length;
|
|
26756
|
+
}
|
|
26757
|
+
}
|
|
26758
|
+
if (count === 0) return [];
|
|
26759
|
+
return [
|
|
26760
|
+
{
|
|
26761
|
+
id: "marketing-buzzword",
|
|
26762
|
+
snippet: `${count} buzzword phrase${count === 1 ? "" : "s"}: "${firstSample}"`,
|
|
26763
|
+
file
|
|
26764
|
+
}
|
|
26765
|
+
];
|
|
26766
|
+
}
|
|
26767
|
+
},
|
|
26768
|
+
// Aphoristic cadence (manufactured contrast + short rebuttal) — the ≥3 gate
|
|
26769
|
+
// needs the whole page's copy, else it's diluted across components.
|
|
26770
|
+
{
|
|
26771
|
+
scope: "page",
|
|
26772
|
+
run: (text, file) => {
|
|
26773
|
+
const body = stripHtmlToText(text);
|
|
26774
|
+
let count = 0;
|
|
26775
|
+
let firstSample = "";
|
|
26776
|
+
for (const m of body.matchAll(/\bNot an? [a-z][^.!?]{1,40}[.!]\s+[A-Z][^.!?]{1,60}[.!]/g)) {
|
|
26777
|
+
count++;
|
|
26778
|
+
if (!firstSample) firstSample = cap2(m, 0).trim().slice(0, 80);
|
|
26779
|
+
}
|
|
26780
|
+
for (const m of body.matchAll(/\b[A-Z][^.!?]{4,80}[.!]\s+(No|Just)\s+[a-z][^.!?]{2,60}[.!]/g)) {
|
|
26781
|
+
count++;
|
|
26782
|
+
if (!firstSample) firstSample = cap2(m, 0).trim().slice(0, 80);
|
|
26783
|
+
}
|
|
26784
|
+
if (count < 3) return [];
|
|
26785
|
+
return [{ id: "aphoristic-cadence", snippet: `${count} aphoristic constructions: "${firstSample}"`, file }];
|
|
26786
|
+
}
|
|
26787
|
+
},
|
|
26788
|
+
// Gradient text in authored CSS. A rule block spans lines, so this scans a
|
|
26789
|
+
// ±6-line window around each `background-clip: text` instead of one line
|
|
26790
|
+
// (the Tailwind class form stays a line matcher above).
|
|
26791
|
+
{
|
|
26792
|
+
scope: "file",
|
|
26793
|
+
run: (text, file) => {
|
|
26794
|
+
const out = [];
|
|
26795
|
+
const lines = text.split("\n");
|
|
26796
|
+
for (const m of text.matchAll(/(?:-webkit-)?background-clip\s*:\s*text/gi)) {
|
|
26797
|
+
const line = lineOf(text, m.index ?? 0);
|
|
26798
|
+
const window = lines.slice(Math.max(0, line - 7), Math.min(lines.length, line + 6)).join("\n");
|
|
26799
|
+
if (/gradient\(/i.test(window)) {
|
|
26800
|
+
out.push({ id: "gradient-text", snippet: "background-clip: text + gradient", file, line });
|
|
26801
|
+
}
|
|
26802
|
+
}
|
|
26803
|
+
return out;
|
|
26804
|
+
}
|
|
26805
|
+
},
|
|
26806
|
+
// Dark glow: a zero-offset chromatic shadow used as decoration. Line-attributed
|
|
26807
|
+
// → per file.
|
|
26808
|
+
{
|
|
26809
|
+
scope: "file",
|
|
26810
|
+
run: (text, file) => {
|
|
26811
|
+
const out = [];
|
|
26812
|
+
for (const m of text.matchAll(/(?:box|text)-shadow\s*:\s*([^;{}]+)/gi)) {
|
|
26813
|
+
if (shadowIsGlow(cap2(m, 1))) {
|
|
26814
|
+
out.push({
|
|
26815
|
+
id: "dark-glow",
|
|
26816
|
+
snippet: `shadow: ${cap2(m, 1).trim().slice(0, 60)}`,
|
|
26817
|
+
file,
|
|
26818
|
+
line: lineOf(text, m.index ?? 0)
|
|
26819
|
+
});
|
|
26820
|
+
}
|
|
26821
|
+
}
|
|
26822
|
+
return out;
|
|
26823
|
+
}
|
|
26824
|
+
},
|
|
26825
|
+
// Marquee (<marquee> element). Fires at ≥1 → per file.
|
|
26826
|
+
{
|
|
26827
|
+
scope: "file",
|
|
26828
|
+
run: (text, file) => /<marquee\b/i.test(text) ? [{ id: "marquee", snippet: "<marquee> element", file }] : []
|
|
26829
|
+
}
|
|
26830
|
+
];
|
|
26831
|
+
function collectSpacingPx(text) {
|
|
26832
|
+
const vals = [];
|
|
26833
|
+
const push = (v) => {
|
|
26834
|
+
if (v > 0 && v < 200) vals.push(v);
|
|
26835
|
+
};
|
|
26836
|
+
for (const m of text.matchAll(/(?:padding|margin)(?:-(?:top|right|bottom|left))?\s*:\s*(\d+)px/gi)) push(num(m, 1));
|
|
26837
|
+
for (const m of text.matchAll(/(?:padding|margin)(?:-(?:top|right|bottom|left))?\s*:\s*([\d.]+)rem/gi))
|
|
26838
|
+
push(Math.round(Number.parseFloat(cap2(m, 1)) * 16));
|
|
26839
|
+
for (const m of text.matchAll(/\b(?:p|px|py|pt|pb|pl|pr|m|mx|my|mt|mb|ml|mr|gap(?:-[xy])?|space-[xy])-([\d.]+)\b/g))
|
|
26840
|
+
push(Number.parseFloat(cap2(m, 1)) * 4);
|
|
26841
|
+
return vals;
|
|
26842
|
+
}
|
|
26843
|
+
function shadowIsGlow(value) {
|
|
26844
|
+
const first = (value.split(/,(?![^(]*\))/)[0] ?? "").trim();
|
|
26845
|
+
const colorMatch = first.match(/#[0-9a-f]{3,8}\b|rgba?\([^)]+\)|hsla?\([^)]+\)/i);
|
|
26846
|
+
if (!colorMatch) return false;
|
|
26847
|
+
const lengthsPart = first.replace(colorMatch[0], " ").replace(/\binset\b/gi, " ");
|
|
26848
|
+
const offsets = lengthsPart.match(/-?\d*\.?\d+(?:px|rem|em)?/g);
|
|
26849
|
+
if (!offsets || offsets.length < 3) return false;
|
|
26850
|
+
const x = Number.parseFloat(offsets[0] ?? "0");
|
|
26851
|
+
const y = Number.parseFloat(offsets[1] ?? "0");
|
|
26852
|
+
const blur = Number.parseFloat(offsets[2] ?? "0");
|
|
26853
|
+
if (!(x === 0 && y === 0 && blur > 4)) return false;
|
|
26854
|
+
const c = parseColor2(colorMatch[0]);
|
|
26855
|
+
return c !== null && hasChroma(c, 30);
|
|
26856
|
+
}
|
|
26857
|
+
function firstFamily(decl) {
|
|
26858
|
+
return (decl.split(",")[0] ?? "").trim().replace(/^['"]|['"]$/g, "").toLowerCase();
|
|
26859
|
+
}
|
|
26860
|
+
function googleFontFamilies(url) {
|
|
26861
|
+
const q = url.indexOf("?");
|
|
26862
|
+
if (q === -1) return [];
|
|
26863
|
+
const params = new URLSearchParams(url.slice(q + 1).replace(/&/g, "&"));
|
|
26864
|
+
const out = [];
|
|
26865
|
+
for (const value of params.getAll("family")) {
|
|
26866
|
+
for (const part of value.split("|")) out.push((part.split(":")[0] ?? "").trim().toLowerCase());
|
|
26867
|
+
}
|
|
26868
|
+
return out.filter(Boolean);
|
|
26869
|
+
}
|
|
26870
|
+
function allGoogleFontFamilies(text) {
|
|
26871
|
+
const out = [];
|
|
26872
|
+
for (const m of text.matchAll(/fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi))
|
|
26873
|
+
out.push(...googleFontFamilies(cap2(m, 0)));
|
|
26874
|
+
return out;
|
|
26875
|
+
}
|
|
26876
|
+
function lineOf(text, index) {
|
|
26877
|
+
let line = 1;
|
|
26878
|
+
for (let i = 0; i < index && i < text.length; i++) if (text[i] === "\n") line++;
|
|
26879
|
+
return line;
|
|
26880
|
+
}
|
|
26881
|
+
function detectSource(source) {
|
|
26882
|
+
const file = source.path;
|
|
26883
|
+
const text = blankAstroFrontmatter(source.text);
|
|
26884
|
+
const findings = [];
|
|
26885
|
+
const lines = text.split("\n");
|
|
26886
|
+
for (const matcher of LINE_MATCHERS) {
|
|
26887
|
+
for (let i = 0; i < lines.length; i++) {
|
|
26888
|
+
const line = lines[i] ?? "";
|
|
26889
|
+
for (const m of line.matchAll(matcher.regex)) {
|
|
26890
|
+
if (matcher.test(m, line)) findings.push({ id: matcher.id, snippet: matcher.fmt(m, line), file, line: i + 1 });
|
|
26891
|
+
}
|
|
26892
|
+
}
|
|
26893
|
+
}
|
|
26894
|
+
for (const analyzer of ANALYZERS) {
|
|
26895
|
+
if (analyzer.scope === "file") findings.push(...analyzer.run(text, file));
|
|
26896
|
+
}
|
|
26897
|
+
return dedupe(findings);
|
|
26898
|
+
}
|
|
26899
|
+
function detectPage(sources) {
|
|
26900
|
+
if (sources.length === 0) return [];
|
|
26901
|
+
const combined = sources.map((s) => blankAstroFrontmatter(s.text)).join("\n");
|
|
26902
|
+
const pageFile = sources.find((s) => s.path === "index.astro" || s.path.endsWith("/index.astro"))?.path ?? sources[0]?.path ?? "index.astro";
|
|
26903
|
+
const findings = [];
|
|
26904
|
+
for (const analyzer of ANALYZERS) {
|
|
26905
|
+
if (analyzer.scope === "page") findings.push(...analyzer.run(combined, pageFile));
|
|
26906
|
+
}
|
|
26907
|
+
return dedupe(findings);
|
|
26908
|
+
}
|
|
26909
|
+
function dedupe(findings) {
|
|
26910
|
+
const out = [];
|
|
26911
|
+
for (const f of findings) {
|
|
26912
|
+
const dupe = out.some(
|
|
26913
|
+
(d) => d.id === f.id && d.snippet === f.snippet && Math.abs((d.line ?? 0) - (f.line ?? 0)) <= 2
|
|
26914
|
+
);
|
|
26915
|
+
if (!dupe) out.push(f);
|
|
26916
|
+
}
|
|
26917
|
+
return out;
|
|
26918
|
+
}
|
|
26919
|
+
|
|
26920
|
+
// src/engine/landing/lib/critique.ts
|
|
26921
|
+
var FAMILIES = ["typography", "color", "borders_depth", "motion", "spacing", "copy", "integrity"];
|
|
26922
|
+
function round4(n) {
|
|
26923
|
+
return Math.round(n * 100) / 100;
|
|
26924
|
+
}
|
|
26925
|
+
function clamp012(n) {
|
|
26926
|
+
return Math.max(0, Math.min(1, n));
|
|
26927
|
+
}
|
|
26928
|
+
function applyBrand(id, snippet, severity, brand) {
|
|
26929
|
+
const meta = RULE_META[id];
|
|
26930
|
+
if (!meta?.brandAware || !brand.hasTokens) return { severity, brandDrift: false };
|
|
26931
|
+
const matched = meta.brandAware === "font" && brandCommitsFont(brand, snippet) || meta.brandAware === "color" && id === "ai-color-palette" && brandCommitsAiHue(brand) || meta.brandAware === "color" && id === "cream-palette" && brandCommitsCream(brand);
|
|
26932
|
+
return matched ? { severity: "advisory", brandDrift: true } : { severity, brandDrift: false };
|
|
26933
|
+
}
|
|
26934
|
+
function critiqueLanding(input) {
|
|
26935
|
+
const sources = Array.isArray(input.sources) ? input.sources : [];
|
|
26936
|
+
const findings = [];
|
|
26937
|
+
const raws = [];
|
|
26938
|
+
for (const source of sources) raws.push(...detectSource(source));
|
|
26939
|
+
raws.push(...detectPage(sources));
|
|
26940
|
+
for (const raw of raws) {
|
|
26941
|
+
const meta = RULE_META[raw.id];
|
|
26942
|
+
if (!meta) continue;
|
|
26943
|
+
const { severity, brandDrift } = applyBrand(raw.id, raw.snippet, meta.severity, input.brand);
|
|
26944
|
+
findings.push({
|
|
26945
|
+
id: raw.id,
|
|
26946
|
+
severity,
|
|
26947
|
+
family: meta.family,
|
|
26948
|
+
snippet: raw.snippet,
|
|
26949
|
+
file: raw.file,
|
|
26950
|
+
line: raw.line,
|
|
26951
|
+
note: meta.note,
|
|
26952
|
+
...brandDrift ? { brandDrift: true } : {}
|
|
26953
|
+
});
|
|
26954
|
+
}
|
|
26955
|
+
const dimensions = FAMILIES.map((family) => {
|
|
26956
|
+
const fam = findings.filter((f) => f.family === family);
|
|
26957
|
+
const penalty = fam.reduce((s, f) => s + SEVERITY_WEIGHT[f.severity], 0);
|
|
26958
|
+
const score = round4(clamp012(1 - penalty));
|
|
26959
|
+
const note = fam.length === 0 ? "clean" : `${fam.length} finding(s): ${describeCounts(fam)}`;
|
|
26960
|
+
return { dimension: family, score, note };
|
|
26961
|
+
});
|
|
26962
|
+
const overall = round4(dimensions.reduce((s, d) => s + d.score, 0) / dimensions.length);
|
|
26963
|
+
const counts = {
|
|
26964
|
+
block: findings.filter((f) => f.severity === "block").length,
|
|
26965
|
+
warn: findings.filter((f) => f.severity === "warn").length,
|
|
26966
|
+
advisory: findings.filter((f) => f.severity === "advisory").length
|
|
26967
|
+
};
|
|
26968
|
+
return { advisory: true, overall, dimensions, findings, counts };
|
|
26969
|
+
}
|
|
26970
|
+
function describeCounts(findings) {
|
|
26971
|
+
const b = findings.filter((f) => f.severity === "block").length;
|
|
26972
|
+
const w = findings.filter((f) => f.severity === "warn").length;
|
|
26973
|
+
const a = findings.filter((f) => f.severity === "advisory").length;
|
|
26974
|
+
return [b ? `${b} block` : "", w ? `${w} warn` : "", a ? `${a} advisory` : ""].filter(Boolean).join(", ");
|
|
26975
|
+
}
|
|
26976
|
+
|
|
26977
|
+
// src/commands/landing/snapshot.ts
|
|
26978
|
+
import { mkdir as mkdir7, rename as rename2, writeFile as writeFile10 } from "fs/promises";
|
|
26979
|
+
import path24 from "path";
|
|
26980
|
+
var CRITIC_VERSION = "1";
|
|
26981
|
+
function critiqueCacheDir(projectRoot) {
|
|
26982
|
+
return path24.join(projectRoot, ".cache", "landing-critique");
|
|
26983
|
+
}
|
|
26984
|
+
function snapshotPath(projectRoot, slug) {
|
|
26985
|
+
return path24.join(critiqueCacheDir(projectRoot), `${slug}.json`);
|
|
26986
|
+
}
|
|
26987
|
+
async function writeCritiqueSnapshot(projectRoot, snapshot) {
|
|
26988
|
+
await mkdir7(critiqueCacheDir(projectRoot), { recursive: true });
|
|
26989
|
+
const dest = snapshotPath(projectRoot, snapshot.slug);
|
|
26990
|
+
const tmp = `${dest}.tmp`;
|
|
26991
|
+
await writeFile10(tmp, `${JSON.stringify(snapshot, null, 2)}
|
|
26992
|
+
`, "utf8");
|
|
26993
|
+
await rename2(tmp, dest);
|
|
26994
|
+
}
|
|
26995
|
+
|
|
26996
|
+
// src/commands/landing/source-version.ts
|
|
26997
|
+
import { readdir as readdir7, readFile as readFile21, stat as stat5 } from "fs/promises";
|
|
26998
|
+
import path25 from "path";
|
|
26999
|
+
async function landingSourceRelPaths(landingDir) {
|
|
27000
|
+
const rel = [];
|
|
27001
|
+
if (await isFile(path25.join(landingDir, "index.astro"))) rel.push("index.astro");
|
|
27002
|
+
const componentsDir = path25.join(landingDir, "_components");
|
|
27003
|
+
for (const abs of await walkAstro(componentsDir)) {
|
|
27004
|
+
rel.push(path25.relative(landingDir, abs).split(path25.sep).join("/"));
|
|
27005
|
+
}
|
|
27006
|
+
return rel.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
27007
|
+
}
|
|
27008
|
+
async function readLandingSources(landingDir) {
|
|
27009
|
+
const rel = await landingSourceRelPaths(landingDir);
|
|
27010
|
+
const out = [];
|
|
27011
|
+
for (const r of rel) out.push({ path: r, text: await readFile21(path25.join(landingDir, r), "utf8") });
|
|
27012
|
+
return out;
|
|
27013
|
+
}
|
|
27014
|
+
async function computeLandingSourceSha(landingDir) {
|
|
27015
|
+
const rel = await landingSourceRelPaths(landingDir);
|
|
27016
|
+
const parts = [];
|
|
27017
|
+
for (const r of rel) {
|
|
27018
|
+
let bytes;
|
|
27019
|
+
try {
|
|
27020
|
+
bytes = await readFile21(path25.join(landingDir, r));
|
|
27021
|
+
} catch {
|
|
27022
|
+
bytes = Buffer.alloc(0);
|
|
27023
|
+
}
|
|
27024
|
+
parts.push(Buffer.from(`${r}\0${bytes.length}\0`), bytes);
|
|
27025
|
+
}
|
|
27026
|
+
return sha256Hex(Buffer.concat(parts));
|
|
27027
|
+
}
|
|
27028
|
+
async function isFile(p) {
|
|
27029
|
+
try {
|
|
27030
|
+
return (await stat5(p)).isFile();
|
|
27031
|
+
} catch {
|
|
27032
|
+
return false;
|
|
27033
|
+
}
|
|
27034
|
+
}
|
|
27035
|
+
async function walkAstro(dir) {
|
|
27036
|
+
let entries;
|
|
27037
|
+
try {
|
|
27038
|
+
entries = await readdir7(dir, { withFileTypes: true });
|
|
27039
|
+
} catch {
|
|
27040
|
+
return [];
|
|
27041
|
+
}
|
|
27042
|
+
const out = [];
|
|
27043
|
+
for (const entry of entries) {
|
|
27044
|
+
const abs = path25.join(dir, entry.name);
|
|
27045
|
+
if (entry.isDirectory()) out.push(...await walkAstro(abs));
|
|
27046
|
+
else if (entry.isFile() && entry.name.endsWith(".astro")) out.push(abs);
|
|
27047
|
+
}
|
|
27048
|
+
return out;
|
|
27049
|
+
}
|
|
27050
|
+
|
|
27051
|
+
// src/commands/landing/critique.ts
|
|
27052
|
+
registerSchema({
|
|
27053
|
+
command: "landing.critique",
|
|
27054
|
+
description: "Start here: `baker landing critique my-landing` after building or editing a landing. Deterministic design-quality critic (ADVISORY \u2014 findings never fail it). Flags the known AI 'slop' tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images\u2026) tiered block/warn/advisory, and records the critique the publish quality gate requires.",
|
|
27055
|
+
args: {
|
|
27056
|
+
slug: { type: "string", description: "Landing slug (folder under src/pages/)", required: true },
|
|
27057
|
+
full: {
|
|
27058
|
+
type: "boolean",
|
|
27059
|
+
description: "Also print advisory-tier findings (default: block + warn only)",
|
|
27060
|
+
required: false
|
|
27061
|
+
}
|
|
27062
|
+
}
|
|
27063
|
+
});
|
|
27064
|
+
var SLUG_RE = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
27065
|
+
function fail4(code, message, fix) {
|
|
27066
|
+
process.stderr.write(
|
|
27067
|
+
`${JSON.stringify({ ok: false, error: { code, message, ...fix ? { fix } : {} } }, null, 2)}
|
|
27068
|
+
`
|
|
27069
|
+
);
|
|
27070
|
+
process.exit(2);
|
|
27071
|
+
}
|
|
27072
|
+
var critiqueCommand2 = defineCommand133({
|
|
27073
|
+
meta: {
|
|
27074
|
+
name: "critique",
|
|
27075
|
+
description: "Start here: `baker landing critique <slug>` after building or editing a landing. Deterministic design-quality critic (ADVISORY \u2014 findings never fail it). Flags the known AI design tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images) tiered block/warn/advisory, respecting the client's BRAND.md as the allowlist. Also records the critique that publishing requires \u2014 run it before finishing a landing."
|
|
27076
|
+
},
|
|
27077
|
+
args: {
|
|
27078
|
+
slug: { type: "positional", required: true, description: "Landing slug (folder under src/pages/)" },
|
|
27079
|
+
full: { type: "boolean", description: "Also print advisory-tier findings", default: false }
|
|
27080
|
+
},
|
|
27081
|
+
async run({ args }) {
|
|
27082
|
+
const slug = String(args.slug);
|
|
27083
|
+
const projectRoot = process.cwd();
|
|
27084
|
+
if (!SLUG_RE.test(slug) || slug.includes("..")) {
|
|
27085
|
+
fail4(
|
|
27086
|
+
"INVALID_SLUG",
|
|
27087
|
+
`"${slug}" is not a landing slug \u2014 use the folder name directly under src/pages/ (letters, digits, dashes; not a path, not a _-private folder).`,
|
|
27088
|
+
{ availableSlugs: await listLandingSlugs(projectRoot) }
|
|
27089
|
+
);
|
|
27090
|
+
}
|
|
27091
|
+
const landingDir = path26.resolve(projectRoot, "src", "pages", slug);
|
|
27092
|
+
if (!await isDir(landingDir)) {
|
|
27093
|
+
fail4("NOT_FOUND", `No landing at src/pages/${slug}/`, {
|
|
27094
|
+
availableSlugs: await listLandingSlugs(projectRoot)
|
|
27095
|
+
});
|
|
27096
|
+
}
|
|
27097
|
+
const [sources, brand, sourceSha] = await Promise.all([
|
|
27098
|
+
readLandingSources(landingDir),
|
|
27099
|
+
loadBrandTokens(projectRoot),
|
|
27100
|
+
computeLandingSourceSha(landingDir)
|
|
27101
|
+
]);
|
|
27102
|
+
const report = critiqueLanding({ slug, sources, brand });
|
|
27103
|
+
let snapshotFailed = false;
|
|
27104
|
+
try {
|
|
27105
|
+
await writeCritiqueSnapshot(projectRoot, {
|
|
27106
|
+
slug,
|
|
27107
|
+
sourceSha,
|
|
27108
|
+
criticVersion: CRITIC_VERSION,
|
|
27109
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27110
|
+
blockCount: report.counts.block,
|
|
27111
|
+
warnCount: report.counts.warn
|
|
27112
|
+
});
|
|
27113
|
+
} catch {
|
|
27114
|
+
snapshotFailed = true;
|
|
27115
|
+
}
|
|
27116
|
+
const shown = report.findings.filter((f) => args.full ? true : f.severity !== "advisory");
|
|
27117
|
+
const hints = [
|
|
27118
|
+
report.counts.block > 0 ? "Block-tier tells are held at publish \u2014 fix them. Warn/advisory are guidance. Scores are 0\u20131 (higher is better)." : "No block-tier tells. Warn/advisory findings are guidance, not gates. Re-run after edits so the publish gate stays fresh.",
|
|
27119
|
+
...snapshotFailed ? [
|
|
27120
|
+
"Could not record the critique snapshot (.cache/ not writable?) \u2014 the publish quality gate will still report this landing as un-critiqued."
|
|
27121
|
+
] : []
|
|
27122
|
+
];
|
|
27123
|
+
process.stdout.write(
|
|
27124
|
+
`${JSON.stringify(
|
|
27125
|
+
{
|
|
27126
|
+
ok: true,
|
|
27127
|
+
data: {
|
|
27128
|
+
advisory: true,
|
|
27129
|
+
slug,
|
|
27130
|
+
overall: report.overall,
|
|
27131
|
+
counts: report.counts,
|
|
27132
|
+
dimensions: report.dimensions,
|
|
27133
|
+
findings: shown.map(present)
|
|
27134
|
+
},
|
|
27135
|
+
hints
|
|
27136
|
+
},
|
|
27137
|
+
null,
|
|
27138
|
+
2
|
|
27139
|
+
)}
|
|
27140
|
+
`
|
|
27141
|
+
);
|
|
27142
|
+
}
|
|
27143
|
+
});
|
|
27144
|
+
async function listLandingSlugs(projectRoot) {
|
|
27145
|
+
try {
|
|
27146
|
+
const entries = await readdir8(path26.join(projectRoot, "src", "pages"), { withFileTypes: true });
|
|
27147
|
+
return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).map((e) => e.name).sort();
|
|
27148
|
+
} catch {
|
|
27149
|
+
return [];
|
|
27150
|
+
}
|
|
27151
|
+
}
|
|
27152
|
+
function present(f) {
|
|
27153
|
+
return {
|
|
27154
|
+
id: f.id,
|
|
27155
|
+
severity: f.severity,
|
|
27156
|
+
family: f.family,
|
|
27157
|
+
where: f.line ? `${f.file}:${f.line}` : f.file,
|
|
27158
|
+
snippet: f.snippet,
|
|
27159
|
+
note: f.note,
|
|
27160
|
+
...f.brandDrift ? { brandDrift: true } : {}
|
|
27161
|
+
};
|
|
27162
|
+
}
|
|
27163
|
+
async function isDir(p) {
|
|
27164
|
+
try {
|
|
27165
|
+
return (await stat6(p)).isDirectory();
|
|
27166
|
+
} catch {
|
|
27167
|
+
return false;
|
|
27168
|
+
}
|
|
27169
|
+
}
|
|
27170
|
+
|
|
27171
|
+
// src/commands/landing/index.ts
|
|
27172
|
+
var landingCommand = defineCommand134({
|
|
27173
|
+
meta: {
|
|
27174
|
+
name: "landing",
|
|
27175
|
+
description: `Design-quality tools for landing pages (src/pages/<slug>/).
|
|
27176
|
+
|
|
27177
|
+
Start here: \`baker landing critique <slug>\` after building or editing a landing.
|
|
27178
|
+
|
|
27179
|
+
Subcommands:
|
|
27180
|
+
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.`
|
|
27181
|
+
},
|
|
27182
|
+
subCommands: {
|
|
27183
|
+
critique: critiqueCommand2
|
|
27184
|
+
}
|
|
27185
|
+
});
|
|
27186
|
+
|
|
27187
|
+
// src/commands/mcp/index.ts
|
|
27188
|
+
import { defineCommand as defineCommand135 } from "citty";
|
|
26015
27189
|
var SCOPES = ["user", "user_org", "company", "org"];
|
|
26016
27190
|
function parseScope(raw) {
|
|
26017
27191
|
const scope = raw === void 0 ? "company" : String(raw);
|
|
@@ -26037,7 +27211,7 @@ function parseHeaders(raw) {
|
|
|
26037
27211
|
}
|
|
26038
27212
|
return Object.keys(headers).length > 0 ? headers : void 0;
|
|
26039
27213
|
}
|
|
26040
|
-
function
|
|
27214
|
+
function fail5(err) {
|
|
26041
27215
|
if (err instanceof ApiError) {
|
|
26042
27216
|
writeJson({ ok: false, error: { code: err.code, message: err.message } });
|
|
26043
27217
|
process.exit(1);
|
|
@@ -26050,7 +27224,7 @@ registerSchema({
|
|
|
26050
27224
|
description: "List every third-party tool this chat can reach: managed integrations (Attio, Slack, Gmail, Google Sheets, \u2026) plus custom MCP servers. Start here when the user mentions an external tool.",
|
|
26051
27225
|
args: {}
|
|
26052
27226
|
});
|
|
26053
|
-
var connectedCommand =
|
|
27227
|
+
var connectedCommand = defineCommand135({
|
|
26054
27228
|
meta: {
|
|
26055
27229
|
name: "connected",
|
|
26056
27230
|
description: `List all connected third-party tools \u2014 managed integrations plus custom MCP servers.
|
|
@@ -26088,7 +27262,7 @@ A tool the user names that is NOT listed here is simply not connected yet.`
|
|
|
26088
27262
|
hints
|
|
26089
27263
|
});
|
|
26090
27264
|
} catch (err) {
|
|
26091
|
-
|
|
27265
|
+
fail5(err);
|
|
26092
27266
|
}
|
|
26093
27267
|
}
|
|
26094
27268
|
});
|
|
@@ -26097,7 +27271,7 @@ registerSchema({
|
|
|
26097
27271
|
description: "List the custom MCP servers this company's chats see (org + company + your own user scope).",
|
|
26098
27272
|
args: {}
|
|
26099
27273
|
});
|
|
26100
|
-
var listCommand8 =
|
|
27274
|
+
var listCommand8 = defineCommand135({
|
|
26101
27275
|
meta: { name: "list", description: "List custom MCP servers visible to this company's chats." },
|
|
26102
27276
|
run: async () => {
|
|
26103
27277
|
try {
|
|
@@ -26108,7 +27282,7 @@ var listCommand8 = defineCommand133({
|
|
|
26108
27282
|
);
|
|
26109
27283
|
writeJson({ ok: true, data: data.servers });
|
|
26110
27284
|
} catch (err) {
|
|
26111
|
-
|
|
27285
|
+
fail5(err);
|
|
26112
27286
|
}
|
|
26113
27287
|
}
|
|
26114
27288
|
});
|
|
@@ -26122,7 +27296,7 @@ registerSchema({
|
|
|
26122
27296
|
header: { type: "string", description: 'Auth header "Key: Value" (repeatable)', required: false }
|
|
26123
27297
|
}
|
|
26124
27298
|
});
|
|
26125
|
-
var addCommand =
|
|
27299
|
+
var addCommand = defineCommand135({
|
|
26126
27300
|
meta: {
|
|
26127
27301
|
name: "add",
|
|
26128
27302
|
description: `Register a custom MCP server. Tools appear as mcp__<name>__* on the NEXT message.
|
|
@@ -26154,7 +27328,7 @@ Examples:
|
|
|
26154
27328
|
});
|
|
26155
27329
|
writeJson({ ok: true, data });
|
|
26156
27330
|
} catch (err) {
|
|
26157
|
-
|
|
27331
|
+
fail5(err);
|
|
26158
27332
|
}
|
|
26159
27333
|
}
|
|
26160
27334
|
});
|
|
@@ -26163,7 +27337,7 @@ registerSchema({
|
|
|
26163
27337
|
description: "Remove a company custom MCP server by name.",
|
|
26164
27338
|
args: { name: { type: "string", description: "Server name to remove", required: true } }
|
|
26165
27339
|
});
|
|
26166
|
-
var removeCommand4 =
|
|
27340
|
+
var removeCommand4 = defineCommand135({
|
|
26167
27341
|
meta: {
|
|
26168
27342
|
name: "remove",
|
|
26169
27343
|
description: `Remove a company custom MCP server by name.
|
|
@@ -26181,11 +27355,11 @@ Example:
|
|
|
26181
27355
|
});
|
|
26182
27356
|
writeJson({ ok: true, data });
|
|
26183
27357
|
} catch (err) {
|
|
26184
|
-
|
|
27358
|
+
fail5(err);
|
|
26185
27359
|
}
|
|
26186
27360
|
}
|
|
26187
27361
|
});
|
|
26188
|
-
var mcpCommand =
|
|
27362
|
+
var mcpCommand = defineCommand135({
|
|
26189
27363
|
meta: {
|
|
26190
27364
|
name: "mcp",
|
|
26191
27365
|
description: `Third-party tools for this company \u2014 see what's connected, register custom HTTPS MCP endpoints.
|
|
@@ -26211,10 +27385,10 @@ Full guide: __tooling__/docs/tools/baker/mcp.md`
|
|
|
26211
27385
|
});
|
|
26212
27386
|
|
|
26213
27387
|
// src/commands/research/index.ts
|
|
26214
|
-
import { defineCommand as
|
|
27388
|
+
import { defineCommand as defineCommand146 } from "citty";
|
|
26215
27389
|
|
|
26216
27390
|
// src/commands/research/advertisers.ts
|
|
26217
|
-
import { defineCommand as
|
|
27391
|
+
import { defineCommand as defineCommand136 } from "citty";
|
|
26218
27392
|
|
|
26219
27393
|
// src/commands/research/output.ts
|
|
26220
27394
|
var RESEARCH_DATA_NOTE = "Estimates based on third-party SERP data \u2014 not exact figures. Use for directional insights, not precise measurement.";
|
|
@@ -26327,7 +27501,7 @@ var FIELDS3 = {
|
|
|
26327
27501
|
etv: "Estimated traffic value (USD)",
|
|
26328
27502
|
visibility: "SERP visibility score (0-1)"
|
|
26329
27503
|
};
|
|
26330
|
-
var advertisersCommand =
|
|
27504
|
+
var advertisersCommand = defineCommand136({
|
|
26331
27505
|
meta: {
|
|
26332
27506
|
name: "advertisers",
|
|
26333
27507
|
description: `Find domains competing for a keyword in Google SERPs.
|
|
@@ -26374,7 +27548,7 @@ Examples:
|
|
|
26374
27548
|
});
|
|
26375
27549
|
|
|
26376
27550
|
// src/commands/research/autocomplete.ts
|
|
26377
|
-
import { defineCommand as
|
|
27551
|
+
import { defineCommand as defineCommand137 } from "citty";
|
|
26378
27552
|
registerSchema({
|
|
26379
27553
|
command: "research.autocomplete",
|
|
26380
27554
|
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).",
|
|
@@ -26397,7 +27571,7 @@ registerSchema({
|
|
|
26397
27571
|
var FIELDS4 = {
|
|
26398
27572
|
suggestion: "Autocomplete suggestion from Google"
|
|
26399
27573
|
};
|
|
26400
|
-
var autocompleteCommand =
|
|
27574
|
+
var autocompleteCommand = defineCommand137({
|
|
26401
27575
|
meta: {
|
|
26402
27576
|
name: "autocomplete",
|
|
26403
27577
|
description: `Get Google Autocomplete suggestions for keyword expansion.
|
|
@@ -26443,7 +27617,7 @@ Examples:
|
|
|
26443
27617
|
});
|
|
26444
27618
|
|
|
26445
27619
|
// src/commands/research/countries.ts
|
|
26446
|
-
import { defineCommand as
|
|
27620
|
+
import { defineCommand as defineCommand138 } from "citty";
|
|
26447
27621
|
registerSchema({
|
|
26448
27622
|
command: "research.countries",
|
|
26449
27623
|
description: "List all supported country codes for --location flag in research commands.",
|
|
@@ -26500,7 +27674,7 @@ var FIELDS5 = {
|
|
|
26500
27674
|
code: "Country code to pass as --location",
|
|
26501
27675
|
name: "Country name"
|
|
26502
27676
|
};
|
|
26503
|
-
var countriesCommand =
|
|
27677
|
+
var countriesCommand = defineCommand138({
|
|
26504
27678
|
meta: {
|
|
26505
27679
|
name: "countries",
|
|
26506
27680
|
description: "List all supported country codes for --location flag."
|
|
@@ -26511,7 +27685,7 @@ var countriesCommand = defineCommand136({
|
|
|
26511
27685
|
});
|
|
26512
27686
|
|
|
26513
27687
|
// src/commands/research/intent.ts
|
|
26514
|
-
import { defineCommand as
|
|
27688
|
+
import { defineCommand as defineCommand139 } from "citty";
|
|
26515
27689
|
registerSchema({
|
|
26516
27690
|
command: "research.intent",
|
|
26517
27691
|
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.",
|
|
@@ -26534,7 +27708,7 @@ var FIELDS6 = {
|
|
|
26534
27708
|
intent: "Primary Google Search intent: informational, navigational, commercial, transactional",
|
|
26535
27709
|
probability: "Confidence score 0.0-1.0"
|
|
26536
27710
|
};
|
|
26537
|
-
var intentCommand =
|
|
27711
|
+
var intentCommand = defineCommand139({
|
|
26538
27712
|
meta: {
|
|
26539
27713
|
name: "intent",
|
|
26540
27714
|
description: `Classify Google Search intent for keywords. Returns intent type and confidence.
|
|
@@ -26582,7 +27756,7 @@ Examples:
|
|
|
26582
27756
|
});
|
|
26583
27757
|
|
|
26584
27758
|
// src/commands/research/keyword-gap.ts
|
|
26585
|
-
import { defineCommand as
|
|
27759
|
+
import { defineCommand as defineCommand140 } from "citty";
|
|
26586
27760
|
registerSchema({
|
|
26587
27761
|
command: "research.keyword-gap",
|
|
26588
27762
|
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.",
|
|
@@ -26611,7 +27785,7 @@ var FIELDS7 = {
|
|
|
26611
27785
|
cpc: "Cost per click USD",
|
|
26612
27786
|
their_position: "Competitor's ranking position"
|
|
26613
27787
|
};
|
|
26614
|
-
var keywordGapCommand =
|
|
27788
|
+
var keywordGapCommand = defineCommand140({
|
|
26615
27789
|
meta: {
|
|
26616
27790
|
name: "keyword-gap",
|
|
26617
27791
|
description: `Find keywords a competitor has that you don't. Supports pagination via --offset.
|
|
@@ -26685,7 +27859,7 @@ Examples:
|
|
|
26685
27859
|
});
|
|
26686
27860
|
|
|
26687
27861
|
// src/commands/research/keywords-for-site.ts
|
|
26688
|
-
import { defineCommand as
|
|
27862
|
+
import { defineCommand as defineCommand141 } from "citty";
|
|
26689
27863
|
registerSchema({
|
|
26690
27864
|
command: "research.keywords-for-site",
|
|
26691
27865
|
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.",
|
|
@@ -26718,7 +27892,7 @@ var FIELDS8 = {
|
|
|
26718
27892
|
competition: "LOW, MEDIUM, or HIGH",
|
|
26719
27893
|
competition_index: "Competition score 0-100"
|
|
26720
27894
|
};
|
|
26721
|
-
var keywordsForSiteCommand =
|
|
27895
|
+
var keywordsForSiteCommand = defineCommand141({
|
|
26722
27896
|
meta: {
|
|
26723
27897
|
name: "keywords-for-site",
|
|
26724
27898
|
description: `Get keywords a competitor targets in Google. Use --type to filter paid/organic.
|
|
@@ -26771,7 +27945,7 @@ Examples:
|
|
|
26771
27945
|
});
|
|
26772
27946
|
|
|
26773
27947
|
// src/commands/research/languages.ts
|
|
26774
|
-
import { defineCommand as
|
|
27948
|
+
import { defineCommand as defineCommand142 } from "citty";
|
|
26775
27949
|
registerSchema({
|
|
26776
27950
|
command: "research.languages",
|
|
26777
27951
|
description: "List all supported language codes for --language flag in research commands.",
|
|
@@ -26801,7 +27975,7 @@ var FIELDS9 = {
|
|
|
26801
27975
|
code: "Language code to pass as --language",
|
|
26802
27976
|
name: "Language name (also accepted by --language)"
|
|
26803
27977
|
};
|
|
26804
|
-
var languagesCommand2 =
|
|
27978
|
+
var languagesCommand2 = defineCommand142({
|
|
26805
27979
|
meta: {
|
|
26806
27980
|
name: "languages",
|
|
26807
27981
|
description: "List all supported language codes for --language flag."
|
|
@@ -26812,7 +27986,7 @@ var languagesCommand2 = defineCommand140({
|
|
|
26812
27986
|
});
|
|
26813
27987
|
|
|
26814
27988
|
// src/commands/research/lighthouse.ts
|
|
26815
|
-
import { defineCommand as
|
|
27989
|
+
import { defineCommand as defineCommand143 } from "citty";
|
|
26816
27990
|
registerSchema({
|
|
26817
27991
|
command: "research.lighthouse",
|
|
26818
27992
|
description: "Landing page performance audit. Returns metrics that affect Google Ads Quality Score and CPC.",
|
|
@@ -26831,7 +28005,7 @@ var FIELDS10 = {
|
|
|
26831
28005
|
speed_index_ms: "Speed Index in ms (good: < 3400)",
|
|
26832
28006
|
interactive_ms: "Time to Interactive in ms (good: < 3800)"
|
|
26833
28007
|
};
|
|
26834
|
-
var lighthouseCommand =
|
|
28008
|
+
var lighthouseCommand = defineCommand143({
|
|
26835
28009
|
meta: {
|
|
26836
28010
|
name: "lighthouse",
|
|
26837
28011
|
description: `Landing page performance audit. Metrics affecting Google Ads Quality Score.
|
|
@@ -26869,7 +28043,7 @@ Examples:
|
|
|
26869
28043
|
});
|
|
26870
28044
|
|
|
26871
28045
|
// src/commands/research/relevant-pages.ts
|
|
26872
|
-
import { defineCommand as
|
|
28046
|
+
import { defineCommand as defineCommand144 } from "citty";
|
|
26873
28047
|
registerSchema({
|
|
26874
28048
|
command: "research.relevant-pages",
|
|
26875
28049
|
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).",
|
|
@@ -26895,7 +28069,7 @@ var FIELDS11 = {
|
|
|
26895
28069
|
keywords: "Total organic keywords the page ranks for",
|
|
26896
28070
|
top_10: "Keywords in positions 1-10"
|
|
26897
28071
|
};
|
|
26898
|
-
var relevantPagesCommand =
|
|
28072
|
+
var relevantPagesCommand = defineCommand144({
|
|
26899
28073
|
meta: {
|
|
26900
28074
|
name: "relevant-pages",
|
|
26901
28075
|
description: `Get the top pages of a competitor domain with traffic data.
|
|
@@ -26941,7 +28115,7 @@ Examples:
|
|
|
26941
28115
|
});
|
|
26942
28116
|
|
|
26943
28117
|
// src/commands/research/web.ts
|
|
26944
|
-
import { defineCommand as
|
|
28118
|
+
import { defineCommand as defineCommand145 } from "citty";
|
|
26945
28119
|
registerSchema({
|
|
26946
28120
|
command: "research.web",
|
|
26947
28121
|
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).",
|
|
@@ -26992,7 +28166,7 @@ async function runDeepResearch(question) {
|
|
|
26992
28166
|
}
|
|
26993
28167
|
throw new Error("Deep research timed out");
|
|
26994
28168
|
}
|
|
26995
|
-
var webCommand =
|
|
28169
|
+
var webCommand = defineCommand145({
|
|
26996
28170
|
meta: {
|
|
26997
28171
|
name: "web",
|
|
26998
28172
|
description: `Search the web with AI to answer any open-ended marketing question. Uses live internet data via Google Search.
|
|
@@ -27052,7 +28226,7 @@ Examples:
|
|
|
27052
28226
|
});
|
|
27053
28227
|
|
|
27054
28228
|
// src/commands/research/index.ts
|
|
27055
|
-
var researchCommand =
|
|
28229
|
+
var researchCommand = defineCommand146({
|
|
27056
28230
|
meta: {
|
|
27057
28231
|
name: "research",
|
|
27058
28232
|
description: `Competitive intelligence and AI-powered research commands.
|
|
@@ -27093,10 +28267,10 @@ Full guide: __tooling__/docs/tools/baker/research.md`
|
|
|
27093
28267
|
});
|
|
27094
28268
|
|
|
27095
28269
|
// src/commands/scheduled-actions/index.ts
|
|
27096
|
-
import { defineCommand as
|
|
28270
|
+
import { defineCommand as defineCommand153 } from "citty";
|
|
27097
28271
|
|
|
27098
28272
|
// src/commands/scheduled-actions/create.ts
|
|
27099
|
-
import { defineCommand as
|
|
28273
|
+
import { defineCommand as defineCommand147 } from "citty";
|
|
27100
28274
|
|
|
27101
28275
|
// src/commands/scheduled-actions/shared.ts
|
|
27102
28276
|
var TEMP_SCHEDULED_ACTION_PREFIX = "temp_sched_";
|
|
@@ -27211,7 +28385,7 @@ registerSchema({
|
|
|
27211
28385
|
prompt: { type: "string", description: "Additional prompt instructions for the spawned agent", required: false }
|
|
27212
28386
|
}
|
|
27213
28387
|
});
|
|
27214
|
-
var createCommand2 =
|
|
28388
|
+
var createCommand2 = defineCommand147({
|
|
27215
28389
|
meta: {
|
|
27216
28390
|
name: "create",
|
|
27217
28391
|
description: 'Stage a scheduled action. Example: baker scheduled-actions create --name "Weekly report" --description "..." --cron "0 9 * * MON"'
|
|
@@ -27260,7 +28434,7 @@ var createCommand2 = defineCommand145({
|
|
|
27260
28434
|
});
|
|
27261
28435
|
|
|
27262
28436
|
// src/commands/scheduled-actions/delete.ts
|
|
27263
|
-
import { defineCommand as
|
|
28437
|
+
import { defineCommand as defineCommand148 } from "citty";
|
|
27264
28438
|
registerSchema({
|
|
27265
28439
|
command: "scheduled-actions.delete",
|
|
27266
28440
|
description: "Stage deletion of a published scheduled action or cancellation of a temp_sched_* draft creation.",
|
|
@@ -27268,7 +28442,7 @@ registerSchema({
|
|
|
27268
28442
|
id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
|
|
27269
28443
|
}
|
|
27270
28444
|
});
|
|
27271
|
-
var deleteCommand2 =
|
|
28445
|
+
var deleteCommand2 = defineCommand148({
|
|
27272
28446
|
meta: {
|
|
27273
28447
|
name: "delete",
|
|
27274
28448
|
description: "Stage scheduled action deletion. Example: baker scheduled-actions delete <id-or-temp_sched_id>"
|
|
@@ -27297,7 +28471,7 @@ var deleteCommand2 = defineCommand146({
|
|
|
27297
28471
|
});
|
|
27298
28472
|
|
|
27299
28473
|
// src/commands/scheduled-actions/get.ts
|
|
27300
|
-
import { defineCommand as
|
|
28474
|
+
import { defineCommand as defineCommand149 } from "citty";
|
|
27301
28475
|
registerSchema({
|
|
27302
28476
|
command: "scheduled-actions.get",
|
|
27303
28477
|
description: "Get a published scheduled action or a temp_sched_* draft-created scheduled action.",
|
|
@@ -27305,7 +28479,7 @@ registerSchema({
|
|
|
27305
28479
|
id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
|
|
27306
28480
|
}
|
|
27307
28481
|
});
|
|
27308
|
-
var getCommand3 =
|
|
28482
|
+
var getCommand3 = defineCommand149({
|
|
27309
28483
|
meta: {
|
|
27310
28484
|
name: "get",
|
|
27311
28485
|
description: "Get a scheduled action. Example: baker scheduled-actions get <id-or-temp_sched_id>"
|
|
@@ -27342,13 +28516,13 @@ var getCommand3 = defineCommand147({
|
|
|
27342
28516
|
});
|
|
27343
28517
|
|
|
27344
28518
|
// src/commands/scheduled-actions/list.ts
|
|
27345
|
-
import { defineCommand as
|
|
28519
|
+
import { defineCommand as defineCommand150 } from "citty";
|
|
27346
28520
|
registerSchema({
|
|
27347
28521
|
command: "scheduled-actions.list",
|
|
27348
28522
|
description: "List published scheduled actions. Includes draft state when BAKER_CHAT_ID is set.",
|
|
27349
28523
|
args: {}
|
|
27350
28524
|
});
|
|
27351
|
-
var listCommand9 =
|
|
28525
|
+
var listCommand9 = defineCommand150({
|
|
27352
28526
|
meta: {
|
|
27353
28527
|
name: "list",
|
|
27354
28528
|
description: "List scheduled actions. Includes staged draft ops when BAKER_CHAT_ID is set."
|
|
@@ -27369,7 +28543,7 @@ var listCommand9 = defineCommand148({
|
|
|
27369
28543
|
});
|
|
27370
28544
|
|
|
27371
28545
|
// src/commands/scheduled-actions/trigger.ts
|
|
27372
|
-
import { defineCommand as
|
|
28546
|
+
import { defineCommand as defineCommand151 } from "citty";
|
|
27373
28547
|
registerSchema({
|
|
27374
28548
|
command: "scheduled-actions.trigger",
|
|
27375
28549
|
description: "Immediately trigger a published scheduled action. Does not require BAKER_CHAT_ID and rejects temp_sched_* IDs.",
|
|
@@ -27377,7 +28551,7 @@ registerSchema({
|
|
|
27377
28551
|
id: { type: "string", description: "Published scheduled action ID", required: true }
|
|
27378
28552
|
}
|
|
27379
28553
|
});
|
|
27380
|
-
var triggerCommand =
|
|
28554
|
+
var triggerCommand = defineCommand151({
|
|
27381
28555
|
meta: {
|
|
27382
28556
|
name: "trigger",
|
|
27383
28557
|
description: "Immediately trigger a published scheduled action. Example: baker scheduled-actions trigger <id>"
|
|
@@ -27414,7 +28588,7 @@ var triggerCommand = defineCommand149({
|
|
|
27414
28588
|
});
|
|
27415
28589
|
|
|
27416
28590
|
// src/commands/scheduled-actions/update.ts
|
|
27417
|
-
import { defineCommand as
|
|
28591
|
+
import { defineCommand as defineCommand152 } from "citty";
|
|
27418
28592
|
registerSchema({
|
|
27419
28593
|
command: "scheduled-actions.update",
|
|
27420
28594
|
description: "Stage an update to a published scheduled action or temp_sched_* draft-created scheduled action.",
|
|
@@ -27439,7 +28613,7 @@ registerSchema({
|
|
|
27439
28613
|
prompt: { type: "string", description: "Replacement additional spawned-agent instructions", required: false }
|
|
27440
28614
|
}
|
|
27441
28615
|
});
|
|
27442
|
-
var updateCommand2 =
|
|
28616
|
+
var updateCommand2 = defineCommand152({
|
|
27443
28617
|
meta: {
|
|
27444
28618
|
name: "update",
|
|
27445
28619
|
description: "Stage a scheduled action update. Example: baker scheduled-actions update <id> --enabled false"
|
|
@@ -27510,7 +28684,7 @@ var updateCommand2 = defineCommand150({
|
|
|
27510
28684
|
});
|
|
27511
28685
|
|
|
27512
28686
|
// src/commands/scheduled-actions/index.ts
|
|
27513
|
-
var scheduledActionsCommand =
|
|
28687
|
+
var scheduledActionsCommand = defineCommand153({
|
|
27514
28688
|
meta: {
|
|
27515
28689
|
name: "scheduled-actions",
|
|
27516
28690
|
description: `Manage Scheduled Actions. Subcommands: list, get, create, update, delete, trigger.
|
|
@@ -27537,8 +28711,8 @@ Full guide: __tooling__/docs/tools/baker/scheduled-actions.md`
|
|
|
27537
28711
|
});
|
|
27538
28712
|
|
|
27539
28713
|
// src/commands/schema.ts
|
|
27540
|
-
import { defineCommand as
|
|
27541
|
-
var schemaCommand =
|
|
28714
|
+
import { defineCommand as defineCommand154 } from "citty";
|
|
28715
|
+
var schemaCommand = defineCommand154({
|
|
27542
28716
|
meta: {
|
|
27543
28717
|
name: "schema",
|
|
27544
28718
|
description: "Inspect command argument schemas (for AI agent introspection). Lists all commands if no argument given. Example: baker schema images.search"
|
|
@@ -27580,7 +28754,7 @@ var schemaCommand = defineCommand152({
|
|
|
27580
28754
|
});
|
|
27581
28755
|
|
|
27582
28756
|
// src/commands/tags/index.ts
|
|
27583
|
-
import { defineCommand as
|
|
28757
|
+
import { defineCommand as defineCommand155 } from "citty";
|
|
27584
28758
|
|
|
27585
28759
|
// src/commands/tags/shared.ts
|
|
27586
28760
|
function failApi3(err) {
|
|
@@ -27646,7 +28820,7 @@ async function listTags(json) {
|
|
|
27646
28820
|
failApi3(err);
|
|
27647
28821
|
}
|
|
27648
28822
|
}
|
|
27649
|
-
var listCommand10 =
|
|
28823
|
+
var listCommand10 = defineCommand155({
|
|
27650
28824
|
meta: {
|
|
27651
28825
|
name: "list",
|
|
27652
28826
|
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"
|
|
@@ -27665,7 +28839,7 @@ async function listDraft3() {
|
|
|
27665
28839
|
failApi3(err);
|
|
27666
28840
|
}
|
|
27667
28841
|
}
|
|
27668
|
-
var draftCommand3 =
|
|
28842
|
+
var draftCommand3 = defineCommand155({
|
|
27669
28843
|
meta: {
|
|
27670
28844
|
name: "draft",
|
|
27671
28845
|
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)."
|
|
@@ -27674,7 +28848,7 @@ var draftCommand3 = defineCommand153({
|
|
|
27674
28848
|
await listDraft3();
|
|
27675
28849
|
}
|
|
27676
28850
|
});
|
|
27677
|
-
var tagsCommand3 =
|
|
28851
|
+
var tagsCommand3 = defineCommand155({
|
|
27678
28852
|
meta: {
|
|
27679
28853
|
name: "tags",
|
|
27680
28854
|
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.
|
|
@@ -27700,10 +28874,10 @@ Full guide: __tooling__/docs/tools/baker/tags.md`
|
|
|
27700
28874
|
});
|
|
27701
28875
|
|
|
27702
28876
|
// src/commands/testimonials/index.ts
|
|
27703
|
-
import { defineCommand as
|
|
28877
|
+
import { defineCommand as defineCommand159 } from "citty";
|
|
27704
28878
|
|
|
27705
28879
|
// src/commands/testimonials/get.ts
|
|
27706
|
-
import { defineCommand as
|
|
28880
|
+
import { defineCommand as defineCommand156 } from "citty";
|
|
27707
28881
|
registerSchema({
|
|
27708
28882
|
command: "testimonials.get",
|
|
27709
28883
|
description: "Get a single testimonial by ID",
|
|
@@ -27711,7 +28885,7 @@ registerSchema({
|
|
|
27711
28885
|
id: { type: "string", description: "Testimonial ID", required: true }
|
|
27712
28886
|
}
|
|
27713
28887
|
});
|
|
27714
|
-
var getCommand4 =
|
|
28888
|
+
var getCommand4 = defineCommand156({
|
|
27715
28889
|
meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
|
|
27716
28890
|
args: {
|
|
27717
28891
|
id: { type: "positional", description: "Testimonial ID", required: false },
|
|
@@ -27748,7 +28922,7 @@ var getCommand4 = defineCommand154({
|
|
|
27748
28922
|
});
|
|
27749
28923
|
|
|
27750
28924
|
// src/commands/testimonials/list.ts
|
|
27751
|
-
import { defineCommand as
|
|
28925
|
+
import { defineCommand as defineCommand157 } from "citty";
|
|
27752
28926
|
registerSchema({
|
|
27753
28927
|
command: "testimonials.list",
|
|
27754
28928
|
description: "List testimonials with optional filters.",
|
|
@@ -27778,7 +28952,7 @@ registerSchema({
|
|
|
27778
28952
|
limit: { type: "number", description: "Max results (default 50)", required: false, default: 50 }
|
|
27779
28953
|
}
|
|
27780
28954
|
});
|
|
27781
|
-
var listCommand11 =
|
|
28955
|
+
var listCommand11 = defineCommand157({
|
|
27782
28956
|
meta: {
|
|
27783
28957
|
name: "list",
|
|
27784
28958
|
description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
|
|
@@ -27827,7 +29001,7 @@ var listCommand11 = defineCommand155({
|
|
|
27827
29001
|
});
|
|
27828
29002
|
|
|
27829
29003
|
// src/commands/testimonials/search.ts
|
|
27830
|
-
import { defineCommand as
|
|
29004
|
+
import { defineCommand as defineCommand158 } from "citty";
|
|
27831
29005
|
function languageBiasHint(results, requestedLanguage) {
|
|
27832
29006
|
if (requestedLanguage) {
|
|
27833
29007
|
return null;
|
|
@@ -27905,7 +29079,7 @@ function buildSearchRequest(query, args) {
|
|
|
27905
29079
|
}
|
|
27906
29080
|
return body;
|
|
27907
29081
|
}
|
|
27908
|
-
var searchCommand2 =
|
|
29082
|
+
var searchCommand2 = defineCommand158({
|
|
27909
29083
|
meta: {
|
|
27910
29084
|
name: "search",
|
|
27911
29085
|
description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
|
|
@@ -27961,7 +29135,7 @@ var searchCommand2 = defineCommand156({
|
|
|
27961
29135
|
var tagsCommand4 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
|
|
27962
29136
|
|
|
27963
29137
|
// src/commands/testimonials/index.ts
|
|
27964
|
-
var testimonialsCommand =
|
|
29138
|
+
var testimonialsCommand = defineCommand159({
|
|
27965
29139
|
meta: {
|
|
27966
29140
|
name: "testimonials",
|
|
27967
29141
|
description: `Find and browse testimonials in Baker. Subcommands: search, get, list, tags.
|
|
@@ -27983,10 +29157,10 @@ Full guide: __tooling__/docs/tools/baker/testimonials.md`
|
|
|
27983
29157
|
});
|
|
27984
29158
|
|
|
27985
29159
|
// src/commands/videos/index.ts
|
|
27986
|
-
import { defineCommand as
|
|
29160
|
+
import { defineCommand as defineCommand164 } from "citty";
|
|
27987
29161
|
|
|
27988
29162
|
// src/commands/videos/delete.ts
|
|
27989
|
-
import { defineCommand as
|
|
29163
|
+
import { defineCommand as defineCommand160 } from "citty";
|
|
27990
29164
|
registerSchema({
|
|
27991
29165
|
command: "videos.delete",
|
|
27992
29166
|
description: "Delete a video by ID",
|
|
@@ -28000,7 +29174,7 @@ registerSchema({
|
|
|
28000
29174
|
}
|
|
28001
29175
|
}
|
|
28002
29176
|
});
|
|
28003
|
-
var deleteCommand3 =
|
|
29177
|
+
var deleteCommand3 = defineCommand160({
|
|
28004
29178
|
meta: {
|
|
28005
29179
|
name: "delete",
|
|
28006
29180
|
description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
|
|
@@ -28041,7 +29215,7 @@ var deleteCommand3 = defineCommand158({
|
|
|
28041
29215
|
});
|
|
28042
29216
|
|
|
28043
29217
|
// src/commands/videos/get.ts
|
|
28044
|
-
import { defineCommand as
|
|
29218
|
+
import { defineCommand as defineCommand161 } from "citty";
|
|
28045
29219
|
registerSchema({
|
|
28046
29220
|
command: "videos.get",
|
|
28047
29221
|
description: "Get a single video by ID",
|
|
@@ -28049,7 +29223,7 @@ registerSchema({
|
|
|
28049
29223
|
id: { type: "string", description: "Video ID", required: true }
|
|
28050
29224
|
}
|
|
28051
29225
|
});
|
|
28052
|
-
var getCommand5 =
|
|
29226
|
+
var getCommand5 = defineCommand161({
|
|
28053
29227
|
meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
|
|
28054
29228
|
args: {
|
|
28055
29229
|
id: { type: "positional", description: "Video ID", required: false },
|
|
@@ -28086,7 +29260,7 @@ var getCommand5 = defineCommand159({
|
|
|
28086
29260
|
});
|
|
28087
29261
|
|
|
28088
29262
|
// src/commands/videos/search.ts
|
|
28089
|
-
import { defineCommand as
|
|
29263
|
+
import { defineCommand as defineCommand162 } from "citty";
|
|
28090
29264
|
registerSchema({
|
|
28091
29265
|
command: "videos.search",
|
|
28092
29266
|
description: "Search videos by text query. Only returns ready videos.",
|
|
@@ -28096,7 +29270,7 @@ registerSchema({
|
|
|
28096
29270
|
tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
|
|
28097
29271
|
}
|
|
28098
29272
|
});
|
|
28099
|
-
var searchCommand3 =
|
|
29273
|
+
var searchCommand3 = defineCommand162({
|
|
28100
29274
|
meta: {
|
|
28101
29275
|
name: "search",
|
|
28102
29276
|
description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
|
|
@@ -28146,9 +29320,9 @@ var searchCommand3 = defineCommand160({
|
|
|
28146
29320
|
var tagsCommand5 = makeTagsCommand("videos", "video", "/api/videos/tags");
|
|
28147
29321
|
|
|
28148
29322
|
// src/commands/videos/upload.ts
|
|
28149
|
-
import { readFile as
|
|
29323
|
+
import { readFile as readFile22, stat as stat7 } from "fs/promises";
|
|
28150
29324
|
import { extname as extname3 } from "path";
|
|
28151
|
-
import { defineCommand as
|
|
29325
|
+
import { defineCommand as defineCommand163 } from "citty";
|
|
28152
29326
|
var MIME_MAP = {
|
|
28153
29327
|
".mp4": "video/mp4",
|
|
28154
29328
|
".mov": "video/quicktime",
|
|
@@ -28182,7 +29356,7 @@ function detectContentType(filePath) {
|
|
|
28182
29356
|
}
|
|
28183
29357
|
return mime;
|
|
28184
29358
|
}
|
|
28185
|
-
var uploadCommand2 =
|
|
29359
|
+
var uploadCommand2 = defineCommand163({
|
|
28186
29360
|
meta: {
|
|
28187
29361
|
name: "upload",
|
|
28188
29362
|
description: "Upload a video file to Baker via Mux direct upload. Auto-detects content type. Example: baker videos upload ./demo.mp4"
|
|
@@ -28201,7 +29375,7 @@ var uploadCommand2 = defineCommand161({
|
|
|
28201
29375
|
}
|
|
28202
29376
|
const contentType = args["content-type"] || detectContentType(filePath);
|
|
28203
29377
|
if (args["dry-run"]) {
|
|
28204
|
-
const fileStats = await
|
|
29378
|
+
const fileStats = await stat7(filePath);
|
|
28205
29379
|
writeJson({
|
|
28206
29380
|
ok: true,
|
|
28207
29381
|
dryRun: true,
|
|
@@ -28211,7 +29385,7 @@ var uploadCommand2 = defineCommand161({
|
|
|
28211
29385
|
return;
|
|
28212
29386
|
}
|
|
28213
29387
|
const { uploadUrl, videoId } = await apiPost("/api/videos/upload", {});
|
|
28214
|
-
const fileBuffer = await
|
|
29388
|
+
const fileBuffer = await readFile22(filePath);
|
|
28215
29389
|
const uploadResponse = await fetch(uploadUrl, {
|
|
28216
29390
|
method: "PUT",
|
|
28217
29391
|
headers: { "Content-Type": contentType },
|
|
@@ -28236,7 +29410,7 @@ var uploadCommand2 = defineCommand161({
|
|
|
28236
29410
|
});
|
|
28237
29411
|
|
|
28238
29412
|
// src/commands/videos/index.ts
|
|
28239
|
-
var videosCommand =
|
|
29413
|
+
var videosCommand = defineCommand164({
|
|
28240
29414
|
meta: {
|
|
28241
29415
|
name: "videos",
|
|
28242
29416
|
description: `Find and manage videos in Baker. Subcommands: search, get, upload, delete, tags.
|
|
@@ -28260,10 +29434,10 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
|
|
|
28260
29434
|
});
|
|
28261
29435
|
|
|
28262
29436
|
// src/commands/winning-ads/index.ts
|
|
28263
|
-
import { defineCommand as
|
|
29437
|
+
import { defineCommand as defineCommand176 } from "citty";
|
|
28264
29438
|
|
|
28265
29439
|
// src/commands/winning-ads/advertisers.ts
|
|
28266
|
-
import { defineCommand as
|
|
29440
|
+
import { defineCommand as defineCommand165 } from "citty";
|
|
28267
29441
|
|
|
28268
29442
|
// src/commands/winning-ads/shared.ts
|
|
28269
29443
|
function splitList(value) {
|
|
@@ -28316,7 +29490,7 @@ function advertiserNormalizer(record, full) {
|
|
|
28316
29490
|
last_synced_at: record.last_synced_at ?? null
|
|
28317
29491
|
};
|
|
28318
29492
|
}
|
|
28319
|
-
var advertisersCommand2 =
|
|
29493
|
+
var advertisersCommand2 = defineCommand165({
|
|
28320
29494
|
meta: {
|
|
28321
29495
|
name: "advertisers",
|
|
28322
29496
|
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'
|
|
@@ -28374,7 +29548,7 @@ var advertisersCommand2 = defineCommand163({
|
|
|
28374
29548
|
});
|
|
28375
29549
|
|
|
28376
29550
|
// src/commands/winning-ads/brief.ts
|
|
28377
|
-
import { defineCommand as
|
|
29551
|
+
import { defineCommand as defineCommand166 } from "citty";
|
|
28378
29552
|
registerSchema({
|
|
28379
29553
|
command: "winning-ads.brief",
|
|
28380
29554
|
description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
|
|
@@ -28420,7 +29594,7 @@ function parseDna(raw) {
|
|
|
28420
29594
|
}
|
|
28421
29595
|
return parsed;
|
|
28422
29596
|
}
|
|
28423
|
-
var briefCommand =
|
|
29597
|
+
var briefCommand = defineCommand166({
|
|
28424
29598
|
meta: {
|
|
28425
29599
|
name: "brief",
|
|
28426
29600
|
description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
|
|
@@ -28456,7 +29630,7 @@ var briefCommand = defineCommand164({
|
|
|
28456
29630
|
});
|
|
28457
29631
|
|
|
28458
29632
|
// src/commands/winning-ads/feed.ts
|
|
28459
|
-
import { defineCommand as
|
|
29633
|
+
import { defineCommand as defineCommand167 } from "citty";
|
|
28460
29634
|
function buildFeedParams(input) {
|
|
28461
29635
|
const params = {};
|
|
28462
29636
|
const advertiser = splitList(input.advertiser);
|
|
@@ -28508,7 +29682,7 @@ registerSchema({
|
|
|
28508
29682
|
format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
|
|
28509
29683
|
}
|
|
28510
29684
|
});
|
|
28511
|
-
var feedCommand =
|
|
29685
|
+
var feedCommand = defineCommand167({
|
|
28512
29686
|
meta: {
|
|
28513
29687
|
name: "feed",
|
|
28514
29688
|
description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
|
|
@@ -28593,7 +29767,7 @@ var feedCommand = defineCommand165({
|
|
|
28593
29767
|
});
|
|
28594
29768
|
|
|
28595
29769
|
// src/commands/winning-ads/follow.ts
|
|
28596
|
-
import { defineCommand as
|
|
29770
|
+
import { defineCommand as defineCommand168 } from "citty";
|
|
28597
29771
|
var PLATFORMS = ["meta", "linkedin"];
|
|
28598
29772
|
registerSchema({
|
|
28599
29773
|
command: "winning-ads.follow",
|
|
@@ -28608,7 +29782,7 @@ registerSchema({
|
|
|
28608
29782
|
label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
|
|
28609
29783
|
}
|
|
28610
29784
|
});
|
|
28611
|
-
var followCommand =
|
|
29785
|
+
var followCommand = defineCommand168({
|
|
28612
29786
|
meta: {
|
|
28613
29787
|
name: "follow",
|
|
28614
29788
|
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'
|
|
@@ -28655,7 +29829,7 @@ var followCommand = defineCommand166({
|
|
|
28655
29829
|
});
|
|
28656
29830
|
|
|
28657
29831
|
// src/commands/winning-ads/follow-competitors.ts
|
|
28658
|
-
import { defineCommand as
|
|
29832
|
+
import { defineCommand as defineCommand169 } from "citty";
|
|
28659
29833
|
var PLATFORMS2 = ["meta", "linkedin"];
|
|
28660
29834
|
var BATCH_TIMEOUT_MS = 3e5;
|
|
28661
29835
|
function buildFollowBatchBody(input) {
|
|
@@ -28688,7 +29862,7 @@ registerSchema({
|
|
|
28688
29862
|
}
|
|
28689
29863
|
}
|
|
28690
29864
|
});
|
|
28691
|
-
var followCompetitorsCommand =
|
|
29865
|
+
var followCompetitorsCommand = defineCommand169({
|
|
28692
29866
|
meta: {
|
|
28693
29867
|
name: "follow-competitors",
|
|
28694
29868
|
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"'
|
|
@@ -28763,7 +29937,7 @@ var followCompetitorsCommand = defineCommand167({
|
|
|
28763
29937
|
});
|
|
28764
29938
|
|
|
28765
29939
|
// src/commands/winning-ads/following.ts
|
|
28766
|
-
import { defineCommand as
|
|
29940
|
+
import { defineCommand as defineCommand170 } from "citty";
|
|
28767
29941
|
registerSchema({
|
|
28768
29942
|
command: "winning-ads.following",
|
|
28769
29943
|
description: "List the brands you follow in your ad-dna library, with each one's status (ready vs still adding) and cached ad counts.",
|
|
@@ -28796,7 +29970,7 @@ function followingNormalizer(record, full) {
|
|
|
28796
29970
|
platforms: Array.isArray(record.platforms) ? record.platforms : []
|
|
28797
29971
|
};
|
|
28798
29972
|
}
|
|
28799
|
-
var followingCommand =
|
|
29973
|
+
var followingCommand = defineCommand170({
|
|
28800
29974
|
meta: {
|
|
28801
29975
|
name: "following",
|
|
28802
29976
|
description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
|
|
@@ -28831,7 +30005,7 @@ var followingCommand = defineCommand168({
|
|
|
28831
30005
|
});
|
|
28832
30006
|
|
|
28833
30007
|
// src/commands/winning-ads/patterns.ts
|
|
28834
|
-
import { defineCommand as
|
|
30008
|
+
import { defineCommand as defineCommand171 } from "citty";
|
|
28835
30009
|
registerSchema({
|
|
28836
30010
|
command: "winning-ads.patterns",
|
|
28837
30011
|
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.",
|
|
@@ -28870,7 +30044,7 @@ function discriminatorRow(record) {
|
|
|
28870
30044
|
top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
|
|
28871
30045
|
};
|
|
28872
30046
|
}
|
|
28873
|
-
var patternsCommand =
|
|
30047
|
+
var patternsCommand = defineCommand171({
|
|
28874
30048
|
meta: {
|
|
28875
30049
|
name: "patterns",
|
|
28876
30050
|
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"
|
|
@@ -28926,7 +30100,7 @@ var patternsCommand = defineCommand169({
|
|
|
28926
30100
|
});
|
|
28927
30101
|
|
|
28928
30102
|
// src/commands/winning-ads/search.ts
|
|
28929
|
-
import { defineCommand as
|
|
30103
|
+
import { defineCommand as defineCommand172 } from "citty";
|
|
28930
30104
|
registerSchema({
|
|
28931
30105
|
command: "winning-ads.search",
|
|
28932
30106
|
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.",
|
|
@@ -29034,7 +30208,7 @@ function buildSearchBody(args) {
|
|
|
29034
30208
|
}
|
|
29035
30209
|
return body;
|
|
29036
30210
|
}
|
|
29037
|
-
var searchCommand4 =
|
|
30211
|
+
var searchCommand4 = defineCommand172({
|
|
29038
30212
|
meta: {
|
|
29039
30213
|
name: "search",
|
|
29040
30214
|
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"
|
|
@@ -29149,7 +30323,7 @@ var searchCommand4 = defineCommand170({
|
|
|
29149
30323
|
});
|
|
29150
30324
|
|
|
29151
30325
|
// src/commands/winning-ads/seeds.ts
|
|
29152
|
-
import { defineCommand as
|
|
30326
|
+
import { defineCommand as defineCommand173 } from "citty";
|
|
29153
30327
|
function leanRow(r) {
|
|
29154
30328
|
return {
|
|
29155
30329
|
key: r.key,
|
|
@@ -29177,7 +30351,7 @@ function makeSeedCommand(opts) {
|
|
|
29177
30351
|
limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
|
|
29178
30352
|
}
|
|
29179
30353
|
});
|
|
29180
|
-
return
|
|
30354
|
+
return defineCommand173({
|
|
29181
30355
|
meta: { name: opts.name, description: opts.description },
|
|
29182
30356
|
args: {
|
|
29183
30357
|
platform: { type: "string", description: "Single platform to segment on", required: false },
|
|
@@ -29226,7 +30400,7 @@ var formatsCommand = makeSeedCommand({
|
|
|
29226
30400
|
});
|
|
29227
30401
|
|
|
29228
30402
|
// src/commands/winning-ads/unfollow.ts
|
|
29229
|
-
import { defineCommand as
|
|
30403
|
+
import { defineCommand as defineCommand174 } from "citty";
|
|
29230
30404
|
registerSchema({
|
|
29231
30405
|
command: "winning-ads.unfollow",
|
|
29232
30406
|
description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
|
|
@@ -29234,7 +30408,7 @@ registerSchema({
|
|
|
29234
30408
|
advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
|
|
29235
30409
|
}
|
|
29236
30410
|
});
|
|
29237
|
-
var unfollowCommand =
|
|
30411
|
+
var unfollowCommand = defineCommand174({
|
|
29238
30412
|
meta: {
|
|
29239
30413
|
name: "unfollow",
|
|
29240
30414
|
description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
|
|
@@ -29255,7 +30429,7 @@ var unfollowCommand = defineCommand172({
|
|
|
29255
30429
|
});
|
|
29256
30430
|
|
|
29257
30431
|
// src/commands/winning-ads/winners.ts
|
|
29258
|
-
import { defineCommand as
|
|
30432
|
+
import { defineCommand as defineCommand175 } from "citty";
|
|
29259
30433
|
registerSchema({
|
|
29260
30434
|
command: "winning-ads.winners",
|
|
29261
30435
|
description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
|
|
@@ -29265,7 +30439,7 @@ registerSchema({
|
|
|
29265
30439
|
platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
|
|
29266
30440
|
}
|
|
29267
30441
|
});
|
|
29268
|
-
var winnersCommand =
|
|
30442
|
+
var winnersCommand = defineCommand175({
|
|
29269
30443
|
meta: {
|
|
29270
30444
|
name: "winners",
|
|
29271
30445
|
description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
|
|
@@ -29315,7 +30489,7 @@ var winnersCommand = defineCommand173({
|
|
|
29315
30489
|
});
|
|
29316
30490
|
|
|
29317
30491
|
// src/commands/winning-ads/index.ts
|
|
29318
|
-
var winningAdsCommand =
|
|
30492
|
+
var winningAdsCommand = defineCommand176({
|
|
29319
30493
|
meta: {
|
|
29320
30494
|
name: "winning-ads",
|
|
29321
30495
|
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.
|
|
@@ -29384,7 +30558,7 @@ function getCliVersion() {
|
|
|
29384
30558
|
}
|
|
29385
30559
|
|
|
29386
30560
|
// src/cli.ts
|
|
29387
|
-
var main =
|
|
30561
|
+
var main = defineCommand177({
|
|
29388
30562
|
meta: {
|
|
29389
30563
|
name: "baker",
|
|
29390
30564
|
version: getCliVersion(),
|
|
@@ -29406,6 +30580,7 @@ Introspection: Run 'baker schema <command>' to inspect argument schemas.`
|
|
|
29406
30580
|
creatives: creativesCommand3,
|
|
29407
30581
|
flows: flowsCommand,
|
|
29408
30582
|
images: imagesCommand,
|
|
30583
|
+
landing: landingCommand,
|
|
29409
30584
|
videos: videosCommand,
|
|
29410
30585
|
testimonials: testimonialsCommand,
|
|
29411
30586
|
canvas: canvasCommand,
|