@koda-sl/baker-cli 0.353.0-dev.df03f88fe → 0.353.0-dev.f5f2d3146
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -10
- package/dist/{chunk-VVQLRFCI.js → chunk-2IC3CGQO.js} +2 -2
- package/dist/{chunk-MZQC4LII.js → chunk-4QXKWIFC.js} +1 -4
- package/dist/chunk-4QXKWIFC.js.map +1 -0
- package/dist/{chunk-SR56QQTD.js → chunk-NA5CPMUA.js} +4 -4
- package/dist/chunk-NA5CPMUA.js.map +1 -0
- package/dist/cli.js +32 -29
- package/dist/cli.js.map +1 -1
- package/dist/{client-3DGBQ2H3.js → client-ODCNYCP4.js} +2 -2
- package/dist/{output-OSSBUGT7.js → output-2XBCYF7L.js} +3 -3
- package/dist/{shared-MSI4UMZ3.js → shared-RYAF7Y7H.js} +4 -4
- package/package.json +1 -1
- package/dist/chunk-MZQC4LII.js.map +0 -1
- package/dist/chunk-SR56QQTD.js.map +0 -1
- /package/dist/{chunk-VVQLRFCI.js.map → chunk-2IC3CGQO.js.map} +0 -0
- /package/dist/{client-3DGBQ2H3.js.map → client-ODCNYCP4.js.map} +0 -0
- /package/dist/{output-OSSBUGT7.js.map → output-2XBCYF7L.js.map} +0 -0
- /package/dist/{shared-MSI4UMZ3.js.map → shared-RYAF7Y7H.js.map} +0 -0
package/README.md
CHANGED
|
@@ -238,6 +238,10 @@ baker ads google query --preset campaign-performance --customer-id 1234567890
|
|
|
238
238
|
# its target instead of holding its recent actual cost, so a target above actual raises cost.
|
|
239
239
|
baker ads google query --preset budget-limited --customer-id 1234567890
|
|
240
240
|
|
|
241
|
+
# Which target each ad group actually bids to: its own, or its campaign's / portfolio's.
|
|
242
|
+
# A campaign or portfolio target change does not reach an ad group whose source is AD_GROUP.
|
|
243
|
+
baker ads google query --preset bid-targets --customer-id 1234567890
|
|
244
|
+
|
|
241
245
|
# Include paused entities too (default is serving-only)
|
|
242
246
|
baker ads google query --preset ad-copy-performance --customer-id 1234567890 --include-paused
|
|
243
247
|
|
|
@@ -2536,16 +2540,6 @@ baker images extract https://example.com --wait-for 1500 --limit 20
|
|
|
2536
2540
|
|
|
2537
2541
|
Every image extracted from a page shares the scraped-page URL as `externalUrl`. Big pages can return 100+ images — cap auto-ingest at 20.
|
|
2538
2542
|
|
|
2539
|
-
A failed extract says which kind of failure it was, with `retryable` and a `fix`:
|
|
2540
|
-
|
|
2541
|
-
| Code | Meaning | Next move |
|
|
2542
|
-
|------|---------|-----------|
|
|
2543
|
-
| `UNAVAILABLE` | The page reader did not answer or timed out just now | Retry once, then fall back |
|
|
2544
|
-
| `VALIDATION_ERROR` | The page itself refused (e.g. HTTP 404) or is too large to read | Do not retry; fall back |
|
|
2545
|
-
| `INTERNAL_ERROR` | A fault on Baker's side | Do not retry; fall back |
|
|
2546
|
-
|
|
2547
|
-
The fallback is `baker images screenshot <url>` for the page as one image, or `baker images logo <domain>` for the brand's mark. A `503` from any command now maps to `UNAVAILABLE` instead of `INTERNAL_ERROR`.
|
|
2548
|
-
|
|
2549
2543
|
**Flags:**
|
|
2550
2544
|
|
|
2551
2545
|
| Flag | Description |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ApiError,
|
|
3
3
|
apiGet
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-4QXKWIFC.js";
|
|
5
5
|
import {
|
|
6
6
|
getEnv
|
|
7
7
|
} from "./chunk-2OKSJNLA.js";
|
|
@@ -294,4 +294,4 @@ export {
|
|
|
294
294
|
writeAdsOutput,
|
|
295
295
|
resolveCustomerId
|
|
296
296
|
};
|
|
297
|
-
//# sourceMappingURL=chunk-
|
|
297
|
+
//# sourceMappingURL=chunk-2IC3CGQO.js.map
|
|
@@ -85,9 +85,6 @@ function mapHttpError(status) {
|
|
|
85
85
|
if (status === 429) {
|
|
86
86
|
return "RATE_LIMITED";
|
|
87
87
|
}
|
|
88
|
-
if (status === 503) {
|
|
89
|
-
return "UNAVAILABLE";
|
|
90
|
-
}
|
|
91
88
|
return "INTERNAL_ERROR";
|
|
92
89
|
}
|
|
93
90
|
async function handleResponse(response) {
|
|
@@ -208,4 +205,4 @@ export {
|
|
|
208
205
|
apiGet,
|
|
209
206
|
apiPost
|
|
210
207
|
};
|
|
211
|
-
//# sourceMappingURL=chunk-
|
|
208
|
+
//# sourceMappingURL=chunk-4QXKWIFC.js.map
|
|
@@ -0,0 +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 | \"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 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":[]}
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
handleConnectionError,
|
|
3
3
|
needsConnectionFix,
|
|
4
4
|
writeAdsJson
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2IC3CGQO.js";
|
|
6
6
|
import {
|
|
7
7
|
ApiError
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-4QXKWIFC.js";
|
|
9
9
|
import {
|
|
10
10
|
getEnv
|
|
11
11
|
} from "./chunk-2OKSJNLA.js";
|
|
@@ -35,7 +35,7 @@ function handleMetaError(err) {
|
|
|
35
35
|
code: err.code,
|
|
36
36
|
message: err.message,
|
|
37
37
|
fix: err.code === "FORBIDDEN" ? metaForbiddenFix(err.message) : { action: "reject", explanation: err.message },
|
|
38
|
-
retryable: err.code === "RATE_LIMITED" || err.code === "
|
|
38
|
+
retryable: err.code === "RATE_LIMITED" || err.code === "INTERNAL_ERROR"
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
writeAdsJson(envelope);
|
|
@@ -108,4 +108,4 @@ export {
|
|
|
108
108
|
csvOrJson,
|
|
109
109
|
resolveEffectiveStatus
|
|
110
110
|
};
|
|
111
|
-
//# sourceMappingURL=chunk-
|
|
111
|
+
//# sourceMappingURL=chunk-NA5CPMUA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/ads/meta/shared.ts"],"sourcesContent":["import { ApiError } from \"../../../client.ts\";\nimport { getEnv } from \"../../../env.ts\";\nimport { handleConnectionError, needsConnectionFix } from \"../../../error-handler.ts\";\nimport { writeAdsJson } from \"../output.ts\";\nimport type { AdsErrorEnvelope, QueryFix } from \"../types.ts\";\n\nconst DAY_MS = 86_400_000;\n\n/**\n * Meta answers a permission refusal (Graph codes 200/10/294) with a bare\n * \"Permissions error\" that covers two causes needing opposite remedies: a\n * consent grant the connection never got — nothing validates Meta's granted\n * scopes after connect, unlike HubSpot's `requireCapability` — or a role the\n * user lacks on one asset, since a Pixel/Dataset, Page or catalog lives in a\n * Business and is assigned separately from the ad account. Only the blast radius\n * separates them, so hand the agent that test rather than a verdict: guessing\n * \"reconnect\" wastes the user's time on an unassigned asset, and ruling it out\n * strands a connection that really is short a scope.\n */\nexport function metaForbiddenFix(message: string): QueryFix {\n return {\n action: \"reject\",\n explanation: [\n message,\n \"This is a permission refusal, not a dead connection, and Meta returns the same code for two causes with opposite remedies. Tell them apart by how much else works before you answer.\",\n \"If other reads on this account are ALSO refused (campaigns, ad sets, insights), the connection is short a permission it was never granted. Only the user can widen it, by reconnecting Meta from the dashboard.\",\n \"If those reads work and only this object is refused, the asset (pixel/dataset, Page, or catalog) belongs to a Business the connected user holds no role in. Ad-account access does not carry asset access and reconnecting cannot grant it — the client assigns that asset to the connected user or partner in Meta Business settings, under the asset's own section (Data sources → Datasets for a pixel).\",\n \"Either way do not retry the same call, and do not abort the wider job. Continue with what you can read, and state in your answer that this specific check could not run and why — never present the task as verified when this step was skipped.\",\n ].join(\"\\n\\n\"),\n };\n}\n\nexport function handleMetaError(err: unknown): never {\n if (err instanceof ApiError) {\n if (needsConnectionFix(err.code, err.message)) {\n handleConnectionError(\"meta_ads\", err.message);\n }\n const envelope: AdsErrorEnvelope = {\n ok: false,\n error: {\n code: err.code,\n message: err.message,\n fix: err.code === \"FORBIDDEN\" ? metaForbiddenFix(err.message) : { action: \"reject\", explanation: err.message },\n retryable: err.code === \"RATE_LIMITED\" || err.code === \"INTERNAL_ERROR\",\n },\n };\n writeAdsJson(envelope);\n process.exit(1);\n }\n const message = err instanceof Error ? err.message : \"Unexpected error\";\n writeAdsJson({ ok: false, error: { code: \"NETWORK_ERROR\", message } });\n process.exit(1);\n}\n\nconst ACCOUNT_ID_RE = /^(act_)?[0-9]+$/;\n\n/** Pull the default Meta ad account ID from `--account-id` arg or `BAKER_META_AD_ACCOUNT_ID` env. */\nexport function resolveAccountIdArg(args: Record<string, unknown>): string {\n const fromArgs = args[\"account-id\"] as string | undefined;\n const id = fromArgs || getEnv().BAKER_META_AD_ACCOUNT_ID;\n if (!id) {\n writeAdsJson({\n ok: false,\n error: {\n code: \"MISSING_ACCOUNT_ID\",\n message: \"Pass --account-id or set BAKER_META_AD_ACCOUNT_ID. Run `baker ads meta accounts` to find IDs.\",\n },\n });\n process.exit(1);\n }\n if (!ACCOUNT_ID_RE.test(id)) {\n writeAdsJson({\n ok: false,\n error: {\n code: \"INVALID_ACCOUNT_ID\",\n message: `Invalid account ID \"${id}\". Expected numeric ID, optionally prefixed with \"act_\".`,\n },\n });\n process.exit(1);\n }\n return id.startsWith(\"act_\") ? id : `act_${id}`;\n}\n\nexport function getDateRange(args: Record<string, unknown>): { datePreset?: string; since?: string; until?: string } {\n const datePreset = args[\"date-preset\"] as string | undefined;\n const since = args.since as string | undefined;\n const until = args.until as string | undefined;\n if (since && until) return { since, until };\n return { datePreset: datePreset ?? \"last_7d\" };\n}\n\nexport function todayIso(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\nexport function daysAgoIso(days: number): string {\n return new Date(Date.now() - days * DAY_MS).toISOString().slice(0, 10);\n}\n\nexport function csvOrJson(args: Record<string, unknown>): string {\n return (args.output as string | undefined) ?? \"json\";\n}\n\n/**\n * Default to ACTIVE-only on listings — that's almost always what an AI agent or\n * a human glance wants. Pass `--effective-status ACTIVE,PAUSED` to override, or\n * `--all-statuses` to drop the filter entirely.\n */\nexport function resolveEffectiveStatus(args: Record<string, unknown>): string | undefined {\n if (args[\"all-statuses\"]) {\n return undefined;\n }\n const explicit = args[\"effective-status\"] as string | undefined;\n if (explicit) {\n return explicit;\n }\n return \"ACTIVE\";\n}\n"],"mappings":";;;;;;;;;;;;;AAMA,IAAM,SAAS;AAaR,SAAS,iBAAiB,SAA2B;AAC1D,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,aAAa;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,MAAM;AAAA,EACf;AACF;AAEO,SAAS,gBAAgB,KAAqB;AACnD,MAAI,eAAe,UAAU;AAC3B,QAAI,mBAAmB,IAAI,MAAM,IAAI,OAAO,GAAG;AAC7C,4BAAsB,YAAY,IAAI,OAAO;AAAA,IAC/C;AACA,UAAM,WAA6B;AAAA,MACjC,IAAI;AAAA,MACJ,OAAO;AAAA,QACL,MAAM,IAAI;AAAA,QACV,SAAS,IAAI;AAAA,QACb,KAAK,IAAI,SAAS,cAAc,iBAAiB,IAAI,OAAO,IAAI,EAAE,QAAQ,UAAU,aAAa,IAAI,QAAQ;AAAA,QAC7G,WAAW,IAAI,SAAS,kBAAkB,IAAI,SAAS;AAAA,MACzD;AAAA,IACF;AACA,iBAAa,QAAQ;AACrB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,QAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,eAAa,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,iBAAiB,QAAQ,EAAE,CAAC;AACrE,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,gBAAgB;AAGf,SAAS,oBAAoB,MAAuC;AACzE,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,KAAK,YAAY,OAAO,EAAE;AAChC,MAAI,CAAC,IAAI;AACP,iBAAa;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,IACF,CAAC;AACD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,MAAI,CAAC,cAAc,KAAK,EAAE,GAAG;AAC3B,iBAAa;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS,uBAAuB,EAAE;AAAA,MACpC;AAAA,IACF,CAAC;AACD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,SAAO,GAAG,WAAW,MAAM,IAAI,KAAK,OAAO,EAAE;AAC/C;AAEO,SAAS,aAAa,MAAwF;AACnH,QAAM,aAAa,KAAK,aAAa;AACrC,QAAM,QAAQ,KAAK;AACnB,QAAM,QAAQ,KAAK;AACnB,MAAI,SAAS,MAAO,QAAO,EAAE,OAAO,MAAM;AAC1C,SAAO,EAAE,YAAY,cAAc,UAAU;AAC/C;AAEO,SAAS,WAAmB;AACjC,UAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAC7C;AAEO,SAAS,WAAW,MAAsB;AAC/C,SAAO,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AACvE;AAEO,SAAS,UAAU,MAAuC;AAC/D,SAAQ,KAAK,UAAiC;AAChD;AAOO,SAAS,uBAAuB,MAAmD;AACxF,MAAI,KAAK,cAAc,GAAG;AACxB,WAAO;AAAA,EACT;AACA,QAAM,WAAW,KAAK,kBAAkB;AACxC,MAAI,UAAU;AACZ,WAAO;AAAA,EACT;AACA,SAAO;AACT;","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -77,7 +77,7 @@ import {
|
|
|
77
77
|
resolveAccountIdArg,
|
|
78
78
|
resolveEffectiveStatus,
|
|
79
79
|
todayIso
|
|
80
|
-
} from "./chunk-
|
|
80
|
+
} from "./chunk-NA5CPMUA.js";
|
|
81
81
|
import {
|
|
82
82
|
buildQueryCacheKey,
|
|
83
83
|
cacheGet,
|
|
@@ -93,14 +93,14 @@ import {
|
|
|
93
93
|
writeAdsJson,
|
|
94
94
|
writeAdsOutput,
|
|
95
95
|
writeJsonEnvelope
|
|
96
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-2IC3CGQO.js";
|
|
97
97
|
import {
|
|
98
98
|
ApiError,
|
|
99
99
|
apiGet,
|
|
100
100
|
apiGetWithHeaders,
|
|
101
101
|
apiPost,
|
|
102
102
|
validateConvexId
|
|
103
|
-
} from "./chunk-
|
|
103
|
+
} from "./chunk-4QXKWIFC.js";
|
|
104
104
|
import {
|
|
105
105
|
installExitRecord,
|
|
106
106
|
installStreamTaps,
|
|
@@ -9184,6 +9184,13 @@ var FIELD_DESCRIPTIONS = {
|
|
|
9184
9184
|
"ad_group.name": "Ad group display name",
|
|
9185
9185
|
"ad_group.status": "ENABLED, PAUSED, or REMOVED",
|
|
9186
9186
|
"ad_group.type": "Ad group type (SEARCH_STANDARD, DISPLAY_STANDARD, etc.)",
|
|
9187
|
+
"ad_group.target_roas": "This ad group's own target ROAS (ratio), overriding its campaign's and portfolio's; 0 or omitted = none",
|
|
9188
|
+
"ad_group.effective_target_roas": "The target ROAS this ad group actually bids to, from whichever level sets it",
|
|
9189
|
+
"ad_group.effective_target_roas_source": "Which level the effective target ROAS comes from: AD_GROUP (its own \u2014 a campaign or portfolio change does not reach it) or CAMPAIGN_BIDDING_STRATEGY (the campaign's strategy, its own or a portfolio)",
|
|
9190
|
+
"ad_group.target_cpa_micros": "This ad group's own target CPA in micros, overriding its campaign's and portfolio's; 0 or omitted = none",
|
|
9191
|
+
"ad_group.effective_target_cpa_micros": "The target CPA (micros) this ad group actually bids to",
|
|
9192
|
+
"ad_group.effective_target_cpa_source": "Which level the effective target CPA comes from: AD_GROUP (its own \u2014 a campaign or portfolio change does not reach it) or CAMPAIGN_BIDDING_STRATEGY (the campaign's strategy, its own or a portfolio)",
|
|
9193
|
+
"campaign.bidding_strategy": "Resource name of the portfolio bidding strategy the campaign uses; blank = its own strategy",
|
|
9187
9194
|
// Ad group ad
|
|
9188
9195
|
"ad_group_ad.ad.id": "Unique ad identifier",
|
|
9189
9196
|
"ad_group_ad.ad.final_urls": "Landing page URLs (array)",
|
|
@@ -12037,6 +12044,18 @@ var PRESETS = [
|
|
|
12037
12044
|
defaultLimit: 200,
|
|
12038
12045
|
statusFields: ["campaign.status"]
|
|
12039
12046
|
},
|
|
12047
|
+
{
|
|
12048
|
+
// A target resolves ad group > campaign > portfolio strategy, so the campaign's or the portfolio's
|
|
12049
|
+
// number is not necessarily the one bidding. `effective_target_*_source` names the level that is;
|
|
12050
|
+
// a change staged anywhere else is inert for that ad group. Undated on purpose: an ad group that
|
|
12051
|
+
// spent nothing in the window still carries its own target, and a dated read would drop its row.
|
|
12052
|
+
name: "bid-targets",
|
|
12053
|
+
description: "Which target each ad group actually bids to \u2014 its own target ROAS / CPA, the effective one and the level it comes from. Read it before changing any target: a campaign or portfolio change does not reach an ad group whose source is AD_GROUP",
|
|
12054
|
+
gaqlTemplate: `SELECT campaign.id, campaign.name, campaign.status, campaign.bidding_strategy_type, campaign.bidding_strategy, ad_group.id, ad_group.name, ad_group.status, ad_group.target_roas, ad_group.effective_target_roas, ad_group.effective_target_roas_source, ad_group.target_cpa_micros, ad_group.effective_target_cpa_micros, ad_group.effective_target_cpa_source FROM ad_group WHERE campaign.bidding_strategy_type IN ('TARGET_ROAS', 'TARGET_CPA', 'MAXIMIZE_CONVERSION_VALUE', 'MAXIMIZE_CONVERSIONS'){statusScope} ORDER BY campaign.name LIMIT {limit}`,
|
|
12055
|
+
defaultDateRange: "ALL_TIME",
|
|
12056
|
+
defaultLimit: 200,
|
|
12057
|
+
statusFields: ["campaign.status", "ad_group.status"]
|
|
12058
|
+
},
|
|
12040
12059
|
{
|
|
12041
12060
|
name: "conversions-by-action",
|
|
12042
12061
|
description: "Which conversion action each campaign's conversions actually are \u2014 the read that says whether two campaigns' conversion rates are measuring the same event",
|
|
@@ -18924,7 +18943,7 @@ function handleLinkedinError(err) {
|
|
|
18924
18943
|
explanation: explanation.message,
|
|
18925
18944
|
correctedCommand: explanation.correctedCommand
|
|
18926
18945
|
},
|
|
18927
|
-
retryable: err.code === "RATE_LIMITED" || err.code === "
|
|
18946
|
+
retryable: err.code === "RATE_LIMITED" || err.code === "INTERNAL_ERROR"
|
|
18928
18947
|
}
|
|
18929
18948
|
};
|
|
18930
18949
|
writeAdsJson(envelope);
|
|
@@ -25463,10 +25482,10 @@ Example: baker ads meta ads retag 120252158038400247 --url-tags "utm_source={{si
|
|
|
25463
25482
|
}
|
|
25464
25483
|
},
|
|
25465
25484
|
run: async ({ args: args5 }) => {
|
|
25466
|
-
const { apiPost: apiPost2 } = await import("./client-
|
|
25485
|
+
const { apiPost: apiPost2 } = await import("./client-ODCNYCP4.js");
|
|
25467
25486
|
const { requireChatId: requireChatId2 } = await import("./env-BSG7TY5Y.js");
|
|
25468
|
-
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-
|
|
25469
|
-
const { handleMetaError: handleMetaError2 } = await import("./shared-
|
|
25487
|
+
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-2XBCYF7L.js");
|
|
25488
|
+
const { handleMetaError: handleMetaError2 } = await import("./shared-RYAF7Y7H.js");
|
|
25470
25489
|
try {
|
|
25471
25490
|
const accountId = bareAccountId2(args5);
|
|
25472
25491
|
const urlTags = typeof args5["url-tags"] === "string" ? args5["url-tags"] : void 0;
|
|
@@ -25611,10 +25630,10 @@ function duplicateCommand2(entity, label) {
|
|
|
25611
25630
|
replace: { type: "boolean", description: "Pause the original once the copy publishes" }
|
|
25612
25631
|
},
|
|
25613
25632
|
run: async ({ args: args5 }) => {
|
|
25614
|
-
const { apiPost: apiPost2 } = await import("./client-
|
|
25633
|
+
const { apiPost: apiPost2 } = await import("./client-ODCNYCP4.js");
|
|
25615
25634
|
const { requireChatId: requireChatId2 } = await import("./env-BSG7TY5Y.js");
|
|
25616
|
-
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-
|
|
25617
|
-
const { handleMetaError: handleMetaError2 } = await import("./shared-
|
|
25635
|
+
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-2XBCYF7L.js");
|
|
25636
|
+
const { handleMetaError: handleMetaError2 } = await import("./shared-RYAF7Y7H.js");
|
|
25618
25637
|
try {
|
|
25619
25638
|
const accountId = bareAccountId2(args5);
|
|
25620
25639
|
const chatId = requireChatId2();
|
|
@@ -49597,7 +49616,7 @@ function loadJsonPayloads(args5) {
|
|
|
49597
49616
|
}
|
|
49598
49617
|
return parsed.map((entry, index) => asObject(entry, "json", index));
|
|
49599
49618
|
}
|
|
49600
|
-
var RETRYABLE_CODES = /* @__PURE__ */ new Set(["RATE_LIMITED", "
|
|
49619
|
+
var RETRYABLE_CODES = /* @__PURE__ */ new Set(["RATE_LIMITED", "INTERNAL_ERROR", "NETWORK_ERROR", "TIMEOUT"]);
|
|
49601
49620
|
function handleError5(err) {
|
|
49602
49621
|
if (err instanceof ApiError) {
|
|
49603
49622
|
const readOnly = err.code === "FORBIDDEN" && /read the property but not change it/i.test(err.message);
|
|
@@ -52849,22 +52868,6 @@ function parseAutoIngest(raw, max) {
|
|
|
52849
52868
|
return { ok: true, value };
|
|
52850
52869
|
}
|
|
52851
52870
|
|
|
52852
|
-
// src/commands/images/extractErrors.ts
|
|
52853
|
-
var RETRYABLE = /* @__PURE__ */ new Set(["UNAVAILABLE", "RATE_LIMITED", "TIMEOUT", "NETWORK_ERROR"]);
|
|
52854
|
-
function extractErrorEnvelope(err, url) {
|
|
52855
|
-
const retryable = RETRYABLE.has(err.code);
|
|
52856
|
-
const fallback = `use \`baker images screenshot ${url}\` to capture the page as one image, or \`baker images logo <domain>\` for the brand's mark, and carry on with the rest of the job.`;
|
|
52857
|
-
return {
|
|
52858
|
-
ok: false,
|
|
52859
|
-
error: {
|
|
52860
|
-
code: err.code,
|
|
52861
|
-
message: err.message,
|
|
52862
|
-
retryable,
|
|
52863
|
-
fix: retryable ? { action: "retry_once", explanation: `Run the same command once more. If it fails again, ${fallback}` } : { action: "use_fallback", explanation: `Retrying the same address will fail the same way \u2014 ${fallback}` }
|
|
52864
|
-
}
|
|
52865
|
-
};
|
|
52866
|
-
}
|
|
52867
|
-
|
|
52868
52871
|
// src/commands/images/extract.ts
|
|
52869
52872
|
registerSchema({
|
|
52870
52873
|
command: "images.extract",
|
|
@@ -52931,7 +52934,7 @@ var extractCommand = defineCommand149({
|
|
|
52931
52934
|
writeJson({ ok: true, data });
|
|
52932
52935
|
} catch (err) {
|
|
52933
52936
|
if (err instanceof ApiError) {
|
|
52934
|
-
writeJson(
|
|
52937
|
+
writeJson({ ok: false, error: { code: err.code, message: err.message } });
|
|
52935
52938
|
process.exit(1);
|
|
52936
52939
|
}
|
|
52937
52940
|
writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: "Unexpected error" } });
|
|
@@ -64299,7 +64302,7 @@ function loadJsonArg2(args5, flag = "json") {
|
|
|
64299
64302
|
return failValidation5(`--${flag} is not valid JSON`);
|
|
64300
64303
|
}
|
|
64301
64304
|
}
|
|
64302
|
-
var RETRYABLE_CODES2 = /* @__PURE__ */ new Set(["RATE_LIMITED", "
|
|
64305
|
+
var RETRYABLE_CODES2 = /* @__PURE__ */ new Set(["RATE_LIMITED", "INTERNAL_ERROR", "NETWORK_ERROR", "TIMEOUT"]);
|
|
64303
64306
|
function handleError8(err) {
|
|
64304
64307
|
if (err instanceof ApiError) {
|
|
64305
64308
|
writeJsonEnvelope({
|