@koda-sl/baker-cli 0.196.0-dev.6d2f498f5 → 0.198.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.
@@ -261,4 +261,4 @@ export {
261
261
  captureProxyCredentials,
262
262
  childEnvWith
263
263
  };
264
- //# sourceMappingURL=chunk-F6OHDJIX.js.map
264
+ //# sourceMappingURL=chunk-MVCCSWDE.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  debugLogSetting
3
- } from "./chunk-F6OHDJIX.js";
3
+ } from "./chunk-MVCCSWDE.js";
4
4
 
5
5
  // src/debugLog.ts
6
6
  import { appendFileSync, mkdirSync, renameSync, statSync } from "fs";
@@ -150,4 +150,4 @@ export {
150
150
  readBodyForLog,
151
151
  installStreamTaps
152
152
  };
153
- //# sourceMappingURL=chunk-YUTDQ4PV.js.map
153
+ //# sourceMappingURL=chunk-RN2XKQIJ.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  ApiError,
3
3
  apiGet
4
- } from "./chunk-OB26WXHZ.js";
4
+ } from "./chunk-ZBS53HJF.js";
5
5
  import {
6
6
  getEnv
7
- } from "./chunk-F6OHDJIX.js";
7
+ } from "./chunk-MVCCSWDE.js";
8
8
 
9
9
  // src/error-handler.ts
10
10
  var REQUEST_CONNECTION_PLATFORM = {
@@ -295,4 +295,4 @@ export {
295
295
  writeAdsOutput,
296
296
  resolveCustomerId
297
297
  };
298
- //# sourceMappingURL=chunk-G3O4HRYR.js.map
298
+ //# sourceMappingURL=chunk-UBTH7NPV.js.map
@@ -2,13 +2,13 @@ import {
2
2
  handleConnectionError,
3
3
  needsConnectionFix,
4
4
  writeAdsJson
5
- } from "./chunk-G3O4HRYR.js";
5
+ } from "./chunk-UBTH7NPV.js";
6
6
  import {
7
7
  ApiError
8
- } from "./chunk-OB26WXHZ.js";
8
+ } from "./chunk-ZBS53HJF.js";
9
9
  import {
10
10
  getEnv
11
- } from "./chunk-F6OHDJIX.js";
11
+ } from "./chunk-MVCCSWDE.js";
12
12
 
13
13
  // src/commands/ads/meta/shared.ts
14
14
  var DAY_MS = 864e5;
@@ -108,4 +108,4 @@ export {
108
108
  csvOrJson,
109
109
  resolveEffectiveStatus
110
110
  };
111
- //# sourceMappingURL=chunk-34J26VIJ.js.map
111
+ //# sourceMappingURL=chunk-XVF27TBY.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  debugLogHttp,
3
3
  readBodyForLog
4
- } from "./chunk-YUTDQ4PV.js";
4
+ } from "./chunk-RN2XKQIJ.js";
5
5
  import {
6
6
  getEnv
7
- } from "./chunk-F6OHDJIX.js";
7
+ } from "./chunk-MVCCSWDE.js";
8
8
 
9
9
  // src/client.ts
10
10
  var MAX_RATE_LIMIT_RETRIES = 3;
@@ -198,4 +198,4 @@ export {
198
198
  apiGet,
199
199
  apiPost
200
200
  };
201
- //# sourceMappingURL=chunk-OB26WXHZ.js.map
201
+ //# sourceMappingURL=chunk-ZBS53HJF.js.map
package/dist/cli.js CHANGED
@@ -48,7 +48,7 @@ import {
48
48
  toModelSafeImage,
49
49
  ulid,
50
50
  validateCanvasDeep
51
- } from "./chunk-SSDYBIZB.js";
51
+ } from "./chunk-CVJCXDWD.js";
52
52
  import {
53
53
  csvOrJson,
54
54
  daysAgoIso,
@@ -57,7 +57,7 @@ import {
57
57
  resolveAccountIdArg,
58
58
  resolveEffectiveStatus,
59
59
  todayIso
60
- } from "./chunk-34J26VIJ.js";
60
+ } from "./chunk-XVF27TBY.js";
61
61
  import {
62
62
  buildQueryCacheKey,
63
63
  cacheGet,
@@ -73,17 +73,17 @@ import {
73
73
  writeAdsJson,
74
74
  writeAdsOutput,
75
75
  writeJsonEnvelope
76
- } from "./chunk-G3O4HRYR.js";
76
+ } from "./chunk-UBTH7NPV.js";
77
77
  import {
78
78
  ApiError,
79
79
  apiGet,
80
80
  apiPost,
81
81
  validateConvexId
82
- } from "./chunk-OB26WXHZ.js";
82
+ } from "./chunk-ZBS53HJF.js";
83
83
  import {
84
84
  installStreamTaps,
85
85
  logInvocation
86
- } from "./chunk-YUTDQ4PV.js";
86
+ } from "./chunk-RN2XKQIJ.js";
87
87
  import {
88
88
  captureBudgetMs,
89
89
  captureProxyCredentials,
@@ -94,7 +94,7 @@ import {
94
94
  requireChatId,
95
95
  resolveChatId,
96
96
  shouldEscalate
97
- } from "./chunk-F6OHDJIX.js";
97
+ } from "./chunk-MVCCSWDE.js";
98
98
 
99
99
  // src/cli.ts
100
100
  import { defineCommand as defineCommand212, runMain } from "citty";
@@ -17011,10 +17011,10 @@ function duplicateCommand2(entity, label) {
17011
17011
  replace: { type: "boolean", description: "Pause the original once the copy publishes" }
17012
17012
  },
17013
17013
  run: async ({ args }) => {
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");
17014
+ const { apiPost: apiPost2 } = await import("./client-YOLAMGIA.js");
17015
+ const { requireChatId: requireChatId2 } = await import("./env-6PUX5HJY.js");
17016
+ const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-VRWY3V6Y.js");
17017
+ const { handleMetaError: handleMetaError2 } = await import("./shared-T2HONXTH.js");
17018
17018
  try {
17019
17019
  const accountId = bareAccountId2(args);
17020
17020
  const chatId = requireChatId2();
@@ -34980,10 +34980,28 @@ var BlockedPageError = class extends Error {
34980
34980
  };
34981
34981
  var CaptureFailureError = class extends Error {
34982
34982
  failure;
34983
- constructor(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 = []) {
34984
35001
  super(failure.message);
34985
35002
  this.name = "CaptureFailureError";
34986
35003
  this.failure = failure;
35004
+ this.rungs = rungs;
34987
35005
  }
34988
35006
  };
34989
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.";
@@ -35014,6 +35032,46 @@ function classifyCaptureFailure(error) {
35014
35032
  import { mkdir as mkdir11, writeFile as writeFile15 } from "fs/promises";
35015
35033
  import path31 from "path";
35016
35034
 
35035
+ // ../proxy/src/preflight.ts
35036
+ import http from "http";
35037
+ function classifyPreflightStatus(status) {
35038
+ if (status === 200) return { usable: true };
35039
+ if (status === 407) return { usable: false, problem: "auth" };
35040
+ return { usable: false, problem: "unreachable" };
35041
+ }
35042
+ async function preflightProxyRoute(route, options = {}) {
35043
+ if (route.kind !== "proxy") return { usable: true };
35044
+ const timeoutMs = options.timeoutMs ?? 8e3;
35045
+ const target = options.target ?? "example.com:443";
35046
+ const endpoint = new URL(route.server);
35047
+ const auth = Buffer.from(`${route.username}:${route.password}`).toString("base64");
35048
+ return await new Promise((resolve5) => {
35049
+ const settle = (result) => {
35050
+ request.destroy();
35051
+ resolve5(result);
35052
+ };
35053
+ const request = http.request({
35054
+ host: endpoint.hostname,
35055
+ port: Number(endpoint.port),
35056
+ method: "CONNECT",
35057
+ path: target,
35058
+ headers: { "Proxy-Authorization": `Basic ${auth}`, Host: target },
35059
+ timeout: timeoutMs
35060
+ });
35061
+ request.on("connect", (response, socket) => {
35062
+ socket.destroy();
35063
+ settle(classifyPreflightStatus(response.statusCode));
35064
+ });
35065
+ request.on("response", (response) => {
35066
+ response.destroy();
35067
+ settle(classifyPreflightStatus(response.statusCode));
35068
+ });
35069
+ request.on("timeout", () => settle({ usable: false, problem: "timeout" }));
35070
+ request.on("error", () => settle({ usable: false, problem: "unreachable" }));
35071
+ request.end();
35072
+ });
35073
+ }
35074
+
35017
35075
  // src/engine/landing-library/browser.ts
35018
35076
  import { createRequire } from "module";
35019
35077
  var require_ = createRequire(import.meta.url);
@@ -35378,6 +35436,18 @@ function attemptSignal(outcome) {
35378
35436
  timedOut: code === "SITE_TOO_SLOW"
35379
35437
  };
35380
35438
  }
35439
+ function ambiguousProxyTimeout(outcome) {
35440
+ return outcome.route.kind === "proxy" && outcome.failure.code === "SITE_TOO_SLOW" && outcome.status === null && outcome.netError === null;
35441
+ }
35442
+ function asProxyFailure(outcome, problem) {
35443
+ return {
35444
+ ...outcome,
35445
+ failure: { code: "PROXY_UNAVAILABLE", message: PROXY_UNAVAILABLE_MESSAGE },
35446
+ // Borrowing Chromium's own vocabulary so `isProxyFailure` recognises this
35447
+ // without having to learn a second way of being told the same thing.
35448
+ netError: problem === "auth" ? "ERR_PROXY_AUTH_REQUESTED" : "ERR_PROXY_CONNECTION_FAILED"
35449
+ };
35450
+ }
35381
35451
  function nextRoute(args) {
35382
35452
  const last = args.attempts[args.attempts.length - 1];
35383
35453
  if (!last) return { next: null, reason: "ladder_exhausted" };
@@ -36736,6 +36806,20 @@ async function captureSections(args) {
36736
36806
  }
36737
36807
  return sections;
36738
36808
  }
36809
+ function rungReport(outcome) {
36810
+ return {
36811
+ tier: outcome.route.kind === "proxy" ? outcome.route.tier : "direct",
36812
+ code: outcome.failure.code,
36813
+ status: outcome.status
36814
+ };
36815
+ }
36816
+ async function resolveAmbiguousTimeout(outcome, log) {
36817
+ if (!ambiguousProxyTimeout(outcome)) return outcome;
36818
+ const verdict = await preflightProxyRoute(outcome.route).catch(() => null);
36819
+ if (!verdict || verdict.usable) return outcome;
36820
+ log(`that timeout was our own route, not the site (${verdict.problem}) \u2014 carrying on up the ladder`);
36821
+ return asProxyFailure(outcome, verdict.problem);
36822
+ }
36739
36823
  async function openViaLadder(args) {
36740
36824
  const { url, routes, timeoutMs, deadline, log } = args;
36741
36825
  const attempts = [];
@@ -36745,7 +36829,7 @@ async function openViaLadder(args) {
36745
36829
  if (attempt.ok) {
36746
36830
  return { opened: attempt, attempts, tier: route.kind === "proxy" ? route.tier : "direct" };
36747
36831
  }
36748
- attempts.push(attempt.outcome);
36832
+ attempts.push(await resolveAmbiguousTimeout(attempt.outcome, log));
36749
36833
  const decision = nextRoute({
36750
36834
  routes,
36751
36835
  attempts,
@@ -36759,7 +36843,7 @@ async function openViaLadder(args) {
36759
36843
  }
36760
36844
  route = decision.next;
36761
36845
  }
36762
- throw new CaptureFailureError(reportableFailure(attempts));
36846
+ throw new CaptureFailureError(reportableFailure(attempts), attempts.map(rungReport));
36763
36847
  }
36764
36848
  async function scrapeLanding(options) {
36765
36849
  const timeoutMs = options.timeoutMs ?? 45e3;
@@ -36868,6 +36952,32 @@ async function recordCapture(manifest, outDir) {
36868
36952
  const written = await recordReferences(process.cwd(), references);
36869
36953
  return written ? references.length : 0;
36870
36954
  }
36955
+ function captureHints(args) {
36956
+ const { manifest, outDir, report, recorded } = args;
36957
+ const shots = manifest.sections.filter((section) => section.desktopShot !== null).length;
36958
+ const anyScored = manifest.sections.some((section) => section.fidelity !== null);
36959
+ const hints = [];
36960
+ if (manifest.security.certificateNotes.length > 0) {
36961
+ hints.push(
36962
+ `Captured anyway, but this site's certificate did not check out: ${manifest.security.certificateNotes.join(" ")} Treat the page as a design reference only \u2014 do not repeat its claims, prices or customer names.`
36963
+ );
36964
+ }
36965
+ if (shots === 0) {
36966
+ hints.push(
36967
+ `No section screenshots were produced, so there is nothing to look at in ${outDir}/sections/. Treat this capture as unusable rather than designing from the counts.`
36968
+ );
36969
+ } else {
36970
+ hints.push(
36971
+ report ? `Read ${outDir}/report.html \u2014 every section side by side${anyScored ? " with its fidelity score" : ""}.` : `Read the section screenshots in ${outDir}/sections/*/desktop.png before you design.`,
36972
+ `${shots} of ${manifest.sections.length} sections captured to ${outDir}/sections/ \u2014 look at them, then decide which the client's page actually needs.`
36973
+ );
36974
+ }
36975
+ hints.push(INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt);
36976
+ hints.push(
36977
+ 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."
36978
+ );
36979
+ return hints;
36980
+ }
36871
36981
  registerSchema({
36872
36982
  command: "landing.inspiration.scrape",
36873
36983
  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.",
@@ -36930,7 +37040,6 @@ var scrapeCommand = defineCommand148({
36930
37040
  `)
36931
37041
  });
36932
37042
  const scored = manifest.sections.map((section) => section.fidelity).filter((value) => value !== null).sort((a, b) => a - b);
36933
- const shotCount = manifest.sections.filter((section) => section.desktopShot !== null).length;
36934
37043
  const recorded = await recordCapture(manifest, args.out);
36935
37044
  writeJson({
36936
37045
  ok: true,
@@ -36952,32 +37061,7 @@ var scrapeCommand = defineCommand148({
36952
37061
  certificate_verified: false,
36953
37062
  ...manifest.security.certificateNotes.length > 0 ? { certificate_notes: manifest.security.certificateNotes } : {}
36954
37063
  },
36955
- hints: [
36956
- ...manifest.security.certificateNotes.length > 0 ? [
36957
- `Captured anyway, but this site's certificate did not check out: ${manifest.security.certificateNotes.join(" ")} Treat the page as a design reference only \u2014 do not repeat its claims, prices or customer names.`
36958
- ] : [],
36959
- // The command that produces the most and says the least about it. It
36960
- // returns a count and a directory; without this the agent has no
36961
- // instruction to open any of it, and a capture nobody looks at taught
36962
- // nobody anything. A nudge tied to one command's result belongs here,
36963
- // not only in the tool doc the agent may not re-read.
36964
- //
36965
- // Every one of these now reads the manifest rather than the flags the
36966
- // caller passed. The flags say what was *asked for*; a screenshot that
36967
- // failed still leaves `desktopShot` null, and `--no-code` leaves every
36968
- // fidelity score null while the report renders them as "—". Telling an
36969
- // agent to go and read a file that is not there costs it a tool call
36970
- // and costs us its trust in the next hint.
36971
- ...shotCount > 0 ? [
36972
- 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.`,
36973
- `${shotCount} of ${manifest.sections.length} sections captured to ${args.out}/sections/ \u2014 look at them, then decide which the client's page actually needs.`
36974
- ] : [
36975
- `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.`
36976
- ],
36977
- INSPIRATION_HINTS.structureNotCopy,
36978
- INSPIRATION_HINTS.adapt,
36979
- 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."
36980
- ]
37064
+ hints: captureHints({ manifest, outDir: args.out, report: args.report, recorded })
36981
37065
  });
36982
37066
  } catch (error) {
36983
37067
  const failure = classifyCaptureFailure(error);
@@ -36988,6 +37072,13 @@ var scrapeCommand = defineCommand148({
36988
37072
  error: {
36989
37073
  code: failure.code,
36990
37074
  message: failure.message,
37075
+ // Which addresses were tried and what each was told. One rung means
37076
+ // the failure was final wherever we stood; three mean the page turned
37077
+ // us away from three different places, which is a fact about the page.
37078
+ // **Absent** means this Runtime is running a CLI that predates the
37079
+ // ladder — the one way the rollout fails without anything looking
37080
+ // wrong.
37081
+ ...error instanceof CaptureFailureError && error.rungs.length > 0 ? { rungs: error.rungs } : {},
36991
37082
  // Degrade, don't abort: one unreadable reference is never a reason to
36992
37083
  // abandon the job it was research for. `retryable` is the decision
36993
37084
  // the agent would otherwise have to guess at.