@koda-sl/baker-cli 0.432.0-dev.795979397 → 0.433.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.
Files changed (29) hide show
  1. package/README.md +60 -32
  2. package/dist/{chunk-PHLFNYGQ.js → chunk-4JBT2CSW.js} +4 -4
  3. package/dist/{chunk-GTHLEQHU.js → chunk-7BQIIJLV.js} +6 -27
  4. package/dist/chunk-7BQIIJLV.js.map +1 -0
  5. package/dist/{chunk-GCTR4MDE.js → chunk-LDS7LUKK.js} +2 -2
  6. package/dist/{chunk-FQOKJYKN.js → chunk-SPPJRGQF.js} +13 -4
  7. package/dist/chunk-SPPJRGQF.js.map +1 -0
  8. package/dist/{chunk-Q6F5UNOU.js → chunk-X46KRGZU.js} +8 -5
  9. package/dist/{chunk-Q6F5UNOU.js.map → chunk-X46KRGZU.js.map} +1 -1
  10. package/dist/{chunk-RFZYRG2Q.js → chunk-YBJ7KJNA.js} +11 -3
  11. package/dist/chunk-YBJ7KJNA.js.map +1 -0
  12. package/dist/cli.js +43651 -43303
  13. package/dist/cli.js.map +1 -1
  14. package/dist/{client-GS7T7ETG.js → client-HQ7LO4CO.js} +4 -4
  15. package/dist/engine/index.js +3 -3
  16. package/dist/{env-SL22S6CM.js → env-M4DKUWLD.js} +4 -2
  17. package/dist/output-T4MY3I56.js +22 -0
  18. package/dist/{shared-XR54KFMH.js → shared-RZMKZE4W.js} +6 -6
  19. package/package.json +1 -1
  20. package/dist/chunk-FQOKJYKN.js.map +0 -1
  21. package/dist/chunk-GTHLEQHU.js.map +0 -1
  22. package/dist/chunk-RFZYRG2Q.js.map +0 -1
  23. package/dist/output-OFDKEPPJ.js +0 -18
  24. /package/dist/{chunk-PHLFNYGQ.js.map → chunk-4JBT2CSW.js.map} +0 -0
  25. /package/dist/{chunk-GCTR4MDE.js.map → chunk-LDS7LUKK.js.map} +0 -0
  26. /package/dist/{client-GS7T7ETG.js.map → client-HQ7LO4CO.js.map} +0 -0
  27. /package/dist/{env-SL22S6CM.js.map → env-M4DKUWLD.js.map} +0 -0
  28. /package/dist/{output-OFDKEPPJ.js.map → output-T4MY3I56.js.map} +0 -0
  29. /package/dist/{shared-XR54KFMH.js.map → shared-RZMKZE4W.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  debugLogSetting
3
- } from "./chunk-FQOKJYKN.js";
3
+ } from "./chunk-SPPJRGQF.js";
4
4
 
5
5
  // src/debugLog.ts
6
6
  import { appendFileSync, mkdirSync, renameSync, statSync } from "fs";
@@ -212,4 +212,4 @@ export {
212
212
  readBodyForLog,
213
213
  installStreamTaps
214
214
  };
215
- //# sourceMappingURL=chunk-GCTR4MDE.js.map
215
+ //# sourceMappingURL=chunk-LDS7LUKK.js.map
@@ -1247,6 +1247,7 @@ function getEnv() {
1247
1247
  ...CONNECTION,
1248
1248
  BAKER_PUBLIC_API_URL: z.url("BAKER_PUBLIC_API_URL must be a valid URL").optional(),
1249
1249
  BAKER_CHAT_ID: z.string().optional(),
1250
+ BAKER_THREAD_ID: z.string().optional(),
1250
1251
  BAKER_ACTING_USER_ID: z.string().optional(),
1251
1252
  BAKER_GOOGLE_ADS_CUSTOMER_ID: z.string().regex(/^\d{10}$/).optional(),
1252
1253
  BAKER_GA4_PROPERTY_ID: z.string().optional(),
@@ -1316,6 +1317,15 @@ function debugLogSetting() {
1316
1317
  const raw = process.env.BAKER_DEBUG_LOG?.trim();
1317
1318
  return raw ? raw : void 0;
1318
1319
  }
1320
+ var MissingChatIdError = class extends Error {
1321
+ code = "MISSING_CHAT_ID";
1322
+ constructor() {
1323
+ super(
1324
+ "BAKER_CHAT_ID is not set. This command stages changes against a chat \u2014 run it from a chat-attached environment."
1325
+ );
1326
+ this.name = "MissingChatIdError";
1327
+ }
1328
+ };
1319
1329
  function canvasAssetOriginsSetting() {
1320
1330
  const raw = process.env.BAKER_CANVAS_ASSET_ORIGINS?.trim();
1321
1331
  return raw ? raw : void 0;
@@ -1328,9 +1338,7 @@ function optionalConnection() {
1328
1338
  function requireChatId() {
1329
1339
  const env = getEnv();
1330
1340
  if (!env.BAKER_CHAT_ID) {
1331
- throw new Error(
1332
- "BAKER_CHAT_ID is not set. This command stages changes against a chat \u2014 run it from a chat-attached environment."
1333
- );
1341
+ throw new MissingChatIdError();
1334
1342
  }
1335
1343
  return env.BAKER_CHAT_ID;
1336
1344
  }
@@ -1402,6 +1410,7 @@ export {
1402
1410
  requireConnection,
1403
1411
  optionalChatId,
1404
1412
  debugLogSetting,
1413
+ MissingChatIdError,
1405
1414
  canvasAssetOriginsSetting,
1406
1415
  optionalConnection,
1407
1416
  requireChatId,
@@ -1415,4 +1424,4 @@ export {
1415
1424
  captureVimeoCookie,
1416
1425
  childEnvWith
1417
1426
  };
1418
- //# sourceMappingURL=chunk-FQOKJYKN.js.map
1427
+ //# sourceMappingURL=chunk-SPPJRGQF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../proxy/src/challenge.ts","../../proxy/src/tiers.ts","../../proxy/src/credentials.ts","../../proxy/src/escalate.ts","../../proxy/src/privateAddress.ts","../../proxy/src/publicAddress.ts","../../proxy/src/route.ts","../../proxy/src/ladder.ts","../src/env.ts","../src/apiKeyFormat.ts","../src/schemas.ts","../src/unknown-flags.ts","../src/output-flag.ts","../src/field-mask.ts","../src/output.ts"],"sourcesContent":["/**\n * Was that a page, or the wall in front of it?\n *\n * A bot filter answers 200. The body parses, it has a title, and every check\n * downstream — `response.ok`, the status, the content type — says the read\n * succeeded. So a challenge is the one block that cannot be seen from the\n * status line, and the only place it is visible is the bytes.\n *\n * This lives in `@baker/proxy` rather than beside any one caller because the\n * escalation ladder is only coherent if every surface agrees on what \"blocked\"\n * means: the rung that gets climbed is chosen from this answer, and a detector\n * that differs per caller would mean the same wall costs money on one surface\n * and is filed as content on another.\n *\n * Pure and dependency-free, so it stays reachable from Convex's V8 runtime\n * through the package barrel.\n */\n\n/**\n * Phrases that only appear on a block or challenge page.\n *\n * Deliberately specific — \"access denied\" alone would match a page *about*\n * access control, so each phrase is one a real marketing page has no reason to\n * use as its title.\n */\nconst CHALLENGE_PHRASES = [\n \"just a moment\",\n \"attention required\",\n \"verify you are human\",\n \"checking your browser\",\n \"enable javascript and cookies to continue\",\n \"unusual traffic\",\n \"access denied\",\n \"you have been blocked\",\n \"request unsuccessful\",\n \"are you a robot\",\n \"security check\",\n \"ddos protection\",\n \"captcha\",\n];\n\n/** Vendors whose block pages carry a fingerprint even when the title does not. */\nconst CHALLENGE_MARKERS = [\"cf-browser-verification\", \"cf_chl_\", \"px-captcha\", \"_incapsula_\", \"distil_r_captcha\"];\n\n/**\n * How long a response may be and still be judged by its *wording*.\n *\n * Phrases like \"captcha\" or \"access denied\" are ordinary English that a real\n * article can legitimately contain, so matching them anywhere would refuse\n * pages we read perfectly well. Length is the precision guard: a page that is\n * mostly content is content.\n */\nexport const CHALLENGE_LENGTH_CEILING = 2_000;\n\n/**\n * Whether these bytes are a bot wall rather than the thing that was asked for.\n *\n * Reads only what the caller already paid for. Nothing here issues a request —\n * asking the host again would deepen the very block this is detecting.\n *\n * The two lists are searched differently, and that asymmetry is the point.\n * Phrases are judged only on a short response, for the precision reason above.\n * Vendor markers are fingerprints nothing but the vendor emits, so they are\n * searched at any length — which is what makes a real interstitial detectable\n * at all. Cloudflare's is 5–15 KB of inlined script wrapped around one visible\n * sentence, so gating markers behind the same length ceiling as the phrases put\n * every genuine large wall in a dead band where neither list could reach it.\n */\nexport function isChallengeBody(content: string, title?: string | null): boolean {\n if (content.length <= CHALLENGE_LENGTH_CEILING) {\n const haystack = `${title ?? \"\"}\\n${content}`.toLowerCase();\n if (CHALLENGE_PHRASES.some((phrase) => haystack.includes(phrase))) return true;\n }\n\n const whole = content.toLowerCase();\n return CHALLENGE_MARKERS.some((marker) => whole.includes(marker));\n}\n","/**\n * The two Oxylabs products, and everything that differs between them.\n *\n * They are NOT credential-swappable. Endpoint, username prefix, geo token and\n * sticky-session mechanism all differ, so \"same URL, other username/password\"\n * fails — and it fails as a 407, which Chromium reports as a plain network\n * error. Before this module existed there was no code path that could tell that\n * apart from the site refusing us, so the failure would have been recorded\n * against the page rather than against our own configuration.\n *\n * That asymmetry is the reason this table exists in exactly one place. Every\n * caller builds its route through `buildProxyRoute`; nobody concatenates an\n * Oxylabs URL by hand.\n */\n\nexport type ProxyTier = \"datacenter\" | \"residential\";\n\nexport interface ProxyTierSpec {\n readonly tier: ProxyTier;\n readonly host: string;\n readonly port: number;\n /** Oxylabs requires this in front of the account name. The two differ. */\n readonly usernamePrefix: \"user-\" | \"customer-\";\n /** Country targeting token. `-country-` on datacenter, `-cc-` on residential. */\n readonly countryKey: \"-country-\" | \"-cc-\";\n /**\n * Sticky-session token, or null when the product has none.\n *\n * Datacenter does stickiness by *port* (8001 for IP #1, 8002 for #2), not by\n * username. We do not implement that: a capture is a single page load, so a\n * reusable IP buys nothing and the knob would only add a way to get it wrong.\n */\n readonly sessionKey: \"-sessid-\" | null;\n /**\n * How many times a blocked request is retried on this tier before climbing.\n *\n * Datacenter's port 8000 hands out a different IP on every new connection\n * (Oxylabs, \"Datacenter Proxies\": port 8000 rotates, 8001+ pins one IP), and\n * every attempt opens a fresh agent, browser or process — so a retry is a new\n * exit IP at datacenter prices. A WAF that blocks one address in a range\n * often serves its neighbour, so three of those are still far cheaper than\n * one residential attempt. Residential is billed per GB at a much higher\n * rate, and gets exactly one.\n */\n readonly attempts: number;\n}\n\nexport const PROXY_TIERS: Readonly<Record<ProxyTier, ProxyTierSpec>> = {\n datacenter: {\n tier: \"datacenter\",\n host: \"dc.oxylabs.io\",\n port: 8000,\n usernamePrefix: \"user-\",\n countryKey: \"-country-\",\n sessionKey: null,\n attempts: 3,\n },\n residential: {\n tier: \"residential\",\n host: \"pr.oxylabs.io\",\n port: 7777,\n usernamePrefix: \"customer-\",\n countryKey: \"-cc-\",\n sessionKey: \"-sessid-\",\n attempts: 1,\n },\n};\n\n/**\n * Cheapest useful route first, and the ladder never skips a rung.\n *\n * Residential costs materially more than datacenter, so it is only ever reached\n * because datacenter was tried and refused — never as a first guess and never\n * speculatively.\n */\nexport const ESCALATION_ORDER = [\"datacenter\", \"residential\"] as const satisfies readonly ProxyTier[];\n\n/** Every prefix this module knows, used to spot a credential in the wrong slot. */\nexport const KNOWN_USERNAME_PREFIXES = [\"user-\", \"customer-\"] as const;\n","import type { ProxyTier } from \"./tiers.ts\";\nimport { ESCALATION_ORDER } from \"./tiers.ts\";\n\n/**\n * The four environment variable names, in one place.\n *\n * Convex holds these and hands them to the sandbox; the CLI reads them back\n * out. A typo on either side is a *silent* no-op — the capability appears to\n * ship, every fetch quietly takes the direct route, and nothing fails. That is\n * the worst failure mode available to this feature, so the names are a shared\n * constant with a contract test rather than eight string literals spread across\n * two packages.\n */\nexport const PROXY_ENV_VARS = {\n datacenter: { username: \"OXYLABS_DATACENTER_USERNAME\", password: \"OXYLABS_DATACENTER_PASSWORD\" },\n residential: { username: \"OXYLABS_RESIDENTIAL_USERNAME\", password: \"OXYLABS_RESIDENTIAL_PASSWORD\" },\n} as const satisfies Record<ProxyTier, { username: string; password: string }>;\n\nexport interface TierCredentials {\n readonly username: string;\n readonly password: string;\n}\n\nexport type ProxyCredentials = Partial<Readonly<Record<ProxyTier, TierCredentials>>>;\n\n/**\n * Read whichever tiers are fully configured.\n *\n * Takes the env record rather than reading `process.env` itself, so the whole\n * package stays pure and the Convex and CLI sides can be tested identically.\n *\n * Half a pair is not a credential. A username with no password would build a\n * route that authenticates as nobody, and Oxylabs answers that with the same\n * 407 as a wrong password — so it would look like a broken proxy rather than an\n * unconfigured one. Dropping it here means `configuredTiers` tells the truth.\n */\nexport function readProxyCredentials(env: Record<string, string | undefined>): ProxyCredentials {\n const credentials: { -readonly [K in ProxyTier]?: TierCredentials } = {};\n for (const tier of ESCALATION_ORDER) {\n const username = env[PROXY_ENV_VARS[tier].username]?.trim();\n const password = env[PROXY_ENV_VARS[tier].password]?.trim();\n if (username && password) credentials[tier] = { username, password };\n }\n return credentials;\n}\n\n/**\n * The inverse of `readProxyCredentials`: credentials back out as env vars.\n *\n * The writing side used to be a loop inside one Convex function, which is how\n * the ladder came to exist everywhere and work in one place. That loop ran only\n * for `baker landing inspiration scrape`, so every other CLI fetch surface in a\n * Session — `images download`, canvas `ingest`, the two scaffolders, yt-dlp —\n * read four variables nobody had set and went direct, silently, exactly as\n * `PROXY_ENV_VARS` warns.\n *\n * Pairing it with the reader here is the point: the round trip is a test, so the\n * two sides cannot drift into agreeing about three variables out of four.\n */\nexport function proxyCredentialEnvs(credentials: ProxyCredentials): Record<string, string> {\n const envs: Record<string, string> = {};\n for (const tier of ESCALATION_ORDER) {\n const held = credentials[tier];\n if (!held) continue;\n envs[PROXY_ENV_VARS[tier].username] = held.username;\n envs[PROXY_ENV_VARS[tier].password] = held.password;\n }\n return envs;\n}\n\n/**\n * Which tiers this process could actually reach, cheapest first.\n *\n * There is no ceiling to apply. A deployment holding both pairs may climb to\n * residential, full stop — the brake is the credential itself. Removing\n * `OXYLABS_RESIDENTIAL_PASSWORD` takes effect on the next restart, needs no\n * deploy, and withholds the tier by the same mechanism a ceiling would have,\n * so the valve that used to sit here only added a second way to say it. It was\n * also never wired into ad-dna's task definition, which is the highest-volume\n * egress in the repo — a control that could not be turned where it mattered\n * most.\n */\nexport function configuredTiers(credentials: ProxyCredentials): ProxyTier[] {\n return ESCALATION_ORDER.filter((tier) => credentials[tier] !== undefined);\n}\n\n/** Whether there is any proxy to escalate to at all. */\nexport function proxyEscalationConfigured(credentials: ProxyCredentials): boolean {\n return configuredTiers(credentials).length > 0;\n}\n","/**\n * When is a failure worth spending a more expensive route on?\n *\n * The ladder only ever climbs on evidence that *who we are* was the problem. A\n * page that 404s, a domain that does not resolve, a certificate that will not\n * negotiate and a site that timed out all fail identically from every IP on\n * earth, so retrying them through a metered exit buys a second identical\n * failure and a bill. That asymmetry — cheap to be wrong in one direction,\n * expensive in the other — is why this is a closed allow-list rather than\n * \"escalate unless we recognise the error\".\n *\n * Shared deliberately: the capture engine, the image fetchers and the ad-media\n * downloaders all have to agree on what \"blocked\" means, or the corpus and the\n * bill disagree about the same event.\n */\n\n/** What one attempt observed, normalised across Playwright and `fetch`. */\nexport interface BlockSignal {\n /** Main-document / response status, when there was one. */\n readonly status?: number | null;\n /** Chromium `net::` name or a Node error code, when the attempt threw one. */\n readonly netError?: string | null;\n /** A challenge body was detected behind an otherwise successful response. */\n readonly challenge?: boolean;\n /** The attempt ran out of time rather than being refused. */\n readonly timedOut?: boolean;\n}\n\n/** \"We don't like *you*\" — a different exit IP is a different you. */\nconst ESCALATABLE_STATUSES = new Set([403, 429, 451]);\n\n/**\n * A TCP reset on ClientHello is how a WAF null-routes a datacenter range.\n *\n * Today all three of these collapse into one \"site didn't respond\" message and\n * are filed non-retryable, which is precisely why this class of block has been\n * invisible: it looks identical to a site that is genuinely down.\n */\nconst ESCALATABLE_NET_ERRORS = new Set([\"ERR_CONNECTION_RESET\", \"ERR_CONNECTION_CLOSED\", \"ERR_EMPTY_RESPONSE\"]);\n\n/**\n * Failures that belong to our route, not to the site.\n *\n * A 407 cannot come from an origin server — only something speaking proxy\n * produces one — so this needs no \"was this attempt proxied\" flag to be safe.\n */\nconst PROXY_NET_ERRORS = new Set([\n \"ERR_TUNNEL_CONNECTION_FAILED\",\n \"ERR_PROXY_CONNECTION_FAILED\",\n \"ERR_PROXY_AUTH_REQUESTED\",\n \"ERR_PROXY_CERTIFICATE_INVALID\",\n \"ERR_UNEXPECTED_PROXY_AUTH\",\n \"ERR_MANDATORY_PROXY_CONFIGURATION_FAILED\",\n \"ERR_HTTPS_PROXY_TUNNEL_RESPONSE_REDIRECT\",\n]);\n\nexport function isProxyFailure(signal: BlockSignal): boolean {\n if (signal.status === 407) return true;\n return signal.netError ? PROXY_NET_ERRORS.has(signal.netError) : false;\n}\n\nexport function shouldEscalate(signal: BlockSignal): boolean {\n // Our own broken route never advances the ladder — it degrades it. Climbing\n // here would spend a more expensive rung reproducing our misconfiguration.\n if (isProxyFailure(signal)) return false;\n if (signal.timedOut) return false;\n if (signal.challenge) return true;\n if (signal.status != null && ESCALATABLE_STATUSES.has(signal.status)) return true;\n return signal.netError ? ESCALATABLE_NET_ERRORS.has(signal.netError) : false;\n}\n","/**\n * The same question as `refuseNonPublicUrl`, asked of an address instead of a name.\n *\n * A URL carries a hostname, and a hostname is not an address. `localtest.me`\n * and `127.0.0.1.nip.io` are public names, with public DNS records, that\n * resolve to `127.0.0.1` — and nothing stops an attacker pointing a domain they\n * own at `169.254.169.254`. Proven against a real loopback server: the hostname\n * guard passed `http://localtest.me:4321/secret.txt` straight through, the bytes\n * were downloaded, and only the image parser complained afterwards.\n *\n * IPv6 is parsed rather than pattern-matched, and that is the second lesson\n * here. The first version tested prefixes with regexes and unwrapped an\n * IPv4-mapped address only when it still had a dot in it — but a URL parser\n * normalises `::ffff:127.0.0.1` to `::ffff:7f00:1`, so the dot is gone by the\n * time anyone looks. `[::ffff:169.254.169.254]` therefore read as public and the\n * ladder returned the metadata body with `outcome: \"ok\"`. One address has many\n * spellings; comparing spellings is the wrong operation.\n *\n * Anything unparseable is treated as private. This is a safety gate, and the\n * only safe reading of \"I do not know what this is\" is \"do not fetch it\".\n */\n\n/** Every IPv4 range that is not routable on the public internet. */\nfunction isPrivateIpv4Octets(octets: readonly number[]): boolean {\n const [a, b] = octets as [number, number, number, number];\n if (a === 0) return true; // \"this network\" — 0.0.0.0/8\n if (a === 10) return true; // private\n if (a === 127) return true; // loopback\n if (a === 169 && b === 254) return true; // link-local, and the cloud metadata address\n if (a === 172 && b >= 16 && b <= 31) return true; // private /12 — NOT all of 172.*\n if (a === 192 && b === 168) return true; // private\n if (a === 100 && b >= 64 && b <= 127) return true; // carrier-grade NAT\n if (a >= 224) return true; // multicast and reserved\n return false;\n}\n\n/** Dotted-quad to octets, or `null` when it is not one. */\nfunction parseIpv4(value: string): number[] | null {\n const parts = value.split(\".\");\n if (parts.length !== 4) return null;\n const octets = parts.map((part) => (/^\\d{1,3}$/.test(part) ? Number(part) : Number.NaN));\n if (octets.some((octet) => !Number.isInteger(octet) || octet < 0 || octet > 255)) return null;\n return octets;\n}\n\n/**\n * One side of a `::` to its 16-bit groups, or `null` if any part is not one.\n *\n * A trailing dotted-quad stands for the final two groups and is only legal in\n * last position — that is the spelling `::ffff:127.0.0.1` uses before a URL\n * parser rewrites it to `::ffff:7f00:1`.\n */\nfunction expandGroups(part: string): number[] | null {\n if (part === \"\") return [];\n const chunks = part.split(\":\");\n const groups: number[] = [];\n for (let index = 0; index < chunks.length; index++) {\n const chunk = chunks[index] as string;\n if (chunk.includes(\".\")) {\n if (index !== chunks.length - 1) return null;\n const octets = parseIpv4(chunk);\n if (!octets) return null;\n groups.push(((octets[0] as number) << 8) | (octets[1] as number));\n groups.push(((octets[2] as number) << 8) | (octets[3] as number));\n continue;\n }\n if (!/^[0-9a-f]{1,4}$/.test(chunk)) return null;\n groups.push(Number.parseInt(chunk, 16));\n }\n return groups;\n}\n\n/**\n * IPv6 to its eight 16-bit groups, or `null` when it is not one.\n *\n * Handles the `::` elision and a trailing dotted-quad, which together are why\n * one address has so many spellings.\n */\nfunction parseIpv6(value: string): number[] | null {\n const halves = value.split(\"::\");\n if (halves.length > 2) return null;\n\n const head = expandGroups(halves[0] as string);\n const tail = halves.length === 2 ? expandGroups(halves[1] as string) : [];\n if (head === null || tail === null) return null;\n\n if (halves.length === 1) return head.length === 8 ? head : null;\n const missing = 8 - head.length - tail.length;\n if (missing < 1) return null;\n return [...head, ...new Array<number>(missing).fill(0), ...tail];\n}\n\nexport function isPrivateAddress(ip: string): boolean {\n const address = ip.trim().toLowerCase();\n if (!address) return true;\n\n const v4 = parseIpv4(address);\n if (v4) return isPrivateIpv4Octets(v4);\n\n const v6 = parseIpv6(address);\n // Not an address we can read. A safety gate may not guess.\n if (!v6) return true;\n\n const [g0, g1, g2, g3, g4, g5, g6, g7] = v6 as [number, number, number, number, number, number, number, number];\n\n // `::` (unspecified) and `::1` (loopback).\n if (g0 === 0 && g1 === 0 && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0 && g6 === 0) return true;\n\n // IPv4-mapped (`::ffff:a.b.c.d`) and IPv4-translated (`::ffff:0:a.b.c.d`).\n // The whole point: by the time this is read the tail is hex, not dotted.\n const mappedV4 = g0 === 0 && g1 === 0 && g2 === 0 && g3 === 0;\n if (mappedV4 && g4 === 0 && g5 === 0xffff) {\n return isPrivateIpv4Octets([g6 >> 8, g6 & 0xff, g7 >> 8, g7 & 0xff]);\n }\n if (mappedV4 && g4 === 0xffff && g5 === 0) {\n return isPrivateIpv4Octets([g6 >> 8, g6 & 0xff, g7 >> 8, g7 & 0xff]);\n }\n\n if ((g0 & 0xfe00) === 0xfc00) return true; // fc00::/7 unique-local\n if ((g0 & 0xffc0) === 0xfe80) return true; // fe80::/10 link-local\n return false;\n}\n","/**\n * Addresses that must never be fetched, and must never be proxied.\n *\n * Lifted from `convex/landingLibrary/utils.ts`, where these three regexes were\n * the only copy in the repo — reachable from Convex and from nowhere else. That\n * was survivable while the capture ran one hop after the check. It is not\n * survivable now: the CLI can be invoked directly, two Convex paths enqueue\n * without re-validating, and a proxy adds a rung where \"which network am I on\"\n * stops being rhetorical.\n *\n * Two distinct reasons live here, and both point the same way:\n *\n * - **There is nothing to read.** A private address only resolves from inside\n * the network asking, so a capture of one produces a screenshot of an error.\n * - **There is something to read, and we must not.** `169.254.169.254` is the\n * cloud metadata endpoint that every capture runner has a route to.\n *\n * The rule for the proxy is *refuse*, not *bypass*. Bypassing would leave a\n * request that still happens, just unproxied; refusing means there is no\n * request to route. That is a stronger guarantee and a simpler one to check.\n */\n\nimport { isPrivateAddress } from \"./privateAddress.ts\";\n\n/**\n * Names — not addresses. Anything that looks like an address goes to\n * `isPrivateAddress`, which parses it instead of matching its spelling.\n */\nconst PRIVATE_HOST = /^(localhost|.*\\.localhost|.*\\.local)$/;\n/** Where a Session's own preview is served while the page is being built. */\nconst SANDBOX_HOST = /\\.e2b\\.(app|dev)$/;\n\n/**\n * A bracketed IPv6 literal, unwrapped.\n *\n * `new URL(\"http://[::1]/\").hostname` keeps the brackets, so a hostname check\n * that does not strip them is comparing against a string no address will ever\n * equal. The previous version tried to absorb that with `\\[?::1\\]?` and caught\n * exactly one address out of the whole of IPv6 — `[::ffff:169.254.169.254]`\n * sailed through and the ladder returned the metadata body with `outcome: \"ok\"`.\n */\nfunction unwrapIpLiteral(host: string): string | null {\n if (host.startsWith(\"[\") && host.endsWith(\"]\")) return host.slice(1, -1);\n // A bare v4 literal, or a v6 one that arrived without brackets.\n if (/^[0-9.]+$/.test(host) || host.includes(\":\")) return host;\n return null;\n}\n\nexport function isPrivateHostname(hostname: string): boolean {\n const host = hostname.trim().toLowerCase();\n if (!host) return true;\n if (PRIVATE_HOST.test(host) || SANDBOX_HOST.test(host)) return true;\n\n // Every address form — v4, v6, bracketed, mapped, elided — is decided by one\n // parser. Spelling an address in a regex is what let four of them through.\n const literal = unwrapIpLiteral(host);\n return literal === null ? false : isPrivateAddress(literal);\n}\n\n/**\n * The same rule again, in the only notation a browser's proxy settings speak.\n *\n * Chromium — and so Playwright and `agent-browser` — matches a bypass entry\n * against the hostname and understands one wildcard and no CIDR at all. The\n * ranges above therefore cannot be handed over as regexes; `172.16/12` has to\n * become sixteen entries, and there is no way to express \"any private address\".\n *\n * That makes this a second copy of one fact, which is worth stating plainly:\n * the day it disagrees with `isPrivateHostname` is the day a Session's own\n * preview at `localhost:4321` goes out through a metered exit and comes back\n * refused. Its test pins the correspondence.\n *\n * Note the direction reverses here, and deliberately. Everywhere else the rule\n * for a private address is *refuse*, because a request that still happens is\n * not a guarantee. A browser the agent drives has to reach `localhost:4321` —\n * that is its main job — so here the private address is the legitimate traffic\n * and the list says \"go direct\", not \"do not go\".\n */\nexport const BROWSER_PROXY_BYPASS = [\n \"localhost\",\n \"*.localhost\",\n \"127.0.0.1\",\n \"0.0.0.0\",\n \"::1\",\n \"*.local\",\n // A Session's own preview, which is served from the sandbox's public host —\n // public in DNS, ours in every sense that matters here.\n \"*.e2b.app\",\n \"*.e2b.dev\",\n \"10.*\",\n \"192.168.*\",\n \"169.254.*\",\n ...Array.from({ length: 16 }, (_, i) => `172.${16 + i}.*`),\n].join(\",\");\n\nexport type NonPublicReason = \"not_public\" | \"unparseable\";\n\n/**\n * `null` when the URL is safe to fetch; a reason when it must be refused.\n *\n * Anything that is not plain http(s) is refused too. A capture is handed URLs\n * from agents and from stored rows, and `file:` reaching a browser we launched\n * is a local file read wearing a URL.\n */\nexport function refuseNonPublicUrl(url: string): NonPublicReason | null {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return \"unparseable\";\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") return \"not_public\";\n return isPrivateHostname(parsed.hostname) ? \"not_public\" : null;\n}\n","import type { ProxyCredentials } from \"./credentials.ts\";\nimport type { ProxyTier, ProxyTierSpec } from \"./tiers.ts\";\nimport { KNOWN_USERNAME_PREFIXES, PROXY_TIERS } from \"./tiers.ts\";\n\n/**\n * How one attempt reaches the internet.\n *\n * Credentials stay in separate fields instead of being embedded in the URL:\n * Playwright's `proxy` option wants them that way, undici's `ProxyAgent` takes\n * them as a header we build once, and — the real reason — a password that never\n * exists as a substring of a URL cannot be leaked by anything that logs a URL.\n */\nexport type ProxyRoute =\n | { readonly kind: \"direct\" }\n | {\n readonly kind: \"proxy\";\n readonly tier: ProxyTier;\n /** e.g. `http://dc.oxylabs.io:8000` */\n readonly server: string;\n readonly username: string;\n readonly password: string;\n };\n\nexport const DIRECT_ROUTE: ProxyRoute = { kind: \"direct\" };\n\nexport interface RouteOptions {\n /** ISO-3166 alpha-2, upper-cased here. Omit for \"wherever\" — the default. */\n readonly country?: string;\n /** Sticky-session id. Ignored by a tier whose product has no session syntax. */\n readonly session?: string;\n}\n\nexport type RouteProblem = \"missing\" | \"wrong_prefix\";\n\nexport type RouteResult = { ok: true; route: ProxyRoute } | { ok: false; problem: RouteProblem };\n\n/**\n * Put the account name into the shape this specific product expects.\n *\n * Three cases, and the third is the one worth being strict about:\n *\n * 1. Bare (`acme`) — someone pasted the account name. Prepend the prefix.\n * 2. Already correct (`user-acme` in the datacenter slot) — someone pasted the\n * full Oxylabs username. Leave it alone.\n * 3. Carrying the OTHER product's prefix (`customer-acme` in the datacenter\n * slot) — refuse.\n *\n * Case 3 must not be \"helpfully\" rewritten. A `customer-` value in the\n * datacenter slot means the residential credentials were pasted into the wrong\n * variable, and rewriting the prefix would authenticate a residential account\n * against the datacenter endpoint. That either 407s — wasting the cheap rung\n * for no reason — or it works, and bills residential rates from the tier whose\n * entire purpose is to be the cheap one. Refusing is the only answer that\n * cannot silently cost money.\n */\nexport function proxyUsername(\n spec: ProxyTierSpec,\n rawUsername: string,\n options?: RouteOptions,\n): { ok: true; username: string } | { ok: false; problem: \"wrong_prefix\" } {\n const raw = rawUsername.trim();\n const foreignPrefix = KNOWN_USERNAME_PREFIXES.find(\n (prefix) => prefix !== spec.usernamePrefix && raw.startsWith(prefix),\n );\n if (foreignPrefix) return { ok: false, problem: \"wrong_prefix\" };\n\n let username = raw.startsWith(spec.usernamePrefix) ? raw : `${spec.usernamePrefix}${raw}`;\n // Geo before session — Oxylabs reads the username left to right and rejects\n // the pair in the other order.\n if (options?.country) username += `${spec.countryKey}${options.country.toUpperCase()}`;\n if (options?.session && spec.sessionKey) username += `${spec.sessionKey}${options.session}`;\n return { ok: true, username };\n}\n\n/** Build the route for one tier, or say why there isn't one. */\nexport function buildProxyRoute(tier: ProxyTier, credentials: ProxyCredentials, options?: RouteOptions): RouteResult {\n const held = credentials[tier];\n if (!held) return { ok: false, problem: \"missing\" };\n\n const spec = PROXY_TIERS[tier];\n const username = proxyUsername(spec, held.username, options);\n if (!username.ok) return username;\n\n return {\n ok: true,\n route: {\n kind: \"proxy\",\n tier,\n server: `http://${spec.host}:${spec.port}`,\n username: username.username,\n password: held.password,\n },\n };\n}\n","import type { ProxyCredentials } from \"./credentials.ts\";\nimport { configuredTiers } from \"./credentials.ts\";\nimport { refuseNonPublicUrl } from \"./publicAddress.ts\";\nimport type { ProxyRoute, RouteOptions } from \"./route.ts\";\nimport { buildProxyRoute, DIRECT_ROUTE } from \"./route.ts\";\nimport type { ProxyTier } from \"./tiers.ts\";\nimport { PROXY_TIERS } from \"./tiers.ts\";\n\n/**\n * Every route this URL is allowed to be attempted on, in the order to try them.\n *\n * Direct is always first and always present: an unblocked page must cost\n * nothing, and a deployment with no credentials has to behave exactly as it did\n * before any of this existed.\n *\n * A tier whose credentials are malformed is *skipped*, not fatal. One\n * mis-pasted variable should cost the use of that rung, not the whole ladder —\n * the alternative is that a typo in the cheap tier silently disables the\n * expensive one that would have worked.\n *\n * A tier appears once per `attempts` in its spec, the same route repeated: the\n * rotation happens at Oxylabs' end, per connection, so the route itself has\n * nothing to vary. Callers walking the list must drop a tier's remaining\n * repeats once it fails as a *proxy* failure — a rejected credential is\n * rejected from every IP.\n */\nexport function plannedRoutes(url: string, credentials: ProxyCredentials, options?: RouteOptions): ProxyRoute[] {\n // A private address is refused upstream; if one reaches here anyway it must\n // not become a billed request, and there is no exit node on earth from which\n // `localhost` means us.\n if (refuseNonPublicUrl(url) !== null) return [DIRECT_ROUTE];\n\n const routes: ProxyRoute[] = [DIRECT_ROUTE];\n for (const tier of configuredTiers(credentials)) {\n const built = buildProxyRoute(tier, credentials, options);\n if (!built.ok) continue;\n for (let attempt = 0; attempt < PROXY_TIERS[tier].attempts; attempt++) routes.push(built.route);\n }\n return routes;\n}\n\n/** What one finished attempt tells the ladder about where to go next. */\nexport interface TriedRoute {\n readonly tier: \"direct\" | ProxyTier;\n /** The route itself failed (407, dead tunnel), rather than the site. */\n readonly proxyFailure: boolean;\n}\n\nfunction routeTier(route: ProxyRoute): \"direct\" | ProxyTier {\n return route.kind === \"proxy\" ? route.tier : \"direct\";\n}\n\n/**\n * The next route in `routes` not yet spent by `tried`, or null.\n *\n * Deciding *whether* to move on is the caller's; this only says *where*. Each\n * tier's repeats are consumed in order, and a tier that failed as a proxy\n * failure loses the repeats it has left — its credential is refused from every\n * IP, so retrying it would only spend another timeout proving that again.\n */\nexport function nextPlannedRoute(routes: readonly ProxyRoute[], tried: readonly TriedRoute[]): ProxyRoute | null {\n const dead = new Set(tried.filter((attempt) => attempt.proxyFailure).map((attempt) => attempt.tier));\n const spent = new Map<string, number>();\n for (const attempt of tried) spent.set(attempt.tier, (spent.get(attempt.tier) ?? 0) + 1);\n\n const offered = new Map<string, number>();\n for (const route of routes) {\n const tier = routeTier(route);\n if (dead.has(tier)) continue;\n const position = (offered.get(tier) ?? 0) + 1;\n offered.set(tier, position);\n if (position > (spent.get(tier) ?? 0)) return route;\n }\n return null;\n}\n","import type { ProxyCredentials } from \"@baker/proxy\";\nimport { readProxyCredentials } from \"@baker/proxy\";\nimport { createEnv } from \"@t3-oss/env-core\";\nimport { z } from \"zod\";\nimport { API_KEY_FORMAT_MESSAGE, isBakerApiKey } from \"./apiKeyFormat.ts\";\nimport { writeJson } from \"./output.ts\";\n\ntype Env = {\n BAKER_API_KEY: string;\n BAKER_API_URL: string;\n /**\n * Baker's own host for the public API — `https://api.withbaker.com` — as\n * opposed to {@link BAKER_API_URL}, which is the deployment this CLI talks\n * to. Anything a client's own systems are told to call must use this one; a\n * `*.convex.site` in somebody else's codebase is a move we can never make.\n *\n * Optional, because development publishes no branded host: the domain fronts\n * one deployment, so there the deployment's own address is the address, and\n * a contract printed there is still a request that runs.\n */\n BAKER_PUBLIC_API_URL?: string;\n BAKER_CHAT_ID?: string;\n /** The thread whose turn runs this call; the bridge sets it per turn. */\n BAKER_THREAD_ID?: string;\n BAKER_ACTING_USER_ID?: string;\n BAKER_GOOGLE_ADS_CUSTOMER_ID?: string;\n BAKER_GA4_PROPERTY_ID?: string;\n BAKER_GSC_SITE_URL?: string;\n BAKER_X_ADS_ACCOUNT_ID?: string;\n BAKER_META_AD_ACCOUNT_ID?: string;\n BAKER_LINKEDIN_AD_ACCOUNT_ID?: string;\n};\n\nlet cached: Env | undefined;\n\nconst CONNECTION = {\n BAKER_API_KEY: z.string().refine(isBakerApiKey, API_KEY_FORMAT_MESSAGE),\n BAKER_API_URL: z.url(\"BAKER_API_URL must be a valid URL\"),\n};\n\nexport function getEnv(): Env {\n if (!cached) {\n cached = createEnv({\n server: {\n ...CONNECTION,\n BAKER_PUBLIC_API_URL: z.url(\"BAKER_PUBLIC_API_URL must be a valid URL\").optional(),\n BAKER_CHAT_ID: z.string().optional(),\n BAKER_THREAD_ID: z.string().optional(),\n BAKER_ACTING_USER_ID: z.string().optional(),\n BAKER_GOOGLE_ADS_CUSTOMER_ID: z\n .string()\n .regex(/^\\d{10}$/)\n .optional(),\n BAKER_GA4_PROPERTY_ID: z.string().optional(),\n BAKER_GSC_SITE_URL: z.string().optional(),\n BAKER_X_ADS_ACCOUNT_ID: z\n .string()\n .regex(/^[a-z0-9]+$/, \"X Ads account ID must be a base36 string\")\n .optional(),\n BAKER_META_AD_ACCOUNT_ID: z.string().optional(),\n BAKER_LINKEDIN_AD_ACCOUNT_ID: z\n .string()\n .regex(/^\\d+$/, \"LinkedIn ad account ID must be the numeric portion of urn:li:sponsoredAccount:N\")\n .optional(),\n },\n runtimeEnv: process.env,\n onValidationError: refuseInvalidEnv,\n });\n }\n return cached;\n}\n\n/**\n * Only the Baker credentials, refused the same way {@link getEnv} refuses them.\n *\n * For a caller that needs the connection and nothing else — the canvas engine —\n * so an unrelated optional override cannot stop a render.\n */\nexport function requireConnection(): Pick<Env, \"BAKER_API_KEY\" | \"BAKER_API_URL\"> {\n return createEnv({ server: CONNECTION, runtimeEnv: process.env, onValidationError: refuseInvalidEnv });\n}\n\ntype EnvIssue = { readonly message: string; readonly path?: ReadonlyArray<PropertyKey | { key: PropertyKey }> };\n\nfunction issueVariable(issue: EnvIssue): string {\n const first = issue.path?.[0];\n const key = typeof first === \"object\" && first !== null ? first.key : first;\n return key === undefined ? \"environment\" : String(key);\n}\n\n/** Optional overrides the agent exports itself (the ads docs tell it to); every other variable is set by the platform. */\nconst AGENT_SET_VARIABLES: ReadonlySet<string> = new Set([\n \"BAKER_GOOGLE_ADS_CUSTOMER_ID\",\n \"BAKER_LINKEDIN_AD_ACCOUNT_ID\",\n \"BAKER_X_ADS_ACCOUNT_ID\",\n]);\n\nfunction listVariables(variables: ReadonlyArray<string>): string {\n return `${variables.join(\", \")} ${variables.length === 1 ? \"is\" : \"are\"}`;\n}\n\n/**\n * The one answer to a Runtime whose environment is missing or malformed.\n *\n * Every networked command reads the env through {@link getEnv}, and ~90 of them\n * end in a catch-all that files any plain `Error` as `INTERNAL_ERROR` — the class\n * that reads as \"a server hiccup, maybe retry\". This is neither, so the answer is\n * settled here, before any caller's catch can misfile it, and the process ends.\n * Who can repair it decides the fix: an ad-account override the agent exported\n * itself is corrected or unset, while a platform-set variable (the credentials)\n * cannot change inside the Runtime. It names the variables and the rule, never\n * their values (the key is a secret), and replaces the validator's stderr dump.\n */\nfunction refuseInvalidEnv(issues: ReadonlyArray<EnvIssue>): never {\n const problems = issues.map((issue) => `${issueVariable(issue)}: ${issue.message}`).join(\"; \");\n const variables = [...new Set(issues.map(issueVariable))];\n if (variables.every((variable) => AGENT_SET_VARIABLES.has(variable))) {\n writeJson({\n ok: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: `An environment variable has the wrong format — ${problems}.`,\n retryable: false,\n fix: {\n action: \"retry_with_changes\",\n explanation:\n `${listVariables(variables)} an optional override set in this workspace, not by the platform. ` +\n `Export it again in the format named above, or unset ${variables.join(\" and \")} and pass the account with ` +\n \"the command's flag instead, then re-run the command.\",\n },\n },\n });\n process.exit(1);\n }\n writeJson({\n ok: false,\n error: {\n code: \"NOT_CONFIGURED\",\n message: `This workspace's Baker connection is misconfigured — ${problems}.`,\n retryable: false,\n fix: {\n action: \"continue_without\",\n explanation:\n `${listVariables(variables)} set by the platform when this workspace starts, and nothing in this workspace can correct it — ` +\n \"do not retry, and do not set or edit it yourself. Every command that reads or writes Baker data will fail the same \" +\n \"way. Carry on with work that does not need Baker's data (files in the repo, local checks), and tell the user \" +\n \"that reading and saving their Baker data is unavailable in this conversation until the platform fixes it.\",\n },\n },\n });\n process.exit(1);\n}\n\n/**\n * This chat's id when one is set, without validating the rest of the env.\n *\n * For best-effort attribution only: a caller that can do its job without a chat\n * must not be ended by {@link getEnv} refusing credentials it never needed yet.\n */\nexport function optionalChatId(): string | undefined {\n return process.env.BAKER_CHAT_ID || undefined;\n}\n\n// Read directly (not via the validated schema): debug logging must work even when\n// the required BAKER_API_KEY/BAKER_API_URL are missing or malformed.\nexport function debugLogSetting(): string | undefined {\n const raw = process.env.BAKER_DEBUG_LOG?.trim();\n return raw ? raw : undefined;\n}\n\n/** A staging command run with no chat to stage against — its own failure, never a network one. */\nexport class MissingChatIdError extends Error {\n readonly code = \"MISSING_CHAT_ID\";\n\n constructor() {\n super(\n \"BAKER_CHAT_ID is not set. This command stages changes against a chat — run it from a chat-attached environment.\",\n );\n this.name = \"MissingChatIdError\";\n }\n}\n\n/** Override for the origins the asset store serves `canvas-assets/` from (comma-separated). */\nexport function canvasAssetOriginsSetting(): string | undefined {\n const raw = process.env.BAKER_CANVAS_ASSET_ORIGINS?.trim();\n return raw ? raw : undefined;\n}\n\n/**\n * The Baker connection when one is configured, else null — for a step that can do without it\n * (healing a canvas path) and must not end the command the way {@link requireConnection} does.\n */\nexport function optionalConnection(): { apiKey: string; baseUrl: string } | null {\n const apiKey = process.env.BAKER_API_KEY;\n const baseUrl = process.env.BAKER_API_URL;\n return apiKey && baseUrl ? { apiKey, baseUrl } : null;\n}\n\nexport function requireChatId(): string {\n const env = getEnv();\n if (!env.BAKER_CHAT_ID) {\n throw new MissingChatIdError();\n }\n return env.BAKER_CHAT_ID;\n}\n\n/**\n * Which chat a *read* addresses. A `--chat` value names another chat in the same company — how an\n * earlier chat's staged changes are recovered verbatim instead of rebuilt from a summary — and with\n * no flag it is this session's own chat.\n *\n * Read paths only. Staging, amending and discarding keep calling `requireChatId`, so one session can\n * never edit another's draft; the backend enforces the same thing twice over, since every mutating\n * internal requires the draft be `active` and every route checks the chat belongs to the caller's\n * company.\n */\nexport function resolveChatId(chat?: unknown): string {\n return typeof chat === \"string\" && chat.length > 0 ? chat : requireChatId();\n}\n\n/**\n * Whether the frame-vision pass can run at all.\n *\n * `hyperframes snapshot --describe` reads `GEMINI_API_KEY`, and it is the only\n * check that can tell a rendered frame apart from an empty one. The backend\n * ships the credential Studio already uses for Gemini, by omission — a\n * deployment without one simply gets the plain smoke test.\n *\n * Kept out of `getEnv()` for the same reason as the capture budget: the render\n * engine also runs where none of the chat-attached variables exist.\n */\nexport function hasFrameVisionKey(): boolean {\n return Boolean(frameVisionKey());\n}\n\n/** The credential itself, for the pass that calls the model directly. */\nexport function frameVisionKey(): string | undefined {\n return process.env.GEMINI_API_KEY;\n}\n\n/**\n * The gateway credential, for a Runtime pass that calls a model itself.\n *\n * The same per-org OpenRouter key the agent already runs on — the backend puts it in\n * the Runtime twice, under `ANTHROPIC_AUTH_TOKEN` for the Agent SDK and here under its\n * own name. Two names rather than one because reading the SDK's variable for a call\n * that has nothing to do with the SDK is how a rename somewhere else silently breaks\n * something over here.\n *\n * Kept out of `getEnv()` alongside `frameVisionKey`, for the same reason: the render\n * engine also runs where none of the chat-attached variables exist.\n */\nexport function openRouterKey(): string | undefined {\n return process.env.OPENROUTER_API_KEY;\n}\n\nexport function hasOpenRouterKey(): boolean {\n return Boolean(openRouterKey());\n}\n\n/**\n * Wall clock the whole capture may spend, when the caller bounded it.\n *\n * Read from the environment rather than taken as a flag, deliberately. A\n * `--budget-ms` argument would land in the command schema, and the schema is\n * what the agent reads — one question away from \"what is the budget for?\", and\n * from there the escalation ladder stops being invisible to it.\n *\n * Kept out of `getEnv()` because that validator is for a chat-attached run; the\n * capture engine also runs where none of those variables exist.\n */\nexport function captureBudgetMs(): number | null {\n const raw = Number(process.env.BAKER_CAPTURE_BUDGET_MS);\n return Number.isFinite(raw) && raw > 0 ? raw : null;\n}\n\n/**\n * Egress-proxy credentials for a capture, or none at all.\n *\n * These arrive as sandbox *command* env, never as arguments — a credential in\n * argv is readable by any `ps` sharing the sandbox. With none set,\n * `plannedRoutes` yields the direct route alone and a capture behaves exactly\n * as it did before the ladder existed.\n *\n * Whichever pairs arrive are the rungs this process may climb; there is nothing\n * to filter. A tier the deployment withholds is simply absent from this env, so\n * a ceiling read on this side could only ever disagree with the one place that\n * decides.\n */\nexport function captureProxyCredentials(): ProxyCredentials {\n return readProxyCredentials(process.env);\n}\n\n/**\n * The Vimeo session cookie the deployment supplied, or undefined.\n *\n * Absent is the normal, supported state: a deployment that has not configured\n * one gets the same \"download it and upload the file\" refusal Vimeo gave before\n * this existed. Read here with the other env, so nothing else reaches into\n * `process.env` for it.\n */\nexport function captureVimeoCookie(): string | undefined {\n const raw = process.env.VIMEO_COOKIE?.trim();\n return raw ? raw : undefined;\n}\n\n/**\n * This process's environment plus `extra`, for handing to a child.\n *\n * Lives here because `process.env` reads belong in this file — but it earns its\n * place beyond the lint rule: `spawn`'s `env` *replaces* the environment rather\n * than extending it, so a caller that passes only its additions silently strips\n * `PATH` and the child fails to start for a reason that looks nothing like the\n * cause.\n */\nexport function childEnvWith(extra: Record<string, string>): NodeJS.ProcessEnv {\n return { ...process.env, ...extra };\n}\n","/**\n * What a `BAKER_API_KEY` may look like, decided once.\n *\n * Two prefixes reach the CLI, and the backend has always accepted both\n * (`convex/lib/authHelpers.ts` → `requireApiKey`): `bk_` for a company's own key,\n * and `bkr_` for the read-only credential a Session review runs with. The client\n * used to hard-require `bk_` in two places, so every `baker` call inside a review\n * failed on env validation before a request was ever sent — including the\n * session-start hooks, which made the reviewer's first impression of its own\n * Runtime a stack trace.\n */\nconst KEY_PREFIXES = [\"bk_\", \"bkr_\"] as const;\n\nexport const API_KEY_FORMAT_MESSAGE = \"API key must start with 'bk_' or 'bkr_'\";\n\nexport function isBakerApiKey(value: string): boolean {\n return KEY_PREFIXES.some((prefix) => value.startsWith(prefix));\n}\n","interface ArgSchema {\n type: string;\n description: string;\n required: boolean;\n default?: unknown;\n enum?: string[];\n}\n\ninterface CommandSchema {\n command: string;\n description: string;\n args: Record<string, ArgSchema>;\n}\n\nconst registry = new Map<string, CommandSchema>();\n\nexport function registerSchema(schema: CommandSchema): void {\n registry.set(schema.command, schema);\n}\n\nexport function getSchema(command: string): CommandSchema | undefined {\n return registry.get(command);\n}\n\nexport function listSchemas(): string[] {\n return [...registry.keys()];\n}\n\n/** A name compared the way the command line spells it: case and hyphens aside. */\nfunction spellingKey(name: string): string {\n return name.toLowerCase().replaceAll(\"-\", \"\");\n}\n\n/**\n * The registered name a command path addresses, however it was typed.\n *\n * Schemas are registered under dotted camelCase names (`tagManager.read`), while\n * the command line — and every message the CLI prints about a command — spells\n * the same path with spaces and kebab-case (`tag-manager read`). Both forms must\n * resolve, or the CLI's own advice to run `baker schema <command>` is a dead end.\n */\nexport function resolveSchemaName(words: string[], names: string[]): string | undefined {\n const joined = words.join(\".\");\n if (names.includes(joined)) {\n return joined;\n }\n const matches = names.filter((name) => spellingKey(name) === spellingKey(joined));\n return matches.length === 1 ? matches[0] : undefined;\n}\n\n/** The schema of the command path, typed with spaces or dots. */\nexport function findSchema(words: string[]): CommandSchema | undefined {\n const name = resolveSchemaName(words, listSchemas());\n return name === undefined ? undefined : registry.get(name);\n}\n","/**\n * Refusing a flag the command never declared.\n *\n * citty drops an unrecognised `--flag` silently, which is the worst possible\n * behaviour for this CLI's caller. An agent asked `ga4 query` for one event with\n * `--dimension-filter`, got the unfiltered totals back, and had no way to tell\n * them apart from filtered ones — five identical tables that looked like five\n * different events. A command that answers a question nobody asked is worse than\n * one that fails, because only the failure is visible.\n *\n * Everything here fails *open*: anything this module cannot introspect with\n * certainty is treated as accepted. A guard that blocks a valid command is a\n * worse bug than the one it exists to catch.\n */\n\nimport { findSchema } from \"./schemas.ts\";\n\n/**\n * All this module reads of a command. Deliberately not citty's `CommandDef`:\n * that type is generic over the command's own args and invariant in it, so every\n * real command is a *different* `CommandDef<…>` and none of them is assignable to\n * the bare one. Naming the two fields actually inspected accepts all of them and\n * says what the module depends on.\n */\nexport type IntrospectableCommand = {\n // `unknown` rather than a record because citty allows both to be declared\n // lazily — a function or a promise — and both are narrowed at runtime below.\n readonly args?: unknown;\n readonly subCommands?: unknown;\n};\n\n/** Handled by citty itself on every command, so never declared in `args`. */\nconst ALWAYS_ACCEPTED = new Set([\"help\", \"h\", \"version\"]);\n\n/**\n * Subcommands can be declared lazily (a function or a promise). None are today,\n * and resolving one would make this async for every caller — so an unresolvable\n * branch stops the walk and the command is treated as accepting anything.\n */\nfunction subCommandsOf(command: IntrospectableCommand): Record<string, unknown> | null {\n const sub = command.subCommands;\n return sub && typeof sub === \"object\" ? (sub as Record<string, unknown>) : null;\n}\n\nfunction argsOf(command: IntrospectableCommand): Record<string, unknown> | null {\n const args = command.args;\n return args && typeof args === \"object\" ? (args as Record<string, unknown>) : null;\n}\n\n/**\n * The command `argv` actually addresses, plus whether the walk completed. A walk\n * that stopped early (a lazy subcommand, an unknown name) yields `resolved:\n * false` and no flag is judged — the command it would have reached is the only\n * thing that knows which flags are its own.\n */\nfunction resolveCommand(\n root: IntrospectableCommand,\n argv: string[],\n): { command: IntrospectableCommand; resolved: boolean } {\n let command = root;\n for (const token of argv) {\n if (token === \"--\" || token.startsWith(\"-\")) {\n break;\n }\n const subCommands = subCommandsOf(command);\n if (!subCommands) {\n break;\n }\n const next = subCommands[token];\n if (next === undefined) {\n // A positional argument, not a subcommand name — the walk is over and this\n // command is the one whose flags apply.\n break;\n }\n if (typeof next !== \"object\") {\n return { command, resolved: false };\n }\n command = next as IntrospectableCommand;\n }\n return { command, resolved: true };\n}\n\nfunction typeOf(def: unknown): string | undefined {\n return def && typeof def === \"object\" ? (def as { type?: string }).type : undefined;\n}\n\n/**\n * Every flag name this command answers to. Declared options, plus the `--no-x`\n * form citty synthesises for a boolean `x`.\n *\n * Positionals are excluded, and that exclusion is the point rather than a\n * detail. citty takes them by position only, so `winning-ads search --query\n * shoes` does not set the query — it is dropped, and the command runs an\n * unfiltered search that looks exactly like a filtered one. Accepting the name\n * because it appears in `args` would wave through the precise failure this\n * module exists to stop.\n */\nfunction acceptedNames(command: IntrospectableCommand): Set<string> | null {\n const args = argsOf(command);\n if (!args) {\n return null;\n }\n const names = new Set<string>(ALWAYS_ACCEPTED);\n for (const [name, def] of Object.entries(args)) {\n if (typeOf(def) === \"positional\") {\n continue;\n }\n // citty normalises between the two spellings, so an arg declared `runId`\n // answers to `--run-id` too. Judging only the declared one refuses a form the\n // command accepts — a guard rejecting a working call, which is the one\n // failure this module is not allowed to have. It also sent someone to\n // \"correct\" a doc that had been right all along.\n for (const spelling of [name, kebab(name), camel(name)]) {\n names.add(spelling);\n if (typeOf(def) === \"boolean\") {\n names.add(`no-${spelling}`);\n }\n }\n }\n return names;\n}\n\nfunction kebab(name: string): string {\n return name.replace(/([a-z0-9])([A-Z])/g, \"$1-$2\").toLowerCase();\n}\n\nfunction camel(name: string): string {\n return name.replace(/-([a-z0-9])/g, (_, c: string) => c.toUpperCase());\n}\n\n/**\n * Flags in `argv` that the addressed command does not declare, in the form the\n * caller typed them (`--dimension-filter`), so the message can quote it back.\n *\n * Returns empty whenever the answer is not certain: a command with no `args` at\n * all is a parent whose real flags live on a subcommand, and judging it would\n * reject the working call `ads meta pixels --stats`.\n */\nexport function findUnknownFlags(root: IntrospectableCommand, argv: string[]): string[] {\n const { command, resolved } = resolveCommand(root, argv);\n if (!resolved) {\n return [];\n }\n const accepted = acceptedNames(command);\n if (!accepted) {\n return [];\n }\n const unknown: string[] = [];\n for (const token of argv) {\n // Everything after `--` is the caller's own payload, not ours to read.\n if (token === \"--\") {\n break;\n }\n if (!token.startsWith(\"--\")) {\n continue;\n }\n const name = token.slice(2).split(\"=\")[0] ?? \"\";\n if (name.length > 0 && !accepted.has(name) && !unknown.includes(token)) {\n unknown.push(token);\n }\n }\n return unknown;\n}\n\n/**\n * Flags an agent invents for an idea the command spells another way, each with\n * the spellings that mean the same, best first. Spelling cannot find these, and\n * worse, it finds a neighbour that means something else: `analytics releases\n * --path` was sent to `--page`, a page NUMBER, and the next call failed on it.\n * Only a candidate the command declares is ever offered, and never `--from` or\n * `--to`: on some commands those are dates, on others an event source or a new\n * name, so a date word is sent only to a flag whose name says it is a date.\n */\nconst SYNONYMS: Readonly<Record<string, readonly string[]>> = {\n from: [\"start-date\", \"start\", \"since\"],\n since: [\"start-date\", \"start\"],\n start: [\"start-date\", \"since\"],\n to: [\"end-date\", \"end\", \"until\"],\n until: [\"end-date\", \"end\"],\n end: [\"end-date\", \"until\"],\n path: [\"landing\"],\n url: [\"landing\"],\n};\n\n/**\n * The declared flag an unknown one was most likely reaching for, or null when\n * nothing is close enough to be worth saying.\n *\n * Shared prefix carries more weight than edit distance, because these misses are\n * plausible inventions rather than typos: `--dimension-filter` is nine characters\n * of agreement with `--dimensions` and seven of disagreement, so distance alone\n * scores it as unrelated and stays silent exactly when the answer is obvious.\n */\nexport function closestFlag(unknown: string, command: IntrospectableCommand): string | null {\n const accepted = acceptedNames(command);\n if (!accepted) {\n return null;\n }\n const name = unknown.replace(/^--/, \"\").split(\"=\")[0] ?? \"\";\n const synonym = SYNONYMS[name]?.find((candidate) => accepted.has(candidate));\n if (synonym) {\n return `--${synonym}`;\n }\n const scored = [...accepted]\n .filter((candidate) => !ALWAYS_ACCEPTED.has(candidate))\n .map((candidate) => ({\n candidate,\n prefix: sharedPrefix(name, candidate),\n distance: editDistance(name, candidate),\n }))\n .filter(({ prefix, distance }) => prefix >= 4 || distance <= typoBudget(name))\n .sort((a, b) => b.prefix - a.prefix || a.distance - b.distance);\n return scored[0] ? `--${scored[0].candidate}` : null;\n}\n\n/**\n * How many edits still read as a typo of `name` rather than another word. A flat\n * three let `--from` reach `--full` and `--path` reach `--page`: on four letters,\n * two or three edits leave a different word, and the agent followed it into a\n * flag that meant something else. A third of the length still reaches\n * `--dimensions` from `--dimenions` and drops those.\n */\nfunction typoBudget(name: string): number {\n return Math.max(1, Math.floor(name.length / 3));\n}\n\nfunction sharedPrefix(a: string, b: string): number {\n let i = 0;\n while (i < a.length && i < b.length && a[i] === b[i]) {\n i++;\n }\n return i;\n}\n\nfunction editDistance(a: string, b: string): number {\n let previous = Array.from({ length: b.length + 1 }, (_, j) => j);\n for (let i = 1; i <= a.length; i++) {\n const current = [i];\n for (let j = 1; j <= b.length; j++) {\n const substitution = (previous[j - 1] ?? 0) + (a[i - 1] === b[j - 1] ? 0 : 1);\n current.push(Math.min((previous[j] ?? 0) + 1, (current[j - 1] ?? 0) + 1, substitution));\n }\n previous = current;\n }\n return previous[b.length] ?? 0;\n}\n\n/** The command `argv` addresses, for a caller that needs it to build a suggestion. */\nexport function commandFor(root: IntrospectableCommand, argv: string[]): IntrospectableCommand | null {\n const { command, resolved } = resolveCommand(root, argv);\n return resolved ? command : null;\n}\n\n/** A flag to offer instead, with what the command says it takes. */\nexport type FlagSuggestion = { flag: string; description?: string };\n\n/**\n * The flag an unknown one was reaching for, with the first sentence of its own\n * description: `--path` is sent to `--landing`, which takes an internalId, and\n * the name alone invites `--landing /pricing`.\n */\nexport function suggestFlag(unknown: string, command: IntrospectableCommand): FlagSuggestion | null {\n const flag = closestFlag(unknown, command);\n if (!flag) {\n return null;\n }\n const args = argsOf(command) ?? {};\n const name = flag.slice(2);\n const def = args[name] ?? args[camel(name)];\n const described = def && typeof def === \"object\" ? (def as { description?: unknown }).description : undefined;\n const description =\n typeof described === \"string\"\n ? described\n .split(/(?<!\\b(?:e\\.g|i\\.e|vs))\\.\\s/)[0]\n ?.trim()\n .replace(/\\.$/, \"\")\n : \"\";\n return description ? { flag, description } : { flag };\n}\n\nfunction suggestionText(suggestion: FlagSuggestion | null): string {\n if (!suggestion) {\n return \"\";\n }\n return suggestion.description\n ? `Did you mean ${suggestion.flag}? It is described as: ${suggestion.description}. `\n : `Did you mean ${suggestion.flag}? `;\n}\n\n/**\n * Said when `--output` is refused. Any command that prints the JSON envelope\n * takes `--output json` (`output-flag.ts`), so the refusal is either another\n * format on a command without that view, or one of the few that print text.\n */\nconst OUTPUT_EXPLANATION =\n \"This command has no --output option: `--output json` is accepted by every command that prints the JSON \" +\n \"envelope, and md or files only where `baker schema` lists `output`. Drop the option and read the default output. \";\n\n/**\n * The refusal itself, as the envelope every other failure uses.\n *\n * `fix` tells the agent to re-read the schema rather than retry the same call:\n * the flag it invented is usually a capability it assumed, and the useful next\n * move is finding out what the command really offers — sometimes another flag,\n * sometimes a different shape of query entirely. Only a command that registers a\n * schema is sent to `baker schema`; the rest are sent to `--help`, because advice\n * that ends in NOT_FOUND costs the agent a call and teaches it nothing.\n */\nexport function unknownFlagEnvelope(\n unknown: string[],\n commandPath: string,\n suggestion: FlagSuggestion | null,\n hasSchema: boolean,\n) {\n const list = unknown.join(\", \");\n return {\n ok: false as const,\n error: {\n code: \"UNKNOWN_ARGUMENT\",\n message: `${list} ${unknown.length > 1 ? \"are not options\" : \"is not an option\"} of \\`baker ${commandPath}\\`.`,\n fix: {\n action: hasSchema ? \"check_schema\" : \"check_help\",\n explanation:\n (unknown.some((flag) => flag === \"--output\" || flag.startsWith(\"--output=\")) ? OUTPUT_EXPLANATION : \"\") +\n suggestionText(suggestion) +\n `Run \\`${hasSchema ? `baker schema ${commandPath}` : `baker ${commandPath} --help`}\\` to see every ` +\n `option this command takes. Do not re-run the same ` +\n `call: until this version an unrecognised option was accepted and ignored, so a command that looked ` +\n `like it had filtered or narrowed the result was in fact answering the unfiltered question.`,\n },\n retryable: false,\n },\n };\n}\n\n/** The subcommand path the caller addressed (`ga4 query`), for the message. */\nexport function commandPathOf(root: IntrospectableCommand, argv: string[]): string {\n const path: string[] = [];\n let command = root;\n for (const token of argv) {\n if (token === \"--\" || token.startsWith(\"-\")) {\n break;\n }\n const subCommands = subCommandsOf(command);\n const next = subCommands?.[token];\n if (next === undefined || typeof next !== \"object\") {\n break;\n }\n path.push(token);\n command = next as IntrospectableCommand;\n }\n return path.join(\" \");\n}\n\n/**\n * Stop before the command runs when it was handed an option it does not have.\n *\n * Deliberately at the entry point rather than inside each command: the fault is\n * in the parse, so by the time any `run` is reached the flag is already gone and\n * there is nothing left to notice.\n */\nexport function refuseUnknownFlags(root: IntrospectableCommand, argv: string[]): void {\n const unknown = findUnknownFlags(root, argv);\n if (unknown.length === 0) {\n return;\n }\n const command = commandFor(root, argv);\n const first = unknown[0];\n const suggestion = command && first ? suggestFlag(first, command) : null;\n const path = commandPathOf(root, argv);\n // Written here rather than through a command's output helper: this runs before\n // any command is resolved, so it cannot depend on one of them for the envelope\n // every command shares.\n process.stdout.write(\n `${JSON.stringify(unknownFlagEnvelope(unknown, path, suggestion, findSchema(path.split(\" \")) !== undefined), null, 2)}\\n`,\n );\n process.exit(1);\n}\n","/**\n * `--output json` on every command.\n *\n * About a third of the commands declare `--output` (the ads, analytics-report,\n * library and research families); the rest print the JSON envelope and declare\n * nothing. An agent that has just used `--output json` on `ads meta` carries it\n * to `chats list`, and since unknown options are refused rather than dropped\n * (`unknown-flags.ts`) the call failed on a flag that asked for exactly what the\n * command was about to print. Four Sessions lost a call and a retry to it.\n *\n * So `json` — and only `json` — is taken on a command that did not declare the\n * option: it is dropped where the envelope is already the output, and handed to\n * the command's own `--json` switch where a readable view is the default. `md`\n * and `files` still need a renderer the command owns, so they are left for the\n * guard to refuse, which names this rule in its fix.\n */\n\nimport { commandFor, commandPathOf, type IntrospectableCommand } from \"./unknown-flags.ts\";\n\nconst FLAG = \"--output\";\nconst ENVELOPE = \"json\";\n\n/**\n * Commands whose stdout is neither the envelope nor switchable to it: progress\n * lines, or a Markdown brief. Accepting `--output json` there would promise a\n * shape they never print, so the guard keeps refusing it.\n */\nexport const PRINTS_NO_ENVELOPE: ReadonlySet<string> = new Set([\n \"analytics sending-events\",\n \"canvas run\",\n \"canvas rerun\",\n \"canvas sync-definition\",\n]);\n\nexport function declaredArg(command: IntrospectableCommand, name: string): { type?: string } | null {\n const args = command.args;\n if (!args || typeof args !== \"object\" || !(name in args)) {\n return null;\n }\n const def = (args as Record<string, unknown>)[name];\n return def && typeof def === \"object\" ? (def as { type?: string }) : {};\n}\n\n/** Index and token count of each `--output json` before `--`. */\nfunction envelopeRequests(argv: string[]): Array<{ index: number; length: 1 | 2 }> {\n const end = argv.indexOf(\"--\");\n const limit = end === -1 ? argv.length : end;\n const found: Array<{ index: number; length: 1 | 2 }> = [];\n for (let index = 0; index < limit; index++) {\n const token = argv[index];\n if (token === `${FLAG}=${ENVELOPE}`) {\n found.push({ index, length: 1 });\n } else if (token === FLAG && argv[index + 1] === ENVELOPE && index + 1 < limit) {\n found.push({ index, length: 2 });\n index++;\n }\n }\n return found;\n}\n\n/**\n * `argv` with `--output json` translated for the command it addresses, before\n * the unknown-option guard reads it. A command that declares `--output` gets\n * the flag untouched; one that cannot print the envelope is left to be refused.\n */\nexport function outputFlagForCommand(root: IntrospectableCommand, argv: string[]): string[] {\n const requests = envelopeRequests(argv);\n if (requests.length === 0) {\n return argv;\n }\n const command = commandFor(root, argv);\n if (!command || declaredArg(command, \"output\") || PRINTS_NO_ENVELOPE.has(commandPathOf(root, argv))) {\n return argv;\n }\n const jsonSwitch = declaredArg(command, \"json\")?.type === \"boolean\";\n const dropped = new Set(requests.flatMap(({ index, length }) => (length === 2 ? [index, index + 1] : [index])));\n const first = requests[0]?.index;\n return argv.flatMap((token, index) => {\n if (!dropped.has(index)) {\n return [token];\n }\n return jsonSwitch && index === first ? [\"--json\"] : [];\n });\n}\n","/**\n * `--fields a,b,c` on every command that prints the JSON envelope.\n *\n * The conventions doc has always presented `--fields` as the way to narrow a\n * read, but only the commands that declared it took it: two dozen image, video,\n * winning-ads and ads commands. Everywhere else the unknown-option guard\n * (`unknown-flags.ts`) refused it, and the agent — told by the doc to narrow in\n * the query rather than in a pipe — burned a call per command finding out\n * (BAKER-666: eight `experiment plan` calls in one Session; BAKER-182, -370,\n * -642 and more for other families).\n *\n * So a command that does not declare `--fields` gets it here, applied to what it\n * prints rather than to what it asks for: stdout is the one output layer every\n * command shares, whichever of the family writers built the envelope. A command\n * that declares `--fields` keeps its own meaning — on `ads meta insights` it is\n * which metrics Meta returns, not a mask — and one that prints no envelope at all is\n * left to the guard, as with `--output json` (`output-flag.ts`).\n */\n\nimport { declaredArg, PRINTS_NO_ENVELOPE } from \"./output-flag.ts\";\nimport { commandFor, commandPathOf, type IntrospectableCommand } from \"./unknown-flags.ts\";\n\nconst FLAG = \"--fields\";\n\nfunction parseFields(value: string): string[] {\n return value\n .split(\",\")\n .map((field) => field.trim())\n .filter((field) => field.length > 0);\n}\n\n/**\n * `argv` without `--fields`, and the fields it asked for, when the addressed\n * command prints the envelope and does not declare the option itself. Otherwise\n * `argv` is returned untouched with `fields: null`, for the command or the guard\n * to judge.\n */\nexport function takeFieldsFlag(\n root: IntrospectableCommand,\n argv: string[],\n): { argv: string[]; fields: string[] | null } {\n const end = argv.indexOf(\"--\");\n const limit = end === -1 ? argv.length : end;\n const taken = new Set<number>();\n const fields: string[] = [];\n for (let index = 0; index < limit; index++) {\n const token = argv[index] ?? \"\";\n if (token.startsWith(`${FLAG}=`)) {\n taken.add(index);\n fields.push(...parseFields(token.slice(FLAG.length + 1)));\n } else if (token === FLAG) {\n const value = argv[index + 1];\n // A bare `--fields` names nothing to keep; the guard says so.\n if (value === undefined || index + 1 >= limit || value.startsWith(\"-\")) {\n return { argv, fields: null };\n }\n taken.add(index).add(index + 1);\n fields.push(...parseFields(value));\n index++;\n }\n }\n if (taken.size === 0 || fields.length === 0) {\n return { argv, fields: null };\n }\n const command = commandFor(root, argv);\n if (!command || declaredArg(command, \"fields\") || PRINTS_NO_ENVELOPE.has(commandPathOf(root, argv))) {\n return { argv, fields: null };\n }\n return { argv: argv.filter((_, index) => !taken.has(index)), fields };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction pick(record: Record<string, unknown>, fields: string[]): Record<string, unknown> {\n return Object.fromEntries(fields.filter((field) => field in record).map((field) => [field, record[field]]));\n}\n\n/**\n * The one non-empty list of records `data` holds (`chats list` prints\n * `{ chats: [...] }`), or null when it holds none or several. An empty list is\n * never it: `[]` is as much a record's `tags` as a wrapper's rows.\n */\nfunction soleList(data: Record<string, unknown>): [string, Record<string, unknown>[]] | null {\n const lists = Object.entries(data).filter(\n (entry): entry is [string, Record<string, unknown>[]] =>\n Array.isArray(entry[1]) && entry[1].length > 0 && entry[1].every((item) => isRecord(item)),\n );\n return lists.length === 1 ? (lists[0] ?? null) : null;\n}\n\n/**\n * The key of the list whose rows the fields address: the fields that are not\n * `data`'s own keys, when a row carries one of them. Otherwise null, and the\n * fields address `data` itself — a list a record merely holds\n * (`levels_not_read`) is not taken for rows no named field is on, or a miss\n * would come back as the whole record with that one list emptied.\n */\nfunction wrappedList(data: Record<string, unknown>, fields: string[]): string | null {\n const rowFields = fields.filter((field) => !(field in data));\n const list = soleList(data);\n if (list === null || !list[1].some((row) => rowFields.some((field) => field in row))) {\n return null;\n }\n return list[0];\n}\n\n/** The records the fields are read against: the rows, or `data` itself. */\nfunction recordsOf(data: unknown, fields: string[]): unknown[] {\n if (Array.isArray(data)) {\n return data;\n }\n if (!isRecord(data)) {\n return [];\n }\n const list = wrappedList(data, fields);\n return list === null ? [data] : (data[list] as unknown[]);\n}\n\n/**\n * `data` narrowed to the named keys of each record — the rows when it is a\n * list or wraps one, `data` itself when it is one record. Everything around the\n * rows (`meta`, `hints`, warnings, a wrapper's totals) is kept: it is what says\n * whether the rows can be trusted, and the reason the doc sends agents to\n * `--fields` instead of a pipe. Fields that name the wrapper's own keys as well\n * as the rows' narrow both: the wrapper to its named keys, the rows to theirs.\n */\nfunction maskData(data: unknown, fields: string[]): unknown {\n const maskWith = (keep: string[]) => (item: unknown) => (isRecord(item) ? pick(item, keep) : item);\n if (Array.isArray(data)) {\n return data.map(maskWith(fields));\n }\n if (!isRecord(data)) {\n return data;\n }\n const list = wrappedList(data, fields);\n if (list === null) {\n return pick(data, fields);\n }\n const wrapperFields = fields.filter((field) => field in data);\n const rows = recordsOf(data, fields).map(maskWith(fields.filter((field) => !(field in data))));\n return { ...(wrapperFields.length > 0 ? pick(data, wrapperFields) : data), [list]: rows };\n}\n\n/**\n * A name no record carried, said out loud. Dropped quietly, it comes back as a\n * row without the key, which reads as \"that value is empty\" rather than \"there\n * is no such field\" (BAKER-197).\n */\nfunction unmatchedHint(data: unknown, fields: string[]): string | null {\n const records = recordsOf(data, fields).filter(isRecord);\n if (records.length === 0) {\n return null;\n }\n // A record's keys and its one list's row keys: either may be what was meant.\n const keys = isRecord(data) ? [data, ...(soleList(data)?.[1] ?? [])] : records;\n const carried = new Set(keys.flatMap((record) => Object.keys(record)));\n const unmatched = fields.filter((field) => !carried.has(field));\n if (unmatched.length === 0) {\n return null;\n }\n return `--fields matched nothing for: ${unmatched.join(\", \")}. The keys data carries are: ${[...carried].join(\", \")}.`;\n}\n\n/** The text re-printed with the indentation and trailing newline it came with. */\nfunction reprint(text: string, envelope: Record<string, unknown>): string {\n const indent = text.trimStart().startsWith(\"{\\n\") ? 2 : undefined;\n return `${JSON.stringify(envelope, null, indent)}${text.endsWith(\"\\n\") ? \"\\n\" : \"\"}`;\n}\n\nfunction withHint(envelope: Record<string, unknown>, hint: string): Record<string, unknown> {\n return { ...envelope, hints: [...(Array.isArray(envelope.hints) ? envelope.hints : []), hint] };\n}\n\nconst NO_DATA_HINT =\n \"--fields was not applied: this result carries no data to narrow, so it is printed whole. Drop --fields on this command.\";\n\n/**\n * The text a command printed, with its envelope masked, or null when the text\n * is no envelope at all (a Markdown view, a progress line). An error or a dry\n * run is an envelope with no `data` to mask, and comes back as it was printed.\n * A success that prints its result beside `ok` rather than under `data`\n * (`canvas validate`, `canvas critique`) comes back whole with a hint saying the\n * flag was not applied, so it is not read as a narrowed answer. The indentation\n * it was printed with is kept, so a masked read looks like any other.\n */\nexport function maskPrintedEnvelope(text: string, fields: string[]): string | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return null;\n }\n if (!isRecord(parsed) || typeof parsed.ok !== \"boolean\") {\n return null;\n }\n if (parsed.ok !== true || \"dryRun\" in parsed) {\n return text;\n }\n return reprint(text, maskEnvelope(parsed, fields));\n}\n\n/**\n * A success envelope with `data` narrowed to the fields, a hint for any field\n * nothing carried, or a hint that nothing was narrowed when there is no `data`.\n * Shared with `writeOutput`, whose commands declare `--fields` themselves.\n */\nexport function maskEnvelope(envelope: Record<string, unknown>, fields: string[]): Record<string, unknown> {\n if (!(\"data\" in envelope)) {\n return withHint(envelope, NO_DATA_HINT);\n }\n const masked: Record<string, unknown> = { ...envelope, data: maskData(envelope.data, fields) };\n const miss = unmatchedHint(envelope.data, fields);\n return miss ? withHint(masked, miss) : masked;\n}\n\n/** The one method of a stream the mask replaces — `process.stdout` in the CLI. */\ntype WriteTarget = { write(chunk: string | Uint8Array, ...rest: never[]): boolean };\n\n/**\n * Mask every envelope written to `stream` from now on. Each family writer prints\n * its envelope in one `write` call, so each call is judged whole; anything that\n * is not an envelope passes through as it was written. `sawEnvelope()` says\n * whether one was printed, so the caller can tell the agent when the command\n * printed none and the flag changed nothing.\n */\nexport function maskWrites(stream: WriteTarget, fields: string[]): { sawEnvelope: () => boolean } {\n let applied = false;\n const write = stream.write.bind(stream);\n stream.write = (chunk: string | Uint8Array, ...rest: never[]): boolean => {\n const masked = typeof chunk === \"string\" ? maskPrintedEnvelope(chunk, fields) : null;\n if (masked === null) {\n return write(chunk, ...rest);\n }\n applied = true;\n return write(masked, ...rest);\n };\n return { sawEnvelope: () => applied };\n}\n\n/**\n * The mask on the CLI's own stdout, and a note on stderr when the command\n * printed no envelope for it to act on — a readable view such as `tags list`\n * without `--json` — so the agent is not left believing the output was narrowed.\n */\nexport function installFieldMask(fields: string[], commandPath: string): void {\n const mask = maskWrites(process.stdout, fields);\n process.on(\"exit\", () => {\n if (!mask.sawEnvelope()) {\n process.stderr.write(\n `--fields was not applied: \\`baker ${commandPath}\\` printed no JSON envelope to narrow. ` +\n \"Add --output json to get the envelope, or drop --fields.\\n\",\n );\n }\n });\n}\n","import { maskEnvelope } from \"./field-mask.ts\";\n\nexport type OutputFormat = \"json\" | \"files\" | \"md\";\n\n/**\n * Why a read returned nothing, measured rather than guessed.\n *\n * An empty array is ambiguous in a way that costs real accuracy: \"your filters\n * excluded everything\", \"the library is empty\", and \"nothing is connected\" are\n * three different situations with three different next moves, and they are\n * indistinguishable from `data.length === 0`. Left to infer, an agent picks the\n * most dramatic reading and writes it down as fact — `baker testimonials list`\n * used to declare \"this company has NO review corpus connected\" on any empty\n * unfiltered browse, having never checked.\n *\n * So a command that can cheaply establish the reason should say which it is, and\n * one that cannot should omit this rather than fill it in speculatively. `reason`\n * is a family-specific slug; `detail` is prose an agent can quote and still be\n * saying something true.\n */\nexport interface EmptyEvidence {\n reason: string;\n detail: string;\n}\n\ntype SuccessEnvelope<T> = {\n ok: true;\n data: T;\n meta?: { cursor?: string; hasMore?: boolean; count?: number; empty?: EmptyEvidence };\n hints?: string[];\n};\n\ninterface ErrorEnvelope {\n ok: false;\n error: {\n code: string;\n message: string;\n /**\n * Structured remediation. `action` + `explanation` are the contract every\n * family honors; a family may hang its own fields off the same object when\n * the fix needs data (`ads google query` carries `correctedCommand`,\n * `brand fonts` carries the weights Google does serve) — otherwise the only\n * remedy an agent can read is \"re-run the command that just failed\".\n */\n fix?: string | ({ action: string; explanation: string } & Record<string, unknown>);\n retryable?: boolean;\n };\n}\n\ninterface DryRunEnvelope {\n ok: true;\n dryRun: true;\n operation: string;\n params: Record<string, unknown>;\n}\n\ntype Envelope<T> = SuccessEnvelope<T> | ErrorEnvelope | DryRunEnvelope;\n\ntype Normalizer = (record: Record<string, unknown>, full: boolean) => Record<string, unknown>;\n\nconst ASPECT_RATIO_MAP: Record<string, number> = {\n \"1:1\": 1,\n \"16:9\": 16 / 9,\n \"9:16\": 9 / 16,\n \"2:3\": 2 / 3,\n \"3:4\": 3 / 4,\n \"1:2\": 1 / 2,\n \"2:1\": 2,\n \"4:5\": 4 / 5,\n \"3:2\": 3 / 2,\n \"4:3\": 4 / 3,\n};\n\nconst ASPECT_RATIO_TOLERANCE = 0.08;\n\nexport function formatAspectRatio(ratio: number | undefined): string | null {\n if (ratio === undefined || ratio === null) {\n return null;\n }\n for (const [label, value] of Object.entries(ASPECT_RATIO_MAP)) {\n if (Math.abs(ratio - value) / value < ASPECT_RATIO_TOLERANCE) {\n return label;\n }\n }\n return null;\n}\n\n/** The set an asset arrived in, flattened for display. Absent for standalone\n * assets. Carousel slides and page scrapes only make sense as a set, so the\n * position travels with every projection that names the asset. */\nfunction groupSummary(record: Record<string, unknown>): Record<string, unknown> | null {\n const key = record.groupKey;\n if (typeof key !== \"string\" || !key) {\n return null;\n }\n const index = typeof record.groupIndex === \"number\" ? record.groupIndex : null;\n const size = typeof record.groupSize === \"number\" ? record.groupSize : null;\n return {\n key,\n kind: typeof record.groupKind === \"string\" ? record.groupKind : null,\n position: index === null ? null : size === null ? `${index + 1}` : `${index + 1} of ${size}`,\n title: typeof record.groupTitle === \"string\" ? record.groupTitle : null,\n url: typeof record.groupUrl === \"string\" ? record.groupUrl : null,\n };\n}\n\n/** People as one readable line each: \"customer — Ana Ruiz (speaking)\". */\nfunction peopleLines(value: unknown): string[] {\n if (!Array.isArray(value)) {\n return [];\n }\n return value.flatMap((entry) => {\n const person = asRecord(entry);\n const role = typeof person.role === \"string\" ? person.role : \"\";\n if (!role) {\n return [];\n }\n const identity = typeof person.identity === \"string\" && person.identity ? ` — ${person.identity}` : \"\";\n const appearance = typeof person.appearance === \"string\" && person.appearance ? ` (${person.appearance})` : \"\";\n const speaking = person.speaking === true ? \" [speaking]\" : \"\";\n return [`${role}${identity}${appearance}${speaking}`];\n });\n}\n\ninterface ImageCompact {\n _id: string;\n name: string;\n imageUrl: string;\n description: string;\n tags: unknown[];\n width: number | null;\n height: number | null;\n subject: string | null;\n brandOwnership: string | null;\n people: string[];\n textInImage: unknown[];\n group: Record<string, unknown> | null;\n}\n\nexport function compactImage(image: Record<string, unknown>): ImageCompact {\n const analysis = asRecord(image.analysis);\n return {\n _id: String(image._id ?? \"\"),\n name: String(image.name ?? \"\"),\n imageUrl: String(image.imageUrl ?? \"\"),\n description: String(image.description ?? \"\"),\n tags: Array.isArray(image.tags) ? image.tags : [],\n // Two numbers that are not about choosing the image but about placing it.\n // A remote <BakerImage> with no width/height makes Astro DOWNLOAD the file\n // at build time just to measure it — one request per image per build, on a\n // build that is otherwise offline, and a single dropped connection there\n // fails a client's whole verify. They are cheap enough to always carry, and\n // burying them behind --full meant the agent placing the image never had\n // them.\n width: typeof image.width === \"number\" ? image.width : null,\n height: typeof image.height === \"number\" ? image.height : null,\n // The facts that decide a pick, so choosing does not need a second call:\n // whose brand it is, who is in it, and what text is burned in.\n subject: typeof analysis.subject === \"string\" ? analysis.subject : null,\n brandOwnership: typeof analysis.brandOwnership === \"string\" ? analysis.brandOwnership : null,\n people: peopleLines(analysis.people),\n textInImage: Array.isArray(analysis.textInImage) ? analysis.textInImage : [],\n group: groupSummary(image),\n };\n}\n\nexport function fullImage(image: Record<string, unknown>): Record<string, unknown> {\n const compact = compactImage(image);\n return {\n ...compact,\n aspectRatio: formatAspectRatio(image.aspectRatio as number | undefined),\n dominantColor: (image.dominantColor as string) ?? null,\n imagePalette: Array.isArray(image.imagePalette) ? image.imagePalette : [],\n source: String(image.source ?? \"\"),\n externalUrl: (image.externalUrl as string) ?? null,\n originalFilename: (image.originalFilename as string) ?? null,\n descriptionContext: (image.descriptionContext as string) ?? null,\n style: typeof asRecord(image.analysis).style === \"string\" ? asRecord(image.analysis).style : null,\n composition: typeof asRecord(image.analysis).composition === \"string\" ? asRecord(image.analysis).composition : null,\n usageNotes: typeof asRecord(image.analysis).usageNotes === \"string\" ? asRecord(image.analysis).usageNotes : null,\n createdAt: (image.createdAt as number) ?? null,\n };\n}\n\nfunction imageNormalizer(record: Record<string, unknown>, full: boolean): Record<string, unknown> {\n if (full) {\n return fullImage(record);\n }\n return compactImage(record) as unknown as Record<string, unknown>;\n}\n\ninterface VideoCompact {\n _id: string;\n name: string;\n thumbnailUrl: string;\n description: string;\n tags: unknown[];\n status: string;\n subject: string | null;\n brandOwnership: string | null;\n people: string[];\n spokenLanguages: unknown[];\n hook: string | null;\n format: string | null;\n tone: string | null;\n productClaims: unknown[] | null;\n sceneCount: number;\n group: Record<string, unknown> | null;\n}\n\n/**\n * Whether a clip can sell: a customer vouching or a creator doing a trend,\n * meant or played for laughs, and what it actually says about the offer.\n * `null` means the clip predates these fields, not that it has none.\n */\nfunction sellingFacts(analysis: Record<string, unknown>): Pick<VideoCompact, \"format\" | \"tone\" | \"productClaims\"> {\n return {\n format: typeof analysis.format === \"string\" ? analysis.format : null,\n tone: typeof analysis.tone === \"string\" ? analysis.tone : null,\n productClaims: Array.isArray(analysis.productClaims) ? analysis.productClaims : null,\n };\n}\n\nexport function compactVideo(video: Record<string, unknown>): VideoCompact {\n const analysis = asRecord(video.analysis);\n const sceneCount =\n typeof video.sceneCount === \"number\" ? video.sceneCount : Array.isArray(video.scenes) ? video.scenes.length : 0;\n return {\n _id: String(video._id ?? \"\"),\n name: String(video.name ?? \"\"),\n thumbnailUrl: String(video.thumbnailUrl ?? \"\"),\n description: String(video.description ?? \"\"),\n tags: Array.isArray(video.tags) ? video.tags : [],\n status: String(video.status ?? \"\"),\n // What decides whether this clip is usable, without a second call: who is\n // on camera and in what role, what language they speak, how it opens, and\n // how many beats there are to cut from.\n subject: typeof analysis.subject === \"string\" ? analysis.subject : null,\n brandOwnership: typeof analysis.brandOwnership === \"string\" ? analysis.brandOwnership : null,\n people: peopleLines(analysis.people),\n spokenLanguages: Array.isArray(analysis.spokenLanguages) ? analysis.spokenLanguages : [],\n hook: typeof analysis.hook === \"string\" ? analysis.hook : null,\n ...sellingFacts(analysis),\n sceneCount,\n group: groupSummary(video),\n };\n}\n\n// Scene breakdown → readable lines, e.g. \"Founder intro (0-4.5s)\". Baker\n// auto-transcribes + scene-splits every uploaded video, so `--full` is where an\n// agent reads what the client's OWN clip actually says and shows.\n/** Shot, overlay state and spoken line for one beat — the part that decides\n * whether you can cut to it or lay copy over it. */\nfunction sceneDecoration(scene: Record<string, unknown>): string {\n const shot = typeof scene.shot === \"string\" && scene.shot ? ` [${scene.shot}]` : \"\";\n // How the beat arrives — \"hard cut\", \"glitch\", \"fades to white\". Where a trim\n // or a splice can land depends on it.\n const change = typeof scene.visualChange === \"string\" && scene.visualChange ? ` <${scene.visualChange}>` : \"\";\n\n const overlayText = Array.isArray(scene.onScreenText) ? scene.onScreenText.filter(Boolean) : [];\n let overlay = \"\";\n if (scene.hasOverlay === true) {\n overlay = overlayText.length > 0 ? ` — banner: \"${overlayText.join('\" / \"')}\"` : \" — banner\";\n } else if (scene.hasOverlay === false) {\n overlay = \" — clean frame\";\n }\n\n const spoken = typeof scene.spoken === \"string\" && scene.spoken ? ` — says: \"${scene.spoken}\"` : \"\";\n return `${shot}${change}${overlay}${spoken}`;\n}\n\nfunction sceneLines(value: unknown): string[] {\n if (!Array.isArray(value)) {\n return [];\n }\n return value.flatMap((s) => {\n const scene = asRecord(s);\n const title = typeof scene.title === \"string\" ? scene.title : \"\";\n if (!title) {\n return [];\n }\n const start = round2(scene.startSecond);\n const end = round2(scene.endSecond);\n const range = start !== null && end !== null ? ` (${start}-${end}s)` : \"\";\n // A bare title + range does not say whether the beat can be reused. The\n // shot, whether a banner already covers it, and what it says do.\n return [`${title}${range}${sceneDecoration(scene)}`];\n });\n}\n\nfunction fullVideo(video: Record<string, unknown>): Record<string, unknown> {\n const compact = compactVideo(video);\n return {\n ...compact,\n duration: (video.duration as number) ?? null,\n muxPlaybackId: String(video.muxPlaybackId ?? \"\"),\n source: String(video.source ?? \"\"),\n // The spoken transcript + AI scene breakdown of the client's own clip — the\n // \"understand what's in this video\" payload. Kept out of the compact/search\n // projection (too large for a list) and surfaced here on --full.\n transcript: (video.transcript as string) ?? null,\n transcript_segment_count: Array.isArray(video.transcriptSegments) ? video.transcriptSegments.length : 0,\n scenes: sceneLines(video.scenes),\n originalFilename: (video.originalFilename as string) ?? null,\n descriptionContext: (video.descriptionContext as string) ?? null,\n shotStyle: typeof asRecord(video.analysis).shotStyle === \"string\" ? asRecord(video.analysis).shotStyle : null,\n audioStyle: typeof asRecord(video.analysis).audioStyle === \"string\" ? asRecord(video.analysis).audioStyle : null,\n onScreenText: Array.isArray(asRecord(video.analysis).onScreenText) ? asRecord(video.analysis).onScreenText : [],\n usageNotes: typeof asRecord(video.analysis).usageNotes === \"string\" ? asRecord(video.analysis).usageNotes : null,\n createdAt: (video.createdAt as number) ?? null,\n };\n}\n\nexport function videoNormalizer(record: Record<string, unknown>, full: boolean): Record<string, unknown> {\n if (full) {\n return fullVideo(record);\n }\n return compactVideo(record) as unknown as Record<string, unknown>;\n}\n\ninterface TestimonialCompact {\n _id: string;\n reviewerName: string;\n reviewText: string;\n rating: number | null;\n sentiment: string | null;\n tags: unknown[];\n sourceType: string;\n}\n\nfunction truncate(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n return `${text.slice(0, maxLength)}...`;\n}\n\nfunction compactTestimonial(record: Record<string, unknown>): TestimonialCompact {\n return {\n _id: String(record._id ?? \"\"),\n reviewerName: String(record.reviewerName ?? \"\"),\n reviewText: truncate(String(record.reviewText ?? \"\"), 200),\n rating: (record.rating as number) ?? null,\n sentiment: (record.sentiment as string) ?? null,\n tags: Array.isArray(record.tags) ? record.tags : [],\n sourceType: String(record.sourceType ?? \"\"),\n };\n}\n\nfunction fullTestimonial(record: Record<string, unknown>): Record<string, unknown> {\n const compact = compactTestimonial(record);\n return {\n ...compact,\n reviewText: String(record.reviewText ?? \"\"),\n highlight: (record.highlight as string) ?? null,\n summary: (record.summary as string) ?? null,\n language: (record.language as string) ?? null,\n reviewerImageUrl: (record.reviewerImageUrl as string) ?? null,\n sourceUrl: (record.sourceUrl as string) ?? null,\n reviewDate: (record.reviewDate as number) ?? null,\n createdAt: (record.createdAt as number) ?? null,\n status: String(record.status ?? \"\"),\n };\n}\n\nexport function testimonialNormalizer(record: Record<string, unknown>, full: boolean): Record<string, unknown> {\n if (full) {\n return fullTestimonial(record);\n }\n return compactTestimonial(record) as unknown as Record<string, unknown>;\n}\n\n// The minimal set an agent needs to DECIDE between reference ads. `summary` is\n// the one-line \"what it's about\"; `media_url` is the creative (video for video,\n// image for static — there is no separate poster). `advertiser_id` is kept so\n// the agent can exclude that advertiser on a re-search. The proof signals\n// (`winner_category`, `days_active`, `active`) ride along because they are what\n// the agent picks on; the rest (DNA detail, reach) is opt-in via --full to keep\n// the agent's context lean.\ninterface WinningAdCompact {\n advertiser: string;\n platform: string;\n format: string;\n relevance: number | null;\n winner_score: number | null;\n winner_category: string | null;\n days_active: number | null;\n active: boolean | null;\n summary: string;\n advertiser_id: string;\n ad_id: string;\n media_url: string;\n // What the viewer sees and the static's container, when the library knows\n // them: enough to pick a reference on its move without opening the image.\n visual?: string;\n container?: string;\n // Why it works, in one line: what a reference is picked on.\n mechanism?: string;\n // Who the ad was made for, when the library knows: a reference transfers\n // when its audience matches the one we are making ours for.\n audience_temperature?: string;\n funnel_stage?: string;\n advertiser_size?: string;\n // Whether the copy only works on viewers who already know the brand, with\n // what they'd need to know when it does. Absent when not judged yet.\n presumes_brand_knowledge?: boolean;\n brand_knowledge_reason?: string;\n // How many Baker companies adapted this ad, and whether this one did: only\n // when any did (a second creative from one ad is a variant, not a concept).\n // `approved` / `rejected` count how those creatives were judged, when any was.\n baker_uses?: number;\n already_adapted?: true;\n approved?: number;\n rejected?: number;\n // Only on a scoped search: whether the brand is one the company follows.\n source?: \"followed\" | \"library\";\n}\n\nfunction round2(value: unknown): number | null {\n if (typeof value !== \"number\" || Number.isNaN(value)) {\n return null;\n }\n return Math.round(value * 100) / 100;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === \"object\" ? (value as Record<string, unknown>) : {};\n}\n\n// Records here are the lean shape the backend proxy already projected\n// (flat fields + a nested `dna` object) — see /api/winning-ads/search.\nfunction bakerUses(value: unknown): Pick<WinningAdCompact, \"baker_uses\" | \"already_adapted\" | \"approved\" | \"rejected\"> {\n const uses = asRecord(value);\n const companies = typeof uses.companies === \"number\" ? uses.companies : 0;\n if (companies === 0) return {};\n const count = (n: unknown) => (typeof n === \"number\" && n > 0 ? n : undefined);\n const approved = count(uses.approved);\n const rejected = count(uses.rejected);\n return {\n baker_uses: companies,\n ...(uses.ours === true ? { already_adapted: true as const } : {}),\n ...(approved !== undefined ? { approved } : {}),\n ...(rejected !== undefined ? { rejected } : {}),\n };\n}\n\n// What the ad shows and why it works, only when the library knows it.\nfunction dnaMove(dna: Record<string, unknown>): Pick<WinningAdCompact, \"visual\" | \"container\" | \"mechanism\"> {\n const text = (value: unknown) => (typeof value === \"string\" && value ? value : undefined);\n const visual = text(dna.visual);\n const container = text(dna.container);\n const mechanism = text(dna.mechanism);\n return {\n ...(visual ? { visual } : {}),\n ...(container ? { container } : {}),\n ...(mechanism ? { mechanism } : {}),\n };\n}\n\ntype AudienceSignals = Pick<\n WinningAdCompact,\n \"audience_temperature\" | \"funnel_stage\" | \"advertiser_size\" | \"presumes_brand_knowledge\" | \"brand_knowledge_reason\"\n>;\n\n// Who the ad was made for, only the signals the library judged. The reason is\n// kept only when the ad leans on its brand: that is when it changes the pick.\nfunction dnaAudience(dna: Record<string, unknown>): AudienceSignals {\n const text = (value: unknown) => (typeof value === \"string\" && value ? value : undefined);\n const audienceTemperature = text(dna.audience_temperature);\n const funnelStage = text(dna.funnel_stage);\n const advertiserSize = text(dna.advertiser_size);\n const presumes = typeof dna.presumes_brand_knowledge === \"boolean\" ? dna.presumes_brand_knowledge : undefined;\n const reason = presumes ? text(dna.brand_knowledge_reason) : undefined;\n return {\n ...(audienceTemperature ? { audience_temperature: audienceTemperature } : {}),\n ...(funnelStage ? { funnel_stage: funnelStage } : {}),\n ...(advertiserSize ? { advertiser_size: advertiserSize } : {}),\n ...(presumes !== undefined ? { presumes_brand_knowledge: presumes } : {}),\n ...(reason ? { brand_knowledge_reason: reason } : {}),\n };\n}\n\nexport function compactWinningAd(record: Record<string, unknown>): WinningAdCompact {\n const dna = asRecord(record.dna);\n return {\n advertiser: String(record.advertiser ?? \"\"),\n platform: String(record.platform ?? \"\"),\n format: String(record.format ?? \"\"),\n relevance: round2(record.relevance),\n winner_score: round2(record.winner_score),\n winner_category: typeof record.winner_category === \"string\" ? record.winner_category : null,\n days_active: typeof record.days_active === \"number\" ? record.days_active : null,\n active: typeof record.active === \"boolean\" ? record.active : null,\n summary: String(dna.creative_concept ?? \"\"),\n advertiser_id: String(record.advertiser_id ?? \"\"),\n ad_id: String(record.ad_id ?? \"\"),\n media_url: String(record.media_url ?? \"\"),\n ...dnaMove(dna),\n ...dnaAudience(dna),\n ...bakerUses(record.baker_uses),\n ...(record.source === \"followed\" || record.source === \"library\" ? { source: record.source } : {}),\n };\n}\n\nexport function fullWinningAd(record: Record<string, unknown>): Record<string, unknown> {\n const compact = compactWinningAd(record);\n const dna = asRecord(record.dna);\n return {\n ...compact,\n media_kind: (record.media_kind as string) ?? null,\n reach: (record.reach as number) ?? null,\n angle: (dna.angle as string) ?? null,\n awareness_stage: (dna.awareness_stage as string) ?? null,\n target_persona: (dna.target_persona as string) ?? null,\n hook_archetype: (dna.hook_archetype as string) ?? null,\n mechanism: (dna.mechanism as string) ?? null,\n industry: (dna.industry as string) ?? null,\n };\n}\n\nexport function winningAdNormalizer(record: Record<string, unknown>, full: boolean): Record<string, unknown> {\n if (full) {\n return fullWinningAd(record);\n }\n return compactWinningAd(record) as unknown as Record<string, unknown>;\n}\n\n// --- Ad content (\"read what's inside one creative\") -------------------------\n\n// `detailed` (--full) appends the overlay's role + on-screen position, e.g.\n// `\"GLOBAL PAYROLL @0.5s [hook, top]\"` — the wire schema and backend mapper\n// carry these per overlay precisely so the agent can place text faithfully\n// when reproducing the creative; compact stays lean (just text + timestamp).\n/**\n * Text drawn into a static image or carousel card has no timing; what matters\n * to reproduce it is the job it does and the card it sits on, so those lead\n * the line (\"card 2 · headline: …\").\n */\nfunction inImageLine(item: Record<string, unknown>, text: string, detailed: boolean): string {\n const card = typeof item.card === \"number\" ? `card ${item.card} · ` : \"\";\n const role = typeof item.role === \"string\" && item.role.length > 0 ? `${item.role}: ` : \"\";\n const position =\n detailed && typeof item.position === \"string\" && item.position.length > 0 ? ` [${item.position}]` : \"\";\n return `${card}${role}${text}${position}`;\n}\n\nfunction overlayLines(value: unknown, detailed = false, inImage = false): string[] {\n if (!Array.isArray(value)) {\n return [];\n }\n return value.flatMap((o) => {\n const item = asRecord(o);\n const text = typeof item.text === \"string\" ? item.text : \"\";\n if (!text) {\n return [];\n }\n if (inImage) {\n return [inImageLine(item, text, detailed)];\n }\n const at = round2(item.appears_at_s);\n let line = at !== null ? `${text} @${at}s` : text;\n if (detailed) {\n const tags = [item.role, item.position].filter((t): t is string => typeof t === \"string\" && t.length > 0);\n if (tags.length) {\n line += ` [${tags.join(\", \")}]`;\n }\n }\n return [line];\n });\n}\n\n// The minimal set to UNDERSTAND a creative: what's said (transcript), what's\n// shown (on_screen_text), and the copy around it. Speech/pacing/soundtrack\n// detail is opt-in via --full so the agent's context stays lean.\nexport function compactAdContent(record: Record<string, unknown>): Record<string, unknown> {\n const inImage = record.format === \"static\" || record.format === \"carousel\";\n return {\n ad_id: (record.ad_id as string) ?? null,\n advertiser: (record.advertiser as string) ?? null,\n format: (record.format as string) ?? null,\n media_kind: (record.media_kind as string) ?? null,\n media_url: (record.media_url as string) ?? null,\n summary: (record.summary as string) ?? null,\n transcript: (record.transcript as string) ?? null,\n on_screen_text: overlayLines(record.on_screen_text, false, inImage),\n // The container a static reference is built on (comparison_table, before_after_split…).\n ...(inImage ? { format_pattern: (asRecord(record.layout).format_pattern as string) ?? null } : {}),\n has_captions: typeof record.has_captions === \"boolean\" ? record.has_captions : null,\n primary_text: (record.primary_text as string) ?? null,\n headline: (record.headline as string) ?? null,\n cta: (record.cta as string) ?? null,\n };\n}\n\n/** Drop null and empty-list fields: in --full a present field is always a finding. */\nfunction filledOnly(value: unknown): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(asRecord(value)).filter(\n ([, v]) => v !== null && v !== undefined && !(Array.isArray(v) && v.length === 0),\n ),\n );\n}\n\nexport function fullAdContent(record: Record<string, unknown>): Record<string, unknown> {\n const speech = asRecord(record.speech);\n const pacing = asRecord(record.pacing);\n const music = asRecord(record.music);\n return {\n ...compactAdContent(record),\n platform: (record.platform as string) ?? null,\n // Re-derive with role/position tags — the lean compact version dropped them.\n on_screen_text: overlayLines(\n record.on_screen_text,\n true,\n record.format === \"static\" || record.format === \"carousel\",\n ),\n winner_score: round2(record.winner_score),\n winner_category: (record.winner_category as string) ?? null,\n angle: (record.angle as string) ?? null,\n hook_archetype: (record.hook_archetype as string) ?? null,\n caption_style: (record.caption_style as string) ?? null,\n has_speech: typeof record.has_speech === \"boolean\" ? record.has_speech : null,\n speech_pct_of_runtime: round2(speech.speech_pct_of_runtime),\n speaker_count: (speech.speaker_count as number) ?? null,\n voiceover_vs_sync: (speech.voiceover_vs_sync as string) ?? null,\n is_direct_address: typeof speech.is_direct_address === \"boolean\" ? speech.is_direct_address : null,\n speech_emotion_dominant: (speech.speech_emotion_dominant as string) ?? null,\n scene_count: (pacing.scene_count as number) ?? null,\n avg_shot_length_s: round2(pacing.avg_shot_length_s),\n pacing_shape: (pacing.pacing_shape as string) ?? null,\n hook_ends_at_s: round2(pacing.hook_ends_at_s),\n cta_first_shown_at_s: round2(pacing.cta_first_shown_at_s),\n product_first_appears_at_s: round2(pacing.product_first_appears_at_s),\n music_track_title: (music.track_title as string) ?? null,\n music_track_artist: (music.track_artist as string) ?? null,\n // How it is built, how it persuades, and what it offers, flattened like\n // speech/pacing above so --output md renders them, and only when present.\n ...filledOnly(record.layout),\n ...filledOnly(record.message),\n ...filledOnly(record.offer),\n };\n}\n\nexport function adContentNormalizer(record: Record<string, unknown>, full: boolean): Record<string, unknown> {\n return full ? fullAdContent(record) : compactAdContent(record);\n}\n\nexport function applyFieldMask<T extends Record<string, unknown>>(data: T, fields: string[]): Partial<T> {\n const result: Partial<T> = {};\n for (const field of fields) {\n if (field in data) {\n (result as Record<string, unknown>)[field] = data[field];\n }\n }\n return result;\n}\n\nfunction write(text: string): void {\n process.stdout.write(text);\n}\n\nexport function writeJson<T>(envelope: Envelope<T>): void {\n write(`${JSON.stringify(envelope, null, 2)}\\n`);\n}\n\nfunction writeFiles(data: Record<string, unknown>[], fields: string[]): void {\n // Header line\n write(`${fields.join(\"\\t\")}\\n`);\n for (const item of data) {\n const values = fields.map((f) => String(item[f] ?? \"\"));\n write(`${values.join(\"\\t\")}\\n`);\n }\n}\n\nfunction writeMd(data: Record<string, unknown>[], fields: string[]): void {\n // Header\n write(`| ${fields.join(\" | \")} |\\n`);\n // Separator\n write(`| ${fields.map(() => \"---\").join(\" | \")} |\\n`);\n // Rows\n for (const item of data) {\n const values = fields.map((f) => String(item[f] ?? \"\"));\n write(`| ${values.join(\" | \")} |\\n`);\n }\n}\n\nfunction normalizeData<T>(\n envelope: SuccessEnvelope<T>,\n fields: string[] | undefined,\n full: boolean,\n normalizer: Normalizer,\n): Record<string, unknown>[] {\n const raw = Array.isArray(envelope.data) ? envelope.data : [envelope.data];\n const items: Record<string, unknown>[] = raw.map((item) => {\n const record = item as Record<string, unknown>;\n return normalizer(record, full);\n });\n\n if (fields) {\n return items.map((item) => applyFieldMask(item, fields));\n }\n return items;\n}\n\nexport function writeOutput<T>(\n envelope: Envelope<T>,\n format: OutputFormat,\n fields?: string[],\n full?: boolean,\n normalizer?: Normalizer,\n): void {\n // Errors and dry runs always output JSON\n if (!envelope.ok || \"dryRun\" in envelope) {\n writeJson(envelope);\n return;\n }\n\n if (format === \"json\") {\n write(`${JSON.stringify(fields ? maskEnvelope(envelope, fields) : envelope, null, 2)}\\n`);\n return;\n }\n\n // A format we do not implement falls back to the envelope rather than off the\n // end of this function. Three `if`s and no `else` meant `--output csv` — which\n // the top-level help advertised and nothing implemented — wrote nothing at\n // all and exited 0, and an empty stdout is indistinguishable from a success\n // with no rows. The note goes to stderr so the JSON on stdout stays parseable.\n if (format !== \"files\" && format !== \"md\") {\n process.stderr.write(`Unknown --output \"${String(format)}\". Valid formats: json, files, md. Using json.\\n`);\n writeJson(envelope);\n return;\n }\n\n const items = normalizeData(envelope, fields, full ?? false, normalizer ?? imageNormalizer);\n const displayFields = fields ?? Object.keys(items[0] ?? {});\n\n if (format === \"files\") {\n writeFiles(items, displayFields);\n } else {\n writeMd(items, displayFields);\n }\n writeHints(envelope.hints);\n}\n\n/**\n * Hints, for the formats whose stdout has no room for them.\n *\n * `files` and `md` project the rows and nothing else, so a hint carried in the\n * envelope disappeared in exactly the case it earns its keep: an empty result,\n * where stdout is a bare header line and the hint is the only thing that says\n * what to do next. stderr keeps the rows parseable while still reaching the\n * agent. (The research family solved this already — `research/output.ts`.)\n */\nfunction writeHints(hints: string[] | undefined): void {\n for (const hint of hints ?? []) {\n process.stderr.write(`${hint}\\n`);\n }\n}\n"],"mappings":"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","names":["FLAG","write"]}
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  debugLogHttp,
3
3
  readBodyForLog
4
- } from "./chunk-GCTR4MDE.js";
4
+ } from "./chunk-LDS7LUKK.js";
5
5
  import {
6
6
  getEnv
7
- } from "./chunk-FQOKJYKN.js";
7
+ } from "./chunk-SPPJRGQF.js";
8
8
 
9
9
  // src/client.ts
10
10
  var MAX_RATE_LIMIT_RETRIES = 3;
@@ -63,8 +63,11 @@ function sanitizeParams(params) {
63
63
  return sanitized;
64
64
  }
65
65
  function chatHeader() {
66
- const chatId = getEnv().BAKER_CHAT_ID;
67
- return chatId ? { "x-baker-chat-id": chatId } : {};
66
+ const { BAKER_CHAT_ID: chatId, BAKER_THREAD_ID: threadId } = getEnv();
67
+ return {
68
+ ...chatId ? { "x-baker-chat-id": chatId } : {},
69
+ ...threadId ? { "x-baker-thread-id": threadId } : {}
70
+ };
68
71
  }
69
72
  function mapHttpError(status) {
70
73
  if (status === 401) {
@@ -208,4 +211,4 @@ export {
208
211
  apiGet,
209
212
  apiPost
210
213
  };
211
- //# sourceMappingURL=chunk-Q6F5UNOU.js.map
214
+ //# sourceMappingURL=chunk-X46KRGZU.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { debugLogHttp, readBodyForLog } from \"./debugLog.ts\";\nimport { getEnv } from \"./env.ts\";\n\nconst MAX_RATE_LIMIT_RETRIES = 3;\nconst MAX_TOTAL_WAIT_MS = 2 * 60 * 1000;\n\nasync function fetchWithRateLimitRetry(url: string, init: RequestInit): Promise<Response> {\n let totalWaited = 0;\n\n for (let attempt = 0; attempt <= MAX_RATE_LIMIT_RETRIES; attempt++) {\n const response = await fetch(url, init);\n\n if (response.status !== 429 || attempt >= MAX_RATE_LIMIT_RETRIES) {\n return response;\n }\n\n const retryAfterHeader = response.headers.get(\"Retry-After\");\n const waitMs = retryAfterHeader ? Number(retryAfterHeader) * 1000 : 2000 * 2 ** attempt;\n\n if (totalWaited + waitMs > MAX_TOTAL_WAIT_MS) {\n return response;\n }\n\n totalWaited += waitMs;\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n }\n\n return fetch(url, init);\n}\n\nexport type ErrorCode =\n | \"UNAUTHORIZED\"\n | \"FORBIDDEN\"\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"VALIDATION_ERROR\"\n | \"RATE_LIMITED\"\n | \"UNAVAILABLE\"\n | \"INTERNAL_ERROR\"\n | \"NETWORK_ERROR\"\n | \"TIMEOUT\"\n | \"IMAGE_PROCESSING_ERROR\";\n\nexport class ApiError extends Error {\n code: ErrorCode;\n\n constructor(code: ErrorCode, message: string) {\n super(message);\n this.name = \"ApiError\";\n this.code = code;\n }\n}\n\nconst CONVEX_ID_RE = /^[a-zA-Z0-9_]+$/;\n\nfunction hasControlCharacters(value: string): boolean {\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n // Allow tab (9), newline (10), carriage return (13)\n if (code < 32 && code !== 9 && code !== 10 && code !== 13) {\n return true;\n }\n }\n return false;\n}\n\nfunction validateStringValue(value: string): void {\n if (hasControlCharacters(value)) {\n throw new ApiError(\"VALIDATION_ERROR\", \"String value contains invalid control characters\");\n }\n}\n\nexport function validateConvexId(id: string): void {\n if (!CONVEX_ID_RE.test(id)) {\n throw new ApiError(\"VALIDATION_ERROR\", `Invalid ID format: \"${id}\". Expected alphanumeric string.`);\n }\n}\n\nfunction sanitizeParams(params: Record<string, string>): Record<string, string> {\n const sanitized: Record<string, string> = {};\n for (const [key, value] of Object.entries(params)) {\n validateStringValue(value);\n sanitized[key] = value;\n }\n return sanitized;\n}\n\n/**\n * Sent on every call so the backend can attribute immediately-applied writes\n * (library images and videos, followed advertisers) to the Session they were\n * made from. Absent outside a chat-attached environment.\n */\nfunction chatHeader(): Record<string, string> {\n const chatId = getEnv().BAKER_CHAT_ID;\n return chatId ? { \"x-baker-chat-id\": chatId } : {};\n}\n\nfunction mapHttpError(status: number): ErrorCode {\n if (status === 401) {\n return \"UNAUTHORIZED\";\n }\n // 403 is never \"not connected\" — every backend `FORBIDDEN` means connected but\n // not allowed (an asset outside the granted scope, a capability the account\n // lacks). Folding it into UNAUTHORIZED sent agents down the reconnect path,\n // which cannot fix a permission gap and wastes the user's time.\n if (status === 403) {\n return \"FORBIDDEN\";\n }\n if (status === 404) {\n return \"NOT_FOUND\";\n }\n if (status === 422 || status === 400) {\n return \"VALIDATION_ERROR\";\n }\n // 409 is a state the caller has to change, never a fault to sit out. It is\n // what the backend returns for \"this workspace has not switched analytics on\n // yet\" and its siblings — folded into INTERNAL_ERROR, that reaches an agent\n // as a transient failure worth retrying, which it never is.\n if (status === 409) {\n return \"CONFLICT\";\n }\n if (status === 429) {\n return \"RATE_LIMITED\";\n }\n // 503 is the backend's `UNAVAILABLE`: a system it depends on did not answer\n // just now, and asking again is the fix. Folded into INTERNAL_ERROR, it reads\n // exactly like a fault of ours, and the agent cannot tell which one waits out.\n if (status === 503) {\n return \"UNAVAILABLE\";\n }\n return \"INTERNAL_ERROR\";\n}\n\nasync function handleResponse<T>(response: Response): Promise<T> {\n const body = await response.text();\n\n if (!response.ok) {\n let message = `HTTP ${response.status}: ${response.statusText}`;\n try {\n const parsed = JSON.parse(body) as { error?: string | { message?: string }; message?: string };\n if (typeof parsed.error === \"string\") {\n message = parsed.error;\n } else if (parsed.error?.message) {\n message = parsed.error.message;\n } else if (parsed.message) {\n message = parsed.message;\n }\n } catch {\n // Use default message\n }\n throw new ApiError(mapHttpError(response.status), message);\n }\n\n try {\n return JSON.parse(body) as T;\n } catch {\n throw new ApiError(\"INTERNAL_ERROR\", \"Failed to parse API response as JSON\");\n }\n}\n\n/**\n * A GET that also hands back the response headers.\n *\n * Some endpoints have to say something *about* the answer that the answer has\n * no room for — `GET /api/testimonials` returns a bare array and sets\n * `X-Baker-Next-Cursor` when rows past this page remain unexamined, so the rows\n * alone cannot tell one page of a library from the whole of it.\n */\nexport async function apiGetWithHeaders<T>(\n path: string,\n params?: Record<string, string>,\n): Promise<{ data: T; headers: Headers }> {\n const env = getEnv();\n const url = new URL(path, env.BAKER_API_URL);\n if (params) {\n const clean = sanitizeParams(params);\n for (const [key, value] of Object.entries(clean)) {\n url.searchParams.set(key, value);\n }\n }\n\n const urlStr = url.toString();\n const startedAt = Date.now();\n let response: Response;\n try {\n response = await fetchWithRateLimitRetry(urlStr, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${env.BAKER_API_KEY}`,\n Accept: \"application/json\",\n ...chatHeader(),\n },\n signal: AbortSignal.timeout(60_000),\n });\n } catch (err) {\n debugLogHttp({ source: \"cli\", method: \"GET\", url: urlStr, durationMs: Date.now() - startedAt, error: err });\n if (err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\")) {\n throw new ApiError(\"TIMEOUT\", \"Request timed out after 60 seconds\");\n }\n throw new ApiError(\"NETWORK_ERROR\", `Request failed: ${err instanceof Error ? err.message : \"Unknown error\"}`);\n }\n\n debugLogHttp({\n source: \"cli\",\n method: \"GET\",\n url: urlStr,\n status: response.status,\n ok: response.ok,\n responseBody: await readBodyForLog(response),\n durationMs: Date.now() - startedAt,\n });\n return { data: await handleResponse<T>(response), headers: response.headers };\n}\n\nexport async function apiGet<T>(path: string, params?: Record<string, string>): Promise<T> {\n return (await apiGetWithHeaders<T>(path, params)).data;\n}\n\nexport async function apiPost<T>(path: string, body: unknown, opts?: { timeoutMs?: number }): Promise<T> {\n const env = getEnv();\n const timeoutMs = opts?.timeoutMs ?? 60_000;\n const urlStr = new URL(path, env.BAKER_API_URL).toString();\n const startedAt = Date.now();\n let response: Response;\n try {\n response = await fetchWithRateLimitRetry(urlStr, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${env.BAKER_API_KEY}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...chatHeader(),\n },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (err) {\n debugLogHttp({\n source: \"cli\",\n method: \"POST\",\n url: urlStr,\n requestBody: body,\n durationMs: Date.now() - startedAt,\n error: err,\n });\n if (err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\")) {\n throw new ApiError(\"TIMEOUT\", `Request timed out after ${Math.round(timeoutMs / 1000)} seconds`);\n }\n throw new ApiError(\"NETWORK_ERROR\", `Request failed: ${err instanceof Error ? err.message : \"Unknown error\"}`);\n }\n\n debugLogHttp({\n source: \"cli\",\n method: \"POST\",\n url: urlStr,\n requestBody: body,\n status: response.status,\n ok: response.ok,\n responseBody: await readBodyForLog(response),\n durationMs: Date.now() - startedAt,\n });\n return handleResponse<T>(response);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { debugLogHttp, readBodyForLog } from \"./debugLog.ts\";\nimport { getEnv } from \"./env.ts\";\n\nconst MAX_RATE_LIMIT_RETRIES = 3;\nconst MAX_TOTAL_WAIT_MS = 2 * 60 * 1000;\n\nasync function fetchWithRateLimitRetry(url: string, init: RequestInit): Promise<Response> {\n let totalWaited = 0;\n\n for (let attempt = 0; attempt <= MAX_RATE_LIMIT_RETRIES; attempt++) {\n const response = await fetch(url, init);\n\n if (response.status !== 429 || attempt >= MAX_RATE_LIMIT_RETRIES) {\n return response;\n }\n\n const retryAfterHeader = response.headers.get(\"Retry-After\");\n const waitMs = retryAfterHeader ? Number(retryAfterHeader) * 1000 : 2000 * 2 ** attempt;\n\n if (totalWaited + waitMs > MAX_TOTAL_WAIT_MS) {\n return response;\n }\n\n totalWaited += waitMs;\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n }\n\n return fetch(url, init);\n}\n\nexport type ErrorCode =\n | \"UNAUTHORIZED\"\n | \"FORBIDDEN\"\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"VALIDATION_ERROR\"\n | \"RATE_LIMITED\"\n | \"UNAVAILABLE\"\n | \"INTERNAL_ERROR\"\n | \"NETWORK_ERROR\"\n | \"TIMEOUT\"\n | \"IMAGE_PROCESSING_ERROR\";\n\nexport class ApiError extends Error {\n code: ErrorCode;\n\n constructor(code: ErrorCode, message: string) {\n super(message);\n this.name = \"ApiError\";\n this.code = code;\n }\n}\n\nconst CONVEX_ID_RE = /^[a-zA-Z0-9_]+$/;\n\nfunction hasControlCharacters(value: string): boolean {\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n // Allow tab (9), newline (10), carriage return (13)\n if (code < 32 && code !== 9 && code !== 10 && code !== 13) {\n return true;\n }\n }\n return false;\n}\n\nfunction validateStringValue(value: string): void {\n if (hasControlCharacters(value)) {\n throw new ApiError(\"VALIDATION_ERROR\", \"String value contains invalid control characters\");\n }\n}\n\nexport function validateConvexId(id: string): void {\n if (!CONVEX_ID_RE.test(id)) {\n throw new ApiError(\"VALIDATION_ERROR\", `Invalid ID format: \"${id}\". Expected alphanumeric string.`);\n }\n}\n\nfunction sanitizeParams(params: Record<string, string>): Record<string, string> {\n const sanitized: Record<string, string> = {};\n for (const [key, value] of Object.entries(params)) {\n validateStringValue(value);\n sanitized[key] = value;\n }\n return sanitized;\n}\n\n/**\n * Sent on every call so the backend can attribute immediately-applied writes\n * (library images and videos, followed advertisers) to the Session they were\n * made from, and vouch for the acting user against the sender of the turn's own\n * thread. Absent outside a chat-attached environment.\n */\nfunction chatHeader(): Record<string, string> {\n const { BAKER_CHAT_ID: chatId, BAKER_THREAD_ID: threadId } = getEnv();\n return {\n ...(chatId ? { \"x-baker-chat-id\": chatId } : {}),\n ...(threadId ? { \"x-baker-thread-id\": threadId } : {}),\n };\n}\n\nfunction mapHttpError(status: number): ErrorCode {\n if (status === 401) {\n return \"UNAUTHORIZED\";\n }\n // 403 is never \"not connected\" — every backend `FORBIDDEN` means connected but\n // not allowed (an asset outside the granted scope, a capability the account\n // lacks). Folding it into UNAUTHORIZED sent agents down the reconnect path,\n // which cannot fix a permission gap and wastes the user's time.\n if (status === 403) {\n return \"FORBIDDEN\";\n }\n if (status === 404) {\n return \"NOT_FOUND\";\n }\n if (status === 422 || status === 400) {\n return \"VALIDATION_ERROR\";\n }\n // 409 is a state the caller has to change, never a fault to sit out. It is\n // what the backend returns for \"this workspace has not switched analytics on\n // yet\" and its siblings — folded into INTERNAL_ERROR, that reaches an agent\n // as a transient failure worth retrying, which it never is.\n if (status === 409) {\n return \"CONFLICT\";\n }\n if (status === 429) {\n return \"RATE_LIMITED\";\n }\n // 503 is the backend's `UNAVAILABLE`: a system it depends on did not answer\n // just now, and asking again is the fix. Folded into INTERNAL_ERROR, it reads\n // exactly like a fault of ours, and the agent cannot tell which one waits out.\n if (status === 503) {\n return \"UNAVAILABLE\";\n }\n return \"INTERNAL_ERROR\";\n}\n\nasync function handleResponse<T>(response: Response): Promise<T> {\n const body = await response.text();\n\n if (!response.ok) {\n let message = `HTTP ${response.status}: ${response.statusText}`;\n try {\n const parsed = JSON.parse(body) as { error?: string | { message?: string }; message?: string };\n if (typeof parsed.error === \"string\") {\n message = parsed.error;\n } else if (parsed.error?.message) {\n message = parsed.error.message;\n } else if (parsed.message) {\n message = parsed.message;\n }\n } catch {\n // Use default message\n }\n throw new ApiError(mapHttpError(response.status), message);\n }\n\n try {\n return JSON.parse(body) as T;\n } catch {\n throw new ApiError(\"INTERNAL_ERROR\", \"Failed to parse API response as JSON\");\n }\n}\n\n/**\n * A GET that also hands back the response headers.\n *\n * Some endpoints have to say something *about* the answer that the answer has\n * no room for — `GET /api/testimonials` returns a bare array and sets\n * `X-Baker-Next-Cursor` when rows past this page remain unexamined, so the rows\n * alone cannot tell one page of a library from the whole of it.\n */\nexport async function apiGetWithHeaders<T>(\n path: string,\n params?: Record<string, string>,\n): Promise<{ data: T; headers: Headers }> {\n const env = getEnv();\n const url = new URL(path, env.BAKER_API_URL);\n if (params) {\n const clean = sanitizeParams(params);\n for (const [key, value] of Object.entries(clean)) {\n url.searchParams.set(key, value);\n }\n }\n\n const urlStr = url.toString();\n const startedAt = Date.now();\n let response: Response;\n try {\n response = await fetchWithRateLimitRetry(urlStr, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${env.BAKER_API_KEY}`,\n Accept: \"application/json\",\n ...chatHeader(),\n },\n signal: AbortSignal.timeout(60_000),\n });\n } catch (err) {\n debugLogHttp({ source: \"cli\", method: \"GET\", url: urlStr, durationMs: Date.now() - startedAt, error: err });\n if (err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\")) {\n throw new ApiError(\"TIMEOUT\", \"Request timed out after 60 seconds\");\n }\n throw new ApiError(\"NETWORK_ERROR\", `Request failed: ${err instanceof Error ? err.message : \"Unknown error\"}`);\n }\n\n debugLogHttp({\n source: \"cli\",\n method: \"GET\",\n url: urlStr,\n status: response.status,\n ok: response.ok,\n responseBody: await readBodyForLog(response),\n durationMs: Date.now() - startedAt,\n });\n return { data: await handleResponse<T>(response), headers: response.headers };\n}\n\nexport async function apiGet<T>(path: string, params?: Record<string, string>): Promise<T> {\n return (await apiGetWithHeaders<T>(path, params)).data;\n}\n\nexport async function apiPost<T>(path: string, body: unknown, opts?: { timeoutMs?: number }): Promise<T> {\n const env = getEnv();\n const timeoutMs = opts?.timeoutMs ?? 60_000;\n const urlStr = new URL(path, env.BAKER_API_URL).toString();\n const startedAt = Date.now();\n let response: Response;\n try {\n response = await fetchWithRateLimitRetry(urlStr, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${env.BAKER_API_KEY}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...chatHeader(),\n },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (err) {\n debugLogHttp({\n source: \"cli\",\n method: \"POST\",\n url: urlStr,\n requestBody: body,\n durationMs: Date.now() - startedAt,\n error: err,\n });\n if (err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\")) {\n throw new ApiError(\"TIMEOUT\", `Request timed out after ${Math.round(timeoutMs / 1000)} seconds`);\n }\n throw new ApiError(\"NETWORK_ERROR\", `Request failed: ${err instanceof Error ? err.message : \"Unknown error\"}`);\n }\n\n debugLogHttp({\n source: \"cli\",\n method: \"POST\",\n url: urlStr,\n requestBody: body,\n status: response.status,\n ok: response.ok,\n responseBody: await readBodyForLog(response),\n durationMs: Date.now() - startedAt,\n });\n return handleResponse<T>(response);\n}\n"],"mappings":"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","names":[]}
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  ApiError,
3
3
  apiGet
4
- } from "./chunk-Q6F5UNOU.js";
4
+ } from "./chunk-X46KRGZU.js";
5
5
  import {
6
6
  getEnv
7
- } from "./chunk-FQOKJYKN.js";
7
+ } from "./chunk-SPPJRGQF.js";
8
8
 
9
9
  // src/error-handler.ts
10
10
  var REQUEST_CONNECTION_PLATFORM = {
@@ -153,6 +153,12 @@ function writeJsonEnvelope(envelope) {
153
153
  process.stdout.write(`${JSON.stringify(envelope, null, 2)}
154
154
  `);
155
155
  }
156
+ var TABLE_OUTPUT_FORMATS = ["json", "csv", "jsonl", "md"];
157
+ function outputFormatRefusal(format, supported) {
158
+ if (supported.includes(format)) return null;
159
+ const list = supported.length > 1 ? `${supported.slice(0, -1).join(", ")} or ${supported.at(-1)}` : supported.join("");
160
+ return `--output ${format} is not supported \u2014 use ${list}`;
161
+ }
156
162
  function toCsvRow(values) {
157
163
  return values.map((v) => {
158
164
  if (v.includes(",") || v.includes('"') || v.includes("\n")) {
@@ -294,8 +300,10 @@ export {
294
300
  getManagerIdForCustomer,
295
301
  writeAdsJson,
296
302
  writeJsonEnvelope,
303
+ TABLE_OUTPUT_FORMATS,
304
+ outputFormatRefusal,
297
305
  toCsvRow,
298
306
  writeAdsOutput,
299
307
  resolveCustomerId
300
308
  };
301
- //# sourceMappingURL=chunk-RFZYRG2Q.js.map
309
+ //# sourceMappingURL=chunk-YBJ7KJNA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/error-handler.ts","../src/commands/ads/cache.ts","../src/commands/ads/output.ts"],"sourcesContent":["import { writeJsonEnvelope } from \"./commands/ads/output.ts\";\n\ntype ConnectionPlatform = \"google_ads\" | \"ga4\" | \"gsc\" | \"x_ads\" | \"meta_ads\" | \"linkedin_ads\";\n\n/**\n * Platforms the agent can get connected without the user leaving the chat, via\n * the blocking `request_connection` tool, and the id that tool expects. Every\n * platform this CLI can talk to is here — keep it in step with\n * `CONNECTION_PLATFORMS` in `packages/bridge/src/hono/connection-input.ts`: a\n * platform the tool accepts but this map omits sends the user to Settings for a\n * door the agent could have opened itself.\n */\nconst REQUEST_CONNECTION_PLATFORM: Record<ConnectionPlatform, string> = {\n google_ads: \"google-ads\",\n ga4: \"google-analytics\",\n gsc: \"google-search-console\",\n linkedin_ads: \"linkedin\",\n meta_ads: \"meta\",\n x_ads: \"x-ads\",\n};\n\nconst PLATFORM_NAME: Record<ConnectionPlatform, string> = {\n google_ads: \"Google Ads\",\n ga4: \"Google Analytics 4\",\n gsc: \"Google Search Console\",\n x_ads: \"X Ads\",\n meta_ads: \"Meta Ads\",\n linkedin_ads: \"LinkedIn Ads\",\n};\n\n/**\n * How to tell the agent to get a platform connected. Never \"send them to\n * Settings\" for a platform `request_connection` covers — that ends the turn on a\n * wall the agent could have opened itself, and it now covers every platform this\n * CLI talks to. The fallback line matters too: if the `request_connection` call\n * fails, the agent must still be able to say where the connect button is instead\n * of stalling.\n */\nexport function connectionFixExplanation(platform: ConnectionPlatform): string {\n const name = PLATFORM_NAME[platform];\n const slug = REQUEST_CONNECTION_PLATFORM[platform];\n return (\n `${name} is not connected for this company (or no account has been picked). Don't send the user to Settings ` +\n `or end the turn here — call the \\`request_connection\\` tool with { platform: \"${slug}\", reason } and ` +\n `they connect and pick their accounts from inside the chat. If the \\`request_connection\\` call fails, say so ` +\n `plainly and give the exact route: dashboard → Brain → Integrations → Platforms → ${name}. Then carry on with the ` +\n `rest of the task and say what stays unavailable until it is connected.`\n );\n}\n\n/**\n * Shared error handler for \"No Connection\" scenarios.\n * Provides a structured error for AI agents with a suggested fix and alternative commands.\n */\nexport function handleConnectionError(platform: ConnectionPlatform, originalMessage?: string): never {\n const platformName = PLATFORM_NAME[platform];\n\n const alternative = {\n google_ads: \"keywords-for-site or research advertisers\",\n ga4: \"lighthouse for page performance\",\n gsc: \"keywords-for-site or research keyword-gap\",\n x_ads: \"research advertisers\",\n meta_ads: \"research advertisers or research keyword-ideas\",\n linkedin_ads: \"research advertisers (LinkedIn Ad Library) or research keyword-ideas\",\n }[platform];\n\n const code = {\n google_ads: \"NO_GOOGLE_ADS_CONNECTION\",\n ga4: \"NO_GA4_CONNECTION\",\n gsc: \"NO_GSC_CONNECTION\",\n x_ads: \"NO_X_ADS_CONNECTION\",\n meta_ads: \"NO_META_ADS_CONNECTION\",\n linkedin_ads: \"NO_LINKEDIN_ADS_CONNECTION\",\n }[platform];\n\n const envelope = {\n ok: false,\n error: {\n code,\n message: originalMessage || `No ${platformName} connection found for this company.`,\n fix: {\n action: \"request_connection\",\n explanation: `${connectionFixExplanation(platform)} (Optional fallback for estimated external insights: 'baker research ${alternative}'.)`,\n },\n retryable: false,\n },\n };\n\n writeJsonEnvelope(envelope);\n process.exit(1);\n}\n\n/**\n * Does this API failure mean \"the platform isn't connected\"? The read commands\n * surface the backend's own 404/403 verbatim, so the connect fix has to be\n * recognised from the code the client mapped it to plus the message.\n */\nexport function isNotConnectedError(code: string, message: string): boolean {\n if (code !== \"NOT_FOUND\" && code !== \"UNAUTHORIZED\" && code !== \"FORBIDDEN\") {\n return false;\n }\n // The last alternative is the write-path 403 (\"customer X is not in this\n // company's connected Google Ads accounts\") — a different sentence for the\n // same remedy: connect, and pick the account.\n return /no .* connection found|not connected|no .* (selected|picked)|is not in .* connected/i.test(message);\n}\n\n/**\n * Does this failure warrant the connect form? A dead token (401) always does —\n * whatever the wording, the remedy is to sign in again.\n *\n * A 404 or 403 only does when the backend says so. Both codes are overloaded:\n * 403 is also an asset-level refusal on a live connection (reconnecting cannot\n * grant it), and 404 is also how \"no staged op <ref> in this draft\" and a\n * mistyped `--chat` come back. Answering either with \"ask the user to connect\n * Meta Ads\" sends the agent to fix something that was never broken, and the\n * real missing-connection 404 says \"No <platform> connection found\" anyway.\n */\nexport function needsConnectionFix(code: string, message: string): boolean {\n if (code === \"UNAUTHORIZED\") {\n return true;\n }\n return isNotConnectedError(code, message);\n}\n","import { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { CacheEntry } from \"./types.ts\";\n\nconst CACHE_DIR = join(homedir(), \".baker\", \"cache\", \"ads\");\n\nfunction ensureDir(dir: string): void {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n}\n\nfunction hashKey(key: string): string {\n return createHash(\"sha256\").update(key).digest(\"hex\").slice(0, 16);\n}\n\nfunction cachePath(category: string, key: string): string {\n const dir = join(CACHE_DIR, category);\n ensureDir(dir);\n return join(dir, `${hashKey(key)}.json`);\n}\n\nexport function cacheGet<T>(category: string, key: string): CacheEntry<T> | null {\n const path = cachePath(category, key);\n if (!existsSync(path)) {\n return null;\n }\n try {\n const raw = readFileSync(path, \"utf-8\");\n const entry = JSON.parse(raw) as CacheEntry<T>;\n if (entry.expiresAt < Date.now()) {\n rmSync(path, { force: true });\n return null;\n }\n return entry;\n } catch {\n rmSync(path, { force: true });\n return null;\n }\n}\n\nexport function cacheSet<T>(\n category: string,\n key: string,\n data: T,\n ttlMs: number,\n fields?: Record<string, string>,\n): void {\n const path = cachePath(category, key);\n const entry: CacheEntry<T> = {\n expiresAt: Date.now() + ttlMs,\n data,\n fields,\n };\n writeFileSync(path, JSON.stringify(entry), \"utf-8\");\n}\n\nconst HOUR = 60 * 60 * 1000;\nconst MINUTE = 60 * 1000;\n\nexport function getQueryTtl(query: string): number {\n const upper = query.toUpperCase();\n if (upper.includes(\"TODAY\")) {\n return 15 * MINUTE;\n }\n if (upper.includes(\"LAST_7_DAYS\") || upper.includes(\"LAST_14_DAYS\")) {\n return 1 * HOUR;\n }\n // Historical data (BETWEEN, LAST_30_DAYS, LAST_90_DAYS, etc.)\n return 6 * HOUR;\n}\n\nexport function buildQueryCacheKey(customerId: string, query: string): string {\n const today = new Date().toISOString().slice(0, 10);\n return `${customerId}:${today}:${query.trim().replace(/\\s+/g, \" \")}`;\n}\n\ninterface CachedAccount {\n id: string;\n access_type: \"direct\" | \"managed\";\n manager_id?: string;\n}\n\nexport function getManagerIdForCustomer(customerId: string): string | undefined {\n const cached = cacheGet<CachedAccount[]>(\"accounts\", \"list\");\n if (!cached) return undefined;\n const account = cached.data.find((a) => a.id === customerId);\n if (account?.access_type === \"managed\") return account.manager_id;\n return undefined;\n}\n","import { ApiError, apiGet } from \"../../client.ts\";\nimport { getEnv } from \"../../env.ts\";\nimport { handleConnectionError, isNotConnectedError } from \"../../error-handler.ts\";\nimport type { QueryContext } from \"../../geo-context.ts\";\nimport { cacheGet, cacheSet } from \"./cache.ts\";\nimport type { AdsErrorEnvelope, AdsFileSummary, AdsSuccessEnvelope } from \"./types.ts\";\n\ntype AdsOutput =\n | AdsSuccessEnvelope<unknown>\n | AdsErrorEnvelope\n | AdsFileSummary\n | { ok: true; data: unknown; query_context?: QueryContext; cached?: true; fields?: Record<string, string> }\n | { ok: false; error: { code: string; message: string } };\n\nexport function writeAdsJson(envelope: AdsOutput): void {\n process.stdout.write(`${JSON.stringify(envelope, null, 2)}\\n`);\n}\n\nexport function writeJsonEnvelope(envelope: Record<string, unknown>): void {\n process.stdout.write(`${JSON.stringify(envelope, null, 2)}\\n`);\n}\n\n/** The formats `writeAdsOutput` prints. */\nexport const TABLE_OUTPUT_FORMATS = [\"json\", \"csv\", \"jsonl\", \"md\"] as const;\n\n/** Why `--output <format>` cannot be printed, or null — rather than quietly falling back to JSON. */\nexport function outputFormatRefusal(format: string, supported: readonly string[]): string | null {\n if (supported.includes(format)) return null;\n const list =\n supported.length > 1 ? `${supported.slice(0, -1).join(\", \")} or ${supported.at(-1)}` : supported.join(\"\");\n return `--output ${format} is not supported — use ${list}`;\n}\n\nexport function toCsvRow(values: string[]): string {\n return values\n .map((v) => {\n if (v.includes(\",\") || v.includes('\"') || v.includes(\"\\n\")) {\n return `\"${v.replace(/\"/g, '\"\"')}\"`;\n }\n return v;\n })\n .join(\",\");\n}\n\nfunction flattenRow(row: Record<string, unknown>): Record<string, string> {\n const flat: Record<string, string> = {};\n for (const [key, val] of Object.entries(row)) {\n flat[key] = typeof val === \"object\" && val !== null ? JSON.stringify(val) : String(val ?? \"\");\n }\n return flat;\n}\n\n/**\n * One write per table: a line-by-line write piped into `head` hits a closed pipe on\n * the next line and crashes with EPIPE, where a single write lands in the pipe buffer.\n */\nfunction writeWhole(lines: string[]): void {\n process.stdout.write(`${lines.join(\"\\n\")}\\n`);\n}\n\nfunction writeAdsCsv(data: Array<Record<string, unknown>>, fields?: string[]): void {\n if (data.length === 0) return;\n const cols = fields ?? Object.keys(data[0] ?? {});\n const lines = [toCsvRow(cols)];\n for (const row of data) {\n const flat = flattenRow(row);\n lines.push(toCsvRow(cols.map((f) => flat[f] ?? \"\")));\n }\n writeWhole(lines);\n}\n\nfunction writeAdsJsonl(data: Array<Record<string, unknown>>): void {\n if (data.length === 0) return;\n writeWhole(data.map((row) => JSON.stringify(row)));\n}\n\n/** A `|` or line break in a name would split its cell and shift every later column. */\nfunction mdCell(value: string): string {\n return value.replace(/\\|/g, \"\\\\|\").replace(/\\r?\\n/g, \" \");\n}\n\nfunction writeAdsMd(data: Array<Record<string, unknown>>, fields?: string[]): void {\n if (data.length === 0) return;\n const cols = fields ?? Object.keys(data[0] ?? {});\n const lines = [`| ${cols.join(\" | \")} |`, `| ${cols.map(() => \"---\").join(\" | \")} |`];\n for (const row of data) {\n const flat = flattenRow(row);\n lines.push(`| ${cols.map((f) => mdCell(flat[f] ?? \"\")).join(\" | \")} |`);\n }\n writeWhole(lines);\n}\n\n/**\n * `hints` ride in the JSON envelope; csv/jsonl/md stdout has no room for them,\n * and an empty result is exactly when the hint matters, so they go to stderr.\n */\nexport function writeAdsOutput(\n data: Array<Record<string, unknown>>,\n format: string,\n fields?: string[],\n hints: string[] = [],\n): void {\n switch (format) {\n case \"csv\":\n writeAdsCsv(data, fields);\n break;\n case \"jsonl\":\n writeAdsJsonl(data);\n break;\n case \"md\":\n writeAdsMd(data, fields);\n break;\n default:\n writeJsonEnvelope(hints.length > 0 ? { ok: true, data, hints } : { ok: true, data });\n return;\n }\n for (const hint of hints) process.stderr.write(`${hint}\\n`);\n}\n\ninterface AccountInfo {\n id: string;\n name: string;\n access_type: \"direct\" | \"managed\";\n level: number;\n manager_id?: string;\n}\n\nasync function fetchAccounts(useCache = true): Promise<AccountInfo[]> {\n if (useCache) {\n const cached = cacheGet<AccountInfo[]>(\"accounts\", \"list\");\n if (cached) return cached.data;\n }\n const params = !useCache ? { \"skip-cache\": \"true\" } : undefined;\n const data = await apiGet<AccountInfo[]>(\"/api/ads/google/accounts\", params);\n if (useCache) {\n cacheSet(\"accounts\", \"list\", data, 60 * 60 * 1000);\n }\n return data;\n}\n\nexport async function resolveCustomerId(args: Record<string, unknown>): Promise<string> {\n const fromArgs = args[\"customer-id\"] as string | undefined;\n const customerId = fromArgs || getEnv().BAKER_GOOGLE_ADS_CUSTOMER_ID;\n\n if (customerId) {\n if (!/^\\d{10}$/.test(customerId)) {\n writeAdsJson({\n ok: false,\n error: {\n code: \"INVALID_CUSTOMER_ID\",\n message:\n \"Customer ID must be exactly 10 digits without dashes. Pass --customer-id or set BAKER_GOOGLE_ADS_CUSTOMER_ID.\",\n },\n });\n process.exit(1);\n }\n return customerId;\n }\n\n const useCache = !args[\"no-cache\"];\n\n try {\n const accounts = await fetchAccounts(useCache);\n const [single] = accounts;\n if (accounts.length === 1 && single) {\n process.stderr.write(`Using account \"${single.name}\" (${single.id})\\n`);\n return single.id;\n }\n if (accounts.length === 0) {\n handleConnectionError(\"google_ads\");\n }\n const list = accounts.map((a) => ` ${a.id} ${a.name}`).join(\"\\n\");\n writeAdsJson({\n ok: false,\n error: {\n code: \"MULTIPLE_ACCOUNTS\",\n message: `Multiple accounts found. Pass --customer-id or set BAKER_GOOGLE_ADS_CUSTOMER_ID:\\n${list}`,\n },\n });\n process.exit(1);\n } catch (err) {\n // \"No connection\" reaches here as a 404/403 from the accounts lookup. Reporting it as\n // an unusable customer id sends the agent hunting for an id that cannot exist yet.\n if (err instanceof ApiError && isNotConnectedError(err.code, err.message)) {\n handleConnectionError(\"google_ads\", err.message);\n }\n writeAdsJson({\n ok: false,\n error: {\n code: \"INVALID_CUSTOMER_ID\",\n message: \"Could not auto-detect account. Pass --customer-id or set BAKER_GOOGLE_ADS_CUSTOMER_ID.\",\n },\n });\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}