siluzan-tso-cli 1.1.20 → 1.1.21-beta.2

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/dist/index.js CHANGED
@@ -3328,7 +3328,7 @@ var DEFAULT_API_BASE;
3328
3328
  var init_defaults = __esm({
3329
3329
  "src/config/defaults.ts"() {
3330
3330
  "use strict";
3331
- DEFAULT_API_BASE = "https://tso-api.siluzan.com";
3331
+ DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
3332
3332
  }
3333
3333
  });
3334
3334
 
@@ -100810,14 +100810,14 @@ function computeBackoffMs(attempt, policy) {
100810
100810
  return capped + jitter;
100811
100811
  }
100812
100812
  function sleep2(ms, signal) {
100813
- return new Promise((resolve8, reject) => {
100813
+ return new Promise((resolve12, reject) => {
100814
100814
  if (signal?.aborted) {
100815
100815
  reject(new Error("retry sleep aborted"));
100816
100816
  return;
100817
100817
  }
100818
100818
  const timer = setTimeout(() => {
100819
100819
  signal?.removeEventListener("abort", onAbort);
100820
- resolve8();
100820
+ resolve12();
100821
100821
  }, ms);
100822
100822
  const onAbort = () => {
100823
100823
  clearTimeout(timer);
@@ -110184,6 +110184,81 @@ function inferMatchTypeFromDisplayText(text) {
110184
110184
  if (t.length >= 2 && t.startsWith("[") && t.endsWith("]")) return "Exact";
110185
110185
  return "Broad";
110186
110186
  }
110187
+ var MATCH_TYPE_SPLIT_ORDER = ["Broad", "Phrase", "Exact"];
110188
+ function resolveTargetMatchTypeForSplit(trimmedRaw, declaredUi) {
110189
+ const inferred = inferMatchTypeFromDisplayText(trimmedRaw);
110190
+ if (declaredUi === null) return inferred;
110191
+ if (inferred === "Broad") return declaredUi;
110192
+ if (inferred !== declaredUi) return inferred;
110193
+ return declaredUi;
110194
+ }
110195
+ function collectKeywordEntriesFromBlock(block) {
110196
+ const entries = [];
110197
+ const texts = readBlockKeywordTextArray(block);
110198
+ if (Array.isArray(texts)) {
110199
+ for (const raw of texts) {
110200
+ if (typeof raw === "string" && raw.trim()) entries.push({ kind: "text", raw });
110201
+ }
110202
+ }
110203
+ const items = readBlockItemsArray(block);
110204
+ if (Array.isArray(items)) {
110205
+ for (const item of items) {
110206
+ if (!item || typeof item !== "object") continue;
110207
+ const raw = readItemText(item);
110208
+ if (raw !== null && raw.trim()) entries.push({ kind: "item", raw, item });
110209
+ }
110210
+ }
110211
+ return entries;
110212
+ }
110213
+ function cloneKeywordBlockShell(block) {
110214
+ const shell = { ...block };
110215
+ delete shell["KeywordText"];
110216
+ delete shell["keywordText"];
110217
+ delete shell["Items"];
110218
+ delete shell["items"];
110219
+ delete shell["MatchTypeV2"];
110220
+ delete shell["matchTypeV2"];
110221
+ return shell;
110222
+ }
110223
+ function splitKeywordsForBatchJobBlockIfMixed(block, path22, warnings) {
110224
+ const declaredUi = matchTypeV2ToUi(readBlockMatchTypeRaw(block));
110225
+ const entries = collectKeywordEntriesFromBlock(block);
110226
+ if (entries.length === 0) return [block];
110227
+ const groups = /* @__PURE__ */ new Map();
110228
+ for (const entry of entries) {
110229
+ const trimmedRaw = collapseDuplicateSpacesInKeywordText(entry.raw.trim());
110230
+ const target = resolveTargetMatchTypeForSplit(trimmedRaw, declaredUi);
110231
+ const list = groups.get(target) ?? [];
110232
+ list.push(
110233
+ entry.kind === "text" ? { kind: "text", raw: trimmedRaw } : { kind: "item", raw: trimmedRaw, item: { ...entry.item } }
110234
+ );
110235
+ groups.set(target, list);
110236
+ }
110237
+ if (groups.size <= 1) return [block];
110238
+ const labels = MATCH_TYPE_SPLIT_ORDER.filter((ui) => groups.has(ui)).map(
110239
+ (ui) => matchTypeUiToV2(ui)
110240
+ );
110241
+ warnings.push(
110242
+ `${path22} \u542B\u591A\u79CD\u5339\u914D\u7C7B\u578B\uFF0C\u5DF2\u81EA\u52A8\u62C6\u5206\u4E3A ${groups.size} \u4E2A KeywordsForBatchJob \u5757\uFF08${labels.join("\u3001")}\uFF09`
110243
+ );
110244
+ const shell = cloneKeywordBlockShell(block);
110245
+ const splitBlocks = [];
110246
+ for (const ui of MATCH_TYPE_SPLIT_ORDER) {
110247
+ const list = groups.get(ui);
110248
+ if (!list?.length) continue;
110249
+ const newBlock = { ...shell, MatchTypeV2: matchTypeUiToV2(ui) };
110250
+ const texts = [];
110251
+ const items = [];
110252
+ for (const e of list) {
110253
+ if (e.kind === "text") texts.push(e.raw);
110254
+ else items.push(e.item);
110255
+ }
110256
+ if (texts.length > 0) newBlock["KeywordText"] = texts;
110257
+ if (items.length > 0) newBlock["Items"] = items;
110258
+ splitBlocks.push(newBlock);
110259
+ }
110260
+ return splitBlocks;
110261
+ }
110187
110262
  function validateKeywordCore(core, path22, errors, lengthViolations) {
110188
110263
  const trimmed = core.trim();
110189
110264
  if (!trimmed) {
@@ -110221,66 +110296,193 @@ function normalizeKeywordSurface(raw, matchTypeV2) {
110221
110296
  const formatted = formatKeywordTextForMatchType(raw, matchType);
110222
110297
  return { formatted, matchType, inferredMatchType: fromField === null };
110223
110298
  }
110224
- function normalizeKeywordsForBatchJobBlock(block, path22, errors, warnings, lengthViolations) {
110225
- const texts = block["KeywordText"];
110226
- if (!Array.isArray(texts)) return;
110227
- const declaredUi = matchTypeV2ToUi(block["MatchTypeV2"]);
110228
- if (block["MatchTypeV2"] !== void 0 && declaredUi === null) {
110229
- errors.push(
110230
- `${path22}.MatchTypeV2 \u65E0\u6548\uFF08${String(block["MatchTypeV2"])}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1ABROAD | PHRASE | EXACT`
110299
+ function readBlockMatchTypeRaw(block) {
110300
+ if (block["MatchTypeV2"] !== void 0) return block["MatchTypeV2"];
110301
+ if (block["matchTypeV2"] !== void 0) return block["matchTypeV2"];
110302
+ return void 0;
110303
+ }
110304
+ function readBlockKeywordTextArray(block) {
110305
+ const pascal = block["KeywordText"];
110306
+ if (Array.isArray(pascal)) return pascal;
110307
+ const camel = block["keywordText"];
110308
+ if (Array.isArray(camel)) return camel;
110309
+ return null;
110310
+ }
110311
+ function readBlockItemsArray(block) {
110312
+ const pascal = block["Items"];
110313
+ if (Array.isArray(pascal)) return pascal;
110314
+ const camel = block["items"];
110315
+ if (Array.isArray(camel)) return camel;
110316
+ return null;
110317
+ }
110318
+ function readItemText(item) {
110319
+ const t = item["Text"] ?? item["text"];
110320
+ return typeof t === "string" ? t : null;
110321
+ }
110322
+ function writeItemText(item, formatted) {
110323
+ item["Text"] = formatted;
110324
+ delete item["text"];
110325
+ }
110326
+ function canonicalizeKeywordBatchBlock(block, resolvedMatchV2, normalizedTexts, hadItems) {
110327
+ if (resolvedMatchV2) block["MatchTypeV2"] = resolvedMatchV2;
110328
+ delete block["matchTypeV2"];
110329
+ if (normalizedTexts !== void 0) {
110330
+ block["KeywordText"] = normalizedTexts;
110331
+ delete block["keywordText"];
110332
+ }
110333
+ if (hadItems) {
110334
+ const items = readBlockItemsArray(block);
110335
+ if (items) {
110336
+ block["Items"] = items;
110337
+ delete block["items"];
110338
+ for (const item of items) {
110339
+ delete item["text"];
110340
+ }
110341
+ }
110342
+ }
110343
+ }
110344
+ function pushKeywordAutoFixWarning(warnings, path22, fieldLabel, trimmedRaw, formatted, declaredUi, beforeUi, matchType, inferredMatchType) {
110345
+ if (inferredMatchType) {
110346
+ warnings.push(
110347
+ `${path22}.${fieldLabel} \u672A\u6307\u5B9A MatchTypeV2\uFF0C\u5DF2\u6309\u8BCD\u9762\u63A8\u65AD\u4E3A ${matchTypeUiToV2(matchType)}`
110231
110348
  );
110232
110349
  return;
110233
110350
  }
110351
+ if (declaredUi && beforeUi !== declaredUi) {
110352
+ warnings.push(
110353
+ `${path22}.${fieldLabel} MatchTypeV2=${matchTypeUiToV2(declaredUi)} \u4E0E\u8BCD\u9762\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u81EA\u52A8\u4FEE\u590D\u4E3A\uFF1A${formatted}`
110354
+ );
110355
+ return;
110356
+ }
110357
+ if (formatted !== trimmedRaw) {
110358
+ warnings.push(`${path22}.${fieldLabel} \u5DF2\u81EA\u52A8\u4FEE\u590D\u8BCD\u9762\uFF1A${trimmedRaw} \u2192 ${formatted}`);
110359
+ }
110360
+ }
110361
+ function normalizeKeywordTextList(texts, matchTypeRaw, path22, fieldLabel, errors, warnings, lengthViolations) {
110362
+ const declaredUi = matchTypeV2ToUi(matchTypeRaw);
110234
110363
  const normalized = [];
110235
110364
  const seen = /* @__PURE__ */ new Set();
110236
110365
  let resolvedUi = declaredUi;
110237
110366
  for (let k = 0; k < texts.length; k++) {
110238
110367
  const raw = texts[k];
110239
110368
  if (typeof raw !== "string" || !raw.trim()) {
110240
- errors.push(`${path22}.KeywordText[${k}] \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
110369
+ errors.push(`${path22}.${fieldLabel}[${k}] \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
110241
110370
  continue;
110242
110371
  }
110243
110372
  const trimmedRaw = collapseDuplicateSpacesInKeywordText(raw.trim());
110244
110373
  const beforeUi = inferMatchTypeFromDisplayText(trimmedRaw);
110245
- const { formatted, matchType, inferredMatchType } = normalizeKeywordSurface(trimmedRaw, block["MatchTypeV2"]);
110246
- if (inferredMatchType) {
110247
- warnings.push(
110248
- `${path22}.KeywordText[${k}] \u672A\u6307\u5B9A MatchTypeV2\uFF0C\u5DF2\u6309\u8BCD\u9762\u63A8\u65AD\u4E3A ${matchTypeUiToV2(matchType)}`
110249
- );
110250
- } else if (declaredUi && beforeUi !== declaredUi) {
110251
- warnings.push(
110252
- `${path22}.KeywordText[${k}] MatchTypeV2=${matchTypeUiToV2(declaredUi)} \u4E0E\u8BCD\u9762\u683C\u5F0F\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u89C4\u8303\u4E3A\uFF1A${formatted}`
110253
- );
110254
- } else if (formatted !== trimmedRaw) {
110255
- warnings.push(`${path22}.KeywordText[${k}] \u8BCD\u9762\u5DF2\u89C4\u8303\u5316\uFF1A${trimmedRaw} \u2192 ${formatted}`);
110256
- }
110374
+ const { formatted, matchType, inferredMatchType } = normalizeKeywordSurface(trimmedRaw, matchTypeRaw);
110375
+ pushKeywordAutoFixWarning(
110376
+ warnings,
110377
+ path22,
110378
+ fieldLabel,
110379
+ trimmedRaw,
110380
+ formatted,
110381
+ declaredUi,
110382
+ beforeUi,
110383
+ matchType,
110384
+ inferredMatchType
110385
+ );
110257
110386
  const core = unwrapKeywordDisplayTextForEdit(formatted);
110258
- if (!validateKeywordCore(core, `${path22}.KeywordText[${k}]`, errors, lengthViolations))
110259
- continue;
110387
+ if (!validateKeywordCore(core, `${path22}.${fieldLabel}[${k}]`, errors, lengthViolations)) continue;
110260
110388
  const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
110261
110389
  if (seen.has(dedupeKey)) {
110262
- warnings.push(`${path22}.KeywordText[${k}] \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
110390
+ warnings.push(`${path22}.${fieldLabel}[${k}] \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
110263
110391
  continue;
110264
110392
  }
110265
110393
  seen.add(dedupeKey);
110266
110394
  normalized.push(formatted);
110267
110395
  if (!resolvedUi) resolvedUi = matchType;
110268
110396
  }
110269
- if (normalized.length === 0 && texts.length > 0) {
110270
- errors.push(`${path22}.KeywordText \u7ECF\u6821\u9A8C\u540E\u65E0\u6709\u6548\u5173\u952E\u8BCD\uFF0C\u8BF7\u4FEE\u6B63\u8BCD\u9762\u6216 MatchTypeV2`);
110397
+ return { normalized, resolvedUi };
110398
+ }
110399
+ function normalizeKeywordsForBatchJobBlock(block, path22, errors, warnings, lengthViolations) {
110400
+ const matchTypeRaw = readBlockMatchTypeRaw(block);
110401
+ const declaredUi = matchTypeV2ToUi(matchTypeRaw);
110402
+ if (matchTypeRaw !== void 0 && declaredUi === null) {
110403
+ errors.push(
110404
+ `${path22}.MatchTypeV2 \u65E0\u6548\uFF08${String(matchTypeRaw)}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1ABROAD | PHRASE | EXACT`
110405
+ );
110406
+ return;
110271
110407
  }
110272
- block["KeywordText"] = normalized;
110273
- if (resolvedUi) {
110274
- block["MatchTypeV2"] = matchTypeUiToV2(resolvedUi);
110408
+ const texts = readBlockKeywordTextArray(block);
110409
+ const items = readBlockItemsArray(block);
110410
+ const hasTexts = Array.isArray(texts) && texts.length > 0;
110411
+ const hasItems = Array.isArray(items) && items.length > 0;
110412
+ if (!hasTexts && !hasItems) return;
110413
+ let resolvedUi = declaredUi;
110414
+ let normalizedTexts;
110415
+ if (hasTexts && texts) {
110416
+ const result = normalizeKeywordTextList(
110417
+ texts,
110418
+ matchTypeRaw,
110419
+ path22,
110420
+ "KeywordText",
110421
+ errors,
110422
+ warnings,
110423
+ lengthViolations
110424
+ );
110425
+ normalizedTexts = result.normalized;
110426
+ if (result.resolvedUi) resolvedUi = result.resolvedUi;
110427
+ if (normalizedTexts.length === 0 && texts.length > 0) {
110428
+ errors.push(`${path22}.KeywordText \u7ECF\u6821\u9A8C\u540E\u65E0\u6709\u6548\u5173\u952E\u8BCD\uFF0C\u8BF7\u4FEE\u6B63\u8BCD\u9762\u6216 MatchTypeV2`);
110429
+ }
110275
110430
  }
110431
+ if (hasItems && items) {
110432
+ const seen = /* @__PURE__ */ new Set();
110433
+ for (let k = 0; k < items.length; k++) {
110434
+ const item = items[k];
110435
+ if (!item || typeof item !== "object") {
110436
+ errors.push(`${path22}.Items[${k}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
110437
+ continue;
110438
+ }
110439
+ const raw = readItemText(item);
110440
+ if (raw === null || !raw.trim()) {
110441
+ errors.push(`${path22}.Items[${k}].Text \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
110442
+ continue;
110443
+ }
110444
+ const trimmedRaw = collapseDuplicateSpacesInKeywordText(raw.trim());
110445
+ const beforeUi = inferMatchTypeFromDisplayText(trimmedRaw);
110446
+ const { formatted, matchType, inferredMatchType } = normalizeKeywordSurface(trimmedRaw, matchTypeRaw);
110447
+ pushKeywordAutoFixWarning(
110448
+ warnings,
110449
+ path22,
110450
+ `Items[${k}].Text`,
110451
+ trimmedRaw,
110452
+ formatted,
110453
+ declaredUi,
110454
+ beforeUi,
110455
+ matchType,
110456
+ inferredMatchType
110457
+ );
110458
+ const core = unwrapKeywordDisplayTextForEdit(formatted);
110459
+ if (!validateKeywordCore(core, `${path22}.Items[${k}].Text`, errors, lengthViolations)) continue;
110460
+ const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
110461
+ if (seen.has(dedupeKey)) {
110462
+ warnings.push(`${path22}.Items[${k}].Text \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
110463
+ continue;
110464
+ }
110465
+ seen.add(dedupeKey);
110466
+ writeItemText(item, formatted);
110467
+ if (!resolvedUi) resolvedUi = matchType;
110468
+ }
110469
+ }
110470
+ canonicalizeKeywordBatchBlock(
110471
+ block,
110472
+ resolvedUi ? matchTypeUiToV2(resolvedUi) : void 0,
110473
+ normalizedTexts,
110474
+ !!hasItems
110475
+ );
110276
110476
  }
110277
110477
  function normalizeCampaignKeywordTrees(campaign, errors, warnings, lengthViolations) {
110278
- const neg = campaign["NegativeKeywordsForBatchJob"];
110478
+ const neg = campaign["NegativeKeywordsForBatchJob"] ?? campaign["negativeKeywordsForBatchJob"];
110279
110479
  if (Array.isArray(neg)) {
110480
+ campaign["NegativeKeywordsForBatchJob"] = neg;
110481
+ delete campaign["negativeKeywordsForBatchJob"];
110280
110482
  for (let i = 0; i < neg.length; i++) {
110281
110483
  const block = neg[i];
110282
110484
  if (block && typeof block === "object") {
110283
- if (block["MatchTypeV2"] === void 0) block["MatchTypeV2"] = "BROAD";
110485
+ if (readBlockMatchTypeRaw(block) === void 0) block["MatchTypeV2"] = "BROAD";
110284
110486
  normalizeKeywordsForBatchJobBlock(
110285
110487
  block,
110286
110488
  `campaign.NegativeKeywordsForBatchJob[${i}]`,
@@ -110291,23 +110493,32 @@ function normalizeCampaignKeywordTrees(campaign, errors, warnings, lengthViolati
110291
110493
  }
110292
110494
  }
110293
110495
  }
110294
- const groups = campaign["AdGroupsForBatchJob"];
110496
+ const groups = campaign["AdGroupsForBatchJob"] ?? campaign["adGroupsForBatchJob"];
110295
110497
  if (!Array.isArray(groups)) return;
110498
+ campaign["AdGroupsForBatchJob"] = groups;
110499
+ delete campaign["adGroupsForBatchJob"];
110296
110500
  for (let i = 0; i < groups.length; i++) {
110297
110501
  const g = groups[i];
110298
- const kws = g?.["KeywordsForBatchJob"];
110502
+ const kws = g?.["KeywordsForBatchJob"] ?? g?.["keywordsForBatchJob"];
110299
110503
  if (!Array.isArray(kws)) continue;
110504
+ delete g["keywordsForBatchJob"];
110505
+ const expandedKws = [];
110300
110506
  for (let j = 0; j < kws.length; j++) {
110301
110507
  const block = kws[j];
110302
- if (block && typeof block === "object") {
110303
- normalizeKeywordsForBatchJobBlock(
110304
- block,
110305
- `campaign.AdGroupsForBatchJob[${i}].KeywordsForBatchJob[${j}]`,
110306
- errors,
110307
- warnings,
110308
- lengthViolations
110309
- );
110310
- }
110508
+ if (!block || typeof block !== "object") continue;
110509
+ const basePath = `campaign.AdGroupsForBatchJob[${i}].KeywordsForBatchJob[${j}]`;
110510
+ const splitBlocks = splitKeywordsForBatchJobBlockIfMixed(block, basePath, warnings);
110511
+ expandedKws.push(...splitBlocks);
110512
+ }
110513
+ g["KeywordsForBatchJob"] = expandedKws;
110514
+ for (let j = 0; j < expandedKws.length; j++) {
110515
+ normalizeKeywordsForBatchJobBlock(
110516
+ expandedKws[j],
110517
+ `campaign.AdGroupsForBatchJob[${i}].KeywordsForBatchJob[${j}]`,
110518
+ errors,
110519
+ warnings,
110520
+ lengthViolations
110521
+ );
110311
110522
  }
110312
110523
  }
110313
110524
  }
@@ -113506,16 +113717,1396 @@ async function runAiCreationUpdate(opts) {
113506
113717
  `);
113507
113718
  }
113508
113719
 
113509
- // src/commands/ad/campaign-validate.ts
113720
+ // src/commands/ad/pmax-create.ts
113721
+ init_auth();
113722
+ init_cli_json_snapshot();
113723
+
113724
+ // src/commands/ad/pmax-create-validate.ts
113725
+ var VALID_PMAX_BIDDING_STRATEGIES = [
113726
+ "MAXIMIZE_CONVERSIONS",
113727
+ "MAXIMIZE_CONVERSION_VALUE",
113728
+ "TARGET_CPA",
113729
+ "TARGET_ROAS"
113730
+ ];
113731
+ var URL_REGEX2 = /^https?:\/\/.+/;
113732
+ var HEADLINE_MAX = 30;
113733
+ var LONG_HEADLINE_MAX = 90;
113734
+ var DESCRIPTION_MAX = 90;
113735
+ var BUSINESS_NAME_MAX = 25;
113736
+ function pushErr3(errors, msg) {
113737
+ errors.push(msg);
113738
+ }
113739
+ function pushWarn3(warnings, msg) {
113740
+ warnings.push(msg);
113741
+ }
113742
+ function nonEmptyStrings(list) {
113743
+ return (list ?? []).map((s) => s?.trim()).filter((s) => Boolean(s));
113744
+ }
113745
+ function hasImageSource(cfg, kind) {
113746
+ const paths = cfg.imagePaths;
113747
+ const base64Key = kind === "marketing" ? "marketingImageBase64" : kind === "square" ? "squareMarketingImageBase64" : "logoImageBase64";
113748
+ const pathKey = kind;
113749
+ const b64 = cfg[base64Key];
113750
+ if (typeof b64 === "string" && b64.trim().length > 0) return true;
113751
+ const p = paths?.[pathKey];
113752
+ return typeof p === "string" && p.trim().length > 0;
113753
+ }
113754
+ function normalizeBidding(raw) {
113755
+ if (raw == null || raw === "") return "MAXIMIZE_CONVERSIONS";
113756
+ const s = String(raw).trim().toUpperCase();
113757
+ if (VALID_PMAX_BIDDING_STRATEGIES.includes(s)) {
113758
+ return s;
113759
+ }
113760
+ const n = Number(raw);
113761
+ const byNum = {
113762
+ 6: "TARGET_CPA",
113763
+ 8: "TARGET_ROAS",
113764
+ 10: "MAXIMIZE_CONVERSIONS",
113765
+ 11: "MAXIMIZE_CONVERSION_VALUE"
113766
+ };
113767
+ return byNum[n] ?? null;
113768
+ }
113769
+ function warnTextLengths(cfg, warnings, prefix, texts, limit, label) {
113770
+ texts.forEach((text, i) => {
113771
+ const len = calcGoogleCharLength(text);
113772
+ if (len > limit) {
113773
+ pushWarn3(
113774
+ warnings,
113775
+ `${prefix}${label}[${i}] \u6709\u6548\u957F\u5EA6 ${len} \u8D85\u8FC7\u5EFA\u8BAE\u4E0A\u9650 ${limit}\uFF08Google \u53EF\u80FD\u62D2\u767B\uFF09`
113776
+ );
113777
+ }
113778
+ });
113779
+ }
113780
+ function runPmaxCreateValidation(cfg) {
113781
+ const errors = [];
113782
+ const warnings = [];
113783
+ const account = cfg.account?.toString().trim();
113784
+ if (!account) {
113785
+ pushErr3(errors, "account\uFF08\u5A92\u4F53\u5BA2\u6237 ID\uFF09\u5FC5\u586B");
113786
+ } else {
113787
+ const n = Number(account);
113788
+ if (!Number.isFinite(n) || n <= 0) {
113789
+ pushErr3(errors, `account \u65E0\u6548\uFF1A${cfg.account}`);
113790
+ }
113791
+ }
113792
+ if (!cfg.name?.trim()) {
113793
+ pushErr3(errors, "name\uFF08\u6D3B\u52A8\u540D\u79F0\uFF09\u5FC5\u586B");
113794
+ }
113795
+ const budget = Number(cfg.budget);
113796
+ if (!Number.isFinite(budget) || budget <= 0) {
113797
+ pushErr3(errors, "budget \u5FC5\u987B\u662F\u5927\u4E8E 0 \u7684\u6570\u5B57\uFF08\u4E3B\u5E01\u79CD\u300C\u5143\u300D\uFF09");
113798
+ }
113799
+ const finalUrls = nonEmptyStrings(cfg.finalUrls);
113800
+ if (finalUrls.length === 0) {
113801
+ pushErr3(errors, "finalUrls \u81F3\u5C11 1 \u4E2A\u6709\u6548 URL");
113802
+ } else {
113803
+ for (const u of finalUrls) {
113804
+ if (!URL_REGEX2.test(u)) {
113805
+ pushErr3(errors, `finalUrls \u542B\u65E0\u6548 URL\uFF1A${u}`);
113806
+ }
113807
+ }
113808
+ }
113809
+ const headlines = nonEmptyStrings(cfg.headlines);
113810
+ const descriptions = nonEmptyStrings(cfg.descriptions);
113811
+ const longHeadlines = nonEmptyStrings(cfg.longHeadlines);
113812
+ if (headlines.length < 3) {
113813
+ pushErr3(errors, "headlines \u81F3\u5C11 3 \u6761\u975E\u7A7A\u6807\u9898");
113814
+ }
113815
+ if (descriptions.length < 2) {
113816
+ pushErr3(errors, "descriptions \u81F3\u5C11 2 \u6761\u975E\u7A7A\u63CF\u8FF0");
113817
+ }
113818
+ if (longHeadlines.length < 1) {
113819
+ pushErr3(errors, "longHeadlines \u81F3\u5C11 1 \u6761\u975E\u7A7A\u957F\u6807\u9898");
113820
+ }
113821
+ if (!cfg.businessName?.trim()) {
113822
+ pushErr3(errors, "businessName\uFF08\u5546\u5BB6\u540D\u79F0\uFF09\u5FC5\u586B");
113823
+ }
113824
+ if (!hasImageSource(cfg, "marketing")) {
113825
+ pushErr3(errors, "\u6A2A\u56FE\u5FC5\u586B\uFF1AimagePaths.marketing \u6216 marketingImageBase64");
113826
+ }
113827
+ if (!hasImageSource(cfg, "square")) {
113828
+ pushErr3(errors, "\u65B9\u56FE\u5FC5\u586B\uFF1AimagePaths.square \u6216 squareMarketingImageBase64");
113829
+ }
113830
+ if (!hasImageSource(cfg, "logo")) {
113831
+ pushErr3(errors, "Logo \u5FC5\u586B\uFF1AimagePaths.logo \u6216 logoImageBase64");
113832
+ }
113833
+ const bidding = normalizeBidding(cfg.biddingStrategyTypeV2);
113834
+ if (!bidding) {
113835
+ pushErr3(
113836
+ errors,
113837
+ `biddingStrategyTypeV2 \u65E0\u6548\uFF1A${cfg.biddingStrategyTypeV2}\uFF1B\u652F\u6301 ${VALID_PMAX_BIDDING_STRATEGIES.join(", ")}`
113838
+ );
113839
+ } else if (bidding === "TARGET_CPA") {
113840
+ const tcpa = Number(cfg.targetCpa_BidingAmount);
113841
+ if (!Number.isFinite(tcpa) || tcpa <= 0) {
113842
+ pushErr3(errors, "biddingStrategyTypeV2 \u4E3A TARGET_CPA \u65F6 targetCpa_BidingAmount \u987B > 0\uFF08\u5143\uFF09");
113843
+ }
113844
+ } else if (bidding === "TARGET_ROAS") {
113845
+ const roas = Number(cfg.targetRoas);
113846
+ if (!Number.isFinite(roas) || roas <= 0) {
113847
+ pushErr3(errors, "biddingStrategyTypeV2 \u4E3A TARGET_ROAS \u65F6 targetRoas \u987B > 0\uFF08\u5982 2.5 \u8868\u793A 250%\uFF09");
113848
+ }
113849
+ }
113850
+ if (cfg.targetedLocations?.length) {
113851
+ for (let i = 0; i < cfg.targetedLocations.length; i++) {
113852
+ const loc = cfg.targetedLocations[i];
113853
+ const id = loc?.id;
113854
+ if (id == null || String(id).trim() === "") {
113855
+ pushErr3(errors, `targetedLocations[${i}].id \u65E0\u6548`);
113856
+ }
113857
+ }
113858
+ }
113859
+ if (cfg.targetedLanguages?.length) {
113860
+ for (let i = 0; i < cfg.targetedLanguages.length; i++) {
113861
+ const lang = cfg.targetedLanguages[i];
113862
+ const id = lang?.id;
113863
+ const n = Number(id);
113864
+ if (!Number.isFinite(n) || n <= 0) {
113865
+ pushErr3(errors, `targetedLanguages[${i}].id \u987B\u4E3A\u6B63\u6574\u6570\uFF08\u5982\u82F1\u8BED 1000\uFF09`);
113866
+ }
113867
+ }
113868
+ }
113869
+ warnTextLengths(cfg, warnings, "", headlines, HEADLINE_MAX, "headlines");
113870
+ warnTextLengths(cfg, warnings, "", longHeadlines, LONG_HEADLINE_MAX, "longHeadlines");
113871
+ warnTextLengths(cfg, warnings, "", descriptions, DESCRIPTION_MAX, "descriptions");
113872
+ if (cfg.businessName?.trim()) {
113873
+ const bn = calcGoogleCharLength(cfg.businessName.trim());
113874
+ if (bn > BUSINESS_NAME_MAX) {
113875
+ pushWarn3(
113876
+ warnings,
113877
+ `businessName \u6709\u6548\u957F\u5EA6 ${bn} \u8D85\u8FC7\u5EFA\u8BAE\u4E0A\u9650 ${BUSINESS_NAME_MAX}`
113878
+ );
113879
+ }
113880
+ }
113881
+ return { errors, warnings };
113882
+ }
113883
+
113884
+ // src/commands/ad/pmax-image-validate.ts
113885
+ import { isAbsolute as isAbsolute2, resolve as resolve6, dirname as dirname7 } from "path";
113886
+
113887
+ // src/commands/ad/pmax-image-dims.ts
113888
+ import { openSync, readSync, closeSync, statSync } from "fs";
113889
+ var PNG_SIG = [137, 80, 78, 71, 13, 10, 26, 10];
113890
+ function parsePng(buf) {
113891
+ if (buf.length < 24) return null;
113892
+ for (let i = 0; i < 8; i++) {
113893
+ if (buf[i] !== PNG_SIG[i]) return null;
113894
+ }
113895
+ return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
113896
+ }
113897
+ function isSOFMarker(marker) {
113898
+ return marker >= 192 && marker <= 195 || marker >= 197 && marker <= 199 || marker >= 201 && marker <= 203 || marker >= 205 && marker <= 207;
113899
+ }
113900
+ function parseJpeg(buf) {
113901
+ if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
113902
+ let i = 2;
113903
+ while (i + 4 <= buf.length) {
113904
+ if (buf[i] !== 255) break;
113905
+ const marker = buf[i + 1];
113906
+ if (isSOFMarker(marker)) {
113907
+ if (i + 9 > buf.length) break;
113908
+ return { height: buf.readUInt16BE(i + 5), width: buf.readUInt16BE(i + 7) };
113909
+ }
113910
+ if (marker === 216 || marker === 217) {
113911
+ i += 2;
113912
+ continue;
113913
+ }
113914
+ if (i + 4 > buf.length) break;
113915
+ const segLen = buf.readUInt16BE(i + 2);
113916
+ if (segLen < 2) break;
113917
+ i += 2 + segLen;
113918
+ }
113919
+ return null;
113920
+ }
113921
+ function parseBuffer(buf) {
113922
+ return parsePng(buf) ?? parseJpeg(buf);
113923
+ }
113924
+ function readImageDimsFromPath(absPath) {
113925
+ let fileSizeBytes = 0;
113926
+ try {
113927
+ fileSizeBytes = statSync(absPath).size;
113928
+ } catch {
113929
+ return null;
113930
+ }
113931
+ const readSize = Math.min(fileSizeBytes, 65536);
113932
+ const buf = Buffer.alloc(readSize);
113933
+ let fd;
113934
+ try {
113935
+ fd = openSync(absPath, "r");
113936
+ } catch {
113937
+ return null;
113938
+ }
113939
+ try {
113940
+ const bytesRead = readSync(fd, buf, 0, readSize, 0);
113941
+ const dims = parseBuffer(buf.subarray(0, bytesRead));
113942
+ if (!dims) return null;
113943
+ return { ...dims, fileSizeBytes };
113944
+ } catch {
113945
+ return null;
113946
+ } finally {
113947
+ closeSync(fd);
113948
+ }
113949
+ }
113950
+ function readImageDimsFromBase64(b64) {
113951
+ try {
113952
+ const full = Buffer.from(b64.trim(), "base64");
113953
+ return parseBuffer(full.subarray(0, 65536));
113954
+ } catch {
113955
+ return null;
113956
+ }
113957
+ }
113958
+
113959
+ // src/commands/ad/pmax-image-validate.ts
113960
+ var PMAX_IMAGE_SPECS = {
113961
+ marketing: {
113962
+ label: "\u6A2A\u56FE (marketing, 1.91:1)",
113963
+ ratio: 1.91,
113964
+ ratioPct: 0.02,
113965
+ minWidth: 600,
113966
+ minHeight: 314,
113967
+ recommendedWidth: 1200,
113968
+ recommendedHeight: 628,
113969
+ maxFileSizeKB: 5120
113970
+ },
113971
+ square: {
113972
+ label: "\u65B9\u56FE (square, 1:1)",
113973
+ ratio: 1,
113974
+ ratioPct: 0.02,
113975
+ minWidth: 300,
113976
+ minHeight: 300,
113977
+ recommendedWidth: 1200,
113978
+ recommendedHeight: 1200,
113979
+ maxFileSizeKB: 5120
113980
+ },
113981
+ logo: {
113982
+ label: "Logo (square logo, 1:1)",
113983
+ ratio: 1,
113984
+ ratioPct: 0.02,
113985
+ minWidth: 128,
113986
+ minHeight: 128,
113987
+ recommendedWidth: 1200,
113988
+ recommendedHeight: 1200,
113989
+ maxFileSizeKB: 5120
113990
+ }
113991
+ };
113992
+ function resolveImagePath(configFile, relOrAbs) {
113993
+ const t = relOrAbs.trim();
113994
+ return isAbsolute2(t) ? t : resolve6(dirname7(configFile), t);
113995
+ }
113996
+ function checkSpec(spec, width, height, fileSizeKB, errors, warnings) {
113997
+ const { label, ratio, ratioPct, minWidth, minHeight, recommendedWidth, recommendedHeight, maxFileSizeKB } = spec;
113998
+ const actualRatio = width / height;
113999
+ const ratioOk = Math.abs(actualRatio - ratio) <= ratio * ratioPct;
114000
+ if (!ratioOk) {
114001
+ errors.push(
114002
+ `${label}\uFF1A\u5BBD\u9AD8\u6BD4 ${width}\xD7${height}\uFF08${actualRatio.toFixed(3)}\uFF09\u504F\u79BB\u76EE\u6807 ${ratio}\uFF0C\u8D85\u51FA \xB1${(ratioPct * 100).toFixed(0)}% \u5BB9\u5DEE`
114003
+ );
114004
+ }
114005
+ if (width < minWidth || height < minHeight) {
114006
+ errors.push(
114007
+ `${label}\uFF1A\u5C3A\u5BF8 ${width}\xD7${height} \u4F4E\u4E8E Google \u6700\u5C0F\u8981\u6C42 ${minWidth}\xD7${minHeight}`
114008
+ );
114009
+ } else if (width < recommendedWidth || height < recommendedHeight) {
114010
+ warnings.push(
114011
+ `${label}\uFF1A\u5C3A\u5BF8 ${width}\xD7${height} \u4F4E\u4E8E\u63A8\u8350\u5C3A\u5BF8 ${recommendedWidth}\xD7${recommendedHeight}\uFF0C\u53EF\u80FD\u5F71\u54CD\u5C55\u793A\u6548\u679C`
114012
+ );
114013
+ }
114014
+ if (fileSizeKB > maxFileSizeKB) {
114015
+ errors.push(
114016
+ `${label}\uFF1A\u6587\u4EF6\u5927\u5C0F ${fileSizeKB.toFixed(0)} KB \u8D85\u8FC7 Google \u4E0A\u9650 ${maxFileSizeKB} KB`
114017
+ );
114018
+ }
114019
+ }
114020
+ async function runPmaxImageValidation(configFile, cfg) {
114021
+ const errors = [];
114022
+ const warnings = [];
114023
+ const slots = [
114024
+ { kind: "marketing", pathRaw: cfg.imagePaths?.marketing, b64: cfg.marketingImageBase64 },
114025
+ { kind: "square", pathRaw: cfg.imagePaths?.square, b64: cfg.squareMarketingImageBase64 },
114026
+ { kind: "logo", pathRaw: cfg.imagePaths?.logo, b64: cfg.logoImageBase64 }
114027
+ ];
114028
+ for (const { kind, pathRaw, b64 } of slots) {
114029
+ const spec = PMAX_IMAGE_SPECS[kind];
114030
+ let dims = null;
114031
+ let fileSizeKB = 0;
114032
+ if (b64?.trim()) {
114033
+ dims = readImageDimsFromBase64(b64.trim());
114034
+ fileSizeKB = Math.ceil(b64.trim().length * 0.75 / 1024);
114035
+ } else if (pathRaw?.trim()) {
114036
+ const abs = resolveImagePath(configFile, pathRaw.trim());
114037
+ const result = readImageDimsFromPath(abs);
114038
+ if (result) {
114039
+ dims = result;
114040
+ fileSizeKB = Math.ceil(result.fileSizeBytes / 1024);
114041
+ } else {
114042
+ warnings.push(`${spec.label}\uFF1A\u65E0\u6CD5\u8BFB\u53D6\u56FE\u7247\u5C3A\u5BF8\uFF08${abs}\uFF09\uFF0C\u8DF3\u8FC7\u89C4\u683C\u6821\u9A8C`);
114043
+ continue;
114044
+ }
114045
+ } else {
114046
+ continue;
114047
+ }
114048
+ if (!dims) {
114049
+ warnings.push(`${spec.label}\uFF1A\u683C\u5F0F\u65E0\u6CD5\u8BC6\u522B\uFF08\u975E PNG/JPEG\uFF09\uFF0C\u8DF3\u8FC7\u89C4\u683C\u6821\u9A8C`);
114050
+ continue;
114051
+ }
114052
+ checkSpec(spec, dims.width, dims.height, fileSizeKB, errors, warnings);
114053
+ }
114054
+ return { errors, warnings };
114055
+ }
114056
+
114057
+ // src/commands/ad/pmax-load.ts
114058
+ import { readFileSync as readFileSync5 } from "fs";
114059
+ import { dirname as dirname8, isAbsolute as isAbsolute3, resolve as resolve7 } from "path";
114060
+ function loadPmaxCreateConfig(configFile) {
114061
+ const cfg = tryLoadPmaxCreateConfig(configFile);
114062
+ if (!cfg) {
114063
+ console.error(`
114064
+ \u274C \u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6\u5931\u8D25\uFF08${configFile}\uFF09
114065
+ `);
114066
+ process.exit(1);
114067
+ }
114068
+ return cfg;
114069
+ }
114070
+ function tryLoadPmaxCreateConfig(configFile) {
114071
+ let raw;
114072
+ try {
114073
+ raw = JSON.parse(readFileSync5(configFile, "utf8"));
114074
+ } catch {
114075
+ return null;
114076
+ }
114077
+ return stripMetaKeys(raw);
114078
+ }
114079
+ function resolveImagePath2(configFile, relOrAbs) {
114080
+ const trimmed = relOrAbs.trim();
114081
+ if (isAbsolute3(trimmed)) return trimmed;
114082
+ return resolve7(dirname8(configFile), trimmed);
114083
+ }
114084
+ function readImageBase64(configFile, pathOrUndefined) {
114085
+ if (!pathOrUndefined?.trim()) return void 0;
114086
+ const abs = resolveImagePath2(configFile, pathOrUndefined);
114087
+ try {
114088
+ return readFileSync5(abs).toString("base64");
114089
+ } catch (e) {
114090
+ const msg = e instanceof Error ? e.message : String(e);
114091
+ throw new Error(`\u8BFB\u53D6\u56FE\u7247\u5931\u8D25\uFF08${abs}\uFF09\uFF1A${msg}`);
114092
+ }
114093
+ }
114094
+ function parseLocationLanguageIds(items) {
114095
+ if (!items?.length) return void 0;
114096
+ return items.map((item) => ({
114097
+ id: Number(item.id)
114098
+ }));
114099
+ }
114100
+ function buildPmaxCreateApiBody(configFile, cfg) {
114101
+ const paths = cfg.imagePaths;
114102
+ const marketingImageBase64 = cfg.marketingImageBase64?.trim() || readImageBase64(configFile, paths?.marketing) || "";
114103
+ const squareMarketingImageBase64 = cfg.squareMarketingImageBase64?.trim() || readImageBase64(configFile, paths?.square) || "";
114104
+ const logoImageBase64 = cfg.logoImageBase64?.trim() || readImageBase64(configFile, paths?.logo) || "";
114105
+ if (!marketingImageBase64) {
114106
+ throw new Error("marketingImageBase64 \u4E3A\u7A7A\uFF08\u8BF7\u914D\u7F6E imagePaths.marketing \u6216 marketingImageBase64\uFF09");
114107
+ }
114108
+ if (!squareMarketingImageBase64) {
114109
+ throw new Error(
114110
+ "squareMarketingImageBase64 \u4E3A\u7A7A\uFF08\u8BF7\u914D\u7F6E imagePaths.square \u6216 squareMarketingImageBase64\uFF09"
114111
+ );
114112
+ }
114113
+ if (!logoImageBase64) {
114114
+ throw new Error("logoImageBase64 \u4E3A\u7A7A\uFF08\u8BF7\u914D\u7F6E imagePaths.logo \u6216 logoImageBase64\uFF09");
114115
+ }
114116
+ const body = {
114117
+ name: cfg.name.trim(),
114118
+ budget: toCentAmount(Number(cfg.budget)),
114119
+ finalUrls: cfg.finalUrls.map((u) => u.trim()).filter(Boolean),
114120
+ headlines: cfg.headlines.map((h) => h.trim()).filter(Boolean),
114121
+ longHeadlines: cfg.longHeadlines.map((h) => h.trim()).filter(Boolean),
114122
+ descriptions: cfg.descriptions.map((d) => d.trim()).filter(Boolean),
114123
+ businessName: cfg.businessName.trim(),
114124
+ marketingImageBase64,
114125
+ squareMarketingImageBase64,
114126
+ logoImageBase64
114127
+ };
114128
+ if (cfg.budgetName?.trim()) body.budgetName = cfg.budgetName.trim();
114129
+ if (cfg.assetGroupName?.trim()) body.assetGroupName = cfg.assetGroupName.trim();
114130
+ const locations = parseLocationLanguageIds(cfg.targetedLocations);
114131
+ if (locations?.length) body.targetedLocations = locations;
114132
+ const languages = parseLocationLanguageIds(cfg.targetedLanguages);
114133
+ if (languages?.length) body.targetedLanguages = languages;
114134
+ const bidding = cfg.biddingStrategyTypeV2?.toString().trim();
114135
+ if (bidding) body.biddingStrategyTypeV2 = bidding.toUpperCase();
114136
+ const tcpa = cfg.targetCpa_BidingAmount;
114137
+ if (tcpa != null && Number(tcpa) > 0) {
114138
+ body.targetCpa_BidingAmount = toCentAmount(Number(tcpa));
114139
+ }
114140
+ const roas = cfg.targetRoas;
114141
+ if (roas != null && Number(roas) > 0) {
114142
+ body.targetRoas = Number(roas);
114143
+ }
114144
+ return body;
114145
+ }
114146
+ function buildPmaxCreateUrl(googleApiUrl, accountId) {
114147
+ const base = googleApiUrl.replace(/\/$/, "");
114148
+ return `${base}/accounts/${accountId}/campaign/pmax`;
114149
+ }
114150
+
114151
+ // src/commands/ad/pmax-create.ts
114152
+ async function runAdPmaxCreate(opts) {
114153
+ const cfg = loadPmaxCreateConfig(opts.configFile);
114154
+ const { errors: cfgErrors, warnings: cfgWarnings } = runPmaxCreateValidation(cfg);
114155
+ const { errors: imgErrors, warnings: imgWarnings } = await runPmaxImageValidation(
114156
+ opts.configFile,
114157
+ cfg
114158
+ );
114159
+ const errors = [...cfgErrors, ...imgErrors];
114160
+ const warnings = [...cfgWarnings, ...imgWarnings];
114161
+ if (warnings.length > 0) {
114162
+ console.warn("\n\u26A0\uFE0F PMax \u914D\u7F6E\u8B66\u544A\uFF08\u4E0D\u963B\u65AD\u63D0\u4EA4\uFF09\uFF1A");
114163
+ for (const w of warnings) console.warn(` \u2022 ${w}`);
114164
+ }
114165
+ if (errors.length > 0) {
114166
+ console.error("\n\u274C PMax \u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A");
114167
+ for (const e of errors) console.error(` \u2022 ${e}`);
114168
+ console.error();
114169
+ process.exit(1);
114170
+ }
114171
+ const config = await ensureDataPermission(loadConfig(opts.token));
114172
+ const googleApiUrl = requireGoogleApi(config);
114173
+ const accountId = cfg.account.toString().trim();
114174
+ let body;
114175
+ try {
114176
+ body = buildPmaxCreateApiBody(opts.configFile, cfg);
114177
+ } catch (err) {
114178
+ console.error(`
114179
+ \u274C \u6784\u5EFA\u8BF7\u6C42\u4F53\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114180
+ `);
114181
+ process.exit(1);
114182
+ }
114183
+ const url = buildPmaxCreateUrl(googleApiUrl, accountId);
114184
+ let data;
114185
+ try {
114186
+ data = await apiFetch2(
114187
+ url,
114188
+ config,
114189
+ { method: "POST", body: JSON.stringify(body) },
114190
+ opts.verbose
114191
+ );
114192
+ } catch (err) {
114193
+ console.error(`
114194
+ \u274C \u521B\u5EFA PMax \u5E7F\u544A\u7CFB\u5217\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114195
+ `);
114196
+ process.exit(1);
114197
+ }
114198
+ if (await emitCliJsonOrSnapshot(opts, {
114199
+ section: `ad-pmax-create-${accountId}`,
114200
+ commandLabel: "ad pmax-create",
114201
+ commandHint: accountId,
114202
+ payload: data,
114203
+ idSuffix: accountId
114204
+ })) {
114205
+ return;
114206
+ }
114207
+ const campaignId = data["campaignId"] ?? data["campaign_id"];
114208
+ const assetGroupId = data["assetGroupId"] ?? data["asset_group_id"];
114209
+ const budgetId = data["budgetId"] ?? data["budget_id"];
114210
+ console.log("\n\u2705 PMax \u5E7F\u544A\u7CFB\u5217\u5DF2\u521B\u5EFA\uFF08\u540C\u6B65\uFF09");
114211
+ console.log(` \u6D3B\u52A8\u540D\u79F0\uFF1A${cfg.name}`);
114212
+ if (campaignId != null) console.log(` campaignId\uFF1A${campaignId}`);
114213
+ if (assetGroupId != null) console.log(` assetGroupId\uFF1A${assetGroupId}`);
114214
+ if (budgetId != null) console.log(` budgetId\uFF1A${budgetId}`);
114215
+ console.log(
114216
+ `
114217
+ \u590D\u6838\uFF1Asiluzan-tso ad campaigns -a ${accountId} --json # channelTypeV2 \u5E94\u4E3A PERFORMANCE_MAX`
114218
+ );
114219
+ console.log();
114220
+ }
114221
+
114222
+ // src/commands/ad/pmax-validate.ts
113510
114223
  import { writeFileSync as writeFileSync3 } from "fs";
113511
114224
  init_cli_json_snapshot();
114225
+ async function runAdPmaxValidate(opts) {
114226
+ const cfg = loadPmaxCreateConfig(opts.configFile);
114227
+ const { errors: cfgErrors, warnings: cfgWarnings } = runPmaxCreateValidation(cfg);
114228
+ const { errors: imgErrors, warnings: imgWarnings } = await runPmaxImageValidation(
114229
+ opts.configFile,
114230
+ cfg
114231
+ );
114232
+ const errors = [...cfgErrors, ...imgErrors];
114233
+ const warnings = [...cfgWarnings, ...imgWarnings];
114234
+ if (opts.writeNormalized) {
114235
+ const toWrite = stripMetaKeysForExport(cfg);
114236
+ writeFileSync3(opts.writeNormalized, `${JSON.stringify(toWrite, null, 2)}
114237
+ `, "utf8");
114238
+ }
114239
+ const payload = {
114240
+ ok: errors.length === 0,
114241
+ configFile: opts.configFile,
114242
+ account: cfg.account?.toString().trim() || void 0,
114243
+ errors,
114244
+ warnings
114245
+ };
114246
+ const accountSuffix = payload.account;
114247
+ if (await emitCliJsonOrSnapshot(opts, {
114248
+ section: "ad-pmax-validate",
114249
+ commandLabel: "ad pmax-validate",
114250
+ commandHint: opts.configFile,
114251
+ payload,
114252
+ idSuffix: accountSuffix
114253
+ })) {
114254
+ if (!payload.ok) process.exit(1);
114255
+ return;
114256
+ }
114257
+ if (warnings.length > 0) {
114258
+ console.warn("\n\u26A0\uFE0F PMax \u914D\u7F6E\u8B66\u544A\uFF1A");
114259
+ for (const w of warnings) console.warn(` \u2022 ${w}`);
114260
+ }
114261
+ if (errors.length > 0) {
114262
+ console.error("\n\u274C PMax \u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A");
114263
+ for (const e of errors) console.error(` \u2022 ${e}`);
114264
+ console.error();
114265
+ process.exit(1);
114266
+ }
114267
+ console.log("\n\u2705 PMax \u914D\u7F6E\u6821\u9A8C\u901A\u8FC7");
114268
+ if (opts.writeNormalized) {
114269
+ console.log(` \u5DF2\u5199\u5165\uFF1A${opts.writeNormalized}`);
114270
+ }
114271
+ console.log();
114272
+ }
114273
+
114274
+ // src/commands/ad/pmax-mgmt.ts
114275
+ import { readFileSync as readFileSync7 } from "fs";
114276
+
114277
+ // src/commands/ad/pmax-shared.ts
114278
+ init_auth();
114279
+ init_cli_json_snapshot();
114280
+ import { readFileSync as readFileSync6 } from "fs";
114281
+ import { dirname as dirname9, isAbsolute as isAbsolute4, resolve as resolve8 } from "path";
114282
+ var PMAX_MONEY_KEYS = /* @__PURE__ */ new Set(["budget", "targetCpa_BidingAmount"]);
114283
+ function loadPmaxJsonFile(configFile) {
114284
+ let raw;
114285
+ try {
114286
+ raw = JSON.parse(readFileSync6(configFile, "utf8"));
114287
+ } catch (e) {
114288
+ const msg = e instanceof Error ? e.message : String(e);
114289
+ console.error(`
114290
+ \u274C \u8BFB\u53D6 JSON \u5931\u8D25\uFF08${configFile}\uFF09\uFF1A${msg}
114291
+ `);
114292
+ process.exit(1);
114293
+ }
114294
+ return stripMetaKeys(raw);
114295
+ }
114296
+ function resolveImagePath3(configFile, relOrAbs) {
114297
+ const trimmed = relOrAbs.trim();
114298
+ if (isAbsolute4(trimmed)) return trimmed;
114299
+ return resolve8(dirname9(configFile), trimmed);
114300
+ }
114301
+ function readImageBase642(configFile, pathOrUndefined) {
114302
+ if (!pathOrUndefined?.trim()) return void 0;
114303
+ const abs = resolveImagePath3(configFile, pathOrUndefined);
114304
+ try {
114305
+ return readFileSync6(abs).toString("base64");
114306
+ } catch (e) {
114307
+ const msg = e instanceof Error ? e.message : String(e);
114308
+ throw new Error(`\u8BFB\u53D6\u56FE\u7247\u5931\u8D25\uFF08${abs}\uFF09\uFF1A${msg}`);
114309
+ }
114310
+ }
114311
+ function convertPmaxMoneyInObject(body) {
114312
+ const out = { ...body };
114313
+ for (const key of PMAX_MONEY_KEYS) {
114314
+ const val = out[key];
114315
+ if (typeof val === "number" && Number.isFinite(val)) {
114316
+ out[key] = toCentAmount(val);
114317
+ }
114318
+ }
114319
+ return out;
114320
+ }
114321
+ function buildPmaxAssetGroupApiBody(configFile, cfg) {
114322
+ const paths = cfg.imagePaths;
114323
+ const marketingImageBase64 = cfg.marketingImageBase64?.trim() || readImageBase642(configFile, paths?.marketing) || "";
114324
+ const squareMarketingImageBase64 = cfg.squareMarketingImageBase64?.trim() || readImageBase642(configFile, paths?.square) || "";
114325
+ const logoImageBase64 = cfg.logoImageBase64?.trim() || readImageBase642(configFile, paths?.logo) || "";
114326
+ if (!marketingImageBase64) {
114327
+ throw new Error("marketingImageBase64 \u4E3A\u7A7A\uFF08\u8BF7\u914D\u7F6E imagePaths.marketing \u6216 marketingImageBase64\uFF09");
114328
+ }
114329
+ if (!squareMarketingImageBase64) {
114330
+ throw new Error(
114331
+ "squareMarketingImageBase64 \u4E3A\u7A7A\uFF08\u8BF7\u914D\u7F6E imagePaths.square \u6216 squareMarketingImageBase64\uFF09"
114332
+ );
114333
+ }
114334
+ if (!logoImageBase64) {
114335
+ throw new Error("logoImageBase64 \u4E3A\u7A7A\uFF08\u8BF7\u914D\u7F6E imagePaths.logo \u6216 logoImageBase64\uFF09");
114336
+ }
114337
+ return {
114338
+ name: cfg.name.trim(),
114339
+ finalUrls: cfg.finalUrls.map((u) => u.trim()).filter(Boolean),
114340
+ headlines: cfg.headlines.map((h) => h.trim()).filter(Boolean),
114341
+ longHeadlines: cfg.longHeadlines.map((h) => h.trim()).filter(Boolean),
114342
+ descriptions: cfg.descriptions.map((d) => d.trim()).filter(Boolean),
114343
+ businessName: cfg.businessName.trim(),
114344
+ marketingImageBase64,
114345
+ squareMarketingImageBase64,
114346
+ logoImageBase64
114347
+ };
114348
+ }
114349
+ function processAssetsUpdateBody(configFile, body) {
114350
+ const out = { ...body };
114351
+ const links = out["assetsToLink"];
114352
+ if (!Array.isArray(links)) return out;
114353
+ out["assetsToLink"] = links.map((item) => {
114354
+ if (!item || typeof item !== "object") return item;
114355
+ const row = { ...item };
114356
+ const imagePath = row["imagePath"];
114357
+ if (typeof imagePath === "string" && imagePath.trim()) {
114358
+ const b64 = readImageBase642(configFile, imagePath);
114359
+ if (b64) row["imageBase64"] = b64;
114360
+ delete row["imagePath"];
114361
+ }
114362
+ return row;
114363
+ });
114364
+ return out;
114365
+ }
114366
+ async function withGoogleApi(opts, fn) {
114367
+ const config = await ensureDataPermission(loadConfig(opts.token));
114368
+ const googleApiUrl = requireGoogleApi(config);
114369
+ return fn(config, googleApiUrl);
114370
+ }
114371
+ async function pmaxApiFetch(url, config, init, verbose) {
114372
+ return apiFetch2(url, config, init ?? {}, verbose);
114373
+ }
114374
+ async function emitPmaxResult(opts, section, commandLabel, payload, idSuffix) {
114375
+ return emitCliJsonOrSnapshot(opts, {
114376
+ section,
114377
+ commandLabel,
114378
+ commandHint: idSuffix ?? opts.account ?? "",
114379
+ payload,
114380
+ idSuffix: idSuffix ?? opts.account
114381
+ });
114382
+ }
114383
+ function requireAccountId(account, label = "-a, --account") {
114384
+ const id = account?.toString().trim();
114385
+ if (!id) {
114386
+ console.error(`
114387
+ \u274C \u8BF7\u6307\u5B9A\u5A92\u4F53\u8D26\u6237 ID\uFF08${label}\uFF09
114388
+ `);
114389
+ process.exit(1);
114390
+ }
114391
+ return id;
114392
+ }
114393
+
114394
+ // src/commands/ad/pmax-urls.ts
114395
+ function pmaxChannelTypesUrl(googleApiUrl) {
114396
+ const base = googleApiUrl.replace(/\/$/, "");
114397
+ return `${base}/campaign/pmax/channel-types`;
114398
+ }
114399
+ function pmaxCampaignUrl(googleApiUrl, accountId, campaignId) {
114400
+ const base = googleApiUrl.replace(/\/$/, "");
114401
+ const path22 = `${base}/accounts/${accountId}/campaign/pmax`;
114402
+ return campaignId ? `${path22}/${campaignId}` : path22;
114403
+ }
114404
+ function pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, suffix) {
114405
+ const base = googleApiUrl.replace(/\/$/, "");
114406
+ const path22 = `${base}/accounts/${accountId}/campaign/pmax/asset-group/${assetGroupId}`;
114407
+ return suffix ? `${path22}/${suffix.replace(/^\//, "")}` : path22;
114408
+ }
114409
+ function pmaxCampaignAssetGroupUrl(googleApiUrl, accountId, campaignId) {
114410
+ const base = googleApiUrl.replace(/\/$/, "");
114411
+ return `${base}/accounts/${accountId}/campaign/pmax/${campaignId}/asset-group`;
114412
+ }
114413
+ function pmaxUserlistsUrl(googleApiUrl, accountId) {
114414
+ const base = googleApiUrl.replace(/\/$/, "");
114415
+ return `${base}/campaignmanagement/userlists/${accountId}`;
114416
+ }
114417
+ function pmaxAudiencesUrl(googleApiUrl, accountId, limit) {
114418
+ const base = googleApiUrl.replace(/\/$/, "");
114419
+ const url = `${base}/campaignmanagement/audiences/${accountId}`;
114420
+ if (limit != null && Number.isFinite(limit)) {
114421
+ return `${url}?limit=${Math.floor(limit)}`;
114422
+ }
114423
+ return url;
114424
+ }
114425
+ function pmaxImageAssetUrl(googleApiUrl, accountId, name) {
114426
+ const base = googleApiUrl.replace(/\/$/, "");
114427
+ const url = `${base}/mediamanagement/imageasset/${accountId}`;
114428
+ if (name?.trim()) {
114429
+ return `${url}?name=${encodeURIComponent(name.trim())}`;
114430
+ }
114431
+ return url;
114432
+ }
114433
+ function pmaxAssetGroupReportUrl(googleApiUrl, accountId, query) {
114434
+ const base = googleApiUrl.replace(/\/$/, "");
114435
+ const qs = query.toString();
114436
+ return `${base}/reporting/media-account/${accountId}/pmax/asset-groups/reports${qs ? `?${qs}` : ""}`;
114437
+ }
114438
+ function pmaxGeoReportUrl(googleApiUrl, accountId, query) {
114439
+ const base = googleApiUrl.replace(/\/$/, "");
114440
+ const qs = query.toString();
114441
+ return `${base}/reporting/media-account/${accountId}/pmax/campaigns/reports/geo${qs ? `?${qs}` : ""}`;
114442
+ }
114443
+
114444
+ // src/commands/ad/pmax-mgmt.ts
114445
+ function buildReportQuery(opts) {
114446
+ const params = new URLSearchParams();
114447
+ params.set("startDate", toGoogleDate(opts.startDate, -30).replace(/\//g, "-"));
114448
+ params.set("endDate", toGoogleDate(opts.endDate, 0).replace(/\//g, "-"));
114449
+ if (opts.campaignId?.trim()) params.set("campaignId", opts.campaignId.trim());
114450
+ if (opts.costGreater != null && Number.isFinite(opts.costGreater)) {
114451
+ params.set("costGreater", String(opts.costGreater));
114452
+ }
114453
+ if (opts.clickGreater != null && Number.isFinite(opts.clickGreater)) {
114454
+ params.set("clickGreater", String(opts.clickGreater));
114455
+ }
114456
+ if (opts.conversionsGreater != null && Number.isFinite(opts.conversionsGreater)) {
114457
+ params.set("conversionsGreater", String(opts.conversionsGreater));
114458
+ }
114459
+ return params;
114460
+ }
114461
+ function parseStatusV2(status) {
114462
+ const s = status.trim();
114463
+ if (/^\d+$/.test(s)) return Number(s);
114464
+ const map = {
114465
+ ENABLED: 2,
114466
+ ENABLED_V2: 2,
114467
+ PAUSED: 3,
114468
+ REMOVED: 4
114469
+ };
114470
+ const upper = s.toUpperCase();
114471
+ if (upper in map) return map[upper];
114472
+ return upper;
114473
+ }
114474
+ async function runAdPmaxChannelTypes(opts) {
114475
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
114476
+ const url = pmaxChannelTypesUrl(googleApiUrl);
114477
+ let data;
114478
+ try {
114479
+ data = await pmaxApiFetch(url, config, {}, opts.verbose);
114480
+ } catch (err) {
114481
+ console.error(
114482
+ `
114483
+ \u274C \u83B7\u53D6 PMax \u6E20\u9053\u7C7B\u578B\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114484
+ `
114485
+ );
114486
+ process.exit(1);
114487
+ }
114488
+ if (await emitPmaxResult(opts, "ad-pmax-channel-types", "ad pmax-channel-types", data, "pmax")) {
114489
+ return;
114490
+ }
114491
+ const list = Array.isArray(data) ? data : data && typeof data === "object" ? data["data"] ?? [] : [];
114492
+ console.log("\nPMax \u6E20\u9053\u7C7B\u578B\uFF08\u542B\u5176\u5B83\u6E20\u9053\u5BF9\u7167\uFF09\n");
114493
+ for (const row of list) {
114494
+ if (row && typeof row === "object") {
114495
+ const kv = row;
114496
+ console.log(` ${kv.key ?? "?"} ${kv.value ?? ""}`);
114497
+ }
114498
+ }
114499
+ console.log();
114500
+ });
114501
+ }
114502
+ async function runAdPmaxGet(opts) {
114503
+ const accountId = requireAccountId(opts.account);
114504
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
114505
+ const url = pmaxCampaignUrl(googleApiUrl, accountId, opts.campaignId.trim());
114506
+ let data;
114507
+ try {
114508
+ const raw = await pmaxApiFetch(url, config, {}, opts.verbose);
114509
+ data = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : { data: raw };
114510
+ } catch (err) {
114511
+ console.error(`
114512
+ \u274C \u83B7\u53D6 PMax \u8BE6\u60C5\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114513
+ `);
114514
+ process.exit(1);
114515
+ }
114516
+ if (await emitPmaxResult(opts, `ad-pmax-get-${accountId}`, "ad pmax-get", data, opts.campaignId)) {
114517
+ return;
114518
+ }
114519
+ console.log(`
114520
+ \u2705 PMax \u6D3B\u52A8 ${opts.campaignId}\uFF08\u8D26\u6237 ${accountId}\uFF09`);
114521
+ console.log(` \u540D\u79F0\uFF1A${data["name"] ?? "\u2014"}`);
114522
+ console.log(` \u72B6\u6001\uFF1A${data["statusV2"] ?? "\u2014"}`);
114523
+ console.log(` \u9884\u7B97\uFF1A${data["budget"] ?? "\u2014"}\uFF08API \u5206\uFF0C\u5C55\u793A \xF7100\uFF09`);
114524
+ const groups = data["assetGroups"];
114525
+ const n = Array.isArray(groups) ? groups.length : 0;
114526
+ console.log(` \u8D44\u4EA7\u7EC4\uFF1A${n} \u4E2A`);
114527
+ console.log(`
114528
+ \u5B8C\u6574\u7ED3\u6784\u8BF7\u4F7F\u7528 --json
114529
+ `);
114530
+ });
114531
+ }
114532
+ async function runAdPmaxEdit(opts) {
114533
+ const accountId = requireAccountId(opts.account);
114534
+ const campaignId = opts.campaignId.trim();
114535
+ let body;
114536
+ if (opts.patchFile) {
114537
+ body = convertPmaxMoneyInObject(loadPmaxJsonFile(opts.patchFile));
114538
+ } else {
114539
+ body = {};
114540
+ if (opts.status !== void 0) body["statusV2"] = parseStatusV2(opts.status);
114541
+ if (opts.budget !== void 0) body["budget"] = toCentAmount(opts.budget);
114542
+ if (opts.budgetId !== void 0) body["budgetId"] = opts.budgetId.trim();
114543
+ if (opts.bidding !== void 0) body["biddingStrategyTypeV2"] = opts.bidding.toUpperCase();
114544
+ if (opts.targetCpa !== void 0) body["targetCpa_BidingAmount"] = toCentAmount(opts.targetCpa);
114545
+ if (opts.targetRoas !== void 0) body["targetRoas"] = opts.targetRoas;
114546
+ if (opts.urlExpansionOptOut !== void 0) {
114547
+ body["urlExpansionOptOut"] = opts.urlExpansionOptOut;
114548
+ }
114549
+ if (Object.keys(body).length === 0) {
114550
+ console.error(
114551
+ "\n\u274C \u8BF7\u6307\u5B9A --patch-file \u6216\u81F3\u5C11\u4E00\u9879\u4FEE\u6539\uFF08--status / --budget / --bidding / \u2026\uFF09\n"
114552
+ );
114553
+ process.exit(1);
114554
+ }
114555
+ if (opts.budget !== void 0 && !opts.budgetId?.trim()) {
114556
+ console.error("\n\u274C \u4FEE\u6539\u9884\u7B97\u987B\u540C\u65F6\u4F20 --budget-id\uFF08\u6765\u81EA pmax-get \u6216\u521B\u5EFA\u54CD\u5E94\uFF09\n");
114557
+ process.exit(1);
114558
+ }
114559
+ }
114560
+ if (Object.keys(body).length === 0) {
114561
+ console.error("\n\u274C PATCH body \u4E3A\u7A7A\n");
114562
+ process.exit(1);
114563
+ }
114564
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
114565
+ const url = pmaxCampaignUrl(googleApiUrl, accountId, campaignId);
114566
+ try {
114567
+ await pmaxApiFetch(
114568
+ url,
114569
+ config,
114570
+ { method: "PATCH", body: JSON.stringify(body) },
114571
+ opts.verbose
114572
+ );
114573
+ } catch (err) {
114574
+ console.error(`
114575
+ \u274C \u66F4\u65B0 PMax \u6D3B\u52A8\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114576
+ `);
114577
+ process.exit(1);
114578
+ }
114579
+ if (await emitPmaxResult(
114580
+ { ...opts, account: accountId },
114581
+ `ad-pmax-edit-${accountId}`,
114582
+ "ad pmax-edit",
114583
+ { ok: true, campaignId, patch: body },
114584
+ campaignId
114585
+ )) {
114586
+ return;
114587
+ }
114588
+ console.log(`
114589
+ \u2705 PMax \u6D3B\u52A8 ${campaignId} \u5DF2\u66F4\u65B0
114590
+ `);
114591
+ });
114592
+ }
114593
+ async function runAdPmaxAssetGroupCreate(opts) {
114594
+ const cfg = loadPmaxJsonFile(opts.configFile);
114595
+ const accountId = requireAccountId(String(cfg["account"] ?? ""));
114596
+ const campaignId = String(cfg["campaignId"] ?? "").trim();
114597
+ if (!campaignId) {
114598
+ console.error("\n\u274C JSON \u987B\u5305\u542B campaignId\n");
114599
+ process.exit(1);
114600
+ }
114601
+ let body;
114602
+ try {
114603
+ body = buildPmaxAssetGroupApiBody(opts.configFile, {
114604
+ name: String(cfg["name"] ?? ""),
114605
+ finalUrls: cfg["finalUrls"] ?? [],
114606
+ businessName: String(cfg["businessName"] ?? ""),
114607
+ headlines: cfg["headlines"] ?? [],
114608
+ longHeadlines: cfg["longHeadlines"] ?? [],
114609
+ descriptions: cfg["descriptions"] ?? [],
114610
+ imagePaths: cfg["imagePaths"],
114611
+ marketingImageBase64: cfg["marketingImageBase64"],
114612
+ squareMarketingImageBase64: cfg["squareMarketingImageBase64"],
114613
+ logoImageBase64: cfg["logoImageBase64"]
114614
+ });
114615
+ } catch (err) {
114616
+ console.error(`
114617
+ \u274C \u6784\u5EFA\u8BF7\u6C42\u4F53\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114618
+ `);
114619
+ process.exit(1);
114620
+ }
114621
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
114622
+ const url = pmaxCampaignAssetGroupUrl(googleApiUrl, accountId, campaignId);
114623
+ let data;
114624
+ try {
114625
+ const raw = await pmaxApiFetch(
114626
+ url,
114627
+ config,
114628
+ { method: "POST", body: JSON.stringify(body) },
114629
+ opts.verbose
114630
+ );
114631
+ data = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : { data: raw };
114632
+ } catch (err) {
114633
+ console.error(
114634
+ `
114635
+ \u274C \u521B\u5EFA PMax \u8D44\u4EA7\u7EC4\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114636
+ `
114637
+ );
114638
+ process.exit(1);
114639
+ }
114640
+ if (await emitPmaxResult(
114641
+ { ...opts, account: accountId },
114642
+ `ad-pmax-asset-group-create-${accountId}`,
114643
+ "ad pmax-asset-group-create",
114644
+ data,
114645
+ String(data["id"] ?? campaignId)
114646
+ )) {
114647
+ return;
114648
+ }
114649
+ console.log(`
114650
+ \u2705 \u5DF2\u521B\u5EFA\u8D44\u4EA7\u7EC4\uFF08\u6D3B\u52A8 ${campaignId}\uFF09`);
114651
+ console.log(` assetGroupId\uFF1A${data["id"] ?? "\u2014"}`);
114652
+ console.log(` \u540D\u79F0\uFF1A${data["name"] ?? body["name"]}
114653
+ `);
114654
+ });
114655
+ }
114656
+ async function runAdPmaxAssetGroupEdit(opts) {
114657
+ const accountId = requireAccountId(opts.account);
114658
+ const assetGroupId = opts.assetGroupId.trim();
114659
+ let body;
114660
+ if (opts.patchFile) {
114661
+ body = loadPmaxJsonFile(opts.patchFile);
114662
+ } else {
114663
+ body = {};
114664
+ if (opts.name !== void 0) body["name"] = opts.name.trim();
114665
+ if (opts.finalUrls !== void 0) {
114666
+ body["finalUrls"] = opts.finalUrls.split(",").map((u) => u.trim()).filter(Boolean);
114667
+ }
114668
+ if (opts.status !== void 0) body["status"] = opts.status.trim().toUpperCase();
114669
+ if (Object.keys(body).length === 0) {
114670
+ console.error(
114671
+ "\n\u274C \u8BF7\u6307\u5B9A --patch-file \u6216 --name / --final-urls / --status \u81F3\u5C11\u4E00\u9879\n"
114672
+ );
114673
+ process.exit(1);
114674
+ }
114675
+ }
114676
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
114677
+ const url = pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId);
114678
+ try {
114679
+ await pmaxApiFetch(
114680
+ url,
114681
+ config,
114682
+ { method: "PATCH", body: JSON.stringify(body) },
114683
+ opts.verbose
114684
+ );
114685
+ } catch (err) {
114686
+ console.error(
114687
+ `
114688
+ \u274C \u66F4\u65B0 PMax \u8D44\u4EA7\u7EC4\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114689
+ `
114690
+ );
114691
+ process.exit(1);
114692
+ }
114693
+ if (await emitPmaxResult(
114694
+ { ...opts, account: accountId },
114695
+ `ad-pmax-asset-group-edit-${accountId}`,
114696
+ "ad pmax-asset-group-edit",
114697
+ { ok: true, assetGroupId, patch: body },
114698
+ assetGroupId
114699
+ )) {
114700
+ return;
114701
+ }
114702
+ console.log(`
114703
+ \u2705 \u8D44\u4EA7\u7EC4 ${assetGroupId} \u5DF2\u66F4\u65B0
114704
+ `);
114705
+ });
114706
+ }
114707
+ async function runAdPmaxAssetsUpdate(opts) {
114708
+ const cfg = loadPmaxJsonFile(opts.configFile);
114709
+ const accountId = requireAccountId(String(cfg["account"] ?? ""));
114710
+ const assetGroupId = String(cfg["assetGroupId"] ?? "").trim();
114711
+ if (!assetGroupId) {
114712
+ console.error("\n\u274C JSON \u987B\u5305\u542B assetGroupId\n");
114713
+ process.exit(1);
114714
+ }
114715
+ if (!String(cfg["campaignId"] ?? "").trim()) {
114716
+ console.error("\n\u274C JSON \u987B\u5305\u542B campaignId\uFF08PUT assets body \u5FC5\u586B\uFF09\n");
114717
+ process.exit(1);
114718
+ }
114719
+ let body;
114720
+ try {
114721
+ const { account: _a, ...rest } = cfg;
114722
+ body = processAssetsUpdateBody(opts.configFile, rest);
114723
+ } catch (err) {
114724
+ console.error(`
114725
+ \u274C \u5904\u7406 assets JSON \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114726
+ `);
114727
+ process.exit(1);
114728
+ }
114729
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
114730
+ const url = pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, "assets");
114731
+ try {
114732
+ await pmaxApiFetch(
114733
+ url,
114734
+ config,
114735
+ { method: "PUT", body: JSON.stringify(body) },
114736
+ opts.verbose
114737
+ );
114738
+ } catch (err) {
114739
+ console.error(
114740
+ `
114741
+ \u274C \u66F4\u65B0 PMax \u8D44\u4EA7\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114742
+ `
114743
+ );
114744
+ process.exit(1);
114745
+ }
114746
+ if (await emitPmaxResult(
114747
+ { ...opts, account: accountId },
114748
+ `ad-pmax-assets-update-${accountId}`,
114749
+ "ad pmax-assets-update",
114750
+ { ok: true, assetGroupId, body },
114751
+ assetGroupId
114752
+ )) {
114753
+ return;
114754
+ }
114755
+ console.log(`
114756
+ \u2705 \u8D44\u4EA7\u7EC4 ${assetGroupId} \u8D44\u4EA7\u5DF2\u66F4\u65B0
114757
+ `);
114758
+ });
114759
+ }
114760
+ async function runAdPmaxYoutubeLink(opts) {
114761
+ const accountId = requireAccountId(opts.account);
114762
+ const assetGroupId = opts.assetGroupId.trim();
114763
+ let body;
114764
+ if (opts.bodyFile) {
114765
+ body = loadPmaxJsonFile(opts.bodyFile);
114766
+ } else {
114767
+ body = {
114768
+ youtubeUrlOrId: opts.youtubeUrlOrId.trim()
114769
+ };
114770
+ if (opts.campaignId?.trim()) body["campaignId"] = opts.campaignId.trim();
114771
+ if (opts.assetName?.trim()) body["assetName"] = opts.assetName.trim();
114772
+ }
114773
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
114774
+ const url = pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, "youtube");
114775
+ let data;
114776
+ try {
114777
+ const raw = await pmaxApiFetch(
114778
+ url,
114779
+ config,
114780
+ { method: "POST", body: JSON.stringify(body) },
114781
+ opts.verbose
114782
+ );
114783
+ data = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : { data: raw };
114784
+ } catch (err) {
114785
+ console.error(
114786
+ `
114787
+ \u274C \u94FE\u63A5 YouTube \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114788
+ `
114789
+ );
114790
+ process.exit(1);
114791
+ }
114792
+ if (await emitPmaxResult(
114793
+ { ...opts, account: accountId },
114794
+ `ad-pmax-youtube-link-${accountId}`,
114795
+ "ad pmax-youtube-link",
114796
+ data,
114797
+ assetGroupId
114798
+ )) {
114799
+ return;
114800
+ }
114801
+ console.log(`
114802
+ \u2705 \u5DF2\u94FE\u63A5 YouTube \u5230\u8D44\u4EA7\u7EC4 ${assetGroupId}`);
114803
+ console.log(` assetResourceName\uFF1A${data["assetResourceName"] ?? "\u2014"}
114804
+ `);
114805
+ });
114806
+ }
114807
+ async function runAdPmaxSignalsGet(opts) {
114808
+ const accountId = requireAccountId(opts.account);
114809
+ const assetGroupId = opts.assetGroupId.trim();
114810
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
114811
+ const url = pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, "signals");
114812
+ let data;
114813
+ try {
114814
+ const raw = await pmaxApiFetch(url, config, {}, opts.verbose);
114815
+ data = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : { signals: raw };
114816
+ } catch (err) {
114817
+ console.error(
114818
+ `
114819
+ \u274C \u83B7\u53D6 PMax \u4FE1\u53F7\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114820
+ `
114821
+ );
114822
+ process.exit(1);
114823
+ }
114824
+ if (await emitPmaxResult(
114825
+ { ...opts, account: accountId },
114826
+ `ad-pmax-signals-get-${accountId}`,
114827
+ "ad pmax-signals-get",
114828
+ data,
114829
+ assetGroupId
114830
+ )) {
114831
+ return;
114832
+ }
114833
+ const signals = data["signals"];
114834
+ const n = Array.isArray(signals) ? signals.length : 0;
114835
+ console.log(`
114836
+ \u8D44\u4EA7\u7EC4 ${assetGroupId} \u4FE1\u53F7\uFF08\u5171 ${n} \u6761\uFF09
114837
+ `);
114838
+ if (Array.isArray(signals)) {
114839
+ for (const s of signals) {
114840
+ if (s && typeof s === "object") {
114841
+ const row = s;
114842
+ console.log(` ${row["type"] ?? "?"} ${row["audienceResourceName"] ?? row["searchThemeText"] ?? ""}`);
114843
+ }
114844
+ }
114845
+ }
114846
+ console.log();
114847
+ });
114848
+ }
114849
+ async function runAdPmaxSignalsSet(opts) {
114850
+ const cfg = loadPmaxJsonFile(opts.configFile);
114851
+ const accountId = requireAccountId(String(cfg["account"] ?? ""));
114852
+ const assetGroupId = String(cfg["assetGroupId"] ?? "").trim();
114853
+ if (!assetGroupId) {
114854
+ console.error("\n\u274C JSON \u987B\u5305\u542B assetGroupId\n");
114855
+ process.exit(1);
114856
+ }
114857
+ const body = {
114858
+ audienceResourceNames: cfg["audienceResourceNames"] ?? [],
114859
+ searchThemes: cfg["searchThemes"] ?? []
114860
+ };
114861
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
114862
+ const url = pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, "signals");
114863
+ try {
114864
+ await pmaxApiFetch(
114865
+ url,
114866
+ config,
114867
+ { method: "PUT", body: JSON.stringify(body) },
114868
+ opts.verbose
114869
+ );
114870
+ } catch (err) {
114871
+ console.error(
114872
+ `
114873
+ \u274C \u8BBE\u7F6E PMax \u4FE1\u53F7\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114874
+ `
114875
+ );
114876
+ process.exit(1);
114877
+ }
114878
+ if (await emitPmaxResult(
114879
+ { ...opts, account: accountId },
114880
+ `ad-pmax-signals-set-${accountId}`,
114881
+ "ad pmax-signals-set",
114882
+ { ok: true, assetGroupId, ...body },
114883
+ assetGroupId
114884
+ )) {
114885
+ return;
114886
+ }
114887
+ console.log(`
114888
+ \u2705 \u8D44\u4EA7\u7EC4 ${assetGroupId} \u4FE1\u53F7\u5DF2\u5168\u91CF\u540C\u6B65
114889
+ `);
114890
+ });
114891
+ }
114892
+ async function runAdPmaxAudiences(opts) {
114893
+ const accountId = requireAccountId(opts.account);
114894
+ const source = opts.source ?? "both";
114895
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
114896
+ const payload = { accountId };
114897
+ if (source === "userlists" || source === "both") {
114898
+ try {
114899
+ payload["userlists"] = await pmaxApiFetch(
114900
+ pmaxUserlistsUrl(googleApiUrl, accountId),
114901
+ config,
114902
+ {},
114903
+ opts.verbose
114904
+ );
114905
+ } catch (err) {
114906
+ if (source === "userlists") {
114907
+ console.error(
114908
+ `
114909
+ \u274C \u83B7\u53D6 userlists \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114910
+ `
114911
+ );
114912
+ process.exit(1);
114913
+ }
114914
+ payload["userlistsError"] = err instanceof Error ? err.message : String(err);
114915
+ }
114916
+ }
114917
+ if (source === "audiences" || source === "both") {
114918
+ try {
114919
+ const aud = await pmaxApiFetch(
114920
+ pmaxAudiencesUrl(googleApiUrl, accountId, opts.limit),
114921
+ config,
114922
+ {},
114923
+ opts.verbose
114924
+ );
114925
+ payload["audiences"] = Array.isArray(aud) ? aud : aud && typeof aud === "object" ? aud["data"] ?? aud : aud;
114926
+ } catch (err) {
114927
+ console.error(
114928
+ `
114929
+ \u274C \u83B7\u53D6 audiences \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114930
+ `
114931
+ );
114932
+ process.exit(1);
114933
+ }
114934
+ }
114935
+ if (await emitPmaxResult(
114936
+ { ...opts, account: accountId },
114937
+ `ad-pmax-audiences-${accountId}`,
114938
+ "ad pmax-audiences",
114939
+ payload,
114940
+ accountId
114941
+ )) {
114942
+ return;
114943
+ }
114944
+ console.log(`
114945
+ PMax \u53D7\u4F17\u6570\u636E\u6E90\uFF08\u8D26\u6237 ${accountId}\uFF09
114946
+ `);
114947
+ if (payload["userlists"] !== void 0) {
114948
+ const ul = payload["userlists"];
114949
+ const arr = Array.isArray(ul) ? ul : [];
114950
+ console.log(` userlists\uFF1A${arr.length} \u6761`);
114951
+ }
114952
+ if (payload["audiences"] !== void 0) {
114953
+ const arr = payload["audiences"];
114954
+ console.log(` audiences\uFF1A${Array.isArray(arr) ? arr.length : 0} \u6761\uFF08\u63A8\u8350\u7528\u4E8E signals PUT\uFF09`);
114955
+ if (Array.isArray(arr)) {
114956
+ for (const row of arr.slice(0, 20)) {
114957
+ if (row && typeof row === "object") {
114958
+ const r = row;
114959
+ console.log(` ${r["name"] ?? ""} ${r["resourceName"] ?? ""}`);
114960
+ }
114961
+ }
114962
+ if (arr.length > 20) console.log(` \u2026 \u5171 ${arr.length} \u6761\uFF0C\u8BF7\u7528 --json`);
114963
+ }
114964
+ }
114965
+ console.log();
114966
+ });
114967
+ }
114968
+ async function runAdPmaxImageUpload(opts) {
114969
+ const accountId = requireAccountId(opts.account);
114970
+ let name;
114971
+ let base64String;
114972
+ if (opts.bodyFile) {
114973
+ const cfg = loadPmaxJsonFile(opts.bodyFile);
114974
+ name = String(cfg["name"] ?? opts.name ?? "cli-image").trim();
114975
+ base64String = String(cfg["base64String"] ?? cfg["Base64String"] ?? "").trim() || readImageBase642(opts.bodyFile, String(cfg["imagePath"] ?? "")) || "";
114976
+ } else {
114977
+ name = (opts.name ?? "cli-image").trim();
114978
+ try {
114979
+ base64String = readFileSync7(opts.imagePath).toString("base64");
114980
+ } catch (err) {
114981
+ console.error(
114982
+ `
114983
+ \u274C \u8BFB\u53D6\u56FE\u7247\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114984
+ `
114985
+ );
114986
+ process.exit(1);
114987
+ }
114988
+ }
114989
+ if (!base64String) {
114990
+ console.error("\n\u274C \u7F3A\u5C11\u56FE\u7247 Base64\uFF08--image-path \u6216 body JSON\uFF09\n");
114991
+ process.exit(1);
114992
+ }
114993
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
114994
+ const url = pmaxImageAssetUrl(googleApiUrl, accountId, name);
114995
+ let data;
114996
+ try {
114997
+ const raw = await pmaxApiFetch(
114998
+ url,
114999
+ config,
115000
+ {
115001
+ method: "POST",
115002
+ body: JSON.stringify({ name, base64String })
115003
+ },
115004
+ opts.verbose
115005
+ );
115006
+ data = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : { data: raw };
115007
+ } catch (err) {
115008
+ console.error(
115009
+ `
115010
+ \u274C \u4E0A\u4F20\u56FE\u7247\u8D44\u4EA7\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
115011
+ `
115012
+ );
115013
+ process.exit(1);
115014
+ }
115015
+ if (await emitPmaxResult(
115016
+ { ...opts, account: accountId },
115017
+ `ad-pmax-image-upload-${accountId}`,
115018
+ "ad pmax-image-upload",
115019
+ data,
115020
+ String(data["id"] ?? accountId)
115021
+ )) {
115022
+ return;
115023
+ }
115024
+ console.log(`
115025
+ \u2705 \u56FE\u7247\u8D44\u4EA7\u5DF2\u4E0A\u4F20`);
115026
+ console.log(` id\uFF1A${data["id"] ?? "\u2014"}`);
115027
+ console.log(` name\uFF1A${data["name"] ?? name}
115028
+ `);
115029
+ });
115030
+ }
115031
+ async function runAdPmaxReportAssetGroups(opts) {
115032
+ const accountId = requireAccountId(opts.account);
115033
+ const query = buildReportQuery(opts);
115034
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
115035
+ const url = pmaxAssetGroupReportUrl(googleApiUrl, accountId, query);
115036
+ let data;
115037
+ try {
115038
+ const raw = await pmaxApiFetch(url, config, {}, opts.verbose);
115039
+ data = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : { data: raw };
115040
+ } catch (err) {
115041
+ console.error(
115042
+ `
115043
+ \u274C \u83B7\u53D6 PMax \u8D44\u4EA7\u7EC4\u62A5\u8868\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
115044
+ `
115045
+ );
115046
+ process.exit(1);
115047
+ }
115048
+ if (await emitPmaxResult(
115049
+ { ...opts, account: accountId },
115050
+ `ad-pmax-report-asset-groups-${accountId}`,
115051
+ "ad pmax-report-asset-groups",
115052
+ data,
115053
+ accountId
115054
+ )) {
115055
+ return;
115056
+ }
115057
+ const campaigns = data["campaigns"];
115058
+ const n = Array.isArray(campaigns) ? campaigns.length : 0;
115059
+ console.log(`
115060
+ PMax \u8D44\u4EA7\u7EC4\u6548\u679C\u62A5\u8868\uFF08${query.get("startDate")} ~ ${query.get("endDate")}\uFF0C${n} \u884C\uFF09
115061
+ `);
115062
+ console.log(" \u5B8C\u6574\u6570\u636E\u8BF7\u4F7F\u7528 --json\n");
115063
+ });
115064
+ }
115065
+ async function runAdPmaxReportGeo(opts) {
115066
+ const accountId = requireAccountId(opts.account);
115067
+ const query = buildReportQuery(opts);
115068
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
115069
+ const url = pmaxGeoReportUrl(googleApiUrl, accountId, query);
115070
+ let data;
115071
+ try {
115072
+ const raw = await pmaxApiFetch(url, config, {}, opts.verbose);
115073
+ data = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : { data: raw };
115074
+ } catch (err) {
115075
+ console.error(
115076
+ `
115077
+ \u274C \u83B7\u53D6 PMax \u5730\u7406\u62A5\u8868\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
115078
+ `
115079
+ );
115080
+ process.exit(1);
115081
+ }
115082
+ if (await emitPmaxResult(
115083
+ { ...opts, account: accountId },
115084
+ `ad-pmax-report-geo-${accountId}`,
115085
+ "ad pmax-report-geo",
115086
+ data,
115087
+ accountId
115088
+ )) {
115089
+ return;
115090
+ }
115091
+ console.log(
115092
+ `
115093
+ PMax \u5730\u7406\u62A5\u8868\uFF08\u6D3B\u52A8\xD7\u56FD\u5BB6\uFF0C${query.get("startDate")} ~ ${query.get("endDate")}\uFF09
115094
+ `
115095
+ );
115096
+ console.log(" \u5B8C\u6574\u6570\u636E\u8BF7\u4F7F\u7528 --json\n");
115097
+ });
115098
+ }
115099
+
115100
+ // src/commands/ad/campaign-validate.ts
115101
+ import { writeFileSync as writeFileSync4 } from "fs";
115102
+ init_cli_json_snapshot();
113512
115103
  var LENGTH_VIOLATION_AGENT_HINT = "\u52FF\u81EA\u52A8\u622A\u65AD JSON\u3002\u8BFB\u53D6\u843D\u76D8 JSON \u7684 lengthViolations\uFF0C\u5C06\u5168\u90E8\u6761\u76EE\u4E0E\u4FEE\u6539\u65B9\u6848\u5217\u7ED9\u7528\u6237\uFF0C\u786E\u8BA4\u540E\u5199\u5165 campaign.json \u5E76\u91CD\u65B0 campaign-validate\u3002";
113513
115104
  async function runAdCampaignValidate(opts) {
113514
115105
  const cfg = loadCampaignCreateConfig(opts.configFile);
113515
115106
  const { errors, warnings, lengthViolations } = runCampaignCreateValidation(cfg);
113516
115107
  if (opts.writeNormalized) {
113517
115108
  const toWrite = stripMetaKeysForExport(cfg);
113518
- writeFileSync3(opts.writeNormalized, `${JSON.stringify(toWrite, null, 2)}
115109
+ writeFileSync4(opts.writeNormalized, `${JSON.stringify(toWrite, null, 2)}
113519
115110
  `, "utf8");
113520
115111
  }
113521
115112
  const payload = {
@@ -113557,6 +115148,118 @@ async function runAdCampaignValidate(opts) {
113557
115148
  console.log();
113558
115149
  }
113559
115150
 
115151
+ // src/commands/ad/pmax-image-convert.ts
115152
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5, readFileSync as readFileSync8, existsSync as existsSync3 } from "fs";
115153
+ import { resolve as resolve9, dirname as dirname10, basename as basename4, extname } from "path";
115154
+ var SPECS = [
115155
+ { kind: "marketing", width: 1200, height: 628, fit: "cover", suffix: "_marketing" },
115156
+ { kind: "square", width: 1200, height: 1200, fit: "cover", suffix: "_square" },
115157
+ { kind: "logo", width: 1200, height: 1200, fit: "contain", suffix: "_logo" }
115158
+ ];
115159
+ async function loadSharp() {
115160
+ try {
115161
+ const mod = await import("sharp");
115162
+ return mod.default ?? mod;
115163
+ } catch {
115164
+ console.error(
115165
+ "\n\u274C \u627E\u4E0D\u5230 sharp \u6A21\u5757\uFF0C\u8BF7\u5148\u6267\u884C\uFF1Apnpm install\uFF08\u6216 npm install\uFF09\n"
115166
+ );
115167
+ process.exit(1);
115168
+ }
115169
+ }
115170
+ async function convertOne(sharpFn, inputPath, outputPath, spec, quality) {
115171
+ let pipeline = sharpFn(inputPath).resize(spec.width, spec.height, {
115172
+ fit: spec.fit,
115173
+ position: "centre",
115174
+ background: { r: 255, g: 255, b: 255, alpha: 1 },
115175
+ withoutEnlargement: false
115176
+ });
115177
+ const ext = extname(outputPath).toLowerCase();
115178
+ if (ext === ".png") {
115179
+ pipeline = pipeline.png({ compressionLevel: 6 });
115180
+ } else {
115181
+ pipeline = pipeline.jpeg({ quality, mozjpeg: true });
115182
+ }
115183
+ await pipeline.toFile(outputPath);
115184
+ }
115185
+ async function runAdPmaxImageConvert(opts) {
115186
+ const { input, inputMarketing, inputSquare, inputLogo } = opts;
115187
+ if (!input && !inputMarketing && !inputSquare && !inputLogo) {
115188
+ console.error("\n\u274C \u8BF7\u81F3\u5C11\u6307\u5B9A --input \u6216 --input-marketing / --input-square / --input-logo\n");
115189
+ process.exit(1);
115190
+ }
115191
+ const sources = {
115192
+ marketing: inputMarketing ?? input,
115193
+ square: inputSquare ?? input,
115194
+ logo: inputLogo ?? input
115195
+ };
115196
+ const specs = SPECS.map(
115197
+ (s) => s.kind === "logo" && opts.logoUseCover ? { ...s, fit: "cover" } : s
115198
+ );
115199
+ const firstInput = input ?? inputMarketing ?? inputSquare ?? inputLogo;
115200
+ const outputDir = opts.outputDir ? resolve9(opts.outputDir) : dirname10(resolve9(firstInput));
115201
+ mkdirSync3(outputDir, { recursive: true });
115202
+ const prefix = opts.prefix ?? basename4(firstInput, extname(firstInput));
115203
+ const quality = opts.quality ?? 85;
115204
+ const sharpMod = await loadSharp();
115205
+ const sharpFn = (p) => sharpMod(p);
115206
+ const outputPaths = {};
115207
+ let hasError = false;
115208
+ for (const spec of specs) {
115209
+ const src = sources[spec.kind];
115210
+ if (!src) {
115211
+ console.warn(`\u26A0\uFE0F ${spec.kind}\uFF1A\u672A\u6307\u5B9A\u8F93\u5165\u56FE\uFF0C\u8DF3\u8FC7`);
115212
+ continue;
115213
+ }
115214
+ const absInput = resolve9(src);
115215
+ if (!existsSync3(absInput)) {
115216
+ console.error(`\u274C \u8F93\u5165\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${absInput}`);
115217
+ hasError = true;
115218
+ continue;
115219
+ }
115220
+ const outExt = extname(absInput).toLowerCase() === ".png" ? ".png" : ".jpg";
115221
+ const outName = `${prefix}${spec.suffix}${outExt}`;
115222
+ const absOutput = resolve9(outputDir, outName);
115223
+ try {
115224
+ await convertOne(sharpFn, absInput, absOutput, spec, quality);
115225
+ const sizeKB = Math.ceil(
115226
+ (await import("fs")).statSync(absOutput).size / 1024
115227
+ );
115228
+ console.log(`\u2705 ${spec.kind.padEnd(9)} ${spec.width}\xD7${spec.height} ${sizeKB} KB \u2192 ${absOutput}`);
115229
+ outputPaths[spec.kind] = absOutput;
115230
+ } catch (err) {
115231
+ const msg = err instanceof Error ? err.message : String(err);
115232
+ console.error(`\u274C ${spec.kind} \u8F6C\u6362\u5931\u8D25\uFF1A${msg}`);
115233
+ hasError = true;
115234
+ }
115235
+ }
115236
+ if (hasError) {
115237
+ console.error("\n\u90E8\u5206\u56FE\u7247\u8F6C\u6362\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u4E0A\u65B9\u9519\u8BEF\u3002\n");
115238
+ process.exit(1);
115239
+ }
115240
+ if (opts.updateConfig) {
115241
+ const configPath = resolve9(opts.updateConfig);
115242
+ try {
115243
+ const raw = JSON.parse(readFileSync8(configPath, "utf8"));
115244
+ const imgPaths = raw["imagePaths"] ?? {};
115245
+ if (outputPaths.marketing) imgPaths["marketing"] = outputPaths.marketing;
115246
+ if (outputPaths.square) imgPaths["square"] = outputPaths.square;
115247
+ if (outputPaths.logo) imgPaths["logo"] = outputPaths.logo;
115248
+ raw["imagePaths"] = imgPaths;
115249
+ writeFileSync5(configPath, `${JSON.stringify(raw, null, 2)}
115250
+ `, "utf8");
115251
+ console.log(`
115252
+ imagePaths \u5DF2\u5199\u56DE\uFF1A${configPath}`);
115253
+ } catch (err) {
115254
+ const msg = err instanceof Error ? err.message : String(err);
115255
+ console.warn(`\u26A0\uFE0F \u66F4\u65B0\u914D\u7F6E\u6587\u4EF6\u5931\u8D25\uFF1A${msg}`);
115256
+ }
115257
+ }
115258
+ console.log(
115259
+ "\n\u63D0\u793A\uFF1A\u8FD0\u884C ad pmax-validate --config-file <your-pmax.json> \u786E\u8BA4\u7D20\u6750\u89C4\u683C\u540E\u518D\u63D0\u4EA4\u521B\u5EFA\u3002\n"
115260
+ );
115261
+ }
115262
+
113560
115263
  // src/commands/ad/_register.ts
113561
115264
  function register20(program2) {
113562
115265
  const adCmd = program2.command("ad").description(
@@ -113942,6 +115645,229 @@ function register20(program2) {
113942
115645
  });
113943
115646
  }
113944
115647
  );
115648
+ adCmd.command("pmax-validate").description(
115649
+ "\u6821\u9A8C pmax-create JSON\uFF08PMax \u4E13\u7528\uFF1B\u4E0D\u8C03\u7528 API\uFF09\n\n \u7528\u6CD5\uFF1A\n siluzan-tso ad pmax-validate --config-file ./pmax.json --json-out ./snap-pmax\n siluzan-tso ad pmax-validate --config-file ./pmax.json --write-normalized ./pmax.normalized.json"
115650
+ ).requiredOption("--config-file <path>", "pmax-create JSON \u8DEF\u5F84").option("--write-normalized <path>", "\u5C06 JSON \u5199\u5165\u8BE5\u8DEF\u5F84\uFF08\u4FDD\u7559 _ \u6CE8\u89E3\u952E\uFF09").option("--json", "\u8F93\u51FA { ok, errors, warnings }\uFF08\u4E0E --json-out \u4E92\u65A5\uFF09", false).option(
115651
+ "--json-out <path>",
115652
+ "\u843D\u76D8\u6821\u9A8C\u7ED3\u679C\u5E76\u66F4\u65B0 cli-manifest\uFF1B\u4E0E --json \u4E92\u65A5"
115653
+ ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
115654
+ async (opts) => {
115655
+ await runAdPmaxValidate({
115656
+ configFile: opts.configFile,
115657
+ writeNormalized: opts.writeNormalized,
115658
+ json: opts.json,
115659
+ jsonOut: opts.jsonOut,
115660
+ verbose: opts.verbose
115661
+ });
115662
+ }
115663
+ );
115664
+ adCmd.command("pmax-create").description(
115665
+ '\u65B0\u5EFA Performance Max \u5E7F\u544A\u7CFB\u5217\uFF08\u540C\u6B65 API\uFF1B\u4EC5\u652F\u6301 JSON \u914D\u7F6E\u6587\u4EF6\uFF09\n\n \u7528\u6CD5\uFF1A\n 1. \u590D\u5236 pmax-create-template.json\uFF0C\u5B57\u6BB5\u8BF4\u660E\u89C1 pmax-create-template.md\n 2. siluzan-tso ad geo search -a <accountId> -q "United States"\n 3. siluzan-tso ad pmax-validate --config-file ./pmax.json\n 4. siluzan-tso ad pmax-create --config-file ./pmax.json\n 5. siluzan-tso ad campaigns -a <accountId> --json # \u786E\u8BA4 PERFORMANCE_MAX'
115666
+ ).requiredOption(
115667
+ "--config-file <path>",
115668
+ "JSON \u914D\u7F6E\u6587\u4EF6\uFF08\u6A21\u677F\u89C1 assets/siluzan-ads/assets/pmax-create-*.json|md\uFF09"
115669
+ ).option("-t, --token <token>", "Auth Token").option("--json", "JSON \u683C\u5F0F\u8F93\u51FA\u7F51\u5173\u54CD\u5E94", false).option(
115670
+ "--json-out <path>",
115671
+ "\u843D\u76D8\u54CD\u5E94\u5E76\u66F4\u65B0 cli-manifest\uFF08\u4E0E --json \u4E92\u65A5\uFF09",
115672
+ void 0
115673
+ ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
115674
+ async (opts) => {
115675
+ await runAdPmaxCreate({
115676
+ configFile: opts.configFile,
115677
+ token: opts.token,
115678
+ json: opts.json,
115679
+ jsonOut: opts.jsonOut,
115680
+ verbose: opts.verbose
115681
+ });
115682
+ }
115683
+ );
115684
+ const addPmaxJsonOptions = (cmd) => cmd.option("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).option("--json-out <path>", "\u843D\u76D8\u54CD\u5E94\u5E76\u66F4\u65B0 cli-manifest", void 0).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false);
115685
+ adCmd.command("pmax-image-convert").description(
115686
+ "\u5C06\u4EFB\u610F\u56FE\u7247\u8F6C\u4E3A PMax \u4E09\u79CD\u89C4\u683C\u7D20\u6750\uFF08marketing 1200\xD7628 / square 1200\xD71200 / logo 1200\xD71200\uFF09\n\n \u7528\u6CD5\uFF1A\n # \u5355\u56FE\u81EA\u52A8\u6D3E\u751F\u4E09\u79CD\u683C\u5F0F\n siluzan-tso ad pmax-image-convert --input ./banner.jpg --output-dir ./assets --prefix my-camp\n # \u5206\u522B\u6307\u5B9A\u5404\u56FE\uFF0C\u5E76\u5C06\u8DEF\u5F84\u5199\u56DE pmax.json\n siluzan-tso ad pmax-image-convert --input-marketing ./hero.jpg --input-logo ./logo.png \\\n --output-dir ./assets --update-config ./pmax.json"
115687
+ ).option("--input <path>", "\u4E3B\u8F93\u5165\u56FE\uFF08\u7528\u4E8E\u6240\u6709\u672A\u5355\u72EC\u6307\u5B9A\u7684\u7C7B\u578B\uFF09").option("--input-marketing <path>", "\u6A2A\u56FE\u8F93\u5165\uFF08\u4F18\u5148\u4E8E --input\uFF09").option("--input-square <path>", "\u65B9\u56FE\u8F93\u5165\uFF08\u4F18\u5148\u4E8E --input\uFF09").option("--input-logo <path>", "Logo \u8F93\u5165\uFF08\u4F18\u5148\u4E8E --input\uFF09").option("--output-dir <dir>", "\u8F93\u51FA\u76EE\u5F55\uFF08\u9ED8\u8BA4\u4E0E\u8F93\u5165\u56FE\u540C\u76EE\u5F55\uFF09").option("--prefix <name>", "\u8F93\u51FA\u6587\u4EF6\u540D\u524D\u7F00\uFF08\u9ED8\u8BA4\u53D6\u8F93\u5165\u56FE\u6587\u4EF6\u540D\uFF09").option("--quality <n>", "JPEG \u8D28\u91CF 1\u2013100\uFF08\u9ED8\u8BA4 85\uFF09", (v) => parseInt(v, 10)).option("--logo-use-cover", "Logo \u4F7F\u7528 cover \u88C1\u5207\uFF08\u9ED8\u8BA4 contain + \u767D\u5E95\uFF09", false).option("--update-config <path>", "\u5C06\u751F\u6210\u7684 imagePaths \u5199\u56DE\u6307\u5B9A pmax.json \u914D\u7F6E\u6587\u4EF6").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
115688
+ async (opts) => {
115689
+ await runAdPmaxImageConvert({
115690
+ input: opts.input,
115691
+ inputMarketing: opts.inputMarketing,
115692
+ inputSquare: opts.inputSquare,
115693
+ inputLogo: opts.inputLogo,
115694
+ outputDir: opts.outputDir,
115695
+ prefix: opts.prefix,
115696
+ quality: opts.quality,
115697
+ logoUseCover: opts.logoUseCover,
115698
+ updateConfig: opts.updateConfig,
115699
+ verbose: opts.verbose
115700
+ });
115701
+ }
115702
+ );
115703
+ addPmaxJsonOptions(
115704
+ adCmd.command("pmax-channel-types").description("\u5217\u51FA PMax \u4E0E\u5176\u5B83\u6E20\u9053\u7C7B\u578B\u679A\u4E3E\uFF08GET /campaign/pmax/channel-types\uFF09").option("-t, --token <token>", "Auth Token")
115705
+ ).action(async (opts) => {
115706
+ await runAdPmaxChannelTypes(opts);
115707
+ });
115708
+ addPmaxJsonOptions(
115709
+ adCmd.command("pmax-get").description("\u83B7\u53D6 PMax \u6D3B\u52A8\u8BE6\u60C5\uFF08\u542B\u8D44\u4EA7\u7EC4\u4E0E ENABLED \u8D44\u4EA7\uFF09").requiredOption("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID").requiredOption("--campaign-id <id>", "\u6D3B\u52A8 ID").option("-t, --token <token>", "Auth Token")
115710
+ ).action(
115711
+ async (opts) => {
115712
+ await runAdPmaxGet(opts);
115713
+ }
115714
+ );
115715
+ addPmaxJsonOptions(
115716
+ adCmd.command("pmax-edit").description(
115717
+ "\u66F4\u65B0 PMax \u6D3B\u52A8\uFF08PATCH\uFF1B\u52FF\u7528 ad campaign-edit\uFF09\n --patch-file \u89C1 assets/pmax-patch-campaign-template.json"
115718
+ ).requiredOption("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID").requiredOption("--campaign-id <id>", "\u6D3B\u52A8 ID").option("--patch-file <path>", "PATCH JSON\uFF08budget/targetCpa \u586B\u5143\uFF09").option("--status <status>", "2|3|4 \u6216 Enabled|Paused|Removed").option("--budget <yuan>", "\u65E5\u9884\u7B97\uFF08\u5143\uFF09\uFF1B\u987B\u914D\u5408 --budget-id").option("--budget-id <id>", "budgetId\uFF08\u6765\u81EA pmax-get\uFF09").option("--bidding <strategy>", "\u51FA\u4EF7\u7B56\u7565\uFF0C\u5982 MAXIMIZE_CONVERSIONS").option("--target-cpa <yuan>", "\u76EE\u6807 CPA\uFF08\u5143\uFF09").option("--target-roas <n>", "\u76EE\u6807 ROAS").option("--url-expansion-opt-out", "\u5173\u95ED\u6700\u7EC8\u5230\u8FBE\u7F51\u5740\u6269\u5C55").option("-t, --token <token>", "Auth Token")
115719
+ ).action(
115720
+ async (opts) => {
115721
+ await runAdPmaxEdit(opts);
115722
+ }
115723
+ );
115724
+ addPmaxJsonOptions(
115725
+ adCmd.command("pmax-asset-group-create").description(
115726
+ "\u5728\u540C\u4E00 PMax \u6D3B\u52A8\u4E0B\u65B0\u5EFA\u8D44\u4EA7\u7EC4\uFF08\u6A21\u677F pmax-asset-group-template.json\uFF09"
115727
+ ).requiredOption("--config-file <path>", "JSON \u914D\u7F6E\u8DEF\u5F84").option("-t, --token <token>", "Auth Token")
115728
+ ).action(
115729
+ async (opts) => {
115730
+ await runAdPmaxAssetGroupCreate(opts);
115731
+ }
115732
+ );
115733
+ addPmaxJsonOptions(
115734
+ adCmd.command("pmax-asset-group-edit").description("\u66F4\u65B0 PMax \u8D44\u4EA7\u7EC4\u540D\u79F0/URL/\u542F\u505C\uFF08PATCH\uFF09").requiredOption("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID").requiredOption("--asset-group-id <id>", "\u8D44\u4EA7\u7EC4 ID").option("--patch-file <path>", "PATCH JSON body").option("--name <name>", "\u65B0\u540D\u79F0").option("--final-urls <urls>", "\u6700\u7EC8 URL\uFF0C\u9017\u53F7\u5206\u9694").option("--status <status>", "ENABLED | PAUSED | REMOVED").option("-t, --token <token>", "Auth Token")
115735
+ ).action(
115736
+ async (opts) => {
115737
+ await runAdPmaxAssetGroupEdit(opts);
115738
+ }
115739
+ );
115740
+ addPmaxJsonOptions(
115741
+ adCmd.command("pmax-assets-update").description(
115742
+ "\u6279\u91CF\u94FE\u63A5/\u53D6\u6D88\u94FE\u63A5/\u66FF\u6362\u8D44\u4EA7\uFF08PUT .../assets\uFF1B\u6A21\u677F pmax-assets-update-template.json\uFF09"
115743
+ ).requiredOption("--config-file <path>", "JSON \u914D\u7F6E\u8DEF\u5F84").option("-t, --token <token>", "Auth Token")
115744
+ ).action(
115745
+ async (opts) => {
115746
+ await runAdPmaxAssetsUpdate(opts);
115747
+ }
115748
+ );
115749
+ addPmaxJsonOptions(
115750
+ adCmd.command("pmax-youtube-link").description("\u4E3A\u8D44\u4EA7\u7EC4\u94FE\u63A5 YouTube \u89C6\u9891\uFF08POST .../youtube\uFF09").requiredOption("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID").requiredOption("--asset-group-id <id>", "\u8D44\u4EA7\u7EC4 ID").option("--campaign-id <id>", "\u6240\u5C5E\u6D3B\u52A8 ID\uFF08\u5EFA\u8BAE\u4F20\uFF09").option("--youtube <urlOrId>", "YouTube URL \u6216 11 \u4F4D\u89C6\u9891 ID").option("--asset-name <name>", "\u8D44\u4EA7\u663E\u793A\u540D").option("--body-file <path>", "\u5B8C\u6574 JSON body\uFF08\u8986\u76D6 --youtube\uFF09").option("-t, --token <token>", "Auth Token")
115751
+ ).action(
115752
+ async (opts) => {
115753
+ if (!opts.bodyFile && !opts.youtube?.trim()) {
115754
+ console.error("\n\u274C \u8BF7\u6307\u5B9A --youtube \u6216 --body-file\n");
115755
+ process.exit(1);
115756
+ }
115757
+ await runAdPmaxYoutubeLink({
115758
+ account: opts.account,
115759
+ assetGroupId: opts.assetGroupId,
115760
+ campaignId: opts.campaignId,
115761
+ youtubeUrlOrId: opts.youtube ?? "",
115762
+ assetName: opts.assetName,
115763
+ bodyFile: opts.bodyFile,
115764
+ token: opts.token,
115765
+ json: opts.json,
115766
+ jsonOut: opts.jsonOut,
115767
+ verbose: opts.verbose
115768
+ });
115769
+ }
115770
+ );
115771
+ addPmaxJsonOptions(
115772
+ adCmd.command("pmax-signals-get").description("\u8BFB\u53D6\u8D44\u4EA7\u7EC4\u53D7\u4F17/\u641C\u7D22\u4E3B\u9898\u4FE1\u53F7\uFF08GET .../signals\uFF09").requiredOption("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID").requiredOption("--asset-group-id <id>", "\u8D44\u4EA7\u7EC4 ID").option("-t, --token <token>", "Auth Token")
115773
+ ).action(
115774
+ async (opts) => {
115775
+ await runAdPmaxSignalsGet(opts);
115776
+ }
115777
+ );
115778
+ addPmaxJsonOptions(
115779
+ adCmd.command("pmax-signals-set").description(
115780
+ "\u5168\u91CF\u540C\u6B65\u8D44\u4EA7\u7EC4\u4FE1\u53F7\uFF08PUT .../signals\uFF1B\u6A21\u677F pmax-signals-template.json\uFF09"
115781
+ ).requiredOption("--config-file <path>", "JSON \u914D\u7F6E\u8DEF\u5F84").option("-t, --token <token>", "Auth Token")
115782
+ ).action(
115783
+ async (opts) => {
115784
+ await runAdPmaxSignalsSet(opts);
115785
+ }
115786
+ );
115787
+ addPmaxJsonOptions(
115788
+ adCmd.command("pmax-audiences").description(
115789
+ "\u62C9\u53D6 PMax \u4FE1\u53F7\u7528\u53D7\u4F17\u5217\u8868\uFF08userlists \u4E0E/\u6216 audiences\uFF1BresourceName \u7528\u4E8E signals PUT\uFF09"
115790
+ ).requiredOption("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID").option(
115791
+ "--source <source>",
115792
+ "userlists | audiences | both\uFF08\u9ED8\u8BA4 both\uFF09",
115793
+ "both"
115794
+ ).option("--limit <n>", "audiences \u63A5\u53E3\u53EF\u9009 limit\uFF081\u20132000\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Auth Token")
115795
+ ).action(
115796
+ async (opts) => {
115797
+ const src = (opts.source ?? "both").toLowerCase();
115798
+ if (src !== "userlists" && src !== "audiences" && src !== "both") {
115799
+ console.error("\n\u274C --source \u987B\u4E3A userlists | audiences | both\n");
115800
+ process.exit(1);
115801
+ }
115802
+ await runAdPmaxAudiences({
115803
+ account: opts.account,
115804
+ source: src,
115805
+ limit: opts.limit,
115806
+ token: opts.token,
115807
+ json: opts.json,
115808
+ jsonOut: opts.jsonOut,
115809
+ verbose: opts.verbose
115810
+ });
115811
+ }
115812
+ );
115813
+ addPmaxJsonOptions(
115814
+ adCmd.command("pmax-image-upload").description("\u4E0A\u4F20\u56FE\u7247\u8D44\u4EA7\u5230\u8D26\u6237\u5E93\uFF08POST /mediamanagement/imageasset/{accountId}\uFF09").requiredOption("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID").option("--image-path <path>", "\u672C\u5730\u56FE\u7247\u8DEF\u5F84").option("--name <name>", "\u8D44\u4EA7\u540D\u79F0").option("--body-file <path>", "JSON\uFF1Aname\u3001imagePath \u6216 base64String").option("-t, --token <token>", "Auth Token")
115815
+ ).action(
115816
+ async (opts) => {
115817
+ if (!opts.bodyFile && !opts.imagePath?.trim()) {
115818
+ console.error("\n\u274C \u8BF7\u6307\u5B9A --image-path \u6216 --body-file\n");
115819
+ process.exit(1);
115820
+ }
115821
+ await runAdPmaxImageUpload({
115822
+ account: opts.account,
115823
+ imagePath: opts.imagePath ?? "",
115824
+ name: opts.name,
115825
+ bodyFile: opts.bodyFile,
115826
+ token: opts.token,
115827
+ json: opts.json,
115828
+ jsonOut: opts.jsonOut,
115829
+ verbose: opts.verbose
115830
+ });
115831
+ }
115832
+ );
115833
+ const pmaxReportOpts = (cmd) => addPmaxJsonOptions(
115834
+ cmd.requiredOption("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID").option("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("--campaign-id <id>", "\u4EC5\u7B5B\u9009\u5355\u6D3B\u52A8").option("-t, --token <token>", "Auth Token")
115835
+ );
115836
+ pmaxReportOpts(
115837
+ adCmd.command("pmax-report-asset-groups").description("PMax \u8D44\u4EA7\u7EC4\u6548\u679C\u62A5\u8868\uFF08GET .../pmax/asset-groups/reports\uFF09")
115838
+ ).action(
115839
+ async (opts) => {
115840
+ await runAdPmaxReportAssetGroups({
115841
+ account: opts.account,
115842
+ startDate: opts.start,
115843
+ endDate: opts.end,
115844
+ campaignId: opts.campaignId,
115845
+ token: opts.token,
115846
+ json: opts.json,
115847
+ jsonOut: opts.jsonOut,
115848
+ verbose: opts.verbose
115849
+ });
115850
+ }
115851
+ );
115852
+ pmaxReportOpts(
115853
+ adCmd.command("pmax-report-geo").description("PMax \u5730\u7406\u62A5\u8868 \u6D3B\u52A8\xD7\u56FD\u5BB6\uFF08GET .../pmax/campaigns/reports/geo\uFF09").option("--cost-greater <n>", "\u82B1\u8D39\u8FC7\u6EE4", (v) => parseFloat(v)).option("--click-greater <n>", "\u70B9\u51FB\u8FC7\u6EE4", (v) => parseFloat(v)).option("--conversions-greater <n>", "\u8F6C\u5316\u8FC7\u6EE4", (v) => parseFloat(v))
115854
+ ).action(
115855
+ async (opts) => {
115856
+ await runAdPmaxReportGeo({
115857
+ account: opts.account,
115858
+ startDate: opts.start,
115859
+ endDate: opts.end,
115860
+ campaignId: opts.campaignId,
115861
+ costGreater: opts.costGreater,
115862
+ clickGreater: opts.clickGreater,
115863
+ conversionsGreater: opts.conversionsGreater,
115864
+ token: opts.token,
115865
+ json: opts.json,
115866
+ jsonOut: opts.jsonOut,
115867
+ verbose: opts.verbose
115868
+ });
115869
+ }
115870
+ );
113945
115871
  adCmd.command("campaign-bidding-strategies").description("\u5217\u51FA\u7F51\u5173\u652F\u6301\u7684\u51FA\u4EF7\u7B56\u7565\u7C7B\u578B\uFF08GET campaignmanagement/biddingStrategyList\uFF09").option("-t, --token <token>", "Auth Token").option("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
113946
115872
  await runAdCampaignBiddingStrategies({
113947
115873
  token: opts.token,