@koda-sl/baker-cli 0.570.0 → 0.571.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.
- package/README.md +1 -0
- package/dist/{account-reads-RXF37O4N.js → account-reads-CQIJCPCH.js} +2 -2
- package/dist/{ads-BT2DF3XN.js → ads-PWTQC6KV.js} +2 -2
- package/dist/{chunk-HI4SKWNW.js → chunk-IB6VWG32.js} +50 -4
- package/dist/chunk-IB6VWG32.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/{conversion-upload-command-G7LLWHSK.js → conversion-upload-command-JAGV6GMG.js} +2 -2
- package/dist/{draft-Q4PEFBJB.js → draft-GSDDK6FO.js} +49 -13
- package/dist/draft-GSDDK6FO.js.map +1 -0
- package/dist/{forecast-ITLKAFHM.js → forecast-HMNQZFVL.js} +2 -2
- package/dist/{google-JY7KDAFC.js → google-3CAUWNWE.js} +10 -10
- package/dist/{themes-QMR5TWTI.js → themes-FW3VWZJE.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-HI4SKWNW.js.map +0 -1
- package/dist/draft-Q4PEFBJB.js.map +0 -1
- /package/dist/{account-reads-RXF37O4N.js.map → account-reads-CQIJCPCH.js.map} +0 -0
- /package/dist/{ads-BT2DF3XN.js.map → ads-PWTQC6KV.js.map} +0 -0
- /package/dist/{conversion-upload-command-G7LLWHSK.js.map → conversion-upload-command-JAGV6GMG.js.map} +0 -0
- /package/dist/{forecast-ITLKAFHM.js.map → forecast-HMNQZFVL.js.map} +0 -0
- /package/dist/{google-JY7KDAFC.js.map → google-3CAUWNWE.js.map} +0 -0
- /package/dist/{themes-QMR5TWTI.js.map → themes-FW3VWZJE.js.map} +0 -0
package/README.md
CHANGED
|
@@ -926,6 +926,7 @@ baker ads google audiences create --customer-id 1234567890 --file '{"name":"Pric
|
|
|
926
926
|
baker ads google draft list # readable campaign ▸ ad group ▸ ad tree + completeness advisories
|
|
927
927
|
baker ads google draft list --json # raw JSON envelope for scripting
|
|
928
928
|
baker ads google draft list --full # the envelope with every op's payload — instead of a show per op
|
|
929
|
+
baker ads google draft list --customer-id 1234567890 # only that account's changes; validate and show take it too
|
|
929
930
|
baker ads google draft validate # dry run the whole draft against Google (validateOnly) — per-op verdicts, and dailyLimit when the draft will not all fit in today's Google Ads changes
|
|
930
931
|
baker ads google draft validate --full # every op, with the built request and Google's raw answer
|
|
931
932
|
baker ads google draft show g_temp_ab12 # full staged payload — the receipt before publish
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
failWriteValidation,
|
|
6
6
|
listFlag,
|
|
7
7
|
requireCustomerId
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-IB6VWG32.js";
|
|
9
9
|
import "./chunk-ERSX3MKH.js";
|
|
10
10
|
import "./chunk-YRWCXYWW.js";
|
|
11
11
|
import "./chunk-4NZ47VXO.js";
|
|
@@ -216,4 +216,4 @@ export {
|
|
|
216
216
|
invoicesCommand,
|
|
217
217
|
previewsCommand
|
|
218
218
|
};
|
|
219
|
-
//# sourceMappingURL=account-reads-
|
|
219
|
+
//# sourceMappingURL=account-reads-CQIJCPCH.js.map
|
|
@@ -33,7 +33,7 @@ Examples:
|
|
|
33
33
|
Full guides: __tooling__/docs/tools/baker/ads-<platform>.md (google|meta|linkedin|x|chatgpt|performance|terms)`
|
|
34
34
|
},
|
|
35
35
|
subCommands: {
|
|
36
|
-
google: () => import("./google-
|
|
36
|
+
google: () => import("./google-3CAUWNWE.js").then((m) => m.googleCommand),
|
|
37
37
|
meta: () => import("./meta-2UK7KIE4.js").then((m) => m.metaCommand),
|
|
38
38
|
linkedin: () => import("./linkedin-UB2CW4KP.js").then((m) => m.linkedinCommand),
|
|
39
39
|
x: () => import("./x-UGTUOCVR.js").then((m) => m.xCommand),
|
|
@@ -45,4 +45,4 @@ Full guides: __tooling__/docs/tools/baker/ads-<platform>.md (google|meta|linkedi
|
|
|
45
45
|
export {
|
|
46
46
|
adsCommand
|
|
47
47
|
};
|
|
48
|
-
//# sourceMappingURL=ads-
|
|
48
|
+
//# sourceMappingURL=ads-PWTQC6KV.js.map
|
|
@@ -42,6 +42,48 @@ import {
|
|
|
42
42
|
payloadArg
|
|
43
43
|
} from "./chunk-NXNOXZGB.js";
|
|
44
44
|
|
|
45
|
+
// src/commands/ads/google/draft-account.ts
|
|
46
|
+
function changes(n, staged) {
|
|
47
|
+
const noun = staged ? "staged change" : "change";
|
|
48
|
+
return n === 1 ? `1 ${noun}` : `${n} ${noun}s`;
|
|
49
|
+
}
|
|
50
|
+
function draftInAccount(data, customerId) {
|
|
51
|
+
const ops = data.ops.filter((op) => op.customerId === customerId);
|
|
52
|
+
const narrowed = {
|
|
53
|
+
...data,
|
|
54
|
+
count: ops.length,
|
|
55
|
+
ops,
|
|
56
|
+
...data.tree ? { tree: data.tree.filter((node) => node.customerId === customerId) } : {}
|
|
57
|
+
};
|
|
58
|
+
const others = data.ops.filter((op) => op.customerId !== customerId);
|
|
59
|
+
if (others.length === 0) return { data: narrowed, hints: [] };
|
|
60
|
+
const staged = data.status === "active";
|
|
61
|
+
const accounts = [...new Set(others.map((op) => op.customerId))];
|
|
62
|
+
const where = `account${accounts.length === 1 ? "" : "s"} ${accounts.join(", ")}`;
|
|
63
|
+
const one = others.length === 1;
|
|
64
|
+
if (ops.length === 0) {
|
|
65
|
+
return {
|
|
66
|
+
data: narrowed,
|
|
67
|
+
empty: `No Google Ads changes in account ${customerId} on this chat.`,
|
|
68
|
+
hints: [`This chat's ${changes(others.length, staged)} ${one ? "is" : "are"} in ${where}, not ${customerId}.`]
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const more = `${others.length} more ${staged ? "staged " : ""}change${one ? "" : "s"}`;
|
|
72
|
+
const verb = staged ? one ? "publishes" : "publish" : one ? "is" : "are";
|
|
73
|
+
return {
|
|
74
|
+
data: narrowed,
|
|
75
|
+
hints: [
|
|
76
|
+
`Showing account ${customerId} only. ${more} in ${where} ${verb} ${staged ? "with this chat too" : "on this chat too"}; \`baker ads google draft list\` without --customer-id shows ${one ? "it" : "them"}.`
|
|
77
|
+
]
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function validateAccountHint(customerId) {
|
|
81
|
+
return `Validated this chat's whole draft, not only account ${customerId}: publish sends every account's changes together, so each verdict above counts.`;
|
|
82
|
+
}
|
|
83
|
+
function showAccountHint(customerId, opCustomerId) {
|
|
84
|
+
return opCustomerId === customerId ? void 0 : `This op is staged in account ${opCustomerId}, not ${customerId}.`;
|
|
85
|
+
}
|
|
86
|
+
|
|
45
87
|
// src/commands/ads/google/write-shared.ts
|
|
46
88
|
import { readFileSync } from "fs";
|
|
47
89
|
import { defineCommand } from "citty";
|
|
@@ -583,18 +625,20 @@ async function draftAction(path, body, chat) {
|
|
|
583
625
|
handleGoogleError(err);
|
|
584
626
|
}
|
|
585
627
|
}
|
|
586
|
-
async function draftListAction(json, chat, full = false) {
|
|
628
|
+
async function draftListAction(json, chat, full = false, customerId) {
|
|
587
629
|
try {
|
|
588
630
|
const chatId = resolveChatId(chat);
|
|
589
|
-
const
|
|
631
|
+
const listed = await apiPost("/api/ads/google/draft", {
|
|
590
632
|
chatId,
|
|
591
633
|
...full ? { full: true } : {}
|
|
592
634
|
});
|
|
635
|
+
const inAccount = customerId !== void 0 && listed.ok && listed.data ? draftInAccount(listed.data, customerId) : void 0;
|
|
636
|
+
const response = inAccount ? withHints({ ...listed, data: inAccount.data }, inAccount.hints) : listed;
|
|
593
637
|
if (json || full || !response.ok || !response.data) {
|
|
594
638
|
writeJsonEnvelope(response);
|
|
595
639
|
return;
|
|
596
640
|
}
|
|
597
|
-
process.stdout.write(`${renderDraftStatus(response.data)}${hintLines(response)}
|
|
641
|
+
process.stdout.write(`${inAccount?.empty ?? renderDraftStatus(response.data)}${hintLines(response)}
|
|
598
642
|
`);
|
|
599
643
|
} catch (err) {
|
|
600
644
|
handleGoogleError(err);
|
|
@@ -678,6 +722,8 @@ function bidModifierFlag(value) {
|
|
|
678
722
|
|
|
679
723
|
export {
|
|
680
724
|
AD_COPY_LIST_USAGE,
|
|
725
|
+
validateAccountHint,
|
|
726
|
+
showAccountHint,
|
|
681
727
|
failWriteValidation,
|
|
682
728
|
requireCustomerId,
|
|
683
729
|
requireTarget,
|
|
@@ -706,4 +752,4 @@ export {
|
|
|
706
752
|
fileCreateCommand,
|
|
707
753
|
bidModifierFlag
|
|
708
754
|
};
|
|
709
|
-
//# sourceMappingURL=chunk-
|
|
755
|
+
//# sourceMappingURL=chunk-IB6VWG32.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/ads/google/draft-account.ts","../src/commands/ads/google/write-shared.ts","../src/commands/ads/google/copy-list-usage.ts","../src/commands/ads/google/draft-status.ts","../src/commands/ads/google/stage-streak.ts"],"sourcesContent":["import type { GoogleDraftListResponse } from \"@baker/api/ads-google\";\n\n/**\n * `--customer-id` on the draft reads. A draft belongs to the chat, not to an account, and may span\n * several; every other Google Ads command takes the flag, so agents carry it here (BAKER-497).\n * `list` narrows to that account and says what the others still hold; `validate` and `show` keep\n * their whole answer, because publish sends every account together and a ref names one op.\n */\n\nfunction changes(n: number, staged: boolean): string {\n const noun = staged ? \"staged change\" : \"change\";\n return n === 1 ? `1 ${noun}` : `${n} ${noun}s`;\n}\n\n/**\n * The draft as one account holds it, with what the rest of the chat's draft holds. `empty` is the\n * readable line for an account holding none of it, where the whole-chat \"nothing staged\" would be false.\n * Only an active draft's other changes are still to publish; a published one's are already done.\n */\nexport function draftInAccount(\n data: GoogleDraftListResponse,\n customerId: string,\n): { data: GoogleDraftListResponse; hints: string[]; empty?: string } {\n const ops = data.ops.filter((op) => op.customerId === customerId);\n const narrowed = {\n ...data,\n count: ops.length,\n ops,\n ...(data.tree ? { tree: data.tree.filter((node) => node.customerId === customerId) } : {}),\n };\n const others = data.ops.filter((op) => op.customerId !== customerId);\n if (others.length === 0) return { data: narrowed, hints: [] };\n const staged = data.status === \"active\";\n const accounts = [...new Set(others.map((op) => op.customerId))];\n const where = `account${accounts.length === 1 ? \"\" : \"s\"} ${accounts.join(\", \")}`;\n const one = others.length === 1;\n if (ops.length === 0) {\n return {\n data: narrowed,\n empty: `No Google Ads changes in account ${customerId} on this chat.`,\n hints: [`This chat's ${changes(others.length, staged)} ${one ? \"is\" : \"are\"} in ${where}, not ${customerId}.`],\n };\n }\n const more = `${others.length} more ${staged ? \"staged \" : \"\"}change${one ? \"\" : \"s\"}`;\n const verb = staged ? (one ? \"publishes\" : \"publish\") : one ? \"is\" : \"are\";\n return {\n data: narrowed,\n hints: [\n `Showing account ${customerId} only. ${more} in ${where} ${verb} ${staged ? \"with this chat too\" : \"on this chat too\"}; \\`baker ads google draft list\\` without --customer-id shows ${one ? \"it\" : \"them\"}.`,\n ],\n };\n}\n\n/** `draft validate --customer-id`: said back, since the verdicts still cover every account. */\nexport function validateAccountHint(customerId: string): string {\n return `Validated this chat's whole draft, not only account ${customerId}: publish sends every account's changes together, so each verdict above counts.`;\n}\n\n/** `draft show --customer-id` naming another account than the op's own. */\nexport function showAccountHint(customerId: string, opCustomerId: string): string | undefined {\n return opCustomerId === customerId ? undefined : `This op is staged in account ${opCustomerId}, not ${customerId}.`;\n}\n","import { readFileSync } from \"node:fs\";\nimport {\n GOOGLE_DRAFT_BATCH_MAX,\n type GoogleDraftListResponse,\n type GoogleDraftStageBatchResponse,\n type GoogleDraftStageResponse,\n googleDraftOpInputSchema,\n OP_TOO_LARGE_FIX,\n OP_TOO_LARGE_MESSAGE_START,\n toMicros,\n} from \"@baker/api/ads-google\";\nimport { describeOpPayload } from \"@baker/api/capabilities\";\nimport { defineCommand } from \"citty\";\nimport { ApiError, apiPost } from \"../../../client.ts\";\nimport { requireChatId, resolveChatId } from \"../../../env.ts\";\nimport { handleConnectionError, isNotConnectedError } from \"../../../error-handler.ts\";\nimport { repeatedValues } from \"../../../lib/repeatedFlags.ts\";\nimport { payloadArg } from \"../../../schemas.ts\";\nimport { payloadFix } from \"../../capabilities/fix.ts\";\nimport { booleanWordPositionalError } from \"../boolean-word-positional.ts\";\nimport { adCopyHints } from \"../copy-hints.ts\";\nimport { hintLines, withHints } from \"../hints.ts\";\nimport { writeJsonEnvelope } from \"../output.ts\";\nimport { termsRefusalFix } from \"../terms-output.ts\";\nimport { copyListCountFix } from \"./copy-list-usage.ts\";\nimport { draftInAccount } from \"./draft-account.ts\";\nimport { renderDraftStatus } from \"./draft-status.ts\";\nimport { recordSingleStage } from \"./stage-streak.ts\";\n\n/**\n * Shared plumbing for the staged Google Ads write commands. Writes NEVER hit\n * the Google Ads API directly: every command stages an op against this chat's\n * draft (`/api/ads/google/draft/stage`), the dashboard shows it as a pending\n * Google Ads change, and the draft applies as one `GoogleAdsService.Mutate`\n * request per advertiser account when the chat is published. `--customer-id` is\n * required on every write — a draft may span several accounts, so nothing is\n * picked for you.\n */\n\nexport function failWriteValidation(message: string): never {\n writeJsonEnvelope({ ok: false, error: { code: \"VALIDATION_ERROR\", message } });\n process.exit(1);\n}\n\nexport function requireCustomerId(args: Record<string, unknown>): string {\n const raw = (args[\"customer-id\"] ?? args.customerId) as string | undefined;\n if (!raw || !/^\\d{10}$/.test(raw)) {\n failWriteValidation(\"pass --customer-id as the 10-digit Google Ads customer id (no dashes)\");\n }\n return raw;\n}\n\nexport function requireTarget(args: Record<string, unknown>, entity: string): string {\n // `--flag false` is refused, not documented: citty parses it as the flag set to TRUE plus a stray\n // positional nobody consumes, so on `conversions update --primary` it silently PROMOTES an action\n // into what every Maximize Conversions campaign bids toward. Caught here because this is the only\n // place on this surface that reads positionals, and every target-taking command goes through it.\n const booleanWord = booleanWordPositionalError(args, entity);\n if (booleanWord) {\n failWriteValidation(booleanWord);\n }\n const positional = Array.isArray(args._) ? args._[0] : undefined;\n const target = args.id ?? args.target ?? args.ref ?? positional;\n if (typeof target !== \"string\" || target.length === 0) {\n failWriteValidation(`pass the ${entity} resource name or id as the positional argument`);\n }\n return target;\n}\n\n/**\n * The targets a bulk status command was given: every positional (comma-separated ones split), plus\n * `--file` with one target per line (blank lines and `#` comments skipped), deduped in order. One\n * call then stages them all as one batch instead of one command per row.\n */\nexport function targetEntries(args: Record<string, unknown>, entity: string): string[] {\n const booleanWord = booleanWordPositionalError(args, entity);\n if (booleanWord) {\n failWriteValidation(booleanWord);\n }\n const positionals = Array.isArray(args._) ? args._.filter((v): v is string => typeof v === \"string\") : [];\n const raw = [...positionals.flatMap((v) => v.split(\",\")), ...rawFileEntries(args.file)]\n .map((v) => v.trim())\n .filter(Boolean);\n const targets = [...new Set(raw)];\n if (targets.length === 0) {\n failWriteValidation(`pass the ${entity} resource name(s) as positional arguments and/or --file (one per line)`);\n }\n if (targets.length > GOOGLE_DRAFT_BATCH_MAX) {\n failWriteValidation(`${targets.length} targets exceeds the batch limit of ${GOOGLE_DRAFT_BATCH_MAX} — split them`);\n }\n return targets;\n}\n\nexport function requireStringFlag(value: unknown, flag: string): string {\n if (typeof value !== \"string\" || value.length === 0) {\n failWriteValidation(`${flag} is required`);\n }\n return value;\n}\n\n/** Money flag in major currency units → micros. */\nexport function microsFlag(value: unknown, flag: string): number | undefined {\n if (value === undefined || value === null || value === \"\") {\n return undefined;\n }\n const num = Number(value);\n if (Number.isNaN(num) || num <= 0) {\n failWriteValidation(`${flag} must be a positive amount (major currency units, e.g. 50 or 50.00)`);\n }\n return toMicros(num);\n}\n\n/** Split a comma-separated flag into trimmed non-empty values. */\nexport function listFlag(value: unknown): string[] | undefined {\n if (typeof value !== \"string\" || value.length === 0) {\n return undefined;\n }\n const items = value\n .split(\",\")\n .map((v) => v.trim())\n .filter(Boolean);\n return items.length > 0 ? items : undefined;\n}\n\nexport interface KeywordEntry {\n text: string;\n matchType: string;\n}\n\nconst MATCH_TYPES = new Set([\"EXACT\", \"PHRASE\", \"BROAD\"]);\n\n/**\n * One raw keyword entry → { text, matchType }. A trailing `:EXACT|:PHRASE|:BROAD`\n * suffix overrides the default match type; colons and commas aren't legal in Google\n * keyword text, so the syntax is unambiguous.\n */\nfunction parseKeywordEntry(raw: string, defaultMatch: string | undefined): KeywordEntry {\n const colon = raw.lastIndexOf(\":\");\n if (colon > 0) {\n const suffix = raw\n .slice(colon + 1)\n .trim()\n .toUpperCase();\n if (MATCH_TYPES.has(suffix)) {\n return { text: raw.slice(0, colon).trim(), matchType: suffix };\n }\n failWriteValidation(`\"${raw}\": \":${raw.slice(colon + 1)}\" is not a match type (EXACT | PHRASE | BROAD)`);\n }\n if (!defaultMatch) {\n failWriteValidation(`\"${raw}\" has no match type — add a :EXACT/:PHRASE/:BROAD suffix or pass --match-type`);\n }\n return { text: raw, matchType: defaultMatch };\n}\n\n/**\n * Raw --text value(s) → individual entries: repeated flags and comma-separated\n * both work. The repeats have to come off `rawArgs`, because citty parses a\n * repeated flag down to its last value alone.\n */\nfunction rawTextEntries(value: unknown, rawArgs: readonly string[] | undefined): string[] {\n return repeatedValues(rawArgs, \"text\", value)\n .flatMap((v) => v.split(\",\"))\n .map((v) => v.trim())\n .filter(Boolean);\n}\n\n/** Newline-separated keyword file: one `text[:MATCH_TYPE]` per line; blank lines and `#` comments skipped. */\nfunction rawFileEntries(path: unknown): string[] {\n return typeof path === \"string\" && path.length > 0 ? fileLines(path) : [];\n}\n\n/** A text file's entries: one per line, trimmed; blank lines and `#` comments skipped. */\nexport function fileLines(path: string): string[] {\n return readFileSync(path, \"utf8\")\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter((line) => line.length > 0 && !line.startsWith(\"#\"));\n}\n\n/**\n * The keyword batch a command was invoked with: comma-separated `--text` entries\n * (optional `:MATCH_TYPE` suffix each) plus `--file` lines, deduped, each falling\n * back to `--match-type`. One entry stages exactly like before; many stage as one batch.\n */\nexport function keywordEntries(args: Record<string, unknown>, rawArgs?: readonly string[]): KeywordEntry[] {\n const defaultMatch = typeof args[\"match-type\"] === \"string\" ? args[\"match-type\"].toUpperCase() : undefined;\n const raw = [...rawTextEntries(args.text, rawArgs), ...rawFileEntries(args.file)];\n if (raw.length === 0) {\n failWriteValidation(\"pass keywords via --text (comma-separated) and/or --file (one per line)\");\n }\n if (raw.length > GOOGLE_DRAFT_BATCH_MAX) {\n failWriteValidation(`${raw.length} keywords exceeds the batch limit of ${GOOGLE_DRAFT_BATCH_MAX}`);\n }\n const entries: KeywordEntry[] = [];\n const seen = new Set<string>();\n for (const item of raw) {\n const entry = parseKeywordEntry(item, defaultMatch);\n const key = JSON.stringify([entry.text, entry.matchType]);\n if (!seen.has(key)) {\n seen.add(key);\n entries.push(entry);\n }\n }\n return entries;\n}\n\n/**\n * The payloads behind `--file`: a path to a JSON file, or the JSON written inline. The file holds one\n * payload object, a JSON array of them, or JSON Lines (one object per line) — many payloads stage as\n * one batch, which is how a bulk change lands in one call instead of hundreds.\n *\n * Inline is here because every doc example already writes it that way — `packages/cli/README.md`\n * and the scaffold's `ads-google.md` both show `--file '{\"type\":\"image\",…}'` — while the arg only\n * ever read a path, so following the documentation answered with a raw ENOENT stack trace. Reading\n * a leading `{` or `[` as the payload makes those examples true rather than making the agent re-learn\n * the one shape that works.\n */\nexport function loadJsonPayloads(path: unknown): Array<Record<string, unknown>> {\n if (typeof path !== \"string\" || path.length === 0) {\n return [];\n }\n const inline = /^\\s*[[{]/.test(path);\n const source = inline ? \"inline JSON\" : path;\n const text = inline ? path : readFileSync(path, \"utf8\");\n return parsedPayloads(text, source).map((item, index, all) => {\n if (item === null || typeof item !== \"object\" || Array.isArray(item)) {\n failWriteValidation(\n all.length === 1 ? `${source} must contain a JSON object` : `${source}: item ${index} must be a JSON object`,\n );\n }\n return item as Record<string, unknown>;\n });\n}\n\n/** One JSON value (an array spreads into its items), else JSON Lines; the JSON error when neither parses. */\nfunction parsedPayloads(text: string, source: string): unknown[] {\n try {\n const parsed = JSON.parse(text) as unknown;\n return Array.isArray(parsed) ? parsed : [parsed];\n } catch (err) {\n if (!(err instanceof SyntaxError)) {\n throw err;\n }\n const lines = text.split(/\\r?\\n/).filter((line) => line.trim().length > 0);\n if (lines.length > 1) {\n return lines.map((line, index) => parseJsonLine(line, index, source));\n }\n failWriteValidation(`${source} is not valid JSON: ${err.message}`);\n }\n}\n\n/** One JSON Lines entry, or the line's own error naming where it is. */\nfunction parseJsonLine(line: string, index: number, source: string): unknown {\n try {\n return JSON.parse(line) as unknown;\n } catch (err) {\n return failWriteValidation(\n `${source} line ${index + 1} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\n/** The ONE op payload behind `--file`, for commands that change a single entity. */\nexport function loadJsonFileArg(path: unknown): Record<string, unknown> {\n const payloads = loadJsonPayloads(path);\n if (payloads.length > 1) {\n failWriteValidation(\n `this command changes one entity, and --file holds ${payloads.length} payloads — stage many changes from one file with \\`baker ads google draft stage --file\\` (one op per item: {\"kind\",\"customerId\",\"target\"?,\"payload\"?})`,\n );\n }\n return payloads[0] ?? {};\n}\n\n/** Flags override file keys — the file is the base, flags are the overrides. */\nexport function mergePayload(file: Record<string, unknown>, flags: Record<string, unknown>): Record<string, unknown> {\n const merged: Record<string, unknown> = { ...file };\n for (const [key, value] of Object.entries(flags)) {\n if (value !== undefined) {\n merged[key] = value;\n }\n }\n return merged;\n}\n\n/**\n * The `draft amend` patch: exactly one of `--file <path>` (JSON file) or `--patch '<json>'`\n * (inline string), and it must parse to a JSON object — RFC-7386 merge-patch needs an object to\n * merge, and a stray array/scalar would silently no-op or replace the whole payload.\n */\nexport function loadPatchArg(args: Record<string, unknown>): Record<string, unknown> {\n const file = typeof args.file === \"string\" && args.file.length > 0 ? args.file : undefined;\n const inline = typeof args.patch === \"string\" && args.patch.length > 0 ? args.patch : undefined;\n if (Boolean(file) === Boolean(inline)) {\n failWriteValidation(\"pass exactly one of --file <patch.json> or --patch '<json>'\");\n }\n if (file) {\n return loadJsonFileArg(file);\n }\n try {\n const parsed = JSON.parse(inline as string) as unknown;\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n failWriteValidation(\"--patch must be a JSON object\");\n }\n return parsed as Record<string, unknown>;\n } catch (err) {\n if (err instanceof SyntaxError) {\n failWriteValidation(`--patch is not valid JSON: ${err.message}`);\n }\n throw err;\n }\n}\n\nexport function handleGoogleError(err: unknown): never {\n if (err instanceof ApiError) {\n // Covers both \"no connection\" and staging's 403 for an account this company\n // never connected — same remedy either way: connect, and pick the account.\n if (isNotConnectedError(err.code, err.message)) {\n handleConnectionError(\"google_ads\", err.message);\n }\n const terms = termsRefusalFix(err.code, err.message);\n if (terms) {\n writeJsonEnvelope({\n ok: false,\n error: { code: terms.code, message: err.message, retryable: false, fix: terms.fix },\n });\n process.exit(1);\n }\n if (err.message.includes(OP_TOO_LARGE_MESSAGE_START)) {\n writeJsonEnvelope({\n ok: false,\n error: {\n code: \"OP_TOO_LARGE\",\n message: err.message,\n retryable: false,\n fix: { action: \"split_the_change\", explanation: OP_TOO_LARGE_FIX },\n },\n });\n process.exit(1);\n }\n writeJsonEnvelope({ ok: false, error: { code: err.code ?? \"API_ERROR\", message: err.message } });\n process.exit(1);\n }\n writeJsonEnvelope({\n ok: false,\n error: { code: \"UNKNOWN\", message: err instanceof Error ? err.message : String(err) },\n });\n process.exit(1);\n}\n\ninterface StageEnvelope extends Record<string, unknown> {\n ok: true;\n data: GoogleDraftStageResponse;\n}\n\n/**\n * A no-op response isn't a failure — the change was already in place, so nothing was\n * staged. Say so explicitly, otherwise the agent re-stages it or reports work it\n * didn't do.\n */\nfunction noopHints(data: GoogleDraftStageResponse): string[] {\n return data.staged\n ? []\n : [`NOT STAGED — ${data.reason}. Nothing to publish for this change; say so rather than reporting it as done.`];\n}\n\n/**\n * What to tell the agent about the ops a batch didn't stage.\n *\n * Two different things land in `skipped`: a change already in place, and a change Google refuses\n * outright (editing an experiment campaign's status, dates or budget). This hint used to assert the\n * first — \"already in the requested state\" — which read as \"done\" for a refusal the user never heard\n * about. So it leads with each op's own reason and asks for the refusals to be passed on.\n */\nexport function skippedHints(skipped: Array<{ summary: string; reason: string }>, total: number): string[] {\n if (skipped.length === 0) {\n return [];\n }\n const detail = skipped.map((s) => `${s.summary} — ${s.reason}`).join(\"; \");\n return [\n `${skipped.length} of ${total} op(s) NOT STAGED: ${detail}. Don't re-stage them; tell the user which changes couldn't be made and why, then carry on with the rest.`,\n ];\n}\n\n/**\n * Preflight-validates the op against the shared `@baker/api/ads-google`\n * contract (the same schema the backend enforces), then stages it. Prints the\n * staged envelope — ref, mode (always `live`: publishing reaches the real\n * account), summary, warnings.\n */\nexport async function stageGoogleOp(raw: Record<string, unknown>, hints?: string[]): Promise<void> {\n const preflight = googleDraftOpInputSchema.safeParse(raw);\n if (!preflight.success) {\n const fields = preflight.error.issues.map((issue) => ({ path: issue.path.join(\".\"), message: issue.message }));\n writeJsonEnvelope({\n ok: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: fields.map((f) => (f.path ? `${f.path}: ${f.message}` : f.message)).join(\"; \"),\n fields,\n fix: copyListCountFix(raw, preflight.error.issues) ?? payloadFix(\"google-ads\", googleDraftOpInputSchema, raw),\n },\n });\n process.exit(1);\n }\n try {\n const chatId = requireChatId();\n const response = await apiPost<StageEnvelope>(\"/api/ads/google/draft/stage\", { chatId, op: preflight.data });\n const allHints = [\n ...noopHints(response.data),\n ...recordSingleStage(chatId, preflight.data.kind),\n ...(hints ?? []),\n ...(await adCopyHints(preflight.data)),\n ];\n writeJsonEnvelope(withHints(response, allHints));\n } catch (err) {\n handleGoogleError(err);\n }\n}\n\n/** Stage a create op: `{ kind, customerId, payload }`. */\nexport async function stageCreate(\n kind: string,\n customerId: string,\n payload: Record<string, unknown>,\n hints?: string[],\n): Promise<void> {\n await stageGoogleOp({ kind, customerId, payload }, hints);\n}\n\ninterface StageBatchEnvelope extends Record<string, unknown> {\n ok: true;\n data: GoogleDraftStageBatchResponse;\n}\n\n/**\n * A batch reads every target's before-state and checks each op against the account, which for a few\n * hundred removals outlasts the default 60 s. Timing out early reported a batch as failed that went on\n * to stage, so a retry then collided with the ops it had already staged.\n */\nconst STAGE_BATCH_TIMEOUT_MS = 5 * 60_000;\n\n/**\n * Stage several ops as ONE request (`/stage-batch`) — all-or-nothing on the backend,\n * one rate-limit hit instead of one per keyword. A single op falls through to the\n * plain stage path so its envelope (and amend behavior) stays exactly as before.\n */\nexport async function stageGoogleOps(rawOps: Array<Record<string, unknown>>, hints?: string[]): Promise<void> {\n if (rawOps.length === 1 && rawOps[0]) {\n await stageGoogleOp(rawOps[0], hints);\n return;\n }\n const ops: unknown[] = [];\n for (const [index, raw] of rawOps.entries()) {\n const preflight = googleDraftOpInputSchema.safeParse(raw);\n if (!preflight.success) {\n const fields = preflight.error.issues.map((issue) => ({\n path: `ops[${index}].${issue.path.join(\".\")}`,\n message: issue.message,\n }));\n writeJsonEnvelope({\n ok: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: fields.map((f) => `${f.path}: ${f.message}`).join(\"; \"),\n fields,\n fix: copyListCountFix(raw, preflight.error.issues) ?? payloadFix(\"google-ads\", googleDraftOpInputSchema, raw),\n },\n });\n process.exit(1);\n }\n ops.push(preflight.data);\n }\n try {\n const chatId = requireChatId();\n const response = await apiPost<StageBatchEnvelope>(\n \"/api/ads/google/draft/stage-batch\",\n { chatId, ops },\n { timeoutMs: STAGE_BATCH_TIMEOUT_MS },\n );\n const allHints = [\n ...skippedHints(response.data.skipped ?? [], ops.length),\n ...(hints ?? []),\n ...(await adCopyHints(ops)),\n ];\n writeJsonEnvelope(withHints(response, allHints));\n } catch (err) {\n handleGoogleError(err);\n }\n}\n\n/** Stage an update op: `{ kind, customerId, target, payload }`. */\nexport async function stageUpdate(\n kind: string,\n customerId: string,\n target: string,\n payload: Record<string, unknown>,\n hints?: string[],\n): Promise<void> {\n await stageGoogleOp({ kind, customerId, target, payload }, hints);\n}\n\n/** Stage a target-only op (pause/resume/remove/detach): `{ kind, customerId, target }`. */\nexport async function stageTarget(kind: string, customerId: string, target: string, hints?: string[]): Promise<void> {\n await stageGoogleOp({ kind, customerId, target }, hints);\n}\n\nexport async function draftAction(path: string, body: Record<string, unknown>, chat?: unknown): Promise<void> {\n try {\n const chatId = resolveChatId(chat);\n const response = await apiPost<Record<string, unknown>>(path, { chatId, ...body });\n writeJsonEnvelope(response);\n } catch (err) {\n handleGoogleError(err);\n }\n}\n\ninterface DraftListEnvelope extends Record<string, unknown> {\n ok: boolean;\n data?: GoogleDraftListResponse;\n}\n\n/**\n * List the staged draft. By default renders a readable campaign ▸ ad group ▸ ad status tree so the\n * agent can review everything it has staged at a glance; `--json` returns the raw envelope for tools,\n * and `--full` that envelope with every op's payload.\n */\nexport async function draftListAction(json: boolean, chat?: unknown, full = false, customerId?: string): Promise<void> {\n try {\n const chatId = resolveChatId(chat);\n const listed = await apiPost<DraftListEnvelope>(\"/api/ads/google/draft\", {\n chatId,\n ...(full ? { full: true } : {}),\n });\n const inAccount =\n customerId !== undefined && listed.ok && listed.data ? draftInAccount(listed.data, customerId) : undefined;\n const response = inAccount ? withHints({ ...listed, data: inAccount.data }, inAccount.hints) : listed;\n if (json || full || !response.ok || !response.data) {\n writeJsonEnvelope(response);\n return;\n }\n process.stdout.write(`${inAccount?.empty ?? renderDraftStatus(response.data)}${hintLines(response)}\\n`);\n } catch (err) {\n handleGoogleError(err);\n }\n}\n\nexport const customerIdArg = {\n \"customer-id\": { type: \"string\" as const, description: \"10-digit Google Ads customer id\" },\n};\n/** The entity a command changes, read by position the way `requireTarget` reads it — declared so --help shows it. */\nexport function targetArg(entity: string) {\n return {\n id: {\n type: \"positional\" as const,\n description: `The ${entity} to change: its id or resource name (customers/…)`,\n required: false,\n },\n };\n}\n\nconst PAYLOAD_FIELDS = \"run `baker schema` on this command for every field the payload takes\";\n\nfunction payloadShape(kind: string): () => string | undefined {\n return () => describeOpPayload(googleDraftOpInputSchema, kind);\n}\n\n/** `--file` for a command that stages one `kind` op; `baker schema` prints that op's payload shape. */\nexport function fileArg(kind: string) {\n return {\n file: payloadArg(\n {\n type: \"string\" as const,\n description: `The op payload: a JSON file path, or the JSON object inline (flags override it) — ${PAYLOAD_FIELDS}`,\n },\n payloadShape(kind),\n ),\n };\n}\n\n/** `--file` that stages one or many `kind` ops; `baker schema` prints that op's payload shape. */\nexport function fileBatchArg(kind: string) {\n return {\n file: payloadArg(\n {\n type: \"string\" as const,\n description: `The op payload: a JSON file path or inline JSON — one object, or a JSON array / JSON Lines of objects to stage many in one call (up to 500) — ${PAYLOAD_FIELDS}`,\n },\n payloadShape(kind),\n ),\n };\n}\n\n/** A create command whose payload is a JSON file or inline JSON — one object or an array staged as one batch. */\nexport function fileCreateCommand(\n name: string,\n kind: string,\n description: string,\n hints?: string[] | ((payload: Record<string, unknown>) => string[]),\n) {\n return defineCommand({\n meta: { name, description },\n args: { ...customerIdArg, ...fileBatchArg(kind) },\n run: async ({ args }) => {\n const customerId = requireCustomerId(args);\n const payloads = loadJsonPayloads(args.file);\n if (payloads.length === 0 || payloads.some((payload) => Object.keys(payload).length === 0)) {\n failWriteValidation(`${kind} needs --file with the op payload (one object, or a JSON array to stage many)`);\n }\n if (payloads.length > GOOGLE_DRAFT_BATCH_MAX) {\n failWriteValidation(\n `${payloads.length} payloads exceeds the batch limit of ${GOOGLE_DRAFT_BATCH_MAX} — split the file`,\n );\n }\n const payloadHints = [\n ...new Set(payloads.flatMap((payload) => (typeof hints === \"function\" ? hints(payload) : (hints ?? [])))),\n ];\n await stageGoogleOps(\n payloads.map((payload) => ({ kind, customerId, payload })),\n payloadHints,\n );\n },\n });\n}\n\n/**\n * `--bid-modifier` as a number, validated against the range Google actually accepts.\n *\n * Rejecting the (0, 0.1) gap here rather than at the backend is the point: 0 and 0.1 are a hundredth\n * apart in value and mean \"never serve\" versus \"bid a tenth\", so a typo between them is a real spend\n * decision, and the error should arrive before anything is staged.\n */\nexport function bidModifierFlag(value: unknown): number {\n if (value === undefined || value === null || value === \"\") {\n failWriteValidation(\n \"--bid-modifier is required: 1 for no adjustment, 1.25 to bid 25% more, 0.9 to bid 10% less, 0 to stop serving on a device\",\n );\n }\n const num = Number(value);\n if (Number.isNaN(num) || num < 0 || num > 10 || (num > 0 && num < 0.1)) {\n failWriteValidation(\"--bid-modifier must be 0 (stop serving on it) or between 0.1 and 10.0\");\n }\n return num;\n}\n","import type { z } from \"zod\";\n\n/** Appended to each copy flag's help, so `--help` and `baker schema` say how to pass a comma. */\nexport const AD_COPY_LIST_USAGE =\n \"repeat the flag once per item, or pass one comma-separated list and wrap an item that contains a comma in double quotes: '\\\"Fast, free quotes\\\",Book today'. A flag given once is read as a list, so a lone item with a comma needs its double quotes too: '\\\"Fast, free quotes\\\"'\";\n\n/** The op's copy-list fields, by the flag an agent passes them with. */\nconst COPY_LIST_FLAG: Readonly<Record<string, string>> = {\n headlines: \"--headlines\",\n descriptions: \"--descriptions\",\n longHeadlines: \"--long-headlines\",\n};\n\ninterface CountRefusal {\n flag: string;\n count: number | undefined;\n bound: string;\n tooMany: boolean;\n}\n\nfunction valueAt(root: unknown, path: readonly PropertyKey[]): unknown {\n let node = root;\n for (const key of path) {\n if (node === null || typeof node !== \"object\") return undefined;\n node = (node as Record<PropertyKey, unknown>)[key];\n }\n return node;\n}\n\nfunction countRefusal(op: unknown, issue: z.core.$ZodIssue): CountRefusal | undefined {\n if ((issue.code !== \"too_big\" && issue.code !== \"too_small\") || issue.origin !== \"array\") return undefined;\n const flag = COPY_LIST_FLAG[String(issue.path.at(-1))];\n if (!flag) return undefined;\n const value = valueAt(op, issue.path);\n const count = Array.isArray(value) ? value.length : undefined;\n return issue.code === \"too_big\"\n ? { flag, count, bound: `at most ${issue.maximum}`, tooMany: true }\n : { flag, count, bound: `at least ${issue.minimum}`, tooMany: false };\n}\n\n/**\n * The fix for a stage refused only for how many headlines, descriptions or long headlines it has.\n *\n * That is a limit on a field the agent named correctly, so the generic \"read `baker capabilities`,\n * do not guess another field name\" sent it through draft list, schema and --help before it saw the\n * cause. Too many items is usually copy cut at its commas — a flag given once is a comma-separated\n * list, so \"Rated 4,5/5\" becomes two items — which is why that case names the forms that keep a\n * comma. Undefined when anything else was refused too, so the capabilities pointer still covers it.\n */\nexport function copyListCountFix(\n op: unknown,\n issues: readonly z.core.$ZodIssue[],\n): { action: string; explanation: string } | undefined {\n const refusals = issues.map((issue) => countRefusal(op, issue));\n if (refusals.length === 0 || refusals.some((refusal) => refusal === undefined)) return undefined;\n const counted = refusals.filter((refusal): refusal is CountRefusal => refusal !== undefined);\n const limits = counted\n .map(({ flag, count, bound }) => `${flag} has ${count ?? \"the wrong number of\"} items; this ad takes ${bound}.`)\n .join(\" \");\n const commaCause = counted.some((refusal) => refusal.tooMany)\n ? ' If you meant fewer items, the copy was cut at its commas: a flag given once is read as a comma-separated list, so an item with a comma in it (\"Fast, free quotes\", a decimal like \"4,5/5\") becomes two. To keep a comma, ' +\n `${AD_COPY_LIST_USAGE} — or put the content in --file as JSON.`\n : \"\";\n return {\n action: \"fix_copy_list_count\",\n explanation: `${limits}${commaCause} ${commaCause ? \"Otherwise add\" : \"Add\"} or drop items to fit the limit, then stage again; the field names are right.`,\n };\n}\n","import type {\n GoogleDraftListResponse,\n GoogleDraftStatusCollection,\n GoogleDraftStatusNode,\n} from \"@baker/api/ads-google\";\n\n/**\n * Render the staged Google Ads draft as a readable campaign ▸ ad group ▸ ad tree — the CLI\n * counterpart to the dashboard's Google Ads tab, so an agent can review everything it has staged\n * with one command instead of reading raw JSON. Pure: takes the draft-list response, returns text.\n */\n\nconst OPERATION_LABELS: Record<string, string> = {\n create: \"Creating\",\n update: \"Updating\",\n pause: \"Pausing\",\n resume: \"Resuming\",\n remove: \"Removing\",\n};\n\nfunction badge(node: GoogleDraftStatusNode): string {\n const label = node.operation ? OPERATION_LABELS[node.operation] : undefined;\n if (label) {\n return label;\n }\n return node.existing ? \"on Google Ads\" : \"staged\";\n}\n\nfunction collectionLine(collection: GoogleDraftStatusCollection): string | undefined {\n const parts: string[] = [];\n if (collection.added > 0) {\n parts.push(`+${collection.added}`);\n }\n if (collection.existing > 0) {\n parts.push(`${collection.existing} existing`);\n }\n if (collection.removed > 0) {\n parts.push(`-${collection.removed}`);\n }\n // Neither an add nor a remove — a member that stays in the set with a changed value (a device's\n // bid adjustment). Left out, a draft whose only criterion change is one printed no line at all.\n if ((collection.updated ?? 0) > 0) {\n parts.push(`~${collection.updated}`);\n }\n return parts.length > 0 ? `${collection.label}: ${parts.join(\", \")}` : undefined;\n}\n\n/**\n * Every advertiser account the tree touches. A draft may legitimately span\n * several, and a criterion id is derived from the keyword's text and match type\n * — so the same keyword carries the same id in every account. The account is\n * printed on each node once there is more than one, and left off when there is not.\n */\nfunction accountsInTree(nodes: readonly GoogleDraftStatusNode[]): Set<string> {\n const accounts = new Set<string>();\n const walk = (node: GoogleDraftStatusNode): void => {\n accounts.add(node.customerId);\n for (const child of node.children) {\n walk(child);\n }\n };\n for (const node of nodes) {\n walk(node);\n }\n return accounts;\n}\n\nfunction renderNode(node: GoogleDraftStatusNode, depth: number, lines: string[], showAccount: boolean): void {\n const indent = \" \".repeat(depth);\n const account = showAccount ? ` · account ${node.customerId}` : \"\";\n lines.push(`${indent}• ${node.name} · ${badge(node)}${account}`);\n for (const warning of node.warnings ?? []) {\n lines.push(`${indent} ⚠ ${warning}`);\n }\n for (const collection of node.collections) {\n const line = collectionLine(collection);\n if (line) {\n lines.push(`${indent} ${line}`);\n }\n }\n for (const child of node.children) {\n renderNode(child, depth + 1, lines, showAccount);\n }\n}\n\nconst RESOURCE_ENTITIES = new Set([\n \"sharedSet\",\n \"biddingStrategy\",\n \"audience\",\n \"conversionAction\",\n \"label\",\n \"budget\",\n \"asset\",\n]);\n\nfunction renderSection(title: string, nodes: GoogleDraftStatusNode[], lines: string[], showAccount: boolean): void {\n if (nodes.length === 0) {\n return;\n }\n lines.push(\"\");\n lines.push(title);\n for (const node of nodes) {\n renderNode(node, 1, lines, showAccount);\n }\n}\n\nfunction renderAdvisories(data: GoogleDraftListResponse, lines: string[]): void {\n const advisories = data.advisories ?? [];\n // Every advisory is phrased as something to settle BEFORE publishing. On a published draft there\n // is nothing left to settle here, and printing them reads as work still outstanding on this chat.\n if (advisories.length === 0 || data.status === \"applied\") {\n return;\n }\n lines.push(\"\");\n // Two kinds of finding share this block: build-out nudges (\"this ad group has one ad\") and\n // whole-draft coherence findings (\"this campaign will report zero conversions\", \"two of your\n // campaigns bid on the same keyword\"). The coherence ones come first and cost money, so the\n // heading no longer frames the block as optional polish.\n lines.push(\"Advisories (non-blocking, checked across the whole draft — settle these before you publish)\");\n for (const advisory of advisories) {\n lines.push(` • ${advisory.message}`);\n }\n}\n\n/**\n * What the publish did, once there is a publish to report.\n *\n * A published Google Ads draft rendered exactly like a staged one — same count, every op still\n * reading \"Creating\" — so the surface `.claude/rules/cli-env.md` sends an agent to for the outcome\n * could not answer, and a failed op was invisible in the human output while sitting in the JSON\n * envelope the whole time. Found by publishing a BUSINESS_LOGO link that Google refused with\n * \"Customer is not verified\": the draft still read \"4 staged changes\". LinkedIn fixed the same\n * shape first; this is its counterpart.\n */\nfunction renderOutcome(data: GoogleDraftListResponse, lines: string[]): void {\n const counts = new Map<string, number>();\n for (const op of data.ops) {\n const status = op.result?.status ?? \"pending\";\n counts.set(status, (counts.get(status) ?? 0) + 1);\n }\n const summary = [...counts].map(([status, count]) => `${count} ${status}`).join(\", \");\n lines.push(`Google Ads — published · ${summary || `${data.count} op(s)`} · ${data.mode}`);\n // A failure is the reason to read this at all, so it is named with its error rather than left as\n // a number to go and look up.\n for (const op of data.ops) {\n const status = op.result?.status;\n if (status !== \"failed\" && status !== \"skipped\") {\n continue;\n }\n const reason = op.result?.error ?? op.result?.skippedBecause ?? \"no reason recorded\";\n lines.push(` ✗ ${op.summary} — ${status}: ${reason}`);\n }\n}\n\nexport function renderDraftStatus(data: GoogleDraftListResponse): string {\n if (data.status === \"none\" || data.count === 0) {\n return \"No staged Google Ads changes on this chat.\";\n }\n const modeNote =\n data.mode === \"live\"\n ? \"Live — publishing writes directly to Google Ads.\"\n : \"Simulated — publishing completes the whole draft without calling Google Ads.\";\n const lines: string[] = [];\n if (data.status === \"applied\") {\n renderOutcome(data, lines);\n } else if (data.status === \"publishing\") {\n lines.push(`Google Ads — publishing ${data.count} change${data.count === 1 ? \"\" : \"s\"} · ${data.mode}`);\n } else {\n lines.push(`Google Ads — ${data.count} staged change${data.count === 1 ? \"\" : \"s\"} · ${data.mode}`);\n }\n lines.push(modeNote);\n\n const tree = data.tree ?? [];\n const showAccount = accountsInTree(tree).size > 1;\n renderSection(\n \"Shared resources\",\n tree.filter((node) => RESOURCE_ENTITIES.has(node.entity)),\n lines,\n showAccount,\n );\n renderSection(\n \"Campaigns\",\n tree.filter((node) => node.entity === \"campaign\"),\n lines,\n showAccount,\n );\n const rest = tree.filter((node) => !RESOURCE_ENTITIES.has(node.entity) && node.entity !== \"campaign\");\n renderSection(\"Other\", rest, lines, showAccount);\n renderAdvisories(data, lines);\n\n return lines.join(\"\\n\");\n}\n","import { cacheGet, cacheSet } from \"../cache.ts\";\n\n/**\n * Bulk work done as a loop of single commands — one `keywords update` per keyword, one\n * `negative-keywords remove` per term — is one request, one rate-limit hit and one tool call per row;\n * production saw 1,284 of them. The CLI cannot see the loop from inside one call, so it remembers the\n * last few single stages per chat and, once the same kind repeats, says how to do the rest in one call.\n */\n\nexport interface StageStreak {\n kind: string;\n count: number;\n lastAt: number;\n}\n\n/** Singles further apart than this are separate decisions, not a loop. */\nconst STREAK_WINDOW_MS = 5 * 60_000;\n/** The streak length that reads as a loop. */\nconst STREAK_THRESHOLD = 5;\n\nexport function nextStageStreak(previous: StageStreak | undefined, kind: string, now: number): StageStreak {\n const continues = previous && previous.kind === kind && now - previous.lastAt <= STREAK_WINDOW_MS;\n return { kind, count: continues ? previous.count + 1 : 1, lastAt: now };\n}\n\n/** The one-call form of a kind, where a command has one beside `draft stage --file`. */\nconst BATCH_FORMS: Record<string, string> = {\n \"google.keyword.add\": \"`keywords add --file keywords.txt` (one term[:MATCH_TYPE] per line)\",\n \"google.keyword.update\":\n \"`keywords pause|resume <resource name> <resource name>…` (or --file, one per line) for status\",\n \"google.keyword.remove\": \"`draft stage --file` with one google.keyword.remove per target\",\n \"google.negativeKeyword.add\": \"`negative-keywords add --file terms.txt`\",\n \"google.sharedSetMember.add\": \"`keyword-lists add --file terms.txt`\",\n \"google.sharedSetMember.remove\": '`keyword-lists remove --list-ref <id> --text \"a,b,c\"`',\n};\n\nexport function stageStreakHints(streak: StageStreak): string[] {\n if (streak.count < STREAK_THRESHOLD) {\n return [];\n }\n const form = BATCH_FORMS[streak.kind];\n return [\n `LOOP: that is ${streak.count} ${streak.kind} ops staged one command at a time. Stop looping — put every remaining change in one file and stage them in ONE call: \\`baker ads google draft stage --file ops.json --customer-id <id>\\` (a JSON array of {\"kind\",\"target\"?,\"payload\"?}, up to 500, any mix of kinds)${form ? `, or ${form}` : \"\"}. Fix staged ops together with \\`draft amend --file\\`.`,\n ];\n}\n\nconst STREAK_CACHE = \"stage-streak\";\n\n/** Record one single stage on this chat and return the nudge it earns, if any. Never throws. */\nexport function recordSingleStage(chatId: string, kind: string): string[] {\n try {\n const streak = nextStageStreak(cacheGet<StageStreak>(STREAK_CACHE, chatId)?.data, kind, Date.now());\n cacheSet(STREAK_CACHE, chatId, streak, STREAK_WINDOW_MS);\n return stageStreakHints(streak);\n } catch {\n return [];\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -518,7 +518,7 @@ Introspection: Run 'baker schema <command>' to inspect argument schemas.`
|
|
|
518
518
|
capabilities: () => import("./capabilities-45RZ6E6W.js").then((m) => m.capabilitiesCommand),
|
|
519
519
|
actions: () => import("./actions-IR47GVZ6.js").then((m) => m.actionsCommand),
|
|
520
520
|
"scheduled-agents": () => import("./scheduled-agents-DTAEP25E.js").then((m) => m.scheduledAgentsCommand),
|
|
521
|
-
ads: () => import("./ads-
|
|
521
|
+
ads: () => import("./ads-PWTQC6KV.js").then((m) => m.adsCommand),
|
|
522
522
|
brand: () => import("./brand-LR5IXW3W.js").then((m) => m.brandCommand),
|
|
523
523
|
analytics: () => import("./analytics-NL2KGDLK.js").then((m) => m.analyticsCommand),
|
|
524
524
|
experiment: () => import("./experiment-XAYSEYTU.js").then((m) => m.experimentCommand),
|
package/dist/{conversion-upload-command-G7LLWHSK.js → conversion-upload-command-JAGV6GMG.js}
RENAMED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
failWriteValidation,
|
|
3
3
|
requireCustomerId,
|
|
4
4
|
stageGoogleOps
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-IB6VWG32.js";
|
|
6
6
|
import "./chunk-ERSX3MKH.js";
|
|
7
7
|
import "./chunk-YRWCXYWW.js";
|
|
8
8
|
import "./chunk-4NZ47VXO.js";
|
|
@@ -436,4 +436,4 @@ var conversionsUploadCommand = defineCommand({
|
|
|
436
436
|
export {
|
|
437
437
|
conversionsUploadCommand
|
|
438
438
|
};
|
|
439
|
-
//# sourceMappingURL=conversion-upload-command-
|
|
439
|
+
//# sourceMappingURL=conversion-upload-command-JAGV6GMG.js.map
|
|
@@ -9,13 +9,18 @@ import {
|
|
|
9
9
|
handleGoogleError,
|
|
10
10
|
loadJsonPayloads,
|
|
11
11
|
loadPatchArg,
|
|
12
|
+
requireCustomerId,
|
|
12
13
|
requireTarget,
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
showAccountHint,
|
|
15
|
+
stageGoogleOps,
|
|
16
|
+
validateAccountHint
|
|
17
|
+
} from "./chunk-IB6VWG32.js";
|
|
15
18
|
import "./chunk-ERSX3MKH.js";
|
|
16
19
|
import "./chunk-YRWCXYWW.js";
|
|
17
20
|
import "./chunk-4NZ47VXO.js";
|
|
18
|
-
import
|
|
21
|
+
import {
|
|
22
|
+
withHints
|
|
23
|
+
} from "./chunk-D5FY4FAK.js";
|
|
19
24
|
import "./chunk-BIBXZJZI.js";
|
|
20
25
|
import "./chunk-QALAJNBG.js";
|
|
21
26
|
import "./chunk-BMMXYR7A.js";
|
|
@@ -41,7 +46,8 @@ import {
|
|
|
41
46
|
} from "./chunk-LJEJCIHR.js";
|
|
42
47
|
import "./chunk-SIRRBWQ2.js";
|
|
43
48
|
import {
|
|
44
|
-
requireChatId
|
|
49
|
+
requireChatId,
|
|
50
|
+
resolveChatId
|
|
45
51
|
} from "./chunk-B64KJW5C.js";
|
|
46
52
|
import {
|
|
47
53
|
registerSchema
|
|
@@ -185,6 +191,11 @@ function selectDraftRefs(ops, where) {
|
|
|
185
191
|
}
|
|
186
192
|
|
|
187
193
|
// src/commands/ads/google/draft.ts
|
|
194
|
+
var DRAFT_ACCOUNT_ARG = {
|
|
195
|
+
type: "string",
|
|
196
|
+
description: "10-digit customer id. A draft is per chat and may span accounts: `list` narrows to this one; `validate` and `show` still answer for the whole draft",
|
|
197
|
+
required: false
|
|
198
|
+
};
|
|
188
199
|
var VALIDATE_DESCRIPTION = `Ask Google whether the whole staged draft would publish \u2014 without applying anything.
|
|
189
200
|
|
|
190
201
|
Start here, after the last staged change and before telling the user the draft is ready:
|
|
@@ -214,7 +225,8 @@ registerSchema({
|
|
|
214
225
|
type: "boolean",
|
|
215
226
|
description: "List every op with its built request and Google's raw answer",
|
|
216
227
|
required: false
|
|
217
|
-
}
|
|
228
|
+
},
|
|
229
|
+
"customer-id": DRAFT_ACCOUNT_ARG
|
|
218
230
|
}
|
|
219
231
|
});
|
|
220
232
|
var STAGE_DESCRIPTION = `Stage many Google Ads changes from ONE file, in one call \u2014 any mix of kinds, up to 500 ops.
|
|
@@ -296,7 +308,8 @@ registerSchema({
|
|
|
296
308
|
args: {
|
|
297
309
|
json: { type: "boolean", description: "Print the raw JSON envelope instead of the readable tree", required: false },
|
|
298
310
|
full: { type: "boolean", description: DRAFT_FULL_ARG.description, required: false },
|
|
299
|
-
chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
|
|
311
|
+
chat: { type: "string", description: CHAT_READ_ARG.description, required: false },
|
|
312
|
+
"customer-id": DRAFT_ACCOUNT_ARG
|
|
300
313
|
}
|
|
301
314
|
});
|
|
302
315
|
var WHERE_ARGS = {
|
|
@@ -474,10 +487,12 @@ var draftCommand = defineCommand({
|
|
|
474
487
|
args: {
|
|
475
488
|
json: { type: "boolean", description: "Print the raw JSON envelope instead of the readable tree" },
|
|
476
489
|
full: DRAFT_FULL_ARG,
|
|
477
|
-
chat: CHAT_READ_ARG
|
|
490
|
+
chat: CHAT_READ_ARG,
|
|
491
|
+
"customer-id": DRAFT_ACCOUNT_ARG
|
|
478
492
|
},
|
|
479
493
|
run: async ({ args }) => {
|
|
480
|
-
|
|
494
|
+
const customerId = args["customer-id"] === void 0 ? void 0 : requireCustomerId(args);
|
|
495
|
+
await draftListAction(args.json === true, args.chat, args.full === true, customerId);
|
|
481
496
|
}
|
|
482
497
|
}),
|
|
483
498
|
remove: defineCommand({
|
|
@@ -536,7 +551,8 @@ var draftCommand = defineCommand({
|
|
|
536
551
|
validate: defineCommand({
|
|
537
552
|
meta: { name: "validate", description: VALIDATE_DESCRIPTION },
|
|
538
553
|
args: {
|
|
539
|
-
full: { type: "boolean", description: "List every op with its built request and Google's raw answer" }
|
|
554
|
+
full: { type: "boolean", description: "List every op with its built request and Google's raw answer" },
|
|
555
|
+
"customer-id": DRAFT_ACCOUNT_ARG
|
|
540
556
|
},
|
|
541
557
|
run: async ({ args }) => {
|
|
542
558
|
try {
|
|
@@ -545,7 +561,15 @@ var draftCommand = defineCommand({
|
|
|
545
561
|
"/api/ads/google/draft/validate",
|
|
546
562
|
{ chatId: requireChatId(), full }
|
|
547
563
|
);
|
|
548
|
-
|
|
564
|
+
if (!response.ok || !response.data) {
|
|
565
|
+
writeJsonEnvelope(response);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
const envelope = validationEnvelope(response.data, full);
|
|
569
|
+
const customerId = args["customer-id"];
|
|
570
|
+
writeJsonEnvelope(
|
|
571
|
+
typeof customerId === "string" ? { ...envelope, hints: [...envelope.hints, validateAccountHint(customerId)] } : envelope
|
|
572
|
+
);
|
|
549
573
|
} catch (err) {
|
|
550
574
|
handleGoogleError(err);
|
|
551
575
|
}
|
|
@@ -562,10 +586,22 @@ var draftCommand = defineCommand({
|
|
|
562
586
|
description: "Staged op ref (g_temp_*) or target id/resource name",
|
|
563
587
|
required: false
|
|
564
588
|
},
|
|
565
|
-
chat: CHAT_READ_ARG
|
|
589
|
+
chat: CHAT_READ_ARG,
|
|
590
|
+
"customer-id": DRAFT_ACCOUNT_ARG
|
|
566
591
|
},
|
|
567
592
|
run: async ({ args }) => {
|
|
568
|
-
|
|
593
|
+
const ref = requireTarget(args, "op");
|
|
594
|
+
const customerId = args["customer-id"];
|
|
595
|
+
try {
|
|
596
|
+
const response = await apiPost(
|
|
597
|
+
"/api/ads/google/draft/show",
|
|
598
|
+
{ chatId: resolveChatId(args.chat), ref }
|
|
599
|
+
);
|
|
600
|
+
const hint = typeof customerId === "string" && response.data ? showAccountHint(customerId, response.data.op.customerId) : void 0;
|
|
601
|
+
writeJsonEnvelope(hint ? withHints(response, [hint]) : response);
|
|
602
|
+
} catch (err) {
|
|
603
|
+
handleGoogleError(err);
|
|
604
|
+
}
|
|
569
605
|
}
|
|
570
606
|
})
|
|
571
607
|
}
|
|
@@ -575,4 +611,4 @@ export {
|
|
|
575
611
|
draftCommand,
|
|
576
612
|
importHints
|
|
577
613
|
};
|
|
578
|
-
//# sourceMappingURL=draft-
|
|
614
|
+
//# sourceMappingURL=draft-GSDDK6FO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/ads/google/draft.ts","../src/commands/ads/google/draft-batch.ts","../src/commands/ads/google/draft-validate.ts","../src/commands/ads/google/draft-where.ts"],"sourcesContent":["import type {\n GoogleDraftListResponse,\n GoogleDraftShowResponse,\n GoogleDraftValidateResponse,\n} from \"@baker/api/ads-google\";\nimport type { GoogleDraftImportResponse } from \"@baker/api/ads-google/wire\";\nimport { defineCommand } from \"citty\";\nimport { apiPost } from \"../../../client.ts\";\nimport { requireChatId, resolveChatId } from \"../../../env.ts\";\nimport { registerSchema } from \"../../../schemas.ts\";\nimport { CHAT_READ_ARG, DRAFT_FULL_ARG } from \"../../chat-arg.ts\";\nimport { draftRefs, removeDraftRefs } from \"../../draft-refs.ts\";\nimport { withHints } from \"../hints.ts\";\nimport { writeJsonEnvelope } from \"../output.ts\";\nimport { showAccountHint, validateAccountHint } from \"./draft-account.ts\";\nimport { amendBatchEntries, stageFileOps } from \"./draft-batch.ts\";\nimport { validationEnvelope } from \"./draft-validate.ts\";\nimport { parseDraftWhere, selectDraftRefs } from \"./draft-where.ts\";\nimport {\n draftAction,\n draftListAction,\n failWriteValidation,\n handleGoogleError,\n loadJsonPayloads,\n loadPatchArg,\n requireCustomerId,\n requireTarget,\n stageGoogleOps,\n} from \"./write-shared.ts\";\n\n/** Accepted on the draft reads, as on every other Google Ads command; the draft itself is the chat's. */\nconst DRAFT_ACCOUNT_ARG = {\n type: \"string\",\n description:\n \"10-digit customer id. A draft is per chat and may span accounts: `list` narrows to this one; `validate` and `show` still answer for the whole draft\",\n required: false,\n} as const;\n\nconst VALIDATE_DESCRIPTION = `Ask Google whether the whole staged draft would publish — without applying anything.\n\nStart here, after the last staged change and before telling the user the draft is ready:\n baker ads google draft validate\n\nEvery staged op is built, batched and sent exactly as publish sends it, with Google's validateOnly\nflag, so each verdict is the answer publish would get: accepted | refused | blocked (a change it\ndepends on was refused) | unchecked (only publish can check it) | applied (an earlier publish did).\nCompact by default — counts, plus only the ops that need attention, each with Google's reason.\nFix refused ops with \\`draft amend <ref>\\` and validate again.\n\nIt also reads Baker's day of Google Ads changes, shared by every company. When the draft does\nnot all fit in what is left, the answer carries dailyLimit: operations, room and fitsAt (ISO, when\nall of it should have gone). Publish still works: Baker sends what Google Ads takes and the rest\nagain on its own. Never mention the limit, a quota or operation counts to the user, and never ask\nthem to cut down or split their work for it; say only that some changes go live over the next\nhours, and by when.\n\nExamples:\n baker ads google draft validate\n baker ads google draft validate --full # every op, with its built request and Google's raw answer`;\n\nregisterSchema({\n command: \"ads.google.draft.validate\",\n description: VALIDATE_DESCRIPTION,\n args: {\n full: {\n type: \"boolean\",\n description: \"List every op with its built request and Google's raw answer\",\n required: false,\n },\n \"customer-id\": DRAFT_ACCOUNT_ARG,\n },\n});\n\nconst STAGE_DESCRIPTION = `Stage many Google Ads changes from ONE file, in one call — any mix of kinds, up to 500 ops.\n\nStart here whenever a change touches more than a few rows (a keyword cleanup, bid changes, pausing\nads, a list of negatives): write the ops to a file and stage them together instead of running one\ncommand per row.\n baker ads google draft stage --file changes.json --customer-id 1234567890\n\nThe file is a JSON array (or JSON Lines, one op per line). Each op is what the single commands\nbuild — kind, customerId (or --customer-id for all), target for a change to something that exists,\npayload. Read the shape for a kind with \\`baker capabilities google-ads\\`.\n [\n {\"kind\":\"google.keyword.add\",\"payload\":{\"adGroup\":\"customers/1234567890/adGroups/111\",\"text\":\"solar panels\",\"matchType\":\"PHRASE\"}},\n {\"kind\":\"google.keyword.update\",\"target\":\"customers/1234567890/adGroupCriteria/111~222\",\"payload\":{\"status\":\"PAUSED\"}},\n {\"kind\":\"google.negativeKeyword.add\",\"payload\":{\"level\":\"campaign\",\"parent\":\"222333\",\"text\":\"free\",\"matchType\":\"BROAD\"}}\n ]\nAn op may name a g_temp_* ref an EARLIER call returned (a campaign or ad group staged before), but\nnot another op in the same file — a ref exists only once its op is staged, so stage new parents first.\nAll-or-nothing: one invalid op stages nothing. Ops already in effect come back as skipped.\nThen: baker ads google draft validate`;\n\nregisterSchema({\n command: \"ads.google.draft.stage\",\n description: STAGE_DESCRIPTION,\n args: {\n file: {\n type: \"string\",\n description: \"JSON array or JSON Lines of ops ({kind, customerId?, target?, payload?}), path or inline\",\n required: true,\n },\n \"customer-id\": {\n type: \"string\",\n description: \"10-digit customer id for every op that does not name its own\",\n required: false,\n },\n },\n});\n\nconst IMPORT_DESCRIPTION = `Move another unpublished chat's staged Google Ads changes into this chat, in one call — and out of that chat, so the two can never publish the same change twice.\n\nStart here when the user wants another chat's changes brought into this one (\"bring over\", \"merge\",\n\"include what the other chat prepared\", \"all of them except …\"):\n baker ads google draft import --from-chat <chat-id>\n baker ads google draft import --from-chat <chat-id> --exclude g_temp_a,customers/123/adGroupAds/4~5\n baker ads google draft import --from-chat <chat-id> --refs g_temp_b\n\nEvery op moves exactly as it was staged — same ref, same payload, nothing retyped — so never copy\nanother chat's changes with \\`draft show --chat\\` and re-stage them. An op moves with the creates it\ndepends on and the ops that depend on it (an asset and the link that attaches it go together).\nThe other chat must be unpublished — still open, or its publish failed (a discarded chat's Google Ads\nchanges are gone with it); its other work (Knowledge, Tasks) stays where it is. A published chat's changes its publish could not apply come\nwith \\`baker chats import --from-chat <chat-id> --failed\\` instead. Find chat ids\nwith \\`baker chats list\\`, and refs with \\`baker ads google draft list --chat <chat-id> --json\\`.\nThen: baker ads google draft validate`;\n\nregisterSchema({\n command: \"ads.google.draft.import\",\n description: IMPORT_DESCRIPTION,\n args: {\n \"from-chat\": {\n type: \"string\",\n description: \"The other unpublished chat (open, or its publish failed) whose staged changes move here\",\n required: true,\n },\n refs: {\n type: \"string\",\n description: \"Comma-separated refs to move (default: all); what they depend on comes too\",\n required: false,\n },\n exclude: {\n type: \"string\",\n description: \"Comma-separated refs to leave in the other chat, with the ops that depend on them\",\n required: false,\n },\n },\n});\n\nconst LIST_DESCRIPTION =\n \"Review everything staged as a campaign ▸ ad group ▸ ad tree (--json for the raw envelope, --full for every op's payload too)\";\n\nregisterSchema({\n command: \"ads.google.draft.list\",\n description: LIST_DESCRIPTION,\n args: {\n json: { type: \"boolean\", description: \"Print the raw JSON envelope instead of the readable tree\", required: false },\n full: { type: \"boolean\", description: DRAFT_FULL_ARG.description, required: false },\n chat: { type: \"string\", description: CHAT_READ_ARG.description, required: false },\n \"customer-id\": DRAFT_ACCOUNT_ARG,\n },\n});\n\nconst WHERE_ARGS = {\n where: {\n type: \"string\",\n description:\n \"Select staged ops instead of naming refs: comma-separated key=value — kind (op kind), under (a campaign/ad group ref or resource name the op sits under), or any payload field (matchType, text, level, parent…); `|` for alternatives\",\n required: false,\n },\n \"dry-run\": { type: \"boolean\", description: \"Print the refs that would change, and change nothing\", required: false },\n} as const;\n\nconst REMOVE_DESCRIPTION = `Remove staged ops — one, many, or every op a selector matches, in one call. Each removal cascades to the ops that depend on it.\n\n baker ads google draft remove g_temp_a\n baker ads google draft remove g_temp_a g_temp_b g_temp_c # or g_temp_a,g_temp_b,g_temp_c\n baker ads google draft remove --where 'kind=google.negativeKeyword.add,text=twilio|baileys'\n baker ads google draft remove --where kind=google.keyword.add,under=g_temp_c1 --dry-run\n\nPrints one summary: every op removed (cascades listed once), and any ref the draft did not hold.`;\n\nconst REMOVE_ARGS = {\n ref: {\n type: \"positional\",\n description: \"Staged op ref(s) (g_temp_* or target) — several, space- or comma-separated\",\n required: false,\n },\n ...WHERE_ARGS,\n} as const;\n\nregisterSchema({ command: \"ads.google.draft.remove\", description: REMOVE_DESCRIPTION, args: REMOVE_ARGS });\n\nconst AMEND_DESCRIPTION = `Update staged ops in place — merges a JSON patch into each payload (RFC-7386: objects deep-merge, null deletes a key, arrays/scalars replace) and re-validates. Use this instead of remove + re-create.\n\n baker ads google draft amend g_temp_a --patch '{\"cpcBidMicros\":1500000}'\n baker ads google draft amend g_temp_a g_temp_b --patch '{\"status\":\"ENABLED\"}' # same patch, many refs\n baker ads google draft amend --where kind=google.keyword.add,under=g_temp_c1,matchType=EXACT --patch '{\"matchType\":\"BROAD\"}'\n baker ads google draft amend --file amends.json # [{\"ref\":\"g_temp_…\",\"patch\":{…}}, …] — a patch per ref\n\nMany refs are amended in one call, all or none: one refused amend changes nothing.`;\n\nconst AMEND_ARGS = {\n ref: {\n type: \"positional\",\n description:\n \"Staged op ref(s) (g_temp_*) or target id/resource name — several, space- or comma-separated, take the same patch; leave out to amend many from --file\",\n required: false,\n },\n file: {\n type: \"string\",\n description: 'JSON file with the patch object — or, with no ref, a JSON array of {\"ref\",\"patch\"} amends',\n required: false,\n },\n patch: { type: \"string\", description: \"Inline JSON patch object\", required: false },\n ...WHERE_ARGS,\n} as const;\n\nregisterSchema({ command: \"ads.google.draft.amend\", description: AMEND_DESCRIPTION, args: AMEND_ARGS });\n\n/**\n * The refs `--where` selects from the staged draft, or undefined without it. Exits with the reason\n * when it cannot select: a malformed selector, refs passed too, or nothing staged that matches.\n */\nasync function refsWhere(args: Record<string, unknown>): Promise<string[] | undefined> {\n if (typeof args.where !== \"string\") return undefined;\n const where = parseDraftWhere(args.where);\n if (\"error\" in where) failWriteValidation(where.error);\n if (draftRefs(args._).length > 0) failWriteValidation(\"pass refs or --where, not both\");\n let ops: GoogleDraftListResponse[\"ops\"] = [];\n try {\n const response = await apiPost<{ ok: boolean; data?: GoogleDraftListResponse }>(\"/api/ads/google/draft\", {\n chatId: requireChatId(),\n full: true,\n });\n ops = response.data?.ops ?? [];\n } catch (err) {\n handleGoogleError(err);\n }\n const refs = selectDraftRefs(ops, where);\n if (refs.length === 0) {\n writeJsonEnvelope({\n ok: false,\n error: {\n code: \"NOT_FOUND\",\n message: `--where ${args.where} matches no staged op`,\n fix: \"Read the staged ops' kinds and payload fields with `baker ads google draft list --full`, then adjust the selector.\",\n },\n });\n process.exit(1);\n }\n return refs;\n}\n\n/** `--dry-run`: the refs the command would change, printed instead of changing them. */\nfunction printDryRun(refs: string[]): void {\n writeJsonEnvelope({\n ok: true,\n data: { matched: refs.length, refs },\n hints: [\"Nothing changed. Run it again without --dry-run to apply it.\"],\n });\n}\n\n/** The refs a comma-separated flag names: undefined when the flag is absent, null when it names none. */\nfunction refList(value: unknown): string[] | undefined | null {\n if (typeof value !== \"string\") return undefined;\n const refs = value\n .split(\",\")\n .map((ref) => ref.trim())\n .filter((ref) => ref.length > 0);\n return refs.length > 0 ? refs : null;\n}\n\n/** What the agent needs after a move: the next step, and what the other chat now holds. */\nexport function importHints(fromChatId: string, data: GoogleDraftImportResponse): string[] {\n const source =\n data.sourceRemaining > 0\n ? `Chat ${fromChatId} still has ${data.sourceRemaining} Google Ads change(s) staged, which publish with that chat.`\n : `Chat ${fromChatId} has no Google Ads changes staged any more, so publishing it cannot apply these twice. Its other work stays there — never tell the user to discard it for this.`;\n return [source, \"Run `baker ads google draft validate` next.\"];\n}\n\n/** After a bulk stage: what to do next, and the reminder that the file — not a loop — is the tool. */\nexport const BULK_STAGE_HINTS = [\n \"Run `baker ads google draft validate` next: it asks Google about every staged op at once and lists only the ones it would refuse.\",\n 'To fix several staged ops, amend them together: `baker ads google draft amend --file amends.json` with [{\"ref\":\"…\",\"patch\":{…}}, …].',\n];\n\n/** Review and undo staged Google Ads ops on this chat's draft; `list`/`show` can also read another chat's. */\nexport const draftCommand = defineCommand({\n meta: {\n name: \"draft\",\n description:\n \"Stage many ops from one file, or list, validate, remove, clear, amend, or show staged Google Ads write ops for this chat. `list` and `show` take --chat <id> to read an earlier chat's changes instead; `import --from-chat <id>` moves another unpublished chat's staged changes here (open, or its publish failed).\",\n },\n subCommands: {\n stage: defineCommand({\n meta: { name: \"stage\", description: STAGE_DESCRIPTION },\n args: {\n file: {\n type: \"string\",\n description: \"JSON array or JSON Lines of ops ({kind, customerId?, target?, payload?}), path or inline\",\n },\n \"customer-id\": { type: \"string\", description: \"10-digit customer id for every op that does not name its own\" },\n },\n run: async ({ args }) => {\n const customerId = typeof args[\"customer-id\"] === \"string\" ? args[\"customer-id\"] : undefined;\n await stageGoogleOps(stageFileOps(loadJsonPayloads(args.file), customerId), BULK_STAGE_HINTS);\n },\n }),\n import: defineCommand({\n meta: { name: \"import\", description: IMPORT_DESCRIPTION },\n args: {\n \"from-chat\": {\n type: \"string\",\n description: \"The other unpublished chat (open, or its publish failed) whose staged changes move here\",\n },\n refs: { type: \"string\", description: \"Comma-separated refs to move (default: all)\" },\n exclude: { type: \"string\", description: \"Comma-separated refs to leave in the other chat\" },\n },\n run: async ({ args }) => {\n const fromChatId = typeof args[\"from-chat\"] === \"string\" ? args[\"from-chat\"].trim() : \"\";\n if (!fromChatId) {\n writeJsonEnvelope({\n ok: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: \"--from-chat is required\",\n fix: \"Name the other chat: baker ads google draft import --from-chat <chat-id> (ids from `baker chats list`).\",\n },\n });\n process.exit(1);\n }\n const refs = refList(args.refs);\n const exclude = refList(args.exclude);\n const empty = refs === null ? \"--refs\" : exclude === null ? \"--exclude\" : undefined;\n if (empty) {\n // An empty selection must never widen to \"every change\": the move takes them out of the other chat.\n writeJsonEnvelope({\n ok: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: `${empty} names no change`,\n fix: `Pass the refs, comma-separated (from \\`baker ads google draft list --chat ${fromChatId} --json\\`), or leave ${empty} off to move every change.`,\n },\n });\n process.exit(1);\n }\n try {\n const response = await apiPost<{ ok: boolean; data?: GoogleDraftImportResponse }>(\n \"/api/ads/google/draft/import\",\n { chatId: requireChatId(), fromChatId, refs: refs ?? undefined, exclude: exclude ?? undefined },\n );\n writeJsonEnvelope(\n response.ok && response.data ? { ...response, hints: importHints(fromChatId, response.data) } : response,\n );\n } catch (err) {\n handleGoogleError(err);\n }\n },\n }),\n list: defineCommand({\n meta: {\n name: \"list\",\n description: LIST_DESCRIPTION,\n },\n args: {\n json: { type: \"boolean\", description: \"Print the raw JSON envelope instead of the readable tree\" },\n full: DRAFT_FULL_ARG,\n chat: CHAT_READ_ARG,\n \"customer-id\": DRAFT_ACCOUNT_ARG,\n },\n run: async ({ args }) => {\n const customerId = args[\"customer-id\"] === undefined ? undefined : requireCustomerId(args);\n await draftListAction(args.json === true, args.chat, args.full === true, customerId);\n },\n }),\n remove: defineCommand({\n meta: { name: \"remove\", description: REMOVE_DESCRIPTION },\n args: REMOVE_ARGS,\n run: async ({ args }) => {\n const selected = await refsWhere(args);\n const refs = selected ?? draftRefs(args._);\n if (args[\"dry-run\"] === true) {\n printDryRun(refs);\n return;\n }\n if (selected || refs.length > 1) {\n await removeDraftRefs(\"/api/ads/google/draft/remove\", refs);\n return;\n }\n await draftAction(\"/api/ads/google/draft/remove\", { ref: requireTarget(args, \"op\") });\n },\n }),\n clear: defineCommand({\n meta: { name: \"clear\", description: \"Discard all staged ops on this chat\" },\n run: async () => {\n await draftAction(\"/api/ads/google/draft/clear\", {});\n },\n }),\n amend: defineCommand({\n meta: { name: \"amend\", description: AMEND_DESCRIPTION },\n args: AMEND_ARGS,\n run: async ({ args }) => {\n const selected = await refsWhere(args);\n const positional = Array.isArray(args._) ? args._[0] : undefined;\n if (!selected && !(args.ref ?? positional) && typeof args.file === \"string\" && args.file.length > 0) {\n const amends = amendBatchEntries(loadJsonPayloads(args.file));\n if (args[\"dry-run\"] === true) {\n printDryRun(amends.map((amend) => amend.ref));\n return;\n }\n await draftAction(\"/api/ads/google/draft/amend-batch\", { amends });\n return;\n }\n const refs = selected ?? draftRefs(args._);\n if (args[\"dry-run\"] === true) {\n printDryRun(refs);\n return;\n }\n if (selected || refs.length > 1) {\n const patch = loadPatchArg(args);\n await draftAction(\"/api/ads/google/draft/amend-batch\", { amends: refs.map((ref) => ({ ref, patch })) });\n return;\n }\n const ref = requireTarget(args, \"op\");\n const patch = loadPatchArg(args);\n await draftAction(\"/api/ads/google/draft/amend\", { ref, patch });\n },\n }),\n validate: defineCommand({\n meta: { name: \"validate\", description: VALIDATE_DESCRIPTION },\n args: {\n full: { type: \"boolean\", description: \"List every op with its built request and Google's raw answer\" },\n \"customer-id\": DRAFT_ACCOUNT_ARG,\n },\n run: async ({ args }) => {\n try {\n const full = args.full === true;\n const response = await apiPost<{ ok: boolean; data?: GoogleDraftValidateResponse }>(\n \"/api/ads/google/draft/validate\",\n { chatId: requireChatId(), full },\n );\n if (!response.ok || !response.data) {\n writeJsonEnvelope(response);\n return;\n }\n const envelope = validationEnvelope(response.data, full);\n const customerId = args[\"customer-id\"];\n writeJsonEnvelope(\n typeof customerId === \"string\"\n ? { ...envelope, hints: [...envelope.hints, validateAccountHint(customerId)] }\n : envelope,\n );\n } catch (err) {\n handleGoogleError(err);\n }\n },\n }),\n show: defineCommand({\n meta: {\n name: \"show\",\n description:\n \"Print the full staged payload for one op — the receipt to verify a change looks right before publish (never truncated).\",\n },\n args: {\n ref: {\n type: \"positional\",\n description: \"Staged op ref (g_temp_*) or target id/resource name\",\n required: false,\n },\n chat: CHAT_READ_ARG,\n \"customer-id\": DRAFT_ACCOUNT_ARG,\n },\n run: async ({ args }) => {\n const ref = requireTarget(args, \"op\");\n const customerId = args[\"customer-id\"];\n try {\n const response = await apiPost<{ ok: boolean; data?: GoogleDraftShowResponse }>(\n \"/api/ads/google/draft/show\",\n { chatId: resolveChatId(args.chat), ref },\n );\n const hint =\n typeof customerId === \"string\" && response.data\n ? showAccountHint(customerId, response.data.op.customerId)\n : undefined;\n writeJsonEnvelope(hint ? withHints(response, [hint]) : response);\n } catch (err) {\n handleGoogleError(err);\n }\n },\n }),\n },\n});\n","import { GOOGLE_DRAFT_BATCH_MAX } from \"@baker/api/ads-google\";\nimport { failWriteValidation } from \"./write-shared.ts\";\n\n/**\n * The bulk halves of `draft`: one file of many changes, staged or amended in ONE request. A bulk\n * change done as a loop of single commands is one request, one rate-limit hit and one tool call per\n * row — production saw 1,284 such calls — while a file is one of each for up to 500 rows.\n */\n\nfunction refuseOversized(count: number, what: string): void {\n if (count > GOOGLE_DRAFT_BATCH_MAX) {\n failWriteValidation(\n `${count} ${what} exceeds the batch limit of ${GOOGLE_DRAFT_BATCH_MAX} — split the file and run the command once per part`,\n );\n }\n}\n\n/**\n * `draft stage --file` items → ops: each item is a whole op (`kind`, `customerId`, `target` for a\n * change to something that exists, `payload`); `--customer-id` fills the account on items that leave\n * it out, so a one-account file need not repeat it. The ops are preflighted by `stageGoogleOps`.\n */\nexport function stageFileOps(\n items: ReadonlyArray<Record<string, unknown>>,\n customerId: string | undefined,\n): Array<Record<string, unknown>> {\n if (items.length === 0) {\n failWriteValidation(\n 'pass --file with the ops to stage: a JSON array (or JSON Lines) of {\"kind\",\"customerId\",\"target\"?,\"payload\"?}',\n );\n }\n refuseOversized(items.length, \"ops\");\n return items.map((item, index) => {\n if (typeof item.kind !== \"string\") {\n failWriteValidation(\n `item ${index} has no \"kind\" — every item is a whole op, e.g. {\"kind\":\"google.keyword.add\",…}`,\n );\n }\n return item.customerId === undefined && customerId ? { ...item, customerId } : item;\n });\n}\n\n/** `draft amend --file` items → the amend batch: each `{ ref, patch }` exactly as `draft amend <ref>` takes it. */\nexport function amendBatchEntries(\n items: ReadonlyArray<Record<string, unknown>>,\n): Array<{ ref: string; patch: Record<string, unknown> }> {\n refuseOversized(items.length, \"amends\");\n return items.map((item, index) => {\n const { ref, patch } = item;\n if (typeof ref !== \"string\" || ref.length === 0) {\n failWriteValidation(`item ${index} has no \"ref\" — name the staged op (g_temp_* or its target) to amend`);\n }\n if (patch === null || typeof patch !== \"object\" || Array.isArray(patch)) {\n failWriteValidation(`item ${index} has no \"patch\" object — {\"ref\":\"…\",\"patch\":{…fields to change…}}`);\n }\n return { ref, patch: patch as Record<string, unknown> };\n });\n}\n","import type { GoogleDraftOpVerdict, GoogleDraftValidateResponse } from \"@baker/api/ads-google\";\n\n/** Verdicts that need nothing from the agent — counted, never listed in the compact view. */\nconst QUIET: ReadonlySet<GoogleDraftOpVerdict[\"verdict\"]> = new Set([\"accepted\", \"applied\"]);\n\nfunction changes(n: number): string {\n return n === 1 ? \"1 change\" : `${n} changes`;\n}\n\n/** What to do next, read off the counts — the agent decides from these without re-reading every op. */\nfunction validationHints(data: GoogleDraftValidateResponse): string[] {\n const { refused, blocked, unchecked } = data.counts;\n const hints: string[] = [];\n if (refused > 0) {\n hints.push(\n `${changes(refused)} would fail at publish. Fix each refused op in place with \\`draft amend <ref>\\` (or \\`draft remove <ref>\\` if it should not exist), then run \\`draft validate\\` again before asking the user to publish. Never tell the user a refused change will go live.`,\n );\n }\n if (blocked > 0) {\n hints.push(\n `${changes(blocked)} would not apply only because a refused change they depend on fails — fixing the refused one clears them.`,\n );\n }\n if (data.publishHeld) {\n hints.push(\n \"Baker would hold this whole publish: a refused change stops a first publish from sending anything. Fix it first.\",\n );\n }\n if (unchecked > 0) {\n hints.push(\n `${changes(unchecked)} can only be checked by Google at publish (the reason is on each one). Nothing here says they will fail.`,\n );\n }\n if (refused + blocked === 0 && !data.publishHeld && data.ops.length > 0) {\n hints.push(\n \"Google accepted every change it was asked about. The draft is ready for the user to review and publish.\",\n );\n }\n if (data.dailyLimit) {\n hints.push(\n `Not every change may go live the moment the user publishes: Baker sends what Google Ads takes then and the rest again on its own, all by about ${data.dailyLimit.fitsAt}, with nothing for the user to do. If you tell the user anything, say only that some changes will go live over the next hours and by when, in their time zone. Never mention a daily limit, quota, allowance or number of operations, and never ask them to cut down or split their work for it.`,\n );\n }\n return hints;\n}\n\n/**\n * The `draft validate` envelope. Compact by default: counts for everything, and only the ops that\n * need attention listed — a 300-keyword draft that Google accepts reads as one line, not 300.\n * `full` keeps every op with its built request and Google's raw answer.\n */\nexport function validationEnvelope(\n data: GoogleDraftValidateResponse,\n full = false,\n): { ok: true; data: GoogleDraftValidateResponse; hints: string[] } {\n const ops = full ? data.ops : data.ops.filter((op) => !QUIET.has(op.verdict));\n return { ok: true, data: { ...data, ops }, hints: validationHints(data) };\n}\n","import type { GoogleDraftListResponse } from \"@baker/api/ads-google\";\n\n/**\n * `draft remove|amend --where kind=…,under=…,<payload field>=…` — pick staged ops by what they are\n * instead of by ref, so a cleanup over a whole campaign (\"every EXACT keyword under it → BROAD\") is\n * one command, not a draft dump, a script and a call per op.\n */\n\ntype DraftOp = GoogleDraftListResponse[\"ops\"][number];\n\nexport interface DraftWhere {\n /** Op kinds, `google.` prefix optional. */\n kinds: string[];\n /** Refs or resource names the op sits under: through its staged parents, or named in its payload. */\n under: string[];\n /** Top-level payload fields and the values (any of, case-insensitive) they must hold. */\n fields: Record<string, string[]>;\n}\n\n/** Parse the selector, or say what is wrong with it. `|` separates alternatives: `matchType=EXACT|PHRASE`. */\nexport function parseDraftWhere(value: string): DraftWhere | { error: string } {\n const where: DraftWhere = { kinds: [], under: [], fields: {} };\n const pairs = value\n .split(\",\")\n .map((pair) => pair.trim())\n .filter(Boolean);\n if (pairs.length === 0) return { error: \"--where names no condition\" };\n for (const pair of pairs) {\n const eq = pair.indexOf(\"=\");\n const key = pair.slice(0, eq).trim();\n const values = pair\n .slice(eq + 1)\n .split(\"|\")\n .map((v) => v.trim())\n .filter(Boolean);\n if (eq <= 0 || values.length === 0) return { error: `--where condition \"${pair}\" is not key=value` };\n if (key === \"kind\") where.kinds.push(...values.map((k) => (k.startsWith(\"google.\") ? k : `google.${k}`)));\n else if (key === \"under\") where.under.push(...values);\n else where.fields[key] = [...(where.fields[key] ?? []), ...values];\n }\n return where;\n}\n\nfunction payloadOf(op: DraftOp): Record<string, unknown> {\n return typeof op.payload === \"object\" && op.payload !== null ? (op.payload as Record<string, unknown>) : {};\n}\n\n/** Everything an op sits under: its staged parents up the chain, and the ids its payload names. */\nfunction parentsOf(op: DraftOp, byRef: Map<string, DraftOp>): Set<string> {\n const seen = new Set<string>();\n const queue = [op];\n for (let current = queue.shift(); current; current = queue.shift()) {\n const named = Object.values(payloadOf(current)).filter((v): v is string => typeof v === \"string\");\n for (const parent of [...current.dependsOn, ...named]) {\n if (seen.has(parent)) continue;\n seen.add(parent);\n const staged = byRef.get(parent);\n if (staged) queue.push(staged);\n }\n }\n return seen;\n}\n\n/** The refs of the staged ops the selector matches, in staging order. */\nexport function selectDraftRefs(ops: readonly DraftOp[], where: DraftWhere): string[] {\n const byRef = new Map(ops.map((op) => [op.ref, op]));\n return ops\n .filter((op) => {\n if (where.kinds.length > 0 && !where.kinds.includes(op.kind)) return false;\n if (where.under.length > 0) {\n const parents = parentsOf(op, byRef);\n if (!where.under.some((ref) => parents.has(ref))) return false;\n }\n const payload = payloadOf(op);\n return Object.entries(where.fields).every(([key, values]) => {\n const actual = payload[key];\n if (typeof actual !== \"string\" && typeof actual !== \"number\" && typeof actual !== \"boolean\") return false;\n return values.some((v) => v.toLowerCase() === String(actual).toLowerCase());\n });\n })\n .map((op) => op.ref);\n}\n"],"mappings":"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","names":["patch","ref"]}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
keywordEntries,
|
|
7
7
|
microsFlag,
|
|
8
8
|
requireCustomerId
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-IB6VWG32.js";
|
|
10
10
|
import "./chunk-ERSX3MKH.js";
|
|
11
11
|
import "./chunk-YRWCXYWW.js";
|
|
12
12
|
import "./chunk-4NZ47VXO.js";
|
|
@@ -166,4 +166,4 @@ export {
|
|
|
166
166
|
forecastBidding,
|
|
167
167
|
forecastCommand
|
|
168
168
|
};
|
|
169
|
-
//# sourceMappingURL=forecast-
|
|
169
|
+
//# sourceMappingURL=forecast-HMNQZFVL.js.map
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
stageUpdate,
|
|
30
30
|
targetArg,
|
|
31
31
|
targetEntries
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-IB6VWG32.js";
|
|
33
33
|
import {
|
|
34
34
|
repeatedFlagValues,
|
|
35
35
|
repeatedValues
|
|
@@ -93,8 +93,8 @@ Examples:
|
|
|
93
93
|
subCommands: {
|
|
94
94
|
discover: () => import("./discover-BPUUWRBP.js").then((m) => m.discoverCommand),
|
|
95
95
|
metrics: () => import("./metrics-4AFEEDVP.js").then((m) => m.metricsCommand),
|
|
96
|
-
forecast: () => import("./forecast-
|
|
97
|
-
themes: () => import("./themes-
|
|
96
|
+
forecast: () => import("./forecast-HMNQZFVL.js").then((m) => m.forecastCommand),
|
|
97
|
+
themes: () => import("./themes-FW3VWZJE.js").then((m) => m.themesCommand),
|
|
98
98
|
locations: () => import("./locations-UWBQLHXW.js").then((m) => m.locationsCommand),
|
|
99
99
|
languages: () => import("./languages-4S26FJDP.js").then((m) => m.languagesCommand)
|
|
100
100
|
}
|
|
@@ -3021,7 +3021,7 @@ var conversionsCommand = defineCommand4({
|
|
|
3021
3021
|
description: "Stage conversion action create/update (via --file), the account-level goal, and manual conversion uploads for a period where tracking was broken"
|
|
3022
3022
|
},
|
|
3023
3023
|
subCommands: {
|
|
3024
|
-
upload: () => import("./conversion-upload-command-
|
|
3024
|
+
upload: () => import("./conversion-upload-command-JAGV6GMG.js").then((m) => m.conversionsUploadCommand),
|
|
3025
3025
|
create: fileCreateCommand(
|
|
3026
3026
|
"create",
|
|
3027
3027
|
"google.conversionAction.create",
|
|
@@ -3283,18 +3283,18 @@ Full guide: __tooling__/docs/tools/baker/ads-google.md`
|
|
|
3283
3283
|
currency: () => import("./currency-EFDQYW5B.js").then((m) => m.currencyCommand),
|
|
3284
3284
|
"url-options": () => import("./url-options-AHJPR5VY.js").then((m) => m.urlOptionsCommand),
|
|
3285
3285
|
changes: () => import("./changes-5OW3UYMF.js").then((m) => m.changesCommand),
|
|
3286
|
-
invoices: () => import("./account-reads-
|
|
3287
|
-
"identity-verification": () => import("./account-reads-
|
|
3288
|
-
previews: () => import("./account-reads-
|
|
3289
|
-
brands: () => import("./account-reads-
|
|
3286
|
+
invoices: () => import("./account-reads-CQIJCPCH.js").then((m) => m.invoicesCommand),
|
|
3287
|
+
"identity-verification": () => import("./account-reads-CQIJCPCH.js").then((m) => m.identityVerificationCommand),
|
|
3288
|
+
previews: () => import("./account-reads-CQIJCPCH.js").then((m) => m.previewsCommand),
|
|
3289
|
+
brands: () => import("./account-reads-CQIJCPCH.js").then((m) => m.brandsCommand),
|
|
3290
3290
|
query: () => import("./query-K7MYBRTS.js").then((m) => m.queryCommand),
|
|
3291
3291
|
keywords: keywordsWithWrites,
|
|
3292
3292
|
...googleWriteCommandsWithLists,
|
|
3293
3293
|
"merchant-center": merchantCenterCommand,
|
|
3294
|
-
draft: () => import("./draft-
|
|
3294
|
+
draft: () => import("./draft-GSDDK6FO.js").then((m) => m.draftCommand)
|
|
3295
3295
|
}
|
|
3296
3296
|
});
|
|
3297
3297
|
export {
|
|
3298
3298
|
googleCommand
|
|
3299
3299
|
};
|
|
3300
|
-
//# sourceMappingURL=google-
|
|
3300
|
+
//# sourceMappingURL=google-3CAUWNWE.js.map
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
failWriteValidation,
|
|
6
6
|
listFlag,
|
|
7
7
|
requireCustomerId
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-IB6VWG32.js";
|
|
9
9
|
import "./chunk-ERSX3MKH.js";
|
|
10
10
|
import "./chunk-YRWCXYWW.js";
|
|
11
11
|
import "./chunk-4NZ47VXO.js";
|
|
@@ -92,4 +92,4 @@ Example:
|
|
|
92
92
|
export {
|
|
93
93
|
themesCommand
|
|
94
94
|
};
|
|
95
|
-
//# sourceMappingURL=themes-
|
|
95
|
+
//# sourceMappingURL=themes-FW3VWZJE.js.map
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/ads/google/write-shared.ts","../src/commands/ads/google/copy-list-usage.ts","../src/commands/ads/google/draft-status.ts","../src/commands/ads/google/stage-streak.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport {\n GOOGLE_DRAFT_BATCH_MAX,\n type GoogleDraftListResponse,\n type GoogleDraftStageBatchResponse,\n type GoogleDraftStageResponse,\n googleDraftOpInputSchema,\n OP_TOO_LARGE_FIX,\n OP_TOO_LARGE_MESSAGE_START,\n toMicros,\n} from \"@baker/api/ads-google\";\nimport { describeOpPayload } from \"@baker/api/capabilities\";\nimport { defineCommand } from \"citty\";\nimport { ApiError, apiPost } from \"../../../client.ts\";\nimport { requireChatId, resolveChatId } from \"../../../env.ts\";\nimport { handleConnectionError, isNotConnectedError } from \"../../../error-handler.ts\";\nimport { repeatedValues } from \"../../../lib/repeatedFlags.ts\";\nimport { payloadArg } from \"../../../schemas.ts\";\nimport { payloadFix } from \"../../capabilities/fix.ts\";\nimport { booleanWordPositionalError } from \"../boolean-word-positional.ts\";\nimport { adCopyHints } from \"../copy-hints.ts\";\nimport { hintLines, withHints } from \"../hints.ts\";\nimport { writeJsonEnvelope } from \"../output.ts\";\nimport { termsRefusalFix } from \"../terms-output.ts\";\nimport { copyListCountFix } from \"./copy-list-usage.ts\";\nimport { renderDraftStatus } from \"./draft-status.ts\";\nimport { recordSingleStage } from \"./stage-streak.ts\";\n\n/**\n * Shared plumbing for the staged Google Ads write commands. Writes NEVER hit\n * the Google Ads API directly: every command stages an op against this chat's\n * draft (`/api/ads/google/draft/stage`), the dashboard shows it as a pending\n * Google Ads change, and the draft applies as one `GoogleAdsService.Mutate`\n * request per advertiser account when the chat is published. `--customer-id` is\n * required on every write — a draft may span several accounts, so nothing is\n * picked for you.\n */\n\nexport function failWriteValidation(message: string): never {\n writeJsonEnvelope({ ok: false, error: { code: \"VALIDATION_ERROR\", message } });\n process.exit(1);\n}\n\nexport function requireCustomerId(args: Record<string, unknown>): string {\n const raw = (args[\"customer-id\"] ?? args.customerId) as string | undefined;\n if (!raw || !/^\\d{10}$/.test(raw)) {\n failWriteValidation(\"pass --customer-id as the 10-digit Google Ads customer id (no dashes)\");\n }\n return raw;\n}\n\nexport function requireTarget(args: Record<string, unknown>, entity: string): string {\n // `--flag false` is refused, not documented: citty parses it as the flag set to TRUE plus a stray\n // positional nobody consumes, so on `conversions update --primary` it silently PROMOTES an action\n // into what every Maximize Conversions campaign bids toward. Caught here because this is the only\n // place on this surface that reads positionals, and every target-taking command goes through it.\n const booleanWord = booleanWordPositionalError(args, entity);\n if (booleanWord) {\n failWriteValidation(booleanWord);\n }\n const positional = Array.isArray(args._) ? args._[0] : undefined;\n const target = args.id ?? args.target ?? args.ref ?? positional;\n if (typeof target !== \"string\" || target.length === 0) {\n failWriteValidation(`pass the ${entity} resource name or id as the positional argument`);\n }\n return target;\n}\n\n/**\n * The targets a bulk status command was given: every positional (comma-separated ones split), plus\n * `--file` with one target per line (blank lines and `#` comments skipped), deduped in order. One\n * call then stages them all as one batch instead of one command per row.\n */\nexport function targetEntries(args: Record<string, unknown>, entity: string): string[] {\n const booleanWord = booleanWordPositionalError(args, entity);\n if (booleanWord) {\n failWriteValidation(booleanWord);\n }\n const positionals = Array.isArray(args._) ? args._.filter((v): v is string => typeof v === \"string\") : [];\n const raw = [...positionals.flatMap((v) => v.split(\",\")), ...rawFileEntries(args.file)]\n .map((v) => v.trim())\n .filter(Boolean);\n const targets = [...new Set(raw)];\n if (targets.length === 0) {\n failWriteValidation(`pass the ${entity} resource name(s) as positional arguments and/or --file (one per line)`);\n }\n if (targets.length > GOOGLE_DRAFT_BATCH_MAX) {\n failWriteValidation(`${targets.length} targets exceeds the batch limit of ${GOOGLE_DRAFT_BATCH_MAX} — split them`);\n }\n return targets;\n}\n\nexport function requireStringFlag(value: unknown, flag: string): string {\n if (typeof value !== \"string\" || value.length === 0) {\n failWriteValidation(`${flag} is required`);\n }\n return value;\n}\n\n/** Money flag in major currency units → micros. */\nexport function microsFlag(value: unknown, flag: string): number | undefined {\n if (value === undefined || value === null || value === \"\") {\n return undefined;\n }\n const num = Number(value);\n if (Number.isNaN(num) || num <= 0) {\n failWriteValidation(`${flag} must be a positive amount (major currency units, e.g. 50 or 50.00)`);\n }\n return toMicros(num);\n}\n\n/** Split a comma-separated flag into trimmed non-empty values. */\nexport function listFlag(value: unknown): string[] | undefined {\n if (typeof value !== \"string\" || value.length === 0) {\n return undefined;\n }\n const items = value\n .split(\",\")\n .map((v) => v.trim())\n .filter(Boolean);\n return items.length > 0 ? items : undefined;\n}\n\nexport interface KeywordEntry {\n text: string;\n matchType: string;\n}\n\nconst MATCH_TYPES = new Set([\"EXACT\", \"PHRASE\", \"BROAD\"]);\n\n/**\n * One raw keyword entry → { text, matchType }. A trailing `:EXACT|:PHRASE|:BROAD`\n * suffix overrides the default match type; colons and commas aren't legal in Google\n * keyword text, so the syntax is unambiguous.\n */\nfunction parseKeywordEntry(raw: string, defaultMatch: string | undefined): KeywordEntry {\n const colon = raw.lastIndexOf(\":\");\n if (colon > 0) {\n const suffix = raw\n .slice(colon + 1)\n .trim()\n .toUpperCase();\n if (MATCH_TYPES.has(suffix)) {\n return { text: raw.slice(0, colon).trim(), matchType: suffix };\n }\n failWriteValidation(`\"${raw}\": \":${raw.slice(colon + 1)}\" is not a match type (EXACT | PHRASE | BROAD)`);\n }\n if (!defaultMatch) {\n failWriteValidation(`\"${raw}\" has no match type — add a :EXACT/:PHRASE/:BROAD suffix or pass --match-type`);\n }\n return { text: raw, matchType: defaultMatch };\n}\n\n/**\n * Raw --text value(s) → individual entries: repeated flags and comma-separated\n * both work. The repeats have to come off `rawArgs`, because citty parses a\n * repeated flag down to its last value alone.\n */\nfunction rawTextEntries(value: unknown, rawArgs: readonly string[] | undefined): string[] {\n return repeatedValues(rawArgs, \"text\", value)\n .flatMap((v) => v.split(\",\"))\n .map((v) => v.trim())\n .filter(Boolean);\n}\n\n/** Newline-separated keyword file: one `text[:MATCH_TYPE]` per line; blank lines and `#` comments skipped. */\nfunction rawFileEntries(path: unknown): string[] {\n return typeof path === \"string\" && path.length > 0 ? fileLines(path) : [];\n}\n\n/** A text file's entries: one per line, trimmed; blank lines and `#` comments skipped. */\nexport function fileLines(path: string): string[] {\n return readFileSync(path, \"utf8\")\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter((line) => line.length > 0 && !line.startsWith(\"#\"));\n}\n\n/**\n * The keyword batch a command was invoked with: comma-separated `--text` entries\n * (optional `:MATCH_TYPE` suffix each) plus `--file` lines, deduped, each falling\n * back to `--match-type`. One entry stages exactly like before; many stage as one batch.\n */\nexport function keywordEntries(args: Record<string, unknown>, rawArgs?: readonly string[]): KeywordEntry[] {\n const defaultMatch = typeof args[\"match-type\"] === \"string\" ? args[\"match-type\"].toUpperCase() : undefined;\n const raw = [...rawTextEntries(args.text, rawArgs), ...rawFileEntries(args.file)];\n if (raw.length === 0) {\n failWriteValidation(\"pass keywords via --text (comma-separated) and/or --file (one per line)\");\n }\n if (raw.length > GOOGLE_DRAFT_BATCH_MAX) {\n failWriteValidation(`${raw.length} keywords exceeds the batch limit of ${GOOGLE_DRAFT_BATCH_MAX}`);\n }\n const entries: KeywordEntry[] = [];\n const seen = new Set<string>();\n for (const item of raw) {\n const entry = parseKeywordEntry(item, defaultMatch);\n const key = JSON.stringify([entry.text, entry.matchType]);\n if (!seen.has(key)) {\n seen.add(key);\n entries.push(entry);\n }\n }\n return entries;\n}\n\n/**\n * The payloads behind `--file`: a path to a JSON file, or the JSON written inline. The file holds one\n * payload object, a JSON array of them, or JSON Lines (one object per line) — many payloads stage as\n * one batch, which is how a bulk change lands in one call instead of hundreds.\n *\n * Inline is here because every doc example already writes it that way — `packages/cli/README.md`\n * and the scaffold's `ads-google.md` both show `--file '{\"type\":\"image\",…}'` — while the arg only\n * ever read a path, so following the documentation answered with a raw ENOENT stack trace. Reading\n * a leading `{` or `[` as the payload makes those examples true rather than making the agent re-learn\n * the one shape that works.\n */\nexport function loadJsonPayloads(path: unknown): Array<Record<string, unknown>> {\n if (typeof path !== \"string\" || path.length === 0) {\n return [];\n }\n const inline = /^\\s*[[{]/.test(path);\n const source = inline ? \"inline JSON\" : path;\n const text = inline ? path : readFileSync(path, \"utf8\");\n return parsedPayloads(text, source).map((item, index, all) => {\n if (item === null || typeof item !== \"object\" || Array.isArray(item)) {\n failWriteValidation(\n all.length === 1 ? `${source} must contain a JSON object` : `${source}: item ${index} must be a JSON object`,\n );\n }\n return item as Record<string, unknown>;\n });\n}\n\n/** One JSON value (an array spreads into its items), else JSON Lines; the JSON error when neither parses. */\nfunction parsedPayloads(text: string, source: string): unknown[] {\n try {\n const parsed = JSON.parse(text) as unknown;\n return Array.isArray(parsed) ? parsed : [parsed];\n } catch (err) {\n if (!(err instanceof SyntaxError)) {\n throw err;\n }\n const lines = text.split(/\\r?\\n/).filter((line) => line.trim().length > 0);\n if (lines.length > 1) {\n return lines.map((line, index) => parseJsonLine(line, index, source));\n }\n failWriteValidation(`${source} is not valid JSON: ${err.message}`);\n }\n}\n\n/** One JSON Lines entry, or the line's own error naming where it is. */\nfunction parseJsonLine(line: string, index: number, source: string): unknown {\n try {\n return JSON.parse(line) as unknown;\n } catch (err) {\n return failWriteValidation(\n `${source} line ${index + 1} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\n/** The ONE op payload behind `--file`, for commands that change a single entity. */\nexport function loadJsonFileArg(path: unknown): Record<string, unknown> {\n const payloads = loadJsonPayloads(path);\n if (payloads.length > 1) {\n failWriteValidation(\n `this command changes one entity, and --file holds ${payloads.length} payloads — stage many changes from one file with \\`baker ads google draft stage --file\\` (one op per item: {\"kind\",\"customerId\",\"target\"?,\"payload\"?})`,\n );\n }\n return payloads[0] ?? {};\n}\n\n/** Flags override file keys — the file is the base, flags are the overrides. */\nexport function mergePayload(file: Record<string, unknown>, flags: Record<string, unknown>): Record<string, unknown> {\n const merged: Record<string, unknown> = { ...file };\n for (const [key, value] of Object.entries(flags)) {\n if (value !== undefined) {\n merged[key] = value;\n }\n }\n return merged;\n}\n\n/**\n * The `draft amend` patch: exactly one of `--file <path>` (JSON file) or `--patch '<json>'`\n * (inline string), and it must parse to a JSON object — RFC-7386 merge-patch needs an object to\n * merge, and a stray array/scalar would silently no-op or replace the whole payload.\n */\nexport function loadPatchArg(args: Record<string, unknown>): Record<string, unknown> {\n const file = typeof args.file === \"string\" && args.file.length > 0 ? args.file : undefined;\n const inline = typeof args.patch === \"string\" && args.patch.length > 0 ? args.patch : undefined;\n if (Boolean(file) === Boolean(inline)) {\n failWriteValidation(\"pass exactly one of --file <patch.json> or --patch '<json>'\");\n }\n if (file) {\n return loadJsonFileArg(file);\n }\n try {\n const parsed = JSON.parse(inline as string) as unknown;\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n failWriteValidation(\"--patch must be a JSON object\");\n }\n return parsed as Record<string, unknown>;\n } catch (err) {\n if (err instanceof SyntaxError) {\n failWriteValidation(`--patch is not valid JSON: ${err.message}`);\n }\n throw err;\n }\n}\n\nexport function handleGoogleError(err: unknown): never {\n if (err instanceof ApiError) {\n // Covers both \"no connection\" and staging's 403 for an account this company\n // never connected — same remedy either way: connect, and pick the account.\n if (isNotConnectedError(err.code, err.message)) {\n handleConnectionError(\"google_ads\", err.message);\n }\n const terms = termsRefusalFix(err.code, err.message);\n if (terms) {\n writeJsonEnvelope({\n ok: false,\n error: { code: terms.code, message: err.message, retryable: false, fix: terms.fix },\n });\n process.exit(1);\n }\n if (err.message.includes(OP_TOO_LARGE_MESSAGE_START)) {\n writeJsonEnvelope({\n ok: false,\n error: {\n code: \"OP_TOO_LARGE\",\n message: err.message,\n retryable: false,\n fix: { action: \"split_the_change\", explanation: OP_TOO_LARGE_FIX },\n },\n });\n process.exit(1);\n }\n writeJsonEnvelope({ ok: false, error: { code: err.code ?? \"API_ERROR\", message: err.message } });\n process.exit(1);\n }\n writeJsonEnvelope({\n ok: false,\n error: { code: \"UNKNOWN\", message: err instanceof Error ? err.message : String(err) },\n });\n process.exit(1);\n}\n\ninterface StageEnvelope extends Record<string, unknown> {\n ok: true;\n data: GoogleDraftStageResponse;\n}\n\n/**\n * A no-op response isn't a failure — the change was already in place, so nothing was\n * staged. Say so explicitly, otherwise the agent re-stages it or reports work it\n * didn't do.\n */\nfunction noopHints(data: GoogleDraftStageResponse): string[] {\n return data.staged\n ? []\n : [`NOT STAGED — ${data.reason}. Nothing to publish for this change; say so rather than reporting it as done.`];\n}\n\n/**\n * What to tell the agent about the ops a batch didn't stage.\n *\n * Two different things land in `skipped`: a change already in place, and a change Google refuses\n * outright (editing an experiment campaign's status, dates or budget). This hint used to assert the\n * first — \"already in the requested state\" — which read as \"done\" for a refusal the user never heard\n * about. So it leads with each op's own reason and asks for the refusals to be passed on.\n */\nexport function skippedHints(skipped: Array<{ summary: string; reason: string }>, total: number): string[] {\n if (skipped.length === 0) {\n return [];\n }\n const detail = skipped.map((s) => `${s.summary} — ${s.reason}`).join(\"; \");\n return [\n `${skipped.length} of ${total} op(s) NOT STAGED: ${detail}. Don't re-stage them; tell the user which changes couldn't be made and why, then carry on with the rest.`,\n ];\n}\n\n/**\n * Preflight-validates the op against the shared `@baker/api/ads-google`\n * contract (the same schema the backend enforces), then stages it. Prints the\n * staged envelope — ref, mode (always `live`: publishing reaches the real\n * account), summary, warnings.\n */\nexport async function stageGoogleOp(raw: Record<string, unknown>, hints?: string[]): Promise<void> {\n const preflight = googleDraftOpInputSchema.safeParse(raw);\n if (!preflight.success) {\n const fields = preflight.error.issues.map((issue) => ({ path: issue.path.join(\".\"), message: issue.message }));\n writeJsonEnvelope({\n ok: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: fields.map((f) => (f.path ? `${f.path}: ${f.message}` : f.message)).join(\"; \"),\n fields,\n fix: copyListCountFix(raw, preflight.error.issues) ?? payloadFix(\"google-ads\", googleDraftOpInputSchema, raw),\n },\n });\n process.exit(1);\n }\n try {\n const chatId = requireChatId();\n const response = await apiPost<StageEnvelope>(\"/api/ads/google/draft/stage\", { chatId, op: preflight.data });\n const allHints = [\n ...noopHints(response.data),\n ...recordSingleStage(chatId, preflight.data.kind),\n ...(hints ?? []),\n ...(await adCopyHints(preflight.data)),\n ];\n writeJsonEnvelope(withHints(response, allHints));\n } catch (err) {\n handleGoogleError(err);\n }\n}\n\n/** Stage a create op: `{ kind, customerId, payload }`. */\nexport async function stageCreate(\n kind: string,\n customerId: string,\n payload: Record<string, unknown>,\n hints?: string[],\n): Promise<void> {\n await stageGoogleOp({ kind, customerId, payload }, hints);\n}\n\ninterface StageBatchEnvelope extends Record<string, unknown> {\n ok: true;\n data: GoogleDraftStageBatchResponse;\n}\n\n/**\n * A batch reads every target's before-state and checks each op against the account, which for a few\n * hundred removals outlasts the default 60 s. Timing out early reported a batch as failed that went on\n * to stage, so a retry then collided with the ops it had already staged.\n */\nconst STAGE_BATCH_TIMEOUT_MS = 5 * 60_000;\n\n/**\n * Stage several ops as ONE request (`/stage-batch`) — all-or-nothing on the backend,\n * one rate-limit hit instead of one per keyword. A single op falls through to the\n * plain stage path so its envelope (and amend behavior) stays exactly as before.\n */\nexport async function stageGoogleOps(rawOps: Array<Record<string, unknown>>, hints?: string[]): Promise<void> {\n if (rawOps.length === 1 && rawOps[0]) {\n await stageGoogleOp(rawOps[0], hints);\n return;\n }\n const ops: unknown[] = [];\n for (const [index, raw] of rawOps.entries()) {\n const preflight = googleDraftOpInputSchema.safeParse(raw);\n if (!preflight.success) {\n const fields = preflight.error.issues.map((issue) => ({\n path: `ops[${index}].${issue.path.join(\".\")}`,\n message: issue.message,\n }));\n writeJsonEnvelope({\n ok: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: fields.map((f) => `${f.path}: ${f.message}`).join(\"; \"),\n fields,\n fix: copyListCountFix(raw, preflight.error.issues) ?? payloadFix(\"google-ads\", googleDraftOpInputSchema, raw),\n },\n });\n process.exit(1);\n }\n ops.push(preflight.data);\n }\n try {\n const chatId = requireChatId();\n const response = await apiPost<StageBatchEnvelope>(\n \"/api/ads/google/draft/stage-batch\",\n { chatId, ops },\n { timeoutMs: STAGE_BATCH_TIMEOUT_MS },\n );\n const allHints = [\n ...skippedHints(response.data.skipped ?? [], ops.length),\n ...(hints ?? []),\n ...(await adCopyHints(ops)),\n ];\n writeJsonEnvelope(withHints(response, allHints));\n } catch (err) {\n handleGoogleError(err);\n }\n}\n\n/** Stage an update op: `{ kind, customerId, target, payload }`. */\nexport async function stageUpdate(\n kind: string,\n customerId: string,\n target: string,\n payload: Record<string, unknown>,\n hints?: string[],\n): Promise<void> {\n await stageGoogleOp({ kind, customerId, target, payload }, hints);\n}\n\n/** Stage a target-only op (pause/resume/remove/detach): `{ kind, customerId, target }`. */\nexport async function stageTarget(kind: string, customerId: string, target: string, hints?: string[]): Promise<void> {\n await stageGoogleOp({ kind, customerId, target }, hints);\n}\n\nexport async function draftAction(path: string, body: Record<string, unknown>, chat?: unknown): Promise<void> {\n try {\n const chatId = resolveChatId(chat);\n const response = await apiPost<Record<string, unknown>>(path, { chatId, ...body });\n writeJsonEnvelope(response);\n } catch (err) {\n handleGoogleError(err);\n }\n}\n\ninterface DraftListEnvelope extends Record<string, unknown> {\n ok: boolean;\n data?: GoogleDraftListResponse;\n}\n\n/**\n * List the staged draft. By default renders a readable campaign ▸ ad group ▸ ad status tree so the\n * agent can review everything it has staged at a glance; `--json` returns the raw envelope for tools,\n * and `--full` that envelope with every op's payload.\n */\nexport async function draftListAction(json: boolean, chat?: unknown, full = false): Promise<void> {\n try {\n const chatId = resolveChatId(chat);\n const response = await apiPost<DraftListEnvelope>(\"/api/ads/google/draft\", {\n chatId,\n ...(full ? { full: true } : {}),\n });\n if (json || full || !response.ok || !response.data) {\n writeJsonEnvelope(response);\n return;\n }\n process.stdout.write(`${renderDraftStatus(response.data)}${hintLines(response)}\\n`);\n } catch (err) {\n handleGoogleError(err);\n }\n}\n\nexport const customerIdArg = {\n \"customer-id\": { type: \"string\" as const, description: \"10-digit Google Ads customer id\" },\n};\n/** The entity a command changes, read by position the way `requireTarget` reads it — declared so --help shows it. */\nexport function targetArg(entity: string) {\n return {\n id: {\n type: \"positional\" as const,\n description: `The ${entity} to change: its id or resource name (customers/…)`,\n required: false,\n },\n };\n}\n\nconst PAYLOAD_FIELDS = \"run `baker schema` on this command for every field the payload takes\";\n\nfunction payloadShape(kind: string): () => string | undefined {\n return () => describeOpPayload(googleDraftOpInputSchema, kind);\n}\n\n/** `--file` for a command that stages one `kind` op; `baker schema` prints that op's payload shape. */\nexport function fileArg(kind: string) {\n return {\n file: payloadArg(\n {\n type: \"string\" as const,\n description: `The op payload: a JSON file path, or the JSON object inline (flags override it) — ${PAYLOAD_FIELDS}`,\n },\n payloadShape(kind),\n ),\n };\n}\n\n/** `--file` that stages one or many `kind` ops; `baker schema` prints that op's payload shape. */\nexport function fileBatchArg(kind: string) {\n return {\n file: payloadArg(\n {\n type: \"string\" as const,\n description: `The op payload: a JSON file path or inline JSON — one object, or a JSON array / JSON Lines of objects to stage many in one call (up to 500) — ${PAYLOAD_FIELDS}`,\n },\n payloadShape(kind),\n ),\n };\n}\n\n/** A create command whose payload is a JSON file or inline JSON — one object or an array staged as one batch. */\nexport function fileCreateCommand(\n name: string,\n kind: string,\n description: string,\n hints?: string[] | ((payload: Record<string, unknown>) => string[]),\n) {\n return defineCommand({\n meta: { name, description },\n args: { ...customerIdArg, ...fileBatchArg(kind) },\n run: async ({ args }) => {\n const customerId = requireCustomerId(args);\n const payloads = loadJsonPayloads(args.file);\n if (payloads.length === 0 || payloads.some((payload) => Object.keys(payload).length === 0)) {\n failWriteValidation(`${kind} needs --file with the op payload (one object, or a JSON array to stage many)`);\n }\n if (payloads.length > GOOGLE_DRAFT_BATCH_MAX) {\n failWriteValidation(\n `${payloads.length} payloads exceeds the batch limit of ${GOOGLE_DRAFT_BATCH_MAX} — split the file`,\n );\n }\n const payloadHints = [\n ...new Set(payloads.flatMap((payload) => (typeof hints === \"function\" ? hints(payload) : (hints ?? [])))),\n ];\n await stageGoogleOps(\n payloads.map((payload) => ({ kind, customerId, payload })),\n payloadHints,\n );\n },\n });\n}\n\n/**\n * `--bid-modifier` as a number, validated against the range Google actually accepts.\n *\n * Rejecting the (0, 0.1) gap here rather than at the backend is the point: 0 and 0.1 are a hundredth\n * apart in value and mean \"never serve\" versus \"bid a tenth\", so a typo between them is a real spend\n * decision, and the error should arrive before anything is staged.\n */\nexport function bidModifierFlag(value: unknown): number {\n if (value === undefined || value === null || value === \"\") {\n failWriteValidation(\n \"--bid-modifier is required: 1 for no adjustment, 1.25 to bid 25% more, 0.9 to bid 10% less, 0 to stop serving on a device\",\n );\n }\n const num = Number(value);\n if (Number.isNaN(num) || num < 0 || num > 10 || (num > 0 && num < 0.1)) {\n failWriteValidation(\"--bid-modifier must be 0 (stop serving on it) or between 0.1 and 10.0\");\n }\n return num;\n}\n","import type { z } from \"zod\";\n\n/** Appended to each copy flag's help, so `--help` and `baker schema` say how to pass a comma. */\nexport const AD_COPY_LIST_USAGE =\n \"repeat the flag once per item, or pass one comma-separated list and wrap an item that contains a comma in double quotes: '\\\"Fast, free quotes\\\",Book today'. A flag given once is read as a list, so a lone item with a comma needs its double quotes too: '\\\"Fast, free quotes\\\"'\";\n\n/** The op's copy-list fields, by the flag an agent passes them with. */\nconst COPY_LIST_FLAG: Readonly<Record<string, string>> = {\n headlines: \"--headlines\",\n descriptions: \"--descriptions\",\n longHeadlines: \"--long-headlines\",\n};\n\ninterface CountRefusal {\n flag: string;\n count: number | undefined;\n bound: string;\n tooMany: boolean;\n}\n\nfunction valueAt(root: unknown, path: readonly PropertyKey[]): unknown {\n let node = root;\n for (const key of path) {\n if (node === null || typeof node !== \"object\") return undefined;\n node = (node as Record<PropertyKey, unknown>)[key];\n }\n return node;\n}\n\nfunction countRefusal(op: unknown, issue: z.core.$ZodIssue): CountRefusal | undefined {\n if ((issue.code !== \"too_big\" && issue.code !== \"too_small\") || issue.origin !== \"array\") return undefined;\n const flag = COPY_LIST_FLAG[String(issue.path.at(-1))];\n if (!flag) return undefined;\n const value = valueAt(op, issue.path);\n const count = Array.isArray(value) ? value.length : undefined;\n return issue.code === \"too_big\"\n ? { flag, count, bound: `at most ${issue.maximum}`, tooMany: true }\n : { flag, count, bound: `at least ${issue.minimum}`, tooMany: false };\n}\n\n/**\n * The fix for a stage refused only for how many headlines, descriptions or long headlines it has.\n *\n * That is a limit on a field the agent named correctly, so the generic \"read `baker capabilities`,\n * do not guess another field name\" sent it through draft list, schema and --help before it saw the\n * cause. Too many items is usually copy cut at its commas — a flag given once is a comma-separated\n * list, so \"Rated 4,5/5\" becomes two items — which is why that case names the forms that keep a\n * comma. Undefined when anything else was refused too, so the capabilities pointer still covers it.\n */\nexport function copyListCountFix(\n op: unknown,\n issues: readonly z.core.$ZodIssue[],\n): { action: string; explanation: string } | undefined {\n const refusals = issues.map((issue) => countRefusal(op, issue));\n if (refusals.length === 0 || refusals.some((refusal) => refusal === undefined)) return undefined;\n const counted = refusals.filter((refusal): refusal is CountRefusal => refusal !== undefined);\n const limits = counted\n .map(({ flag, count, bound }) => `${flag} has ${count ?? \"the wrong number of\"} items; this ad takes ${bound}.`)\n .join(\" \");\n const commaCause = counted.some((refusal) => refusal.tooMany)\n ? ' If you meant fewer items, the copy was cut at its commas: a flag given once is read as a comma-separated list, so an item with a comma in it (\"Fast, free quotes\", a decimal like \"4,5/5\") becomes two. To keep a comma, ' +\n `${AD_COPY_LIST_USAGE} — or put the content in --file as JSON.`\n : \"\";\n return {\n action: \"fix_copy_list_count\",\n explanation: `${limits}${commaCause} ${commaCause ? \"Otherwise add\" : \"Add\"} or drop items to fit the limit, then stage again; the field names are right.`,\n };\n}\n","import type {\n GoogleDraftListResponse,\n GoogleDraftStatusCollection,\n GoogleDraftStatusNode,\n} from \"@baker/api/ads-google\";\n\n/**\n * Render the staged Google Ads draft as a readable campaign ▸ ad group ▸ ad tree — the CLI\n * counterpart to the dashboard's Google Ads tab, so an agent can review everything it has staged\n * with one command instead of reading raw JSON. Pure: takes the draft-list response, returns text.\n */\n\nconst OPERATION_LABELS: Record<string, string> = {\n create: \"Creating\",\n update: \"Updating\",\n pause: \"Pausing\",\n resume: \"Resuming\",\n remove: \"Removing\",\n};\n\nfunction badge(node: GoogleDraftStatusNode): string {\n const label = node.operation ? OPERATION_LABELS[node.operation] : undefined;\n if (label) {\n return label;\n }\n return node.existing ? \"on Google Ads\" : \"staged\";\n}\n\nfunction collectionLine(collection: GoogleDraftStatusCollection): string | undefined {\n const parts: string[] = [];\n if (collection.added > 0) {\n parts.push(`+${collection.added}`);\n }\n if (collection.existing > 0) {\n parts.push(`${collection.existing} existing`);\n }\n if (collection.removed > 0) {\n parts.push(`-${collection.removed}`);\n }\n // Neither an add nor a remove — a member that stays in the set with a changed value (a device's\n // bid adjustment). Left out, a draft whose only criterion change is one printed no line at all.\n if ((collection.updated ?? 0) > 0) {\n parts.push(`~${collection.updated}`);\n }\n return parts.length > 0 ? `${collection.label}: ${parts.join(\", \")}` : undefined;\n}\n\n/**\n * Every advertiser account the tree touches. A draft may legitimately span\n * several, and a criterion id is derived from the keyword's text and match type\n * — so the same keyword carries the same id in every account. The account is\n * printed on each node once there is more than one, and left off when there is not.\n */\nfunction accountsInTree(nodes: readonly GoogleDraftStatusNode[]): Set<string> {\n const accounts = new Set<string>();\n const walk = (node: GoogleDraftStatusNode): void => {\n accounts.add(node.customerId);\n for (const child of node.children) {\n walk(child);\n }\n };\n for (const node of nodes) {\n walk(node);\n }\n return accounts;\n}\n\nfunction renderNode(node: GoogleDraftStatusNode, depth: number, lines: string[], showAccount: boolean): void {\n const indent = \" \".repeat(depth);\n const account = showAccount ? ` · account ${node.customerId}` : \"\";\n lines.push(`${indent}• ${node.name} · ${badge(node)}${account}`);\n for (const warning of node.warnings ?? []) {\n lines.push(`${indent} ⚠ ${warning}`);\n }\n for (const collection of node.collections) {\n const line = collectionLine(collection);\n if (line) {\n lines.push(`${indent} ${line}`);\n }\n }\n for (const child of node.children) {\n renderNode(child, depth + 1, lines, showAccount);\n }\n}\n\nconst RESOURCE_ENTITIES = new Set([\n \"sharedSet\",\n \"biddingStrategy\",\n \"audience\",\n \"conversionAction\",\n \"label\",\n \"budget\",\n \"asset\",\n]);\n\nfunction renderSection(title: string, nodes: GoogleDraftStatusNode[], lines: string[], showAccount: boolean): void {\n if (nodes.length === 0) {\n return;\n }\n lines.push(\"\");\n lines.push(title);\n for (const node of nodes) {\n renderNode(node, 1, lines, showAccount);\n }\n}\n\nfunction renderAdvisories(data: GoogleDraftListResponse, lines: string[]): void {\n const advisories = data.advisories ?? [];\n // Every advisory is phrased as something to settle BEFORE publishing. On a published draft there\n // is nothing left to settle here, and printing them reads as work still outstanding on this chat.\n if (advisories.length === 0 || data.status === \"applied\") {\n return;\n }\n lines.push(\"\");\n // Two kinds of finding share this block: build-out nudges (\"this ad group has one ad\") and\n // whole-draft coherence findings (\"this campaign will report zero conversions\", \"two of your\n // campaigns bid on the same keyword\"). The coherence ones come first and cost money, so the\n // heading no longer frames the block as optional polish.\n lines.push(\"Advisories (non-blocking, checked across the whole draft — settle these before you publish)\");\n for (const advisory of advisories) {\n lines.push(` • ${advisory.message}`);\n }\n}\n\n/**\n * What the publish did, once there is a publish to report.\n *\n * A published Google Ads draft rendered exactly like a staged one — same count, every op still\n * reading \"Creating\" — so the surface `.claude/rules/cli-env.md` sends an agent to for the outcome\n * could not answer, and a failed op was invisible in the human output while sitting in the JSON\n * envelope the whole time. Found by publishing a BUSINESS_LOGO link that Google refused with\n * \"Customer is not verified\": the draft still read \"4 staged changes\". LinkedIn fixed the same\n * shape first; this is its counterpart.\n */\nfunction renderOutcome(data: GoogleDraftListResponse, lines: string[]): void {\n const counts = new Map<string, number>();\n for (const op of data.ops) {\n const status = op.result?.status ?? \"pending\";\n counts.set(status, (counts.get(status) ?? 0) + 1);\n }\n const summary = [...counts].map(([status, count]) => `${count} ${status}`).join(\", \");\n lines.push(`Google Ads — published · ${summary || `${data.count} op(s)`} · ${data.mode}`);\n // A failure is the reason to read this at all, so it is named with its error rather than left as\n // a number to go and look up.\n for (const op of data.ops) {\n const status = op.result?.status;\n if (status !== \"failed\" && status !== \"skipped\") {\n continue;\n }\n const reason = op.result?.error ?? op.result?.skippedBecause ?? \"no reason recorded\";\n lines.push(` ✗ ${op.summary} — ${status}: ${reason}`);\n }\n}\n\nexport function renderDraftStatus(data: GoogleDraftListResponse): string {\n if (data.status === \"none\" || data.count === 0) {\n return \"No staged Google Ads changes on this chat.\";\n }\n const modeNote =\n data.mode === \"live\"\n ? \"Live — publishing writes directly to Google Ads.\"\n : \"Simulated — publishing completes the whole draft without calling Google Ads.\";\n const lines: string[] = [];\n if (data.status === \"applied\") {\n renderOutcome(data, lines);\n } else if (data.status === \"publishing\") {\n lines.push(`Google Ads — publishing ${data.count} change${data.count === 1 ? \"\" : \"s\"} · ${data.mode}`);\n } else {\n lines.push(`Google Ads — ${data.count} staged change${data.count === 1 ? \"\" : \"s\"} · ${data.mode}`);\n }\n lines.push(modeNote);\n\n const tree = data.tree ?? [];\n const showAccount = accountsInTree(tree).size > 1;\n renderSection(\n \"Shared resources\",\n tree.filter((node) => RESOURCE_ENTITIES.has(node.entity)),\n lines,\n showAccount,\n );\n renderSection(\n \"Campaigns\",\n tree.filter((node) => node.entity === \"campaign\"),\n lines,\n showAccount,\n );\n const rest = tree.filter((node) => !RESOURCE_ENTITIES.has(node.entity) && node.entity !== \"campaign\");\n renderSection(\"Other\", rest, lines, showAccount);\n renderAdvisories(data, lines);\n\n return lines.join(\"\\n\");\n}\n","import { cacheGet, cacheSet } from \"../cache.ts\";\n\n/**\n * Bulk work done as a loop of single commands — one `keywords update` per keyword, one\n * `negative-keywords remove` per term — is one request, one rate-limit hit and one tool call per row;\n * production saw 1,284 of them. The CLI cannot see the loop from inside one call, so it remembers the\n * last few single stages per chat and, once the same kind repeats, says how to do the rest in one call.\n */\n\nexport interface StageStreak {\n kind: string;\n count: number;\n lastAt: number;\n}\n\n/** Singles further apart than this are separate decisions, not a loop. */\nconst STREAK_WINDOW_MS = 5 * 60_000;\n/** The streak length that reads as a loop. */\nconst STREAK_THRESHOLD = 5;\n\nexport function nextStageStreak(previous: StageStreak | undefined, kind: string, now: number): StageStreak {\n const continues = previous && previous.kind === kind && now - previous.lastAt <= STREAK_WINDOW_MS;\n return { kind, count: continues ? previous.count + 1 : 1, lastAt: now };\n}\n\n/** The one-call form of a kind, where a command has one beside `draft stage --file`. */\nconst BATCH_FORMS: Record<string, string> = {\n \"google.keyword.add\": \"`keywords add --file keywords.txt` (one term[:MATCH_TYPE] per line)\",\n \"google.keyword.update\":\n \"`keywords pause|resume <resource name> <resource name>…` (or --file, one per line) for status\",\n \"google.keyword.remove\": \"`draft stage --file` with one google.keyword.remove per target\",\n \"google.negativeKeyword.add\": \"`negative-keywords add --file terms.txt`\",\n \"google.sharedSetMember.add\": \"`keyword-lists add --file terms.txt`\",\n \"google.sharedSetMember.remove\": '`keyword-lists remove --list-ref <id> --text \"a,b,c\"`',\n};\n\nexport function stageStreakHints(streak: StageStreak): string[] {\n if (streak.count < STREAK_THRESHOLD) {\n return [];\n }\n const form = BATCH_FORMS[streak.kind];\n return [\n `LOOP: that is ${streak.count} ${streak.kind} ops staged one command at a time. Stop looping — put every remaining change in one file and stage them in ONE call: \\`baker ads google draft stage --file ops.json --customer-id <id>\\` (a JSON array of {\"kind\",\"target\"?,\"payload\"?}, up to 500, any mix of kinds)${form ? `, or ${form}` : \"\"}. Fix staged ops together with \\`draft amend --file\\`.`,\n ];\n}\n\nconst STREAK_CACHE = \"stage-streak\";\n\n/** Record one single stage on this chat and return the nudge it earns, if any. Never throws. */\nexport function recordSingleStage(chatId: string, kind: string): string[] {\n try {\n const streak = nextStageStreak(cacheGet<StageStreak>(STREAK_CACHE, chatId)?.data, kind, Date.now());\n cacheSet(STREAK_CACHE, chatId, streak, STREAK_WINDOW_MS);\n return stageStreakHints(streak);\n } catch {\n return [];\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/ads/google/draft.ts","../src/commands/ads/google/draft-batch.ts","../src/commands/ads/google/draft-validate.ts","../src/commands/ads/google/draft-where.ts"],"sourcesContent":["import type { GoogleDraftListResponse, GoogleDraftValidateResponse } from \"@baker/api/ads-google\";\nimport type { GoogleDraftImportResponse } from \"@baker/api/ads-google/wire\";\nimport { defineCommand } from \"citty\";\nimport { apiPost } from \"../../../client.ts\";\nimport { requireChatId } from \"../../../env.ts\";\nimport { registerSchema } from \"../../../schemas.ts\";\nimport { CHAT_READ_ARG, DRAFT_FULL_ARG } from \"../../chat-arg.ts\";\nimport { draftRefs, removeDraftRefs } from \"../../draft-refs.ts\";\nimport { writeJsonEnvelope } from \"../output.ts\";\nimport { amendBatchEntries, stageFileOps } from \"./draft-batch.ts\";\nimport { validationEnvelope } from \"./draft-validate.ts\";\nimport { parseDraftWhere, selectDraftRefs } from \"./draft-where.ts\";\nimport {\n draftAction,\n draftListAction,\n failWriteValidation,\n handleGoogleError,\n loadJsonPayloads,\n loadPatchArg,\n requireTarget,\n stageGoogleOps,\n} from \"./write-shared.ts\";\n\nconst VALIDATE_DESCRIPTION = `Ask Google whether the whole staged draft would publish — without applying anything.\n\nStart here, after the last staged change and before telling the user the draft is ready:\n baker ads google draft validate\n\nEvery staged op is built, batched and sent exactly as publish sends it, with Google's validateOnly\nflag, so each verdict is the answer publish would get: accepted | refused | blocked (a change it\ndepends on was refused) | unchecked (only publish can check it) | applied (an earlier publish did).\nCompact by default — counts, plus only the ops that need attention, each with Google's reason.\nFix refused ops with \\`draft amend <ref>\\` and validate again.\n\nIt also reads Baker's day of Google Ads changes, shared by every company. When the draft does\nnot all fit in what is left, the answer carries dailyLimit: operations, room and fitsAt (ISO, when\nall of it should have gone). Publish still works: Baker sends what Google Ads takes and the rest\nagain on its own. Never mention the limit, a quota or operation counts to the user, and never ask\nthem to cut down or split their work for it; say only that some changes go live over the next\nhours, and by when.\n\nExamples:\n baker ads google draft validate\n baker ads google draft validate --full # every op, with its built request and Google's raw answer`;\n\nregisterSchema({\n command: \"ads.google.draft.validate\",\n description: VALIDATE_DESCRIPTION,\n args: {\n full: {\n type: \"boolean\",\n description: \"List every op with its built request and Google's raw answer\",\n required: false,\n },\n },\n});\n\nconst STAGE_DESCRIPTION = `Stage many Google Ads changes from ONE file, in one call — any mix of kinds, up to 500 ops.\n\nStart here whenever a change touches more than a few rows (a keyword cleanup, bid changes, pausing\nads, a list of negatives): write the ops to a file and stage them together instead of running one\ncommand per row.\n baker ads google draft stage --file changes.json --customer-id 1234567890\n\nThe file is a JSON array (or JSON Lines, one op per line). Each op is what the single commands\nbuild — kind, customerId (or --customer-id for all), target for a change to something that exists,\npayload. Read the shape for a kind with \\`baker capabilities google-ads\\`.\n [\n {\"kind\":\"google.keyword.add\",\"payload\":{\"adGroup\":\"customers/1234567890/adGroups/111\",\"text\":\"solar panels\",\"matchType\":\"PHRASE\"}},\n {\"kind\":\"google.keyword.update\",\"target\":\"customers/1234567890/adGroupCriteria/111~222\",\"payload\":{\"status\":\"PAUSED\"}},\n {\"kind\":\"google.negativeKeyword.add\",\"payload\":{\"level\":\"campaign\",\"parent\":\"222333\",\"text\":\"free\",\"matchType\":\"BROAD\"}}\n ]\nAn op may name a g_temp_* ref an EARLIER call returned (a campaign or ad group staged before), but\nnot another op in the same file — a ref exists only once its op is staged, so stage new parents first.\nAll-or-nothing: one invalid op stages nothing. Ops already in effect come back as skipped.\nThen: baker ads google draft validate`;\n\nregisterSchema({\n command: \"ads.google.draft.stage\",\n description: STAGE_DESCRIPTION,\n args: {\n file: {\n type: \"string\",\n description: \"JSON array or JSON Lines of ops ({kind, customerId?, target?, payload?}), path or inline\",\n required: true,\n },\n \"customer-id\": {\n type: \"string\",\n description: \"10-digit customer id for every op that does not name its own\",\n required: false,\n },\n },\n});\n\nconst IMPORT_DESCRIPTION = `Move another unpublished chat's staged Google Ads changes into this chat, in one call — and out of that chat, so the two can never publish the same change twice.\n\nStart here when the user wants another chat's changes brought into this one (\"bring over\", \"merge\",\n\"include what the other chat prepared\", \"all of them except …\"):\n baker ads google draft import --from-chat <chat-id>\n baker ads google draft import --from-chat <chat-id> --exclude g_temp_a,customers/123/adGroupAds/4~5\n baker ads google draft import --from-chat <chat-id> --refs g_temp_b\n\nEvery op moves exactly as it was staged — same ref, same payload, nothing retyped — so never copy\nanother chat's changes with \\`draft show --chat\\` and re-stage them. An op moves with the creates it\ndepends on and the ops that depend on it (an asset and the link that attaches it go together).\nThe other chat must be unpublished — still open, or its publish failed (a discarded chat's Google Ads\nchanges are gone with it); its other work (Knowledge, Tasks) stays where it is. A published chat's changes its publish could not apply come\nwith \\`baker chats import --from-chat <chat-id> --failed\\` instead. Find chat ids\nwith \\`baker chats list\\`, and refs with \\`baker ads google draft list --chat <chat-id> --json\\`.\nThen: baker ads google draft validate`;\n\nregisterSchema({\n command: \"ads.google.draft.import\",\n description: IMPORT_DESCRIPTION,\n args: {\n \"from-chat\": {\n type: \"string\",\n description: \"The other unpublished chat (open, or its publish failed) whose staged changes move here\",\n required: true,\n },\n refs: {\n type: \"string\",\n description: \"Comma-separated refs to move (default: all); what they depend on comes too\",\n required: false,\n },\n exclude: {\n type: \"string\",\n description: \"Comma-separated refs to leave in the other chat, with the ops that depend on them\",\n required: false,\n },\n },\n});\n\nconst LIST_DESCRIPTION =\n \"Review everything staged as a campaign ▸ ad group ▸ ad tree (--json for the raw envelope, --full for every op's payload too)\";\n\nregisterSchema({\n command: \"ads.google.draft.list\",\n description: LIST_DESCRIPTION,\n args: {\n json: { type: \"boolean\", description: \"Print the raw JSON envelope instead of the readable tree\", required: false },\n full: { type: \"boolean\", description: DRAFT_FULL_ARG.description, required: false },\n chat: { type: \"string\", description: CHAT_READ_ARG.description, required: false },\n },\n});\n\nconst WHERE_ARGS = {\n where: {\n type: \"string\",\n description:\n \"Select staged ops instead of naming refs: comma-separated key=value — kind (op kind), under (a campaign/ad group ref or resource name the op sits under), or any payload field (matchType, text, level, parent…); `|` for alternatives\",\n required: false,\n },\n \"dry-run\": { type: \"boolean\", description: \"Print the refs that would change, and change nothing\", required: false },\n} as const;\n\nconst REMOVE_DESCRIPTION = `Remove staged ops — one, many, or every op a selector matches, in one call. Each removal cascades to the ops that depend on it.\n\n baker ads google draft remove g_temp_a\n baker ads google draft remove g_temp_a g_temp_b g_temp_c # or g_temp_a,g_temp_b,g_temp_c\n baker ads google draft remove --where 'kind=google.negativeKeyword.add,text=twilio|baileys'\n baker ads google draft remove --where kind=google.keyword.add,under=g_temp_c1 --dry-run\n\nPrints one summary: every op removed (cascades listed once), and any ref the draft did not hold.`;\n\nconst REMOVE_ARGS = {\n ref: {\n type: \"positional\",\n description: \"Staged op ref(s) (g_temp_* or target) — several, space- or comma-separated\",\n required: false,\n },\n ...WHERE_ARGS,\n} as const;\n\nregisterSchema({ command: \"ads.google.draft.remove\", description: REMOVE_DESCRIPTION, args: REMOVE_ARGS });\n\nconst AMEND_DESCRIPTION = `Update staged ops in place — merges a JSON patch into each payload (RFC-7386: objects deep-merge, null deletes a key, arrays/scalars replace) and re-validates. Use this instead of remove + re-create.\n\n baker ads google draft amend g_temp_a --patch '{\"cpcBidMicros\":1500000}'\n baker ads google draft amend g_temp_a g_temp_b --patch '{\"status\":\"ENABLED\"}' # same patch, many refs\n baker ads google draft amend --where kind=google.keyword.add,under=g_temp_c1,matchType=EXACT --patch '{\"matchType\":\"BROAD\"}'\n baker ads google draft amend --file amends.json # [{\"ref\":\"g_temp_…\",\"patch\":{…}}, …] — a patch per ref\n\nMany refs are amended in one call, all or none: one refused amend changes nothing.`;\n\nconst AMEND_ARGS = {\n ref: {\n type: \"positional\",\n description:\n \"Staged op ref(s) (g_temp_*) or target id/resource name — several, space- or comma-separated, take the same patch; leave out to amend many from --file\",\n required: false,\n },\n file: {\n type: \"string\",\n description: 'JSON file with the patch object — or, with no ref, a JSON array of {\"ref\",\"patch\"} amends',\n required: false,\n },\n patch: { type: \"string\", description: \"Inline JSON patch object\", required: false },\n ...WHERE_ARGS,\n} as const;\n\nregisterSchema({ command: \"ads.google.draft.amend\", description: AMEND_DESCRIPTION, args: AMEND_ARGS });\n\n/**\n * The refs `--where` selects from the staged draft, or undefined without it. Exits with the reason\n * when it cannot select: a malformed selector, refs passed too, or nothing staged that matches.\n */\nasync function refsWhere(args: Record<string, unknown>): Promise<string[] | undefined> {\n if (typeof args.where !== \"string\") return undefined;\n const where = parseDraftWhere(args.where);\n if (\"error\" in where) failWriteValidation(where.error);\n if (draftRefs(args._).length > 0) failWriteValidation(\"pass refs or --where, not both\");\n let ops: GoogleDraftListResponse[\"ops\"] = [];\n try {\n const response = await apiPost<{ ok: boolean; data?: GoogleDraftListResponse }>(\"/api/ads/google/draft\", {\n chatId: requireChatId(),\n full: true,\n });\n ops = response.data?.ops ?? [];\n } catch (err) {\n handleGoogleError(err);\n }\n const refs = selectDraftRefs(ops, where);\n if (refs.length === 0) {\n writeJsonEnvelope({\n ok: false,\n error: {\n code: \"NOT_FOUND\",\n message: `--where ${args.where} matches no staged op`,\n fix: \"Read the staged ops' kinds and payload fields with `baker ads google draft list --full`, then adjust the selector.\",\n },\n });\n process.exit(1);\n }\n return refs;\n}\n\n/** `--dry-run`: the refs the command would change, printed instead of changing them. */\nfunction printDryRun(refs: string[]): void {\n writeJsonEnvelope({\n ok: true,\n data: { matched: refs.length, refs },\n hints: [\"Nothing changed. Run it again without --dry-run to apply it.\"],\n });\n}\n\n/** The refs a comma-separated flag names: undefined when the flag is absent, null when it names none. */\nfunction refList(value: unknown): string[] | undefined | null {\n if (typeof value !== \"string\") return undefined;\n const refs = value\n .split(\",\")\n .map((ref) => ref.trim())\n .filter((ref) => ref.length > 0);\n return refs.length > 0 ? refs : null;\n}\n\n/** What the agent needs after a move: the next step, and what the other chat now holds. */\nexport function importHints(fromChatId: string, data: GoogleDraftImportResponse): string[] {\n const source =\n data.sourceRemaining > 0\n ? `Chat ${fromChatId} still has ${data.sourceRemaining} Google Ads change(s) staged, which publish with that chat.`\n : `Chat ${fromChatId} has no Google Ads changes staged any more, so publishing it cannot apply these twice. Its other work stays there — never tell the user to discard it for this.`;\n return [source, \"Run `baker ads google draft validate` next.\"];\n}\n\n/** After a bulk stage: what to do next, and the reminder that the file — not a loop — is the tool. */\nexport const BULK_STAGE_HINTS = [\n \"Run `baker ads google draft validate` next: it asks Google about every staged op at once and lists only the ones it would refuse.\",\n 'To fix several staged ops, amend them together: `baker ads google draft amend --file amends.json` with [{\"ref\":\"…\",\"patch\":{…}}, …].',\n];\n\n/** Review and undo staged Google Ads ops on this chat's draft; `list`/`show` can also read another chat's. */\nexport const draftCommand = defineCommand({\n meta: {\n name: \"draft\",\n description:\n \"Stage many ops from one file, or list, validate, remove, clear, amend, or show staged Google Ads write ops for this chat. `list` and `show` take --chat <id> to read an earlier chat's changes instead; `import --from-chat <id>` moves another unpublished chat's staged changes here (open, or its publish failed).\",\n },\n subCommands: {\n stage: defineCommand({\n meta: { name: \"stage\", description: STAGE_DESCRIPTION },\n args: {\n file: {\n type: \"string\",\n description: \"JSON array or JSON Lines of ops ({kind, customerId?, target?, payload?}), path or inline\",\n },\n \"customer-id\": { type: \"string\", description: \"10-digit customer id for every op that does not name its own\" },\n },\n run: async ({ args }) => {\n const customerId = typeof args[\"customer-id\"] === \"string\" ? args[\"customer-id\"] : undefined;\n await stageGoogleOps(stageFileOps(loadJsonPayloads(args.file), customerId), BULK_STAGE_HINTS);\n },\n }),\n import: defineCommand({\n meta: { name: \"import\", description: IMPORT_DESCRIPTION },\n args: {\n \"from-chat\": {\n type: \"string\",\n description: \"The other unpublished chat (open, or its publish failed) whose staged changes move here\",\n },\n refs: { type: \"string\", description: \"Comma-separated refs to move (default: all)\" },\n exclude: { type: \"string\", description: \"Comma-separated refs to leave in the other chat\" },\n },\n run: async ({ args }) => {\n const fromChatId = typeof args[\"from-chat\"] === \"string\" ? args[\"from-chat\"].trim() : \"\";\n if (!fromChatId) {\n writeJsonEnvelope({\n ok: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: \"--from-chat is required\",\n fix: \"Name the other chat: baker ads google draft import --from-chat <chat-id> (ids from `baker chats list`).\",\n },\n });\n process.exit(1);\n }\n const refs = refList(args.refs);\n const exclude = refList(args.exclude);\n const empty = refs === null ? \"--refs\" : exclude === null ? \"--exclude\" : undefined;\n if (empty) {\n // An empty selection must never widen to \"every change\": the move takes them out of the other chat.\n writeJsonEnvelope({\n ok: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: `${empty} names no change`,\n fix: `Pass the refs, comma-separated (from \\`baker ads google draft list --chat ${fromChatId} --json\\`), or leave ${empty} off to move every change.`,\n },\n });\n process.exit(1);\n }\n try {\n const response = await apiPost<{ ok: boolean; data?: GoogleDraftImportResponse }>(\n \"/api/ads/google/draft/import\",\n { chatId: requireChatId(), fromChatId, refs: refs ?? undefined, exclude: exclude ?? undefined },\n );\n writeJsonEnvelope(\n response.ok && response.data ? { ...response, hints: importHints(fromChatId, response.data) } : response,\n );\n } catch (err) {\n handleGoogleError(err);\n }\n },\n }),\n list: defineCommand({\n meta: {\n name: \"list\",\n description: LIST_DESCRIPTION,\n },\n args: {\n json: { type: \"boolean\", description: \"Print the raw JSON envelope instead of the readable tree\" },\n full: DRAFT_FULL_ARG,\n chat: CHAT_READ_ARG,\n },\n run: async ({ args }) => {\n await draftListAction(args.json === true, args.chat, args.full === true);\n },\n }),\n remove: defineCommand({\n meta: { name: \"remove\", description: REMOVE_DESCRIPTION },\n args: REMOVE_ARGS,\n run: async ({ args }) => {\n const selected = await refsWhere(args);\n const refs = selected ?? draftRefs(args._);\n if (args[\"dry-run\"] === true) {\n printDryRun(refs);\n return;\n }\n if (selected || refs.length > 1) {\n await removeDraftRefs(\"/api/ads/google/draft/remove\", refs);\n return;\n }\n await draftAction(\"/api/ads/google/draft/remove\", { ref: requireTarget(args, \"op\") });\n },\n }),\n clear: defineCommand({\n meta: { name: \"clear\", description: \"Discard all staged ops on this chat\" },\n run: async () => {\n await draftAction(\"/api/ads/google/draft/clear\", {});\n },\n }),\n amend: defineCommand({\n meta: { name: \"amend\", description: AMEND_DESCRIPTION },\n args: AMEND_ARGS,\n run: async ({ args }) => {\n const selected = await refsWhere(args);\n const positional = Array.isArray(args._) ? args._[0] : undefined;\n if (!selected && !(args.ref ?? positional) && typeof args.file === \"string\" && args.file.length > 0) {\n const amends = amendBatchEntries(loadJsonPayloads(args.file));\n if (args[\"dry-run\"] === true) {\n printDryRun(amends.map((amend) => amend.ref));\n return;\n }\n await draftAction(\"/api/ads/google/draft/amend-batch\", { amends });\n return;\n }\n const refs = selected ?? draftRefs(args._);\n if (args[\"dry-run\"] === true) {\n printDryRun(refs);\n return;\n }\n if (selected || refs.length > 1) {\n const patch = loadPatchArg(args);\n await draftAction(\"/api/ads/google/draft/amend-batch\", { amends: refs.map((ref) => ({ ref, patch })) });\n return;\n }\n const ref = requireTarget(args, \"op\");\n const patch = loadPatchArg(args);\n await draftAction(\"/api/ads/google/draft/amend\", { ref, patch });\n },\n }),\n validate: defineCommand({\n meta: { name: \"validate\", description: VALIDATE_DESCRIPTION },\n args: {\n full: { type: \"boolean\", description: \"List every op with its built request and Google's raw answer\" },\n },\n run: async ({ args }) => {\n try {\n const full = args.full === true;\n const response = await apiPost<{ ok: boolean; data?: GoogleDraftValidateResponse }>(\n \"/api/ads/google/draft/validate\",\n { chatId: requireChatId(), full },\n );\n writeJsonEnvelope(response.ok && response.data ? validationEnvelope(response.data, full) : response);\n } catch (err) {\n handleGoogleError(err);\n }\n },\n }),\n show: defineCommand({\n meta: {\n name: \"show\",\n description:\n \"Print the full staged payload for one op — the receipt to verify a change looks right before publish (never truncated).\",\n },\n args: {\n ref: {\n type: \"positional\",\n description: \"Staged op ref (g_temp_*) or target id/resource name\",\n required: false,\n },\n chat: CHAT_READ_ARG,\n },\n run: async ({ args }) => {\n await draftAction(\"/api/ads/google/draft/show\", { ref: requireTarget(args, \"op\") }, args.chat);\n },\n }),\n },\n});\n","import { GOOGLE_DRAFT_BATCH_MAX } from \"@baker/api/ads-google\";\nimport { failWriteValidation } from \"./write-shared.ts\";\n\n/**\n * The bulk halves of `draft`: one file of many changes, staged or amended in ONE request. A bulk\n * change done as a loop of single commands is one request, one rate-limit hit and one tool call per\n * row — production saw 1,284 such calls — while a file is one of each for up to 500 rows.\n */\n\nfunction refuseOversized(count: number, what: string): void {\n if (count > GOOGLE_DRAFT_BATCH_MAX) {\n failWriteValidation(\n `${count} ${what} exceeds the batch limit of ${GOOGLE_DRAFT_BATCH_MAX} — split the file and run the command once per part`,\n );\n }\n}\n\n/**\n * `draft stage --file` items → ops: each item is a whole op (`kind`, `customerId`, `target` for a\n * change to something that exists, `payload`); `--customer-id` fills the account on items that leave\n * it out, so a one-account file need not repeat it. The ops are preflighted by `stageGoogleOps`.\n */\nexport function stageFileOps(\n items: ReadonlyArray<Record<string, unknown>>,\n customerId: string | undefined,\n): Array<Record<string, unknown>> {\n if (items.length === 0) {\n failWriteValidation(\n 'pass --file with the ops to stage: a JSON array (or JSON Lines) of {\"kind\",\"customerId\",\"target\"?,\"payload\"?}',\n );\n }\n refuseOversized(items.length, \"ops\");\n return items.map((item, index) => {\n if (typeof item.kind !== \"string\") {\n failWriteValidation(\n `item ${index} has no \"kind\" — every item is a whole op, e.g. {\"kind\":\"google.keyword.add\",…}`,\n );\n }\n return item.customerId === undefined && customerId ? { ...item, customerId } : item;\n });\n}\n\n/** `draft amend --file` items → the amend batch: each `{ ref, patch }` exactly as `draft amend <ref>` takes it. */\nexport function amendBatchEntries(\n items: ReadonlyArray<Record<string, unknown>>,\n): Array<{ ref: string; patch: Record<string, unknown> }> {\n refuseOversized(items.length, \"amends\");\n return items.map((item, index) => {\n const { ref, patch } = item;\n if (typeof ref !== \"string\" || ref.length === 0) {\n failWriteValidation(`item ${index} has no \"ref\" — name the staged op (g_temp_* or its target) to amend`);\n }\n if (patch === null || typeof patch !== \"object\" || Array.isArray(patch)) {\n failWriteValidation(`item ${index} has no \"patch\" object — {\"ref\":\"…\",\"patch\":{…fields to change…}}`);\n }\n return { ref, patch: patch as Record<string, unknown> };\n });\n}\n","import type { GoogleDraftOpVerdict, GoogleDraftValidateResponse } from \"@baker/api/ads-google\";\n\n/** Verdicts that need nothing from the agent — counted, never listed in the compact view. */\nconst QUIET: ReadonlySet<GoogleDraftOpVerdict[\"verdict\"]> = new Set([\"accepted\", \"applied\"]);\n\nfunction changes(n: number): string {\n return n === 1 ? \"1 change\" : `${n} changes`;\n}\n\n/** What to do next, read off the counts — the agent decides from these without re-reading every op. */\nfunction validationHints(data: GoogleDraftValidateResponse): string[] {\n const { refused, blocked, unchecked } = data.counts;\n const hints: string[] = [];\n if (refused > 0) {\n hints.push(\n `${changes(refused)} would fail at publish. Fix each refused op in place with \\`draft amend <ref>\\` (or \\`draft remove <ref>\\` if it should not exist), then run \\`draft validate\\` again before asking the user to publish. Never tell the user a refused change will go live.`,\n );\n }\n if (blocked > 0) {\n hints.push(\n `${changes(blocked)} would not apply only because a refused change they depend on fails — fixing the refused one clears them.`,\n );\n }\n if (data.publishHeld) {\n hints.push(\n \"Baker would hold this whole publish: a refused change stops a first publish from sending anything. Fix it first.\",\n );\n }\n if (unchecked > 0) {\n hints.push(\n `${changes(unchecked)} can only be checked by Google at publish (the reason is on each one). Nothing here says they will fail.`,\n );\n }\n if (refused + blocked === 0 && !data.publishHeld && data.ops.length > 0) {\n hints.push(\n \"Google accepted every change it was asked about. The draft is ready for the user to review and publish.\",\n );\n }\n if (data.dailyLimit) {\n hints.push(\n `Not every change may go live the moment the user publishes: Baker sends what Google Ads takes then and the rest again on its own, all by about ${data.dailyLimit.fitsAt}, with nothing for the user to do. If you tell the user anything, say only that some changes will go live over the next hours and by when, in their time zone. Never mention a daily limit, quota, allowance or number of operations, and never ask them to cut down or split their work for it.`,\n );\n }\n return hints;\n}\n\n/**\n * The `draft validate` envelope. Compact by default: counts for everything, and only the ops that\n * need attention listed — a 300-keyword draft that Google accepts reads as one line, not 300.\n * `full` keeps every op with its built request and Google's raw answer.\n */\nexport function validationEnvelope(\n data: GoogleDraftValidateResponse,\n full = false,\n): { ok: true; data: GoogleDraftValidateResponse; hints: string[] } {\n const ops = full ? data.ops : data.ops.filter((op) => !QUIET.has(op.verdict));\n return { ok: true, data: { ...data, ops }, hints: validationHints(data) };\n}\n","import type { GoogleDraftListResponse } from \"@baker/api/ads-google\";\n\n/**\n * `draft remove|amend --where kind=…,under=…,<payload field>=…` — pick staged ops by what they are\n * instead of by ref, so a cleanup over a whole campaign (\"every EXACT keyword under it → BROAD\") is\n * one command, not a draft dump, a script and a call per op.\n */\n\ntype DraftOp = GoogleDraftListResponse[\"ops\"][number];\n\nexport interface DraftWhere {\n /** Op kinds, `google.` prefix optional. */\n kinds: string[];\n /** Refs or resource names the op sits under: through its staged parents, or named in its payload. */\n under: string[];\n /** Top-level payload fields and the values (any of, case-insensitive) they must hold. */\n fields: Record<string, string[]>;\n}\n\n/** Parse the selector, or say what is wrong with it. `|` separates alternatives: `matchType=EXACT|PHRASE`. */\nexport function parseDraftWhere(value: string): DraftWhere | { error: string } {\n const where: DraftWhere = { kinds: [], under: [], fields: {} };\n const pairs = value\n .split(\",\")\n .map((pair) => pair.trim())\n .filter(Boolean);\n if (pairs.length === 0) return { error: \"--where names no condition\" };\n for (const pair of pairs) {\n const eq = pair.indexOf(\"=\");\n const key = pair.slice(0, eq).trim();\n const values = pair\n .slice(eq + 1)\n .split(\"|\")\n .map((v) => v.trim())\n .filter(Boolean);\n if (eq <= 0 || values.length === 0) return { error: `--where condition \"${pair}\" is not key=value` };\n if (key === \"kind\") where.kinds.push(...values.map((k) => (k.startsWith(\"google.\") ? k : `google.${k}`)));\n else if (key === \"under\") where.under.push(...values);\n else where.fields[key] = [...(where.fields[key] ?? []), ...values];\n }\n return where;\n}\n\nfunction payloadOf(op: DraftOp): Record<string, unknown> {\n return typeof op.payload === \"object\" && op.payload !== null ? (op.payload as Record<string, unknown>) : {};\n}\n\n/** Everything an op sits under: its staged parents up the chain, and the ids its payload names. */\nfunction parentsOf(op: DraftOp, byRef: Map<string, DraftOp>): Set<string> {\n const seen = new Set<string>();\n const queue = [op];\n for (let current = queue.shift(); current; current = queue.shift()) {\n const named = Object.values(payloadOf(current)).filter((v): v is string => typeof v === \"string\");\n for (const parent of [...current.dependsOn, ...named]) {\n if (seen.has(parent)) continue;\n seen.add(parent);\n const staged = byRef.get(parent);\n if (staged) queue.push(staged);\n }\n }\n return seen;\n}\n\n/** The refs of the staged ops the selector matches, in staging order. */\nexport function selectDraftRefs(ops: readonly DraftOp[], where: DraftWhere): string[] {\n const byRef = new Map(ops.map((op) => [op.ref, op]));\n return ops\n .filter((op) => {\n if (where.kinds.length > 0 && !where.kinds.includes(op.kind)) return false;\n if (where.under.length > 0) {\n const parents = parentsOf(op, byRef);\n if (!where.under.some((ref) => parents.has(ref))) return false;\n }\n const payload = payloadOf(op);\n return Object.entries(where.fields).every(([key, values]) => {\n const actual = payload[key];\n if (typeof actual !== \"string\" && typeof actual !== \"number\" && typeof actual !== \"boolean\") return false;\n return values.some((v) => v.toLowerCase() === String(actual).toLowerCase());\n });\n })\n .map((op) => op.ref);\n}\n"],"mappings":"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","names":["patch","ref"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|