@koda-sl/baker-cli 0.195.0-dev.6d2f498f5 → 0.197.0-dev.6d2f498f5
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 +11 -7
- package/dist/{chunk-PXXJ3HJW.js → chunk-34J26VIJ.js} +4 -4
- package/dist/chunk-F6OHDJIX.js +264 -0
- package/dist/chunk-F6OHDJIX.js.map +1 -0
- package/dist/{chunk-X5C6HE24.js → chunk-G3O4HRYR.js} +3 -3
- package/dist/{chunk-ZWYQIEBI.js → chunk-OB26WXHZ.js} +3 -3
- package/dist/{chunk-IDRQDUBA.js → chunk-SSDYBIZB.js} +290 -16
- package/dist/chunk-SSDYBIZB.js.map +1 -0
- package/dist/{chunk-K3PWXVF7.js → chunk-YUTDQ4PV.js} +2 -2
- package/dist/cli.js +339 -103
- package/dist/cli.js.map +1 -1
- package/dist/client-LTDQPQ7T.js +15 -0
- package/dist/engine/index.js +3 -3
- package/dist/env-GV4VDT5W.js +19 -0
- package/dist/{output-NWX3YW64.js → output-PXTEFDVX.js} +5 -5
- package/dist/{shared-5ZEOG664.js → shared-AR6VAAIE.js} +6 -6
- package/package.json +3 -1
- package/dist/chunk-IDRQDUBA.js.map +0 -1
- package/dist/chunk-YL3HDEIJ.js +0 -76
- package/dist/chunk-YL3HDEIJ.js.map +0 -1
- package/dist/client-PJ7ID35L.js +0 -15
- package/dist/env-6QJCMTRK.js +0 -13
- /package/dist/{chunk-PXXJ3HJW.js.map → chunk-34J26VIJ.js.map} +0 -0
- /package/dist/{chunk-X5C6HE24.js.map → chunk-G3O4HRYR.js.map} +0 -0
- /package/dist/{chunk-ZWYQIEBI.js.map → chunk-OB26WXHZ.js.map} +0 -0
- /package/dist/{chunk-K3PWXVF7.js.map → chunk-YUTDQ4PV.js.map} +0 -0
- /package/dist/{client-PJ7ID35L.js.map → client-LTDQPQ7T.js.map} +0 -0
- /package/dist/{env-6QJCMTRK.js.map → env-GV4VDT5W.js.map} +0 -0
- /package/dist/{output-NWX3YW64.js.map → output-PXTEFDVX.js.map} +0 -0
- /package/dist/{shared-5ZEOG664.js.map → shared-AR6VAAIE.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
describeFailureReason,
|
|
25
25
|
elementMentionKeywords,
|
|
26
26
|
estimateVideoCredits,
|
|
27
|
+
fetchExternalBytes,
|
|
27
28
|
generateCatalog,
|
|
28
29
|
imageProfileFor,
|
|
29
30
|
isPersistedAssetRef,
|
|
@@ -47,7 +48,7 @@ import {
|
|
|
47
48
|
toModelSafeImage,
|
|
48
49
|
ulid,
|
|
49
50
|
validateCanvasDeep
|
|
50
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-SSDYBIZB.js";
|
|
51
52
|
import {
|
|
52
53
|
csvOrJson,
|
|
53
54
|
daysAgoIso,
|
|
@@ -56,7 +57,7 @@ import {
|
|
|
56
57
|
resolveAccountIdArg,
|
|
57
58
|
resolveEffectiveStatus,
|
|
58
59
|
todayIso
|
|
59
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-34J26VIJ.js";
|
|
60
61
|
import {
|
|
61
62
|
buildQueryCacheKey,
|
|
62
63
|
cacheGet,
|
|
@@ -72,22 +73,28 @@ import {
|
|
|
72
73
|
writeAdsJson,
|
|
73
74
|
writeAdsOutput,
|
|
74
75
|
writeJsonEnvelope
|
|
75
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-G3O4HRYR.js";
|
|
76
77
|
import {
|
|
77
78
|
ApiError,
|
|
78
79
|
apiGet,
|
|
79
80
|
apiPost,
|
|
80
81
|
validateConvexId
|
|
81
|
-
} from "./chunk-
|
|
82
|
+
} from "./chunk-OB26WXHZ.js";
|
|
82
83
|
import {
|
|
83
84
|
installStreamTaps,
|
|
84
85
|
logInvocation
|
|
85
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-YUTDQ4PV.js";
|
|
86
87
|
import {
|
|
88
|
+
captureBudgetMs,
|
|
89
|
+
captureProxyCredentials,
|
|
87
90
|
getEnv,
|
|
91
|
+
isProxyFailure,
|
|
92
|
+
plannedRoutes,
|
|
93
|
+
refuseNonPublicUrl,
|
|
88
94
|
requireChatId,
|
|
89
|
-
resolveChatId
|
|
90
|
-
|
|
95
|
+
resolveChatId,
|
|
96
|
+
shouldEscalate
|
|
97
|
+
} from "./chunk-F6OHDJIX.js";
|
|
91
98
|
|
|
92
99
|
// src/cli.ts
|
|
93
100
|
import { defineCommand as defineCommand212, runMain } from "citty";
|
|
@@ -483,15 +490,26 @@ function writeOutput(envelope, format, fields, full, normalizer) {
|
|
|
483
490
|
writeJson(envelope);
|
|
484
491
|
return;
|
|
485
492
|
}
|
|
493
|
+
if (format !== "files" && format !== "md") {
|
|
494
|
+
process.stderr.write(`Unknown --output "${String(format)}". Valid formats: json, files, md. Using json.
|
|
495
|
+
`);
|
|
496
|
+
writeJson(envelope);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
486
499
|
const items = normalizeData(envelope, fields, full ?? false, normalizer ?? imageNormalizer);
|
|
487
500
|
const displayFields = fields ?? Object.keys(items[0] ?? {});
|
|
488
501
|
if (format === "files") {
|
|
489
502
|
writeFiles(items, displayFields);
|
|
490
|
-
|
|
491
|
-
}
|
|
492
|
-
if (format === "md") {
|
|
503
|
+
} else {
|
|
493
504
|
writeMd(items, displayFields);
|
|
494
505
|
}
|
|
506
|
+
writeHints(envelope.hints);
|
|
507
|
+
}
|
|
508
|
+
function writeHints(hints) {
|
|
509
|
+
for (const hint of hints ?? []) {
|
|
510
|
+
process.stderr.write(`${hint}
|
|
511
|
+
`);
|
|
512
|
+
}
|
|
495
513
|
}
|
|
496
514
|
|
|
497
515
|
// src/schemas.ts
|
|
@@ -16993,10 +17011,10 @@ function duplicateCommand2(entity, label) {
|
|
|
16993
17011
|
replace: { type: "boolean", description: "Pause the original once the copy publishes" }
|
|
16994
17012
|
},
|
|
16995
17013
|
run: async ({ args }) => {
|
|
16996
|
-
const { apiPost: apiPost2 } = await import("./client-
|
|
16997
|
-
const { requireChatId: requireChatId2 } = await import("./env-
|
|
16998
|
-
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-
|
|
16999
|
-
const { handleMetaError: handleMetaError2 } = await import("./shared-
|
|
17014
|
+
const { apiPost: apiPost2 } = await import("./client-LTDQPQ7T.js");
|
|
17015
|
+
const { requireChatId: requireChatId2 } = await import("./env-GV4VDT5W.js");
|
|
17016
|
+
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-PXTEFDVX.js");
|
|
17017
|
+
const { handleMetaError: handleMetaError2 } = await import("./shared-AR6VAAIE.js");
|
|
17000
17018
|
try {
|
|
17001
17019
|
const accountId = bareAccountId2(args);
|
|
17002
17020
|
const chatId = requireChatId2();
|
|
@@ -25345,9 +25363,11 @@ async function fileExists(target) {
|
|
|
25345
25363
|
async function uploadSourceAsReference(source, isUrl, client) {
|
|
25346
25364
|
let bytes;
|
|
25347
25365
|
if (isUrl) {
|
|
25348
|
-
|
|
25349
|
-
|
|
25350
|
-
|
|
25366
|
+
try {
|
|
25367
|
+
({ buffer: bytes } = await fetchExternalBytes(source));
|
|
25368
|
+
} catch (e) {
|
|
25369
|
+
throw new Error(`failed to download source image (${e instanceof Error ? e.message : String(e)})`);
|
|
25370
|
+
}
|
|
25351
25371
|
} else {
|
|
25352
25372
|
bytes = await readFile12(source);
|
|
25353
25373
|
}
|
|
@@ -26042,18 +26062,17 @@ function videoDefinitionDescription(blueprint) {
|
|
|
26042
26062
|
}
|
|
26043
26063
|
async function materializeReferenceVideo(fileArg2) {
|
|
26044
26064
|
if (!/^https?:\/\//i.test(fileArg2)) return path21.resolve(fileArg2);
|
|
26045
|
-
let
|
|
26065
|
+
let bytes;
|
|
26066
|
+
let contentType;
|
|
26046
26067
|
try {
|
|
26047
|
-
|
|
26068
|
+
({ buffer: bytes, contentType } = await fetchExternalBytes(fileArg2, { sniffChallenge: false }));
|
|
26048
26069
|
} catch (e) {
|
|
26049
26070
|
throw new Error(`failed to download reference video: ${e instanceof Error ? e.message : String(e)}`);
|
|
26050
26071
|
}
|
|
26051
|
-
if (!res.ok) throw new Error(`failed to download reference video (${res.status} ${res.statusText})`);
|
|
26052
|
-
const bytes = Buffer.from(await res.arrayBuffer());
|
|
26053
26072
|
if (bytes.length === 0) throw new Error("reference video download was empty");
|
|
26054
26073
|
const dest = path21.join(
|
|
26055
26074
|
tmpdir2(),
|
|
26056
|
-
`baker-ref-${sha256Hex(bytes).slice(0, 16)}${referenceVideoExt(fileArg2,
|
|
26075
|
+
`baker-ref-${sha256Hex(bytes).slice(0, 16)}${referenceVideoExt(fileArg2, contentType)}`
|
|
26057
26076
|
);
|
|
26058
26077
|
await writeFile8(dest, bytes);
|
|
26059
26078
|
return dest;
|
|
@@ -29695,11 +29714,8 @@ async function expandInputs(spec) {
|
|
|
29695
29714
|
}
|
|
29696
29715
|
async function readImageBuffer(pathOrUrl) {
|
|
29697
29716
|
if (isRemoteUrl(pathOrUrl)) {
|
|
29698
|
-
const
|
|
29699
|
-
|
|
29700
|
-
throw new Error(`Failed to fetch ${pathOrUrl}: ${response.status} ${response.statusText}`);
|
|
29701
|
-
}
|
|
29702
|
-
return Buffer.from(await response.arrayBuffer());
|
|
29717
|
+
const { buffer } = await fetchExternalBytes(pathOrUrl);
|
|
29718
|
+
return buffer;
|
|
29703
29719
|
}
|
|
29704
29720
|
return readFile19(pathOrUrl);
|
|
29705
29721
|
}
|
|
@@ -29988,15 +30004,19 @@ function resolveDownloadPath({ baseName, extension, out, outIsDirectory: outIsDi
|
|
|
29988
30004
|
return out;
|
|
29989
30005
|
}
|
|
29990
30006
|
function disambiguate(paths) {
|
|
29991
|
-
const
|
|
30007
|
+
const taken = /* @__PURE__ */ new Set();
|
|
29992
30008
|
return paths.map((path35) => {
|
|
29993
|
-
|
|
29994
|
-
|
|
29995
|
-
if (count === 0) {
|
|
30009
|
+
if (!taken.has(path35)) {
|
|
30010
|
+
taken.add(path35);
|
|
29996
30011
|
return path35;
|
|
29997
30012
|
}
|
|
29998
30013
|
const ext = extname3(path35);
|
|
29999
|
-
|
|
30014
|
+
const stem = path35.slice(0, path35.length - ext.length);
|
|
30015
|
+
let n = 2;
|
|
30016
|
+
while (taken.has(`${stem}-${n}${ext}`)) n += 1;
|
|
30017
|
+
const unique = `${stem}-${n}${ext}`;
|
|
30018
|
+
taken.add(unique);
|
|
30019
|
+
return unique;
|
|
30000
30020
|
});
|
|
30001
30021
|
}
|
|
30002
30022
|
|
|
@@ -30063,17 +30083,10 @@ async function resolveTarget(target) {
|
|
|
30063
30083
|
return { input: target.raw, url: doc.imageUrl, baseName: stem };
|
|
30064
30084
|
}
|
|
30065
30085
|
async function fetchBytes(url) {
|
|
30066
|
-
|
|
30067
|
-
if (!response.ok) {
|
|
30068
|
-
throw new Error(`${response.status} ${response.statusText}`);
|
|
30069
|
-
}
|
|
30070
|
-
return {
|
|
30071
|
-
buffer: Buffer.from(await response.arrayBuffer()),
|
|
30072
|
-
contentType: response.headers.get("content-type")
|
|
30073
|
-
};
|
|
30086
|
+
return await fetchExternalBytes(url);
|
|
30074
30087
|
}
|
|
30075
30088
|
var NEXT_STEP_HINT = "Downloaded files are local paths \u2014 the local transforms take them directly: `baker images normalize <file> --color '#FFFFFF' --height 40`, `baker images crop <file> --x \u2026 --y \u2026 --width \u2026 --height \u2026`, `baker images dimensions <file>`. For a landing, page-scoped images belong in `src/pages/{landing}/_images/`.";
|
|
30076
|
-
var PARTIAL_FAILURE_HINT = "Some targets did not download. Carry on with the ones that did \u2014 a missing asset never blocks the rest of the job. Retry a failed URL only if it was a transient 5xx; a 403 means the host
|
|
30089
|
+
var PARTIAL_FAILURE_HINT = "Some targets did not download. Carry on with the ones that did \u2014 a missing asset never blocks the rest of the job. Retry a failed URL only if it was a transient 5xx; a 403 means the host will not serve these bytes to anyone but a browser, so re-source it (`baker images extract <page>`) instead.";
|
|
30077
30090
|
var ALL_FAILED_FIX = {
|
|
30078
30091
|
action: "use_different_resource",
|
|
30079
30092
|
explanation: "None of the targets could be downloaded. A 403/404 means the host will not serve these bytes \u2014 re-source the asset (`baker images extract <page-url>`, `baker images stock <query>`) or, for a library image, check the row is `ready` with `baker images get <id>`. Do not retry the same URLs in a loop, and do not fall back to `curl` \u2014 continue the job with a placeholder if nothing can be fetched."
|
|
@@ -34842,6 +34855,26 @@ function capturedCopyStrings(markup) {
|
|
|
34842
34855
|
return [...seen];
|
|
34843
34856
|
}
|
|
34844
34857
|
|
|
34858
|
+
// src/engine/landing-library/proxyFailure.ts
|
|
34859
|
+
var PROXY_STATUS = 407;
|
|
34860
|
+
var PROXY_NET_ERRORS = [
|
|
34861
|
+
"ERR_TUNNEL_CONNECTION_FAILED",
|
|
34862
|
+
"ERR_PROXY_CONNECTION_FAILED",
|
|
34863
|
+
"ERR_PROXY_AUTH_REQUESTED",
|
|
34864
|
+
"ERR_PROXY_CERTIFICATE_INVALID",
|
|
34865
|
+
"ERR_UNEXPECTED_PROXY_AUTH",
|
|
34866
|
+
"ERR_MANDATORY_PROXY_CONFIGURATION_FAILED",
|
|
34867
|
+
"ERR_HTTPS_PROXY_TUNNEL_RESPONSE_REDIRECT"
|
|
34868
|
+
];
|
|
34869
|
+
var PROXY_UNAVAILABLE_MESSAGE = "We couldn't reach this page just now. Please try adding it again.";
|
|
34870
|
+
function describeProxyFailure(error) {
|
|
34871
|
+
const raw = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
34872
|
+
if (!raw) return null;
|
|
34873
|
+
const netError = /net::(ERR_[A-Z0-9_]+)/.exec(raw)?.[1];
|
|
34874
|
+
if (!netError || !PROXY_NET_ERRORS.includes(netError)) return null;
|
|
34875
|
+
return { code: "PROXY_UNAVAILABLE", message: PROXY_UNAVAILABLE_MESSAGE };
|
|
34876
|
+
}
|
|
34877
|
+
|
|
34845
34878
|
// src/engine/landing-library/unreachable.ts
|
|
34846
34879
|
var NET_ERROR_REASONS = [
|
|
34847
34880
|
{
|
|
@@ -34893,6 +34926,10 @@ function describeUnreachableSite(error) {
|
|
|
34893
34926
|
}
|
|
34894
34927
|
return null;
|
|
34895
34928
|
}
|
|
34929
|
+
function netErrorName(error) {
|
|
34930
|
+
const raw = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
34931
|
+
return raw ? /net::(ERR_[A-Z0-9_]+)/.exec(raw)?.[1] ?? null : null;
|
|
34932
|
+
}
|
|
34896
34933
|
|
|
34897
34934
|
// src/engine/landing-library/blocked.ts
|
|
34898
34935
|
var CHALLENGE_PHRASES = [
|
|
@@ -34912,6 +34949,9 @@ var CHALLENGE_PHRASES = [
|
|
|
34912
34949
|
];
|
|
34913
34950
|
var CHALLENGE_MARKERS = ["cf-browser-verification", "cf_chl_", "px-captcha", "_incapsula_", "distil_r_captcha"];
|
|
34914
34951
|
function detectBlockedPage(page) {
|
|
34952
|
+
if (page.status === PROXY_STATUS) {
|
|
34953
|
+
return { code: "PROXY_UNAVAILABLE", message: PROXY_UNAVAILABLE_MESSAGE };
|
|
34954
|
+
}
|
|
34915
34955
|
if (page.status !== null && page.status >= 400) {
|
|
34916
34956
|
return {
|
|
34917
34957
|
code: "HTTP_ERROR",
|
|
@@ -34938,14 +34978,44 @@ var BlockedPageError = class extends Error {
|
|
|
34938
34978
|
this.code = blocked.code;
|
|
34939
34979
|
}
|
|
34940
34980
|
};
|
|
34981
|
+
var CaptureFailureError = class extends Error {
|
|
34982
|
+
failure;
|
|
34983
|
+
/**
|
|
34984
|
+
* Which rungs were tried, and what each of them said.
|
|
34985
|
+
*
|
|
34986
|
+
* Carried out of the engine because nothing downstream could otherwise tell a
|
|
34987
|
+
* page refused from three different addresses apart from one refused once —
|
|
34988
|
+
* and those two call for opposite conclusions about whether to try again.
|
|
34989
|
+
*
|
|
34990
|
+
* It is also the only way to see the rollout fail silently. The E2B image
|
|
34991
|
+
* installs the CLI at build time, so until the template is rebuilt a Runtime
|
|
34992
|
+
* runs an older binary that ignores the credentials Convex hands it. That
|
|
34993
|
+
* capture returns an envelope with **no rungs at all** — indistinguishable,
|
|
34994
|
+
* from the outside, from a page that simply was not worth escalating.
|
|
34995
|
+
*
|
|
34996
|
+
* Deliberately thin: tier names and status codes only, so nothing here can
|
|
34997
|
+
* carry a credential or the raw browser text that once reached a user.
|
|
34998
|
+
*/
|
|
34999
|
+
rungs;
|
|
35000
|
+
constructor(failure, rungs = []) {
|
|
35001
|
+
super(failure.message);
|
|
35002
|
+
this.name = "CaptureFailureError";
|
|
35003
|
+
this.failure = failure;
|
|
35004
|
+
this.rungs = rungs;
|
|
35005
|
+
}
|
|
35006
|
+
};
|
|
35007
|
+
var LOCAL_ADDRESS_REFUSAL = "That address only opens from inside your own network, so there is nothing there for us to read. A reference page has to be live on the web.";
|
|
34941
35008
|
var NODE_LEVEL_UNREACHABLE = ["econnrefused", "enotfound", "getaddrinfo"];
|
|
34942
35009
|
var UNREACHABLE_MESSAGE = "We couldn't reach this page \u2014 the site didn't answer, or its security certificate isn't one we can read.";
|
|
34943
35010
|
var TOO_SLOW_MESSAGE = "This page took too long to load, so we stopped waiting for it.";
|
|
34944
35011
|
var CAPTURE_FAILED_MESSAGE = "We couldn't read this page.";
|
|
34945
35012
|
function classifyCaptureFailure(error) {
|
|
35013
|
+
if (error instanceof CaptureFailureError) return error.failure;
|
|
34946
35014
|
if (error instanceof BlockedPageError) {
|
|
34947
35015
|
return { code: error.code, message: error.message };
|
|
34948
35016
|
}
|
|
35017
|
+
const proxyFailure = describeProxyFailure(error);
|
|
35018
|
+
if (proxyFailure) return proxyFailure;
|
|
34949
35019
|
const unreachable = describeUnreachableSite(error);
|
|
34950
35020
|
if (unreachable) return unreachable;
|
|
34951
35021
|
const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
@@ -34969,11 +35039,12 @@ var pwSpecifier = ["play", "wright"].join("");
|
|
|
34969
35039
|
var DESKTOP_VIEWPORT = { width: 1440, height: 900 };
|
|
34970
35040
|
var MOBILE_VIEWPORT = { width: 390, height: 844 };
|
|
34971
35041
|
var DEVICE_SCALE_FACTOR = 2;
|
|
34972
|
-
async function launchBrowser() {
|
|
35042
|
+
async function launchBrowser(route = { kind: "direct" }) {
|
|
34973
35043
|
const playwright = require_(pwSpecifier);
|
|
34974
35044
|
return await playwright.chromium.launch({
|
|
34975
35045
|
headless: true,
|
|
34976
|
-
args: ["--hide-scrollbars", "--disable-blink-features=AutomationControlled", "--mute-audio"]
|
|
35046
|
+
args: ["--hide-scrollbars", "--disable-blink-features=AutomationControlled", "--mute-audio"],
|
|
35047
|
+
...route.kind === "proxy" ? { proxy: { server: route.server, username: route.username, password: route.password } } : {}
|
|
34977
35048
|
});
|
|
34978
35049
|
}
|
|
34979
35050
|
async function newPage(browser, viewport, opts = { motion: false }) {
|
|
@@ -35314,6 +35385,42 @@ async function captureSectionOnMobile(page, section) {
|
|
|
35314
35385
|
return await locator.screenshot({ type: "png", timeout: 15e3 }).catch(() => null);
|
|
35315
35386
|
}
|
|
35316
35387
|
|
|
35388
|
+
// src/engine/landing-library/escalation.ts
|
|
35389
|
+
var CAPTURE_RESERVE_MS = 18e4;
|
|
35390
|
+
function attemptSignal(outcome) {
|
|
35391
|
+
const code = outcome.failure.code;
|
|
35392
|
+
return {
|
|
35393
|
+
challenge: code === "BOT_CHALLENGE",
|
|
35394
|
+
status: outcome.status,
|
|
35395
|
+
netError: outcome.netError,
|
|
35396
|
+
timedOut: code === "SITE_TOO_SLOW"
|
|
35397
|
+
};
|
|
35398
|
+
}
|
|
35399
|
+
function nextRoute(args) {
|
|
35400
|
+
const last = args.attempts[args.attempts.length - 1];
|
|
35401
|
+
if (!last) return { next: null, reason: "ladder_exhausted" };
|
|
35402
|
+
const brokenProxy = isProxyFailure(attemptSignal(last));
|
|
35403
|
+
if (!brokenProxy && !shouldEscalate(attemptSignal(last))) {
|
|
35404
|
+
return { next: null, reason: "site_fault_is_final" };
|
|
35405
|
+
}
|
|
35406
|
+
const tried = new Set(args.attempts.map((attempt) => routeKey(attempt.route)));
|
|
35407
|
+
const next = args.routes.find((route) => !tried.has(routeKey(route)));
|
|
35408
|
+
if (!next) return { next: null, reason: args.routes.length === 1 ? "not_configured" : "ladder_exhausted" };
|
|
35409
|
+
if (args.remainingMs < args.reserveMs + args.navTimeoutMs) {
|
|
35410
|
+
return { next: null, reason: "out_of_budget" };
|
|
35411
|
+
}
|
|
35412
|
+
return { next, reason: "escalate" };
|
|
35413
|
+
}
|
|
35414
|
+
function reportableFailure(attempts) {
|
|
35415
|
+
const siteVerdicts = attempts.filter((attempt) => !isProxyFailure(attemptSignal(attempt)));
|
|
35416
|
+
const chosen = siteVerdicts.find((attempt) => attempt.status !== null) ?? siteVerdicts[siteVerdicts.length - 1] ?? attempts[attempts.length - 1];
|
|
35417
|
+
if (!chosen) throw new Error("reportableFailure called with no attempts");
|
|
35418
|
+
return chosen.failure;
|
|
35419
|
+
}
|
|
35420
|
+
function routeKey(route) {
|
|
35421
|
+
return route.kind === "direct" ? "direct" : route.tier;
|
|
35422
|
+
}
|
|
35423
|
+
|
|
35317
35424
|
// src/engine/landing-library/fidelity.ts
|
|
35318
35425
|
import sharp3 from "sharp";
|
|
35319
35426
|
var COMPARISON_SIZE = 32;
|
|
@@ -36573,17 +36680,16 @@ async function captureMotionTakes(args) {
|
|
|
36573
36680
|
})
|
|
36574
36681
|
);
|
|
36575
36682
|
}
|
|
36576
|
-
async function
|
|
36577
|
-
const
|
|
36578
|
-
const
|
|
36579
|
-
|
|
36580
|
-
const browser = await launchBrowser();
|
|
36683
|
+
async function openPageVia(route, url, timeoutMs) {
|
|
36684
|
+
const startedAt = Date.now();
|
|
36685
|
+
const browser = await launchBrowser(route);
|
|
36686
|
+
let status = null;
|
|
36581
36687
|
try {
|
|
36582
36688
|
const { context, page } = await newPage(browser, DESKTOP_VIEWPORT);
|
|
36583
36689
|
await blockConsentManagers(page);
|
|
36584
36690
|
await installPageRuntime(page);
|
|
36585
|
-
|
|
36586
|
-
|
|
36691
|
+
const prepared = await preparePage(page, url, timeoutMs);
|
|
36692
|
+
status = prepared.status;
|
|
36587
36693
|
const blocked = detectBlockedPage({
|
|
36588
36694
|
status: prepared.status,
|
|
36589
36695
|
title: prepared.title,
|
|
@@ -36591,58 +36697,148 @@ async function scrapeLanding(options) {
|
|
|
36591
36697
|
html: await page.content().catch(() => "")
|
|
36592
36698
|
});
|
|
36593
36699
|
if (blocked) throw new BlockedPageError(blocked);
|
|
36594
|
-
|
|
36595
|
-
|
|
36596
|
-
|
|
36597
|
-
|
|
36598
|
-
|
|
36599
|
-
|
|
36600
|
-
|
|
36601
|
-
|
|
36602
|
-
|
|
36603
|
-
|
|
36604
|
-
|
|
36605
|
-
|
|
36606
|
-
|
|
36607
|
-
|
|
36608
|
-
|
|
36609
|
-
|
|
36610
|
-
|
|
36611
|
-
|
|
36612
|
-
|
|
36613
|
-
}
|
|
36614
|
-
|
|
36615
|
-
|
|
36616
|
-
|
|
36700
|
+
return { ok: true, browser, context, page, prepared };
|
|
36701
|
+
} catch (error) {
|
|
36702
|
+
await browser.close().catch(() => void 0);
|
|
36703
|
+
return {
|
|
36704
|
+
ok: false,
|
|
36705
|
+
outcome: {
|
|
36706
|
+
route,
|
|
36707
|
+
failure: classifyCaptureFailure(error),
|
|
36708
|
+
status,
|
|
36709
|
+
netError: netErrorName(error),
|
|
36710
|
+
elapsedMs: Date.now() - startedAt
|
|
36711
|
+
}
|
|
36712
|
+
};
|
|
36713
|
+
}
|
|
36714
|
+
}
|
|
36715
|
+
async function captureAlternateViews(args) {
|
|
36716
|
+
const { browser, sections, sectionsDir, outDir, pageUrl, timeoutMs, withMobile, withMotion, log } = args;
|
|
36717
|
+
if (withMobile) {
|
|
36718
|
+
log("capturing mobile");
|
|
36719
|
+
await captureMobileShots({ browser, sections, sectionsDir, outDir, pageUrl, timeoutMs });
|
|
36720
|
+
}
|
|
36721
|
+
if (withMotion) {
|
|
36722
|
+
await captureMotionTakes({ browser, sections, sectionsDir, outDir, pageUrl, log });
|
|
36723
|
+
}
|
|
36724
|
+
}
|
|
36725
|
+
async function reproduceWholePage(args) {
|
|
36726
|
+
const { browser, page, outDir, pageUrl, withCode, log } = args;
|
|
36727
|
+
const fullPage = await page.screenshot({ type: "png", fullPage: true }).catch(() => null);
|
|
36728
|
+
if (fullPage) await writeFile15(path31.join(outDir, "full-page.png"), fullPage);
|
|
36729
|
+
if (!withCode) return { bundle: null, fidelity: null };
|
|
36730
|
+
const reproduction = await reproducePage({ browser, page, outDir, pageUrl, livePageShot: fullPage });
|
|
36731
|
+
log(`page reproduction: ${reproduction.fidelity === null ? "unavailable" : reproduction.fidelity.toFixed(2)}`);
|
|
36732
|
+
return reproduction;
|
|
36733
|
+
}
|
|
36734
|
+
async function captureSections(args) {
|
|
36735
|
+
const { browser, page, sectionsDir, outDir, pageUrl, withCode, log } = args;
|
|
36736
|
+
log("segmenting");
|
|
36737
|
+
const candidates = await segmentPage(page, DEFAULT_SEGMENT_OPTIONS);
|
|
36738
|
+
log(`found ${candidates.length} sections`);
|
|
36739
|
+
const sections = [];
|
|
36740
|
+
for (const candidate of candidates) {
|
|
36741
|
+
const section = await captureOneSection({
|
|
36617
36742
|
browser,
|
|
36618
36743
|
page,
|
|
36744
|
+
candidate,
|
|
36745
|
+
sectionsDir,
|
|
36746
|
+
outDir,
|
|
36747
|
+
pageUrl,
|
|
36748
|
+
withCode
|
|
36749
|
+
});
|
|
36750
|
+
sections.push(section);
|
|
36751
|
+
log(
|
|
36752
|
+
` [${candidate.index}] ${candidate.rect.width}x${candidate.rect.height}${section.fidelity === null ? "" : ` fidelity=${section.fidelity.toFixed(2)}`} \u2014 ${candidate.textPreview.slice(0, 50)}`
|
|
36753
|
+
);
|
|
36754
|
+
}
|
|
36755
|
+
return sections;
|
|
36756
|
+
}
|
|
36757
|
+
function rungReport(outcome) {
|
|
36758
|
+
return {
|
|
36759
|
+
tier: outcome.route.kind === "proxy" ? outcome.route.tier : "direct",
|
|
36760
|
+
code: outcome.failure.code,
|
|
36761
|
+
status: outcome.status
|
|
36762
|
+
};
|
|
36763
|
+
}
|
|
36764
|
+
async function openViaLadder(args) {
|
|
36765
|
+
const { url, routes, timeoutMs, deadline, log } = args;
|
|
36766
|
+
const attempts = [];
|
|
36767
|
+
for (let route = routes[0] ?? { kind: "direct" }; route; ) {
|
|
36768
|
+
log(`loading ${url}${route.kind === "proxy" ? ` (retrying via ${route.tier})` : ""}`);
|
|
36769
|
+
const attempt = await openPageVia(route, url, timeoutMs);
|
|
36770
|
+
if (attempt.ok) {
|
|
36771
|
+
return { opened: attempt, attempts, tier: route.kind === "proxy" ? route.tier : "direct" };
|
|
36772
|
+
}
|
|
36773
|
+
attempts.push(attempt.outcome);
|
|
36774
|
+
const decision = nextRoute({
|
|
36775
|
+
routes,
|
|
36776
|
+
attempts,
|
|
36777
|
+
remainingMs: deadline ? deadline - Date.now() : Number.POSITIVE_INFINITY,
|
|
36778
|
+
reserveMs: CAPTURE_RESERVE_MS,
|
|
36779
|
+
navTimeoutMs: timeoutMs
|
|
36780
|
+
});
|
|
36781
|
+
if (!decision.next) {
|
|
36782
|
+
log(`giving up after ${attempts.length} attempt(s) \u2014 ${decision.reason}`);
|
|
36783
|
+
break;
|
|
36784
|
+
}
|
|
36785
|
+
route = decision.next;
|
|
36786
|
+
}
|
|
36787
|
+
throw new CaptureFailureError(reportableFailure(attempts), attempts.map(rungReport));
|
|
36788
|
+
}
|
|
36789
|
+
async function scrapeLanding(options) {
|
|
36790
|
+
const timeoutMs = options.timeoutMs ?? 45e3;
|
|
36791
|
+
const log = options.onProgress ?? (() => void 0);
|
|
36792
|
+
const sectionsDir = path31.join(options.outDir, "sections");
|
|
36793
|
+
const nonPublic = refuseNonPublicUrl(options.url);
|
|
36794
|
+
if (nonPublic) {
|
|
36795
|
+
throw new BlockedPageError({
|
|
36796
|
+
code: "HTTP_ERROR",
|
|
36797
|
+
message: nonPublic === "unparseable" ? "That doesn't look like a web address we can open." : LOCAL_ADDRESS_REFUSAL
|
|
36798
|
+
});
|
|
36799
|
+
}
|
|
36800
|
+
const { opened, attempts, tier } = await openViaLadder({
|
|
36801
|
+
url: options.url,
|
|
36802
|
+
routes: plannedRoutes(options.url, options.proxyCredentials ?? {}),
|
|
36803
|
+
timeoutMs,
|
|
36804
|
+
deadline: options.budgetMs ? Date.now() + options.budgetMs : null,
|
|
36805
|
+
log
|
|
36806
|
+
});
|
|
36807
|
+
const { browser, context, page, prepared } = opened;
|
|
36808
|
+
const escalated = attempts.length > 0;
|
|
36809
|
+
const withMotion = options.motion !== false && !escalated;
|
|
36810
|
+
const renderBrowser = options.code === false ? null : await launchBrowser();
|
|
36811
|
+
try {
|
|
36812
|
+
await mkdir11(sectionsDir, { recursive: true });
|
|
36813
|
+
const sections = await captureSections({
|
|
36814
|
+
browser: renderBrowser ?? browser,
|
|
36815
|
+
page,
|
|
36816
|
+
sectionsDir,
|
|
36817
|
+
outDir: options.outDir,
|
|
36818
|
+
pageUrl: prepared.finalUrl,
|
|
36819
|
+
withCode: options.code !== false,
|
|
36820
|
+
log
|
|
36821
|
+
});
|
|
36822
|
+
const reproduction = await reproduceWholePage({
|
|
36823
|
+
browser: renderBrowser ?? browser,
|
|
36824
|
+
page,
|
|
36619
36825
|
outDir: options.outDir,
|
|
36620
36826
|
pageUrl: prepared.finalUrl,
|
|
36621
|
-
|
|
36827
|
+
withCode: options.code !== false,
|
|
36828
|
+
log
|
|
36622
36829
|
});
|
|
36623
|
-
log(`page reproduction: ${reproduction.fidelity === null ? "unavailable" : reproduction.fidelity.toFixed(2)}`);
|
|
36624
36830
|
await context.close();
|
|
36625
|
-
|
|
36626
|
-
|
|
36627
|
-
|
|
36628
|
-
|
|
36629
|
-
|
|
36630
|
-
|
|
36631
|
-
|
|
36632
|
-
|
|
36633
|
-
|
|
36634
|
-
|
|
36635
|
-
}
|
|
36636
|
-
if (options.motion !== false) {
|
|
36637
|
-
await captureMotionTakes({
|
|
36638
|
-
browser,
|
|
36639
|
-
sections,
|
|
36640
|
-
sectionsDir,
|
|
36641
|
-
outDir: options.outDir,
|
|
36642
|
-
pageUrl: prepared.finalUrl,
|
|
36643
|
-
log
|
|
36644
|
-
});
|
|
36645
|
-
}
|
|
36831
|
+
await captureAlternateViews({
|
|
36832
|
+
browser,
|
|
36833
|
+
sections,
|
|
36834
|
+
sectionsDir,
|
|
36835
|
+
outDir: options.outDir,
|
|
36836
|
+
pageUrl: prepared.finalUrl,
|
|
36837
|
+
timeoutMs,
|
|
36838
|
+
withMobile: options.mobile !== false,
|
|
36839
|
+
withMotion,
|
|
36840
|
+
log
|
|
36841
|
+
});
|
|
36646
36842
|
const manifest = {
|
|
36647
36843
|
url: options.url,
|
|
36648
36844
|
finalUrl: prepared.finalUrl,
|
|
@@ -36652,7 +36848,8 @@ async function scrapeLanding(options) {
|
|
|
36652
36848
|
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
36653
36849
|
sections,
|
|
36654
36850
|
page: reproduction,
|
|
36655
|
-
security: prepared.security
|
|
36851
|
+
security: prepared.security,
|
|
36852
|
+
captureTier: tier
|
|
36656
36853
|
};
|
|
36657
36854
|
await writeFile15(path31.join(options.outDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
36658
36855
|
`);
|
|
@@ -36663,11 +36860,18 @@ async function scrapeLanding(options) {
|
|
|
36663
36860
|
return manifest;
|
|
36664
36861
|
} finally {
|
|
36665
36862
|
await browser.close();
|
|
36863
|
+
if (renderBrowser) await renderBrowser.close();
|
|
36666
36864
|
}
|
|
36667
36865
|
}
|
|
36668
36866
|
|
|
36669
36867
|
// src/commands/landing/inspiration/scrape.ts
|
|
36670
|
-
var RETRYABLE_FAILURES = /* @__PURE__ */ new Set([
|
|
36868
|
+
var RETRYABLE_FAILURES = /* @__PURE__ */ new Set([
|
|
36869
|
+
"SITE_TOO_SLOW",
|
|
36870
|
+
"CAPTURE_FAILED",
|
|
36871
|
+
// Ours, not the site's. Filing it as final is exactly how a mistyped password
|
|
36872
|
+
// would blacklist a page that was never actually judged.
|
|
36873
|
+
"PROXY_UNAVAILABLE"
|
|
36874
|
+
]);
|
|
36671
36875
|
async function recordCapture(manifest, outDir) {
|
|
36672
36876
|
const consultedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
36673
36877
|
const references = [];
|
|
@@ -36686,7 +36890,8 @@ async function recordCapture(manifest, outDir) {
|
|
|
36686
36890
|
} catch {
|
|
36687
36891
|
}
|
|
36688
36892
|
}
|
|
36689
|
-
|
|
36893
|
+
const written = await recordReferences(process.cwd(), references);
|
|
36894
|
+
return written ? references.length : 0;
|
|
36690
36895
|
}
|
|
36691
36896
|
registerSchema({
|
|
36692
36897
|
command: "landing.inspiration.scrape",
|
|
@@ -36740,11 +36945,17 @@ var scrapeCommand = defineCommand148({
|
|
|
36740
36945
|
code: args.code,
|
|
36741
36946
|
motion: args.motion,
|
|
36742
36947
|
report: args.report,
|
|
36948
|
+
// Both come from the environment, never from a flag — see their docs in
|
|
36949
|
+
// `src/env.ts`. Absent credentials this is the direct route only, which
|
|
36950
|
+
// is exactly the behaviour that shipped before the ladder existed.
|
|
36951
|
+
proxyCredentials: captureProxyCredentials(),
|
|
36952
|
+
budgetMs: captureBudgetMs() ?? void 0,
|
|
36743
36953
|
// Progress goes to stderr so stdout stays a clean JSON envelope.
|
|
36744
36954
|
onProgress: (message) => process.stderr.write(`${message}
|
|
36745
36955
|
`)
|
|
36746
36956
|
});
|
|
36747
36957
|
const scored = manifest.sections.map((section) => section.fidelity).filter((value) => value !== null).sort((a, b) => a - b);
|
|
36958
|
+
const shotCount = manifest.sections.filter((section) => section.desktopShot !== null).length;
|
|
36748
36959
|
const recorded = await recordCapture(manifest, args.out);
|
|
36749
36960
|
writeJson({
|
|
36750
36961
|
ok: true,
|
|
@@ -36755,6 +36966,11 @@ var scrapeCommand = defineCommand148({
|
|
|
36755
36966
|
pageFidelity: manifest.page.fidelity,
|
|
36756
36967
|
out: args.out,
|
|
36757
36968
|
report: args.report ? `${args.out}/report.html` : null,
|
|
36969
|
+
// Never omitted, and that is the point: an envelope without it is a
|
|
36970
|
+
// capture that ran a build predating the ladder, which is otherwise
|
|
36971
|
+
// indistinguishable from one that took the direct route and is the
|
|
36972
|
+
// exact silence the E2B template rebuild is easy to forget about.
|
|
36973
|
+
capture_tier: manifest.captureTier,
|
|
36758
36974
|
// Compact by default, but never omitted: a capture that could not
|
|
36759
36975
|
// verify who served the page is worth less as evidence, and the
|
|
36760
36976
|
// reader has to be told without going looking for it.
|
|
@@ -36770,11 +36986,22 @@ var scrapeCommand = defineCommand148({
|
|
|
36770
36986
|
// instruction to open any of it, and a capture nobody looks at taught
|
|
36771
36987
|
// nobody anything. A nudge tied to one command's result belongs here,
|
|
36772
36988
|
// not only in the tool doc the agent may not re-read.
|
|
36773
|
-
|
|
36774
|
-
|
|
36989
|
+
//
|
|
36990
|
+
// Every one of these now reads the manifest rather than the flags the
|
|
36991
|
+
// caller passed. The flags say what was *asked for*; a screenshot that
|
|
36992
|
+
// failed still leaves `desktopShot` null, and `--no-code` leaves every
|
|
36993
|
+
// fidelity score null while the report renders them as "—". Telling an
|
|
36994
|
+
// agent to go and read a file that is not there costs it a tool call
|
|
36995
|
+
// and costs us its trust in the next hint.
|
|
36996
|
+
...shotCount > 0 ? [
|
|
36997
|
+
args.report ? `Read ${args.out}/report.html \u2014 every section side by side${scored.length > 0 ? " with its fidelity score" : ""}.` : `Read the section screenshots in ${args.out}/sections/*/desktop.png before you design.`,
|
|
36998
|
+
`${shotCount} of ${manifest.sections.length} sections captured to ${args.out}/sections/ \u2014 look at them, then decide which the client's page actually needs.`
|
|
36999
|
+
] : [
|
|
37000
|
+
`No section screenshots were produced, so there is nothing to look at in ${args.out}/sections/. Treat this capture as unusable rather than designing from the counts.`
|
|
37001
|
+
],
|
|
36775
37002
|
INSPIRATION_HINTS.structureNotCopy,
|
|
36776
37003
|
INSPIRATION_HINTS.adapt,
|
|
36777
|
-
recorded ?
|
|
37004
|
+
recorded > 0 ? `${recorded} sections are recorded for the originality check \u2014 \`baker landing critique\` will block a publish that ships their copy.` : "Nothing from this capture is recorded for the originality check, so it cannot see this page. Be especially careful not to reuse its copy."
|
|
36778
37005
|
]
|
|
36779
37006
|
});
|
|
36780
37007
|
} catch (error) {
|
|
@@ -36786,6 +37013,13 @@ var scrapeCommand = defineCommand148({
|
|
|
36786
37013
|
error: {
|
|
36787
37014
|
code: failure.code,
|
|
36788
37015
|
message: failure.message,
|
|
37016
|
+
// Which addresses were tried and what each was told. One rung means
|
|
37017
|
+
// the failure was final wherever we stood; three mean the page turned
|
|
37018
|
+
// us away from three different places, which is a fact about the page.
|
|
37019
|
+
// **Absent** means this Runtime is running a CLI that predates the
|
|
37020
|
+
// ladder — the one way the rollout fails without anything looking
|
|
37021
|
+
// wrong.
|
|
37022
|
+
...error instanceof CaptureFailureError && error.rungs.length > 0 ? { rungs: error.rungs } : {},
|
|
36789
37023
|
// Degrade, don't abort: one unreadable reference is never a reason to
|
|
36790
37024
|
// abandon the job it was research for. `retryable` is the decision
|
|
36791
37025
|
// the agent would otherwise have to guess at.
|
|
@@ -38258,6 +38492,7 @@ Examples:
|
|
|
38258
38492
|
const format = args.output || "json";
|
|
38259
38493
|
if (format !== "json") {
|
|
38260
38494
|
writeResearchOutput(result.data, format);
|
|
38495
|
+
writeResearchHints([RESEARCH_DATA_NOTE], format);
|
|
38261
38496
|
return;
|
|
38262
38497
|
}
|
|
38263
38498
|
writeResearchJson({
|
|
@@ -38538,6 +38773,7 @@ Examples:
|
|
|
38538
38773
|
const format = args.output || "json";
|
|
38539
38774
|
if (format !== "json") {
|
|
38540
38775
|
writeResearchOutput(data, format);
|
|
38776
|
+
writeResearchHints([RESEARCH_DATA_NOTE], format);
|
|
38541
38777
|
return;
|
|
38542
38778
|
}
|
|
38543
38779
|
writeResearchJson({ ok: true, data, fields: FIELDS12, note: RESEARCH_DATA_NOTE, query_context: queryContext });
|
|
@@ -42917,7 +43153,7 @@ var main = defineCommand212({
|
|
|
42917
43153
|
Auth: Set BAKER_API_KEY (starts with bk_) and BAKER_API_URL environment variables.
|
|
42918
43154
|
Chat: Set BAKER_CHAT_ID for action and scheduled-action commands that stage changes against a chat.
|
|
42919
43155
|
Output: All commands return JSON envelopes: { ok: true, data: ... } or { ok: false, error: { code, message } }.
|
|
42920
|
-
Formats: Use --output json|
|
|
43156
|
+
Formats: Use --output json|files|md to control output format. Default: json. (The research commands additionally accept csv and jsonl.)
|
|
42921
43157
|
Introspection: Run 'baker schema <command>' to inspect argument schemas.`
|
|
42922
43158
|
},
|
|
42923
43159
|
subCommands: {
|