@koda-sl/baker-cli 0.182.0-dev.3c0641b3f → 0.182.1-dev.c8185c1b5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/cli.js +167 -92
- package/dist/cli.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -4989,7 +4989,7 @@ baker landing inspiration code <section id> # the standalone bundle
|
|
|
4989
4989
|
baker landing inspiration page <source id> # a whole page as a section sequence
|
|
4990
4990
|
baker landing inspiration add https://linear.app --note "client likes this density"
|
|
4991
4991
|
baker landing inspiration favorites # what this company has saved
|
|
4992
|
-
baker landing inspiration scrape <url> --out <dir> #
|
|
4992
|
+
baker landing inspiration scrape <url> --out <dir> # capture any page now, synchronously
|
|
4993
4993
|
```
|
|
4994
4994
|
|
|
4995
4995
|
- **Hybrid search over three signals** — keywords, meaning, and *appearance* (the screenshot is embedded, so a query like "dark developer hero with a terminal" can match a section whose text never says "terminal"). Filters: `--type --composition --register --interaction --motion --media --device --theme --max-rank --min-craft --min-fidelity --domain --similar-to --scope --limit`.
|
|
@@ -4997,7 +4997,8 @@ baker landing inspiration scrape <url> --out <dir> # run the capture locally (
|
|
|
4997
4997
|
- **`fidelity` says how much to trust the code.** A measured 0–1 match between the live section and our standalone re-render, always reported. Above ~0.95 the markup renders like the original; below ~0.75 the section is scroll- or JS-driven and it does not.
|
|
4998
4998
|
- **`used_on_pages`** appears when a section shows up on more than one of that site's pages — a nav or footer they ship everywhere, which is a stronger reference than a one-off.
|
|
4999
4999
|
- **The corpus is shared across companies; favorites are per-company**, and search defaults to this company's saved sections.
|
|
5000
|
-
-
|
|
5000
|
+
- **`add` studies in the background; `scrape` returns with the page on disk.** `add` grows the shared library and takes minutes, so it can never answer "build our page like this one" within the same turn. `scrape` runs the identical capture locally and blocks until it finishes, writing section screenshots, standalone markup, a whole-page reproduction and `report.html`. `--no-motion` roughly halves the runtime; `--no-mobile`, `--no-code` and `--no-report` skip further passes.
|
|
5001
|
+
- **Inspiration, never a clipboard.** Both `code` and `scrape` record what was consulted, and `critique`'s `originality` family blocks a publish that reuses a reference's copy verbatim.
|
|
5001
5002
|
|
|
5002
5003
|
### `baker landing critique`
|
|
5003
5004
|
|
package/dist/cli.js
CHANGED
|
@@ -8125,11 +8125,11 @@ function rawTextEntries(value) {
|
|
|
8125
8125
|
const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
8126
8126
|
return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
8127
8127
|
}
|
|
8128
|
-
function rawFileEntries(
|
|
8129
|
-
if (typeof
|
|
8128
|
+
function rawFileEntries(path35) {
|
|
8129
|
+
if (typeof path35 !== "string" || path35.length === 0) {
|
|
8130
8130
|
return [];
|
|
8131
8131
|
}
|
|
8132
|
-
return readFileSync2(
|
|
8132
|
+
return readFileSync2(path35, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
8133
8133
|
}
|
|
8134
8134
|
function keywordEntries(args) {
|
|
8135
8135
|
const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
|
|
@@ -8152,19 +8152,19 @@ function keywordEntries(args) {
|
|
|
8152
8152
|
}
|
|
8153
8153
|
return entries;
|
|
8154
8154
|
}
|
|
8155
|
-
function loadJsonFileArg(
|
|
8156
|
-
if (typeof
|
|
8155
|
+
function loadJsonFileArg(path35) {
|
|
8156
|
+
if (typeof path35 !== "string" || path35.length === 0) {
|
|
8157
8157
|
return {};
|
|
8158
8158
|
}
|
|
8159
8159
|
try {
|
|
8160
|
-
const parsed = JSON.parse(readFileSync2(
|
|
8160
|
+
const parsed = JSON.parse(readFileSync2(path35, "utf8"));
|
|
8161
8161
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8162
|
-
failWriteValidation(`${
|
|
8162
|
+
failWriteValidation(`${path35} must contain a JSON object`);
|
|
8163
8163
|
}
|
|
8164
8164
|
return parsed;
|
|
8165
8165
|
} catch (err) {
|
|
8166
8166
|
if (err instanceof SyntaxError) {
|
|
8167
|
-
failWriteValidation(`${
|
|
8167
|
+
failWriteValidation(`${path35} is not valid JSON: ${err.message}`);
|
|
8168
8168
|
}
|
|
8169
8169
|
throw err;
|
|
8170
8170
|
}
|
|
@@ -8294,10 +8294,10 @@ async function stageUpdate(kind, customerId, target, payload, hints) {
|
|
|
8294
8294
|
async function stageTarget(kind, customerId, target, hints) {
|
|
8295
8295
|
await stageGoogleOp({ kind, customerId, target }, hints);
|
|
8296
8296
|
}
|
|
8297
|
-
async function draftAction(
|
|
8297
|
+
async function draftAction(path35, body, chat) {
|
|
8298
8298
|
try {
|
|
8299
8299
|
const chatId = resolveChatId(chat);
|
|
8300
|
-
const response = await apiPost(
|
|
8300
|
+
const response = await apiPost(path35, { chatId, ...body });
|
|
8301
8301
|
writeJsonEnvelope(response);
|
|
8302
8302
|
} catch (err) {
|
|
8303
8303
|
handleGoogleError(err);
|
|
@@ -10965,19 +10965,19 @@ function failWriteValidation2(message) {
|
|
|
10965
10965
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
10966
10966
|
process.exit(1);
|
|
10967
10967
|
}
|
|
10968
|
-
function loadJsonFileArg2(
|
|
10969
|
-
if (typeof
|
|
10968
|
+
function loadJsonFileArg2(path35) {
|
|
10969
|
+
if (typeof path35 !== "string" || path35.length === 0) {
|
|
10970
10970
|
return {};
|
|
10971
10971
|
}
|
|
10972
10972
|
try {
|
|
10973
|
-
const parsed = JSON.parse(readFileSync4(
|
|
10973
|
+
const parsed = JSON.parse(readFileSync4(path35, "utf8"));
|
|
10974
10974
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
10975
|
-
failWriteValidation2(`${
|
|
10975
|
+
failWriteValidation2(`${path35} must contain a JSON object`);
|
|
10976
10976
|
}
|
|
10977
10977
|
return parsed;
|
|
10978
10978
|
} catch (err) {
|
|
10979
10979
|
if (err instanceof SyntaxError) {
|
|
10980
|
-
failWriteValidation2(`${
|
|
10980
|
+
failWriteValidation2(`${path35} is not valid JSON: ${err.message}`);
|
|
10981
10981
|
}
|
|
10982
10982
|
throw err;
|
|
10983
10983
|
}
|
|
@@ -11062,15 +11062,15 @@ function parseLocaleFlag(value) {
|
|
|
11062
11062
|
}
|
|
11063
11063
|
return { language: match[1], country: match[2].toUpperCase() };
|
|
11064
11064
|
}
|
|
11065
|
-
function loadTargetingFileArg(
|
|
11066
|
-
if (typeof
|
|
11065
|
+
function loadTargetingFileArg(path35) {
|
|
11066
|
+
if (typeof path35 !== "string" || path35.length === 0) {
|
|
11067
11067
|
return void 0;
|
|
11068
11068
|
}
|
|
11069
|
-
const parsed = loadJsonFileArg2(
|
|
11069
|
+
const parsed = loadJsonFileArg2(path35);
|
|
11070
11070
|
const criteria = parsed.targetingCriteria ?? parsed;
|
|
11071
11071
|
if (!criteria.include) {
|
|
11072
11072
|
failWriteValidation2(
|
|
11073
|
-
`${
|
|
11073
|
+
`${path35} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
|
|
11074
11074
|
);
|
|
11075
11075
|
}
|
|
11076
11076
|
return criteria;
|
|
@@ -11105,14 +11105,14 @@ function parseCsvLine(line) {
|
|
|
11105
11105
|
cells.push(current);
|
|
11106
11106
|
return cells.map((cell) => cell.trim());
|
|
11107
11107
|
}
|
|
11108
|
-
function parseListFileArg(
|
|
11109
|
-
if (typeof
|
|
11108
|
+
function parseListFileArg(path35, maxRows) {
|
|
11109
|
+
if (typeof path35 !== "string" || path35.length === 0) {
|
|
11110
11110
|
return void 0;
|
|
11111
11111
|
}
|
|
11112
|
-
const raw = readFileSync4(
|
|
11112
|
+
const raw = readFileSync4(path35, "utf8");
|
|
11113
11113
|
const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
11114
11114
|
if (lines.length < 2) {
|
|
11115
|
-
failWriteValidation2(`${
|
|
11115
|
+
failWriteValidation2(`${path35} needs a header row and at least one data row`);
|
|
11116
11116
|
}
|
|
11117
11117
|
const columns = parseCsvLine(lines[0]).map((column) => column.trim());
|
|
11118
11118
|
const rows = [];
|
|
@@ -11131,7 +11131,7 @@ function parseListFileArg(path34, maxRows) {
|
|
|
11131
11131
|
}
|
|
11132
11132
|
}
|
|
11133
11133
|
if (rows.length > maxRows) {
|
|
11134
|
-
failWriteValidation2(`${
|
|
11134
|
+
failWriteValidation2(`${path35} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
|
|
11135
11135
|
}
|
|
11136
11136
|
return { columns, rows };
|
|
11137
11137
|
}
|
|
@@ -11227,11 +11227,11 @@ function readPositionals(args) {
|
|
|
11227
11227
|
function splitIdList(raw) {
|
|
11228
11228
|
return raw.split(",").map((id) => id.trim()).filter(Boolean);
|
|
11229
11229
|
}
|
|
11230
|
-
function idsFileEntries(
|
|
11231
|
-
if (typeof
|
|
11230
|
+
function idsFileEntries(path35) {
|
|
11231
|
+
if (typeof path35 !== "string" || path35.length === 0) {
|
|
11232
11232
|
return [];
|
|
11233
11233
|
}
|
|
11234
|
-
return readFileSync4(
|
|
11234
|
+
return readFileSync4(path35, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
|
|
11235
11235
|
}
|
|
11236
11236
|
function requireTargets(args, entity) {
|
|
11237
11237
|
const positionals = readPositionals(args);
|
|
@@ -13854,9 +13854,9 @@ function compactRow(row) {
|
|
|
13854
13854
|
...destination.postUrn ? { postUrn: destination.postUrn } : {}
|
|
13855
13855
|
};
|
|
13856
13856
|
}
|
|
13857
|
-
function readPath(row,
|
|
13857
|
+
function readPath(row, path35) {
|
|
13858
13858
|
let current = row;
|
|
13859
|
-
for (const segment of
|
|
13859
|
+
for (const segment of path35.split(".")) {
|
|
13860
13860
|
const record = asRecord2(current);
|
|
13861
13861
|
if (!record) return void 0;
|
|
13862
13862
|
current = record[segment];
|
|
@@ -13866,10 +13866,10 @@ function readPath(row, path34) {
|
|
|
13866
13866
|
function projectFields(rows, paths) {
|
|
13867
13867
|
return rows.map((row) => {
|
|
13868
13868
|
const projected = {};
|
|
13869
|
-
for (const
|
|
13870
|
-
const value = readPath(row,
|
|
13869
|
+
for (const path35 of paths) {
|
|
13870
|
+
const value = readPath(row, path35);
|
|
13871
13871
|
if (value !== void 0) {
|
|
13872
|
-
projected[
|
|
13872
|
+
projected[path35] = value;
|
|
13873
13873
|
}
|
|
13874
13874
|
}
|
|
13875
13875
|
return projected;
|
|
@@ -15076,11 +15076,11 @@ var updateStatusSchema = z18.enum(UPDATE_STATUSES);
|
|
|
15076
15076
|
function currencyMinimums2(currencyCode) {
|
|
15077
15077
|
return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
|
|
15078
15078
|
}
|
|
15079
|
-
function validateDailyBudgetFloor(money, ctx,
|
|
15079
|
+
function validateDailyBudgetFloor(money, ctx, path35) {
|
|
15080
15080
|
if (money?.currencyCode) {
|
|
15081
15081
|
const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
|
|
15082
15082
|
if (Number(money.amount) < min) {
|
|
15083
|
-
ctx.addIssue({ code: "custom", path:
|
|
15083
|
+
ctx.addIssue({ code: "custom", path: path35, message: `below the ${min} ${money.currencyCode} daily minimum` });
|
|
15084
15084
|
}
|
|
15085
15085
|
}
|
|
15086
15086
|
}
|
|
@@ -15723,19 +15723,19 @@ function failWriteValidation3(message) {
|
|
|
15723
15723
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
15724
15724
|
process.exit(1);
|
|
15725
15725
|
}
|
|
15726
|
-
function loadJsonFileArg3(
|
|
15727
|
-
if (typeof
|
|
15726
|
+
function loadJsonFileArg3(path35) {
|
|
15727
|
+
if (typeof path35 !== "string" || path35.length === 0) {
|
|
15728
15728
|
return {};
|
|
15729
15729
|
}
|
|
15730
15730
|
try {
|
|
15731
|
-
const parsed = JSON.parse(readFileSync8(
|
|
15731
|
+
const parsed = JSON.parse(readFileSync8(path35, "utf8"));
|
|
15732
15732
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15733
|
-
failWriteValidation3(`${
|
|
15733
|
+
failWriteValidation3(`${path35} must contain a JSON object`);
|
|
15734
15734
|
}
|
|
15735
15735
|
return parsed;
|
|
15736
15736
|
} catch (err) {
|
|
15737
15737
|
if (err instanceof SyntaxError) {
|
|
15738
|
-
failWriteValidation3(`${
|
|
15738
|
+
failWriteValidation3(`${path35} is not valid JSON: ${err.message}`);
|
|
15739
15739
|
}
|
|
15740
15740
|
throw err;
|
|
15741
15741
|
}
|
|
@@ -26531,12 +26531,12 @@ function listFlowSlugs() {
|
|
|
26531
26531
|
return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_") && entry.name !== ".gitkeep").map((entry) => entry.name).sort();
|
|
26532
26532
|
}
|
|
26533
26533
|
function readFlowTree(slug) {
|
|
26534
|
-
const
|
|
26535
|
-
if (!existsSync4(
|
|
26534
|
+
const path35 = join3(flowsDir(), slug, "_data.json");
|
|
26535
|
+
if (!existsSync4(path35)) {
|
|
26536
26536
|
failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
|
|
26537
26537
|
}
|
|
26538
26538
|
try {
|
|
26539
|
-
return JSON.parse(readFileSync9(
|
|
26539
|
+
return JSON.parse(readFileSync9(path35, "utf-8"));
|
|
26540
26540
|
} catch (error) {
|
|
26541
26541
|
failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
|
|
26542
26542
|
}
|
|
@@ -26949,10 +26949,10 @@ async function stageOps(ops) {
|
|
|
26949
26949
|
handleError(err);
|
|
26950
26950
|
}
|
|
26951
26951
|
}
|
|
26952
|
-
async function draftAction2(
|
|
26952
|
+
async function draftAction2(path35, body, chat) {
|
|
26953
26953
|
const chatId = resolveChatId(chat);
|
|
26954
26954
|
try {
|
|
26955
|
-
const data = await apiPost(
|
|
26955
|
+
const data = await apiPost(path35, { chatId, ...body });
|
|
26956
26956
|
writeJsonEnvelope({ ok: true, data });
|
|
26957
26957
|
return data;
|
|
26958
26958
|
} catch (err) {
|
|
@@ -28976,9 +28976,9 @@ async function readImageBuffer(pathOrUrl) {
|
|
|
28976
28976
|
}
|
|
28977
28977
|
return readFile19(pathOrUrl);
|
|
28978
28978
|
}
|
|
28979
|
-
async function isDirectory(
|
|
28979
|
+
async function isDirectory(path35) {
|
|
28980
28980
|
try {
|
|
28981
|
-
const s = await stat4(
|
|
28981
|
+
const s = await stat4(path35);
|
|
28982
28982
|
return s.isDirectory();
|
|
28983
28983
|
} catch {
|
|
28984
28984
|
return false;
|
|
@@ -32870,9 +32870,14 @@ async function readReferences(projectRoot) {
|
|
|
32870
32870
|
}
|
|
32871
32871
|
}
|
|
32872
32872
|
async function recordReference(projectRoot, reference) {
|
|
32873
|
+
return await recordReferences(projectRoot, [reference]);
|
|
32874
|
+
}
|
|
32875
|
+
async function recordReferences(projectRoot, references) {
|
|
32876
|
+
if (references.length === 0) return true;
|
|
32873
32877
|
try {
|
|
32874
32878
|
const existing = await readReferences(projectRoot);
|
|
32875
|
-
const
|
|
32879
|
+
const replaced = new Set(references.map((reference) => reference.sectionId));
|
|
32880
|
+
const merged = [...existing.filter((entry) => !replaced.has(entry.sectionId)), ...references];
|
|
32876
32881
|
const file = path25.join(projectRoot, REFERENCES_FILE);
|
|
32877
32882
|
await mkdir8(path25.dirname(file), { recursive: true });
|
|
32878
32883
|
await writeFile11(file, `${JSON.stringify(merged, null, 2)}
|
|
@@ -33419,8 +33424,25 @@ var pageCommand = defineCommand146({
|
|
|
33419
33424
|
});
|
|
33420
33425
|
|
|
33421
33426
|
// src/commands/landing/inspiration/scrape.ts
|
|
33427
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
33428
|
+
import path32 from "path";
|
|
33422
33429
|
import { defineCommand as defineCommand147 } from "citty";
|
|
33423
33430
|
|
|
33431
|
+
// src/engine/landing/lib/capturedReferences.ts
|
|
33432
|
+
var MAX_STRINGS_PER_SECTION = 40;
|
|
33433
|
+
function bodyOf(markup) {
|
|
33434
|
+
const body = markup.indexOf("<body");
|
|
33435
|
+
return body === -1 ? markup : markup.slice(body);
|
|
33436
|
+
}
|
|
33437
|
+
function capturedCopyStrings(markup) {
|
|
33438
|
+
const seen = /* @__PURE__ */ new Set();
|
|
33439
|
+
for (const { value } of extractVisibleStrings(bodyOf(markup))) {
|
|
33440
|
+
seen.add(value);
|
|
33441
|
+
if (seen.size >= MAX_STRINGS_PER_SECTION) break;
|
|
33442
|
+
}
|
|
33443
|
+
return [...seen];
|
|
33444
|
+
}
|
|
33445
|
+
|
|
33424
33446
|
// src/engine/landing-library/blocked.ts
|
|
33425
33447
|
var CHALLENGE_PHRASES = [
|
|
33426
33448
|
"just a moment",
|
|
@@ -33846,7 +33868,10 @@ async function toGreyGrid(image) {
|
|
|
33846
33868
|
// src/engine/landing-library/motion.ts
|
|
33847
33869
|
var EMPTY_MOTION = {
|
|
33848
33870
|
hasMotion: false,
|
|
33849
|
-
|
|
33871
|
+
// `none` rather than "no motion": it is the `MOTION_KINDS` member for this,
|
|
33872
|
+
// so a section with no movement is findable by the same facet as everything
|
|
33873
|
+
// else instead of being a special case only prose describes.
|
|
33874
|
+
summary: "none",
|
|
33850
33875
|
entrance: [],
|
|
33851
33876
|
hover: [],
|
|
33852
33877
|
scroll: [],
|
|
@@ -33862,19 +33887,43 @@ async function collectMotion(page, selector) {
|
|
|
33862
33887
|
if (!raw) return EMPTY_MOTION;
|
|
33863
33888
|
return { ...raw, summary: summarizeMotion(raw) };
|
|
33864
33889
|
}
|
|
33865
|
-
|
|
33866
|
-
|
|
33867
|
-
|
|
33868
|
-
|
|
33869
|
-
|
|
33870
|
-
|
|
33890
|
+
var HOVER_TRANSFORMS = /* @__PURE__ */ new Set(["lift", "grow", "tilt", "shift", "spin", "shadow", "animate"]);
|
|
33891
|
+
var KEYFRAME_PATTERNS = [
|
|
33892
|
+
[/marquee|ticker|scroll(ing)?-?(x|left|right)/, "marquee"],
|
|
33893
|
+
[/parallax/, "parallax"],
|
|
33894
|
+
[/sticky|pin(ned)?/, "sticky-pin"],
|
|
33895
|
+
[/count(er|up)/, "counter"],
|
|
33896
|
+
[/zoom|scale|pulse|grow/, "zoom"],
|
|
33897
|
+
[/fade|opacity|appear/, "fade"],
|
|
33898
|
+
[/slide|translate|in-?(left|right|up|down)/, "slide-in"]
|
|
33899
|
+
];
|
|
33900
|
+
function toMotionKinds(kind, channel) {
|
|
33901
|
+
if (channel === "hover") return [HOVER_TRANSFORMS.has(kind) ? "hover-lift" : "fade"];
|
|
33902
|
+
const name = kind.toLowerCase();
|
|
33903
|
+
const matched = KEYFRAME_PATTERNS.filter(([pattern]) => pattern.test(name)).map(([, motionKind]) => motionKind);
|
|
33904
|
+
if (matched.length > 0) return matched;
|
|
33905
|
+
return [channel === "loop" ? "marquee" : "scroll-reveal"];
|
|
33906
|
+
}
|
|
33907
|
+
function motionKindsOf(motion) {
|
|
33908
|
+
const kinds = /* @__PURE__ */ new Set();
|
|
33909
|
+
const collect = (effects, channel) => {
|
|
33910
|
+
for (const effect of effects) {
|
|
33911
|
+
for (const kind of toMotionKinds(effect.kind, channel)) kinds.add(kind);
|
|
33912
|
+
}
|
|
33871
33913
|
};
|
|
33872
|
-
|
|
33873
|
-
|
|
33874
|
-
|
|
33875
|
-
|
|
33914
|
+
collect(motion.entrance, "entrance");
|
|
33915
|
+
collect(motion.hover, "hover");
|
|
33916
|
+
collect(motion.scroll, "scroll");
|
|
33917
|
+
collect(motion.loop, "loop");
|
|
33918
|
+
const delays = new Set(motion.entrance.map((effect) => effect.delayMs ?? 0).filter((ms) => ms > 0));
|
|
33919
|
+
if (delays.size > 1) kinds.add("stagger");
|
|
33920
|
+
return [...kinds];
|
|
33921
|
+
}
|
|
33922
|
+
function summarizeMotion(motion) {
|
|
33923
|
+
const kinds = motionKindsOf(motion);
|
|
33924
|
+
if (kinds.length === 0 && motion.libraries.length === 0) return "none";
|
|
33925
|
+
const parts = [...kinds];
|
|
33876
33926
|
if (motion.libraries.length > 0) parts.push(`via ${motion.libraries.join("/")}`);
|
|
33877
|
-
if (parts.length === 0) return "no motion";
|
|
33878
33927
|
return parts.join(", ") + (motion.respectsReducedMotion ? "" : " (ignores reduced-motion)");
|
|
33879
33928
|
}
|
|
33880
33929
|
var inPageCollectMotion = (selector) => {
|
|
@@ -34968,53 +35017,74 @@ async function scrapeLanding(options) {
|
|
|
34968
35017
|
}
|
|
34969
35018
|
|
|
34970
35019
|
// src/commands/landing/inspiration/scrape.ts
|
|
35020
|
+
async function recordCapture(manifest, outDir) {
|
|
35021
|
+
const consultedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
35022
|
+
const references = [];
|
|
35023
|
+
for (const section of manifest.sections) {
|
|
35024
|
+
if (!section.bundle) continue;
|
|
35025
|
+
try {
|
|
35026
|
+
const markup = await readFile24(path32.join(outDir, section.bundle), "utf8");
|
|
35027
|
+
const copyStrings = capturedCopyStrings(markup);
|
|
35028
|
+
if (copyStrings.length === 0) continue;
|
|
35029
|
+
references.push({
|
|
35030
|
+
sectionId: `local:${manifest.finalUrl}#${section.index}`,
|
|
35031
|
+
sourceUrl: manifest.finalUrl,
|
|
35032
|
+
copyStrings,
|
|
35033
|
+
consultedAt
|
|
35034
|
+
});
|
|
35035
|
+
} catch {
|
|
35036
|
+
}
|
|
35037
|
+
}
|
|
35038
|
+
return await recordReferences(process.cwd(), references);
|
|
35039
|
+
}
|
|
34971
35040
|
registerSchema({
|
|
34972
35041
|
command: "landing.inspiration.scrape",
|
|
34973
|
-
description: "
|
|
35042
|
+
description: "Start here when the user points at a specific page and wants it now: capture one landing page into a directory of section screenshots, standalone HTML bundles, a whole-page reproduction and motion filmstrips. Returns when the capture is done, unlike `add`. Read the screenshots. Also records every captured section for the originality check.",
|
|
34974
35043
|
args: {
|
|
34975
35044
|
url: { type: "string", description: "Page to capture", required: true },
|
|
34976
35045
|
out: { type: "string", description: "Output directory", required: true },
|
|
34977
|
-
|
|
34978
|
-
|
|
35046
|
+
mobile: { type: "boolean", description: "Phone-viewport pass. `--no-mobile` to skip", required: false },
|
|
35047
|
+
code: {
|
|
34979
35048
|
type: "boolean",
|
|
34980
|
-
description: "
|
|
35049
|
+
description: "Standalone HTML+CSS bundles and fidelity scores. `--no-code` to segment and screenshot only",
|
|
34981
35050
|
required: false
|
|
34982
35051
|
},
|
|
34983
|
-
|
|
35052
|
+
motion: {
|
|
34984
35053
|
type: "boolean",
|
|
34985
|
-
description: "
|
|
35054
|
+
description: "Film sections that move. `--no-motion` to skip \u2014 roughly halves runtime",
|
|
34986
35055
|
required: false
|
|
34987
35056
|
},
|
|
34988
|
-
|
|
35057
|
+
report: { type: "boolean", description: "Write report.html. `--no-report` to skip", required: false }
|
|
34989
35058
|
}
|
|
34990
35059
|
});
|
|
34991
35060
|
var scrapeCommand = defineCommand147({
|
|
34992
35061
|
meta: {
|
|
34993
35062
|
name: "scrape",
|
|
34994
|
-
description: "
|
|
35063
|
+
description: "Capture a landing page to a directory, now. Example: baker landing inspiration scrape https://linear.app --out .baker/inspiration/linear.app"
|
|
34995
35064
|
},
|
|
34996
35065
|
args: {
|
|
34997
35066
|
url: { type: "positional", description: "Page to capture", required: true },
|
|
34998
35067
|
out: { type: "string", description: "Output directory", required: true },
|
|
34999
|
-
|
|
35000
|
-
|
|
35001
|
-
|
|
35002
|
-
|
|
35068
|
+
mobile: { type: "boolean", description: "Phone-viewport pass (--no-mobile to skip)", required: false, default: true },
|
|
35069
|
+
code: { type: "boolean", description: "Bundles + fidelity (--no-code to skip)", required: false, default: true },
|
|
35070
|
+
motion: { type: "boolean", description: "Film moving sections (--no-motion to skip)", required: false, default: true },
|
|
35071
|
+
report: { type: "boolean", description: "Write report.html (--no-report to skip)", required: false, default: true }
|
|
35003
35072
|
},
|
|
35004
35073
|
run: async ({ args }) => {
|
|
35005
35074
|
try {
|
|
35006
35075
|
const manifest = await scrapeLanding({
|
|
35007
35076
|
url: args.url,
|
|
35008
35077
|
outDir: args.out,
|
|
35009
|
-
mobile:
|
|
35010
|
-
code:
|
|
35011
|
-
motion:
|
|
35012
|
-
report:
|
|
35078
|
+
mobile: args.mobile,
|
|
35079
|
+
code: args.code,
|
|
35080
|
+
motion: args.motion,
|
|
35081
|
+
report: args.report,
|
|
35013
35082
|
// Progress goes to stderr so stdout stays a clean JSON envelope.
|
|
35014
35083
|
onProgress: (message) => process.stderr.write(`${message}
|
|
35015
35084
|
`)
|
|
35016
35085
|
});
|
|
35017
35086
|
const scored = manifest.sections.map((section) => section.fidelity).filter((value) => value !== null).sort((a, b) => a - b);
|
|
35087
|
+
const recorded = await recordCapture(manifest, args.out);
|
|
35018
35088
|
writeJson({
|
|
35019
35089
|
ok: true,
|
|
35020
35090
|
data: {
|
|
@@ -35023,8 +35093,13 @@ var scrapeCommand = defineCommand147({
|
|
|
35023
35093
|
medianFidelity: scored.length > 0 ? scored[Math.floor(scored.length / 2)] : null,
|
|
35024
35094
|
pageFidelity: manifest.page.fidelity,
|
|
35025
35095
|
out: args.out,
|
|
35026
|
-
report: args
|
|
35027
|
-
}
|
|
35096
|
+
report: args.report ? `${args.out}/report.html` : null
|
|
35097
|
+
},
|
|
35098
|
+
hints: [
|
|
35099
|
+
INSPIRATION_HINTS.structureNotCopy,
|
|
35100
|
+
INSPIRATION_HINTS.adapt,
|
|
35101
|
+
recorded ? "These sections are recorded for the originality check \u2014 `baker landing critique` will block a publish that ships their copy." : "Could not record this capture, so the originality check cannot see it. Be especially careful not to reuse its copy."
|
|
35102
|
+
]
|
|
35028
35103
|
});
|
|
35029
35104
|
} catch (error) {
|
|
35030
35105
|
if (error instanceof BlockedPageError) {
|
|
@@ -35038,7 +35113,7 @@ var scrapeCommand = defineCommand147({
|
|
|
35038
35113
|
|
|
35039
35114
|
// src/commands/landing/inspiration/search.ts
|
|
35040
35115
|
import { mkdir as mkdir12, writeFile as writeFile16 } from "fs/promises";
|
|
35041
|
-
import
|
|
35116
|
+
import path33 from "path";
|
|
35042
35117
|
import { defineCommand as defineCommand148 } from "citty";
|
|
35043
35118
|
registerSchema({
|
|
35044
35119
|
command: "landing.inspiration.search",
|
|
@@ -35120,7 +35195,7 @@ function buildSearchBody(args) {
|
|
|
35120
35195
|
return body;
|
|
35121
35196
|
}
|
|
35122
35197
|
async function downloadShots(results) {
|
|
35123
|
-
const dir =
|
|
35198
|
+
const dir = path33.join(process.cwd(), ".baker", "inspiration");
|
|
35124
35199
|
await mkdir12(dir, { recursive: true });
|
|
35125
35200
|
const saved = /* @__PURE__ */ new Map();
|
|
35126
35201
|
await Promise.all(
|
|
@@ -35129,9 +35204,9 @@ async function downloadShots(results) {
|
|
|
35129
35204
|
try {
|
|
35130
35205
|
const response = await fetch(result.desktopShotUrl);
|
|
35131
35206
|
if (!response.ok) return;
|
|
35132
|
-
const file =
|
|
35207
|
+
const file = path33.join(dir, `${result.id}.png`);
|
|
35133
35208
|
await writeFile16(file, Buffer.from(await response.arrayBuffer()));
|
|
35134
|
-
saved.set(result.id,
|
|
35209
|
+
saved.set(result.id, path33.relative(process.cwd(), file));
|
|
35135
35210
|
} catch {
|
|
35136
35211
|
}
|
|
35137
35212
|
})
|
|
@@ -35218,7 +35293,7 @@ var searchCommand2 = defineCommand148({
|
|
|
35218
35293
|
|
|
35219
35294
|
// src/commands/landing/inspiration/view.ts
|
|
35220
35295
|
import { mkdir as mkdir13, writeFile as writeFile17 } from "fs/promises";
|
|
35221
|
-
import
|
|
35296
|
+
import path34 from "path";
|
|
35222
35297
|
import { defineCommand as defineCommand149 } from "citty";
|
|
35223
35298
|
registerSchema({
|
|
35224
35299
|
command: "landing.inspiration.view",
|
|
@@ -35230,9 +35305,9 @@ async function download(url, file) {
|
|
|
35230
35305
|
try {
|
|
35231
35306
|
const response = await fetch(url);
|
|
35232
35307
|
if (!response.ok) return null;
|
|
35233
|
-
await mkdir13(
|
|
35308
|
+
await mkdir13(path34.dirname(file), { recursive: true });
|
|
35234
35309
|
await writeFile17(file, Buffer.from(await response.arrayBuffer()));
|
|
35235
|
-
return
|
|
35310
|
+
return path34.relative(process.cwd(), file);
|
|
35236
35311
|
} catch {
|
|
35237
35312
|
return null;
|
|
35238
35313
|
}
|
|
@@ -35248,11 +35323,11 @@ var viewCommand2 = defineCommand149({
|
|
|
35248
35323
|
const id = args.id;
|
|
35249
35324
|
const data = await apiGet("/api/landing-inspiration/section", { id });
|
|
35250
35325
|
const section = data.section;
|
|
35251
|
-
const dir =
|
|
35326
|
+
const dir = path34.join(process.cwd(), ".baker", "inspiration", id);
|
|
35252
35327
|
const [desktop, mobile, filmstrip] = await Promise.all([
|
|
35253
|
-
download(section.desktopShotUrl,
|
|
35254
|
-
download(section.mobileShotUrl,
|
|
35255
|
-
download(section.motionFilmstripUrl,
|
|
35328
|
+
download(section.desktopShotUrl, path34.join(dir, "desktop.png")),
|
|
35329
|
+
download(section.mobileShotUrl, path34.join(dir, "mobile.png")),
|
|
35330
|
+
download(section.motionFilmstripUrl, path34.join(dir, "motion-filmstrip.png"))
|
|
35256
35331
|
]);
|
|
35257
35332
|
const hints = [INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt];
|
|
35258
35333
|
const fidelity = fidelityHint(section.fidelity);
|
|
@@ -37081,10 +37156,10 @@ async function stageOp4(op) {
|
|
|
37081
37156
|
handleError4(err);
|
|
37082
37157
|
}
|
|
37083
37158
|
}
|
|
37084
|
-
async function draftAction3(
|
|
37159
|
+
async function draftAction3(path35, body, chat) {
|
|
37085
37160
|
const chatId = resolveChatId(chat);
|
|
37086
37161
|
try {
|
|
37087
|
-
const data = await apiPost(
|
|
37162
|
+
const data = await apiPost(path35, { chatId, ...body });
|
|
37088
37163
|
writeJsonEnvelope({ ok: true, data });
|
|
37089
37164
|
return data;
|
|
37090
37165
|
} catch (err) {
|
|
@@ -38103,7 +38178,7 @@ var searchCommand4 = defineCommand183({
|
|
|
38103
38178
|
var tagsCommand5 = makeTagsCommand("videos", "video", "/api/videos/tags");
|
|
38104
38179
|
|
|
38105
38180
|
// src/commands/videos/upload.ts
|
|
38106
|
-
import { readFile as
|
|
38181
|
+
import { readFile as readFile25, stat as stat7 } from "fs/promises";
|
|
38107
38182
|
import { extname as extname3 } from "path";
|
|
38108
38183
|
import { defineCommand as defineCommand184 } from "citty";
|
|
38109
38184
|
var MIME_MAP = {
|
|
@@ -38168,7 +38243,7 @@ var uploadCommand2 = defineCommand184({
|
|
|
38168
38243
|
return;
|
|
38169
38244
|
}
|
|
38170
38245
|
const { uploadUrl, videoId } = await apiPost("/api/videos/upload", {});
|
|
38171
|
-
const fileBuffer = await
|
|
38246
|
+
const fileBuffer = await readFile25(filePath);
|
|
38172
38247
|
const uploadResponse = await fetch(uploadUrl, {
|
|
38173
38248
|
method: "PUT",
|
|
38174
38249
|
headers: { "Content-Type": contentType },
|