siluzan-tso-cli 1.1.56-beta.3 → 1.1.56

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
@@ -4049,7 +4049,7 @@ var DEFAULT_API_BASE;
4049
4049
  var init_defaults = __esm({
4050
4050
  "src/config/defaults.ts"() {
4051
4051
  "use strict";
4052
- DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
4052
+ DEFAULT_API_BASE = "https://tso-api.siluzan.com";
4053
4053
  }
4054
4054
  });
4055
4055
 
@@ -113620,6 +113620,192 @@ import { readFileSync as readFileSync4 } from "fs";
113620
113620
  init_cli_json_snapshot();
113621
113621
  init_strip_legacy_google_fields();
113622
113622
  init_cli_table();
113623
+
113624
+ // src/commands/ad/write-verification.ts
113625
+ var CAMPAIGN_WRITE_SCOPE = "gateway-full-put";
113626
+ function sameWriteValue(expected, actual) {
113627
+ if (expected === actual) return true;
113628
+ if (typeof expected === "string" && typeof actual === "string") {
113629
+ return expected.trim() === actual.trim();
113630
+ }
113631
+ if (typeof expected === "number" && typeof actual === "number") return expected === actual;
113632
+ if (typeof expected === "boolean" && typeof actual === "boolean") return expected === actual;
113633
+ if (Array.isArray(expected) && Array.isArray(actual)) {
113634
+ if (expected.length !== actual.length) return false;
113635
+ return expected.every((item, i) => sameWriteValue(item, actual[i]));
113636
+ }
113637
+ return false;
113638
+ }
113639
+ function fieldCheck(field, expected, actual, present) {
113640
+ if (!present) return { field, expected, actual: void 0, state: "unread" };
113641
+ return {
113642
+ field,
113643
+ expected,
113644
+ actual,
113645
+ state: sameWriteValue(expected, actual) ? "match" : "mismatch"
113646
+ };
113647
+ }
113648
+ function classifyFieldChecks(checks, readFailed) {
113649
+ if (readFailed) return "unknown";
113650
+ if (checks.length === 0) return "submitted";
113651
+ if (checks.some((c) => c.state === "unread")) return "unknown";
113652
+ if (checks.every((c) => c.state === "match")) return "verified";
113653
+ return "partial";
113654
+ }
113655
+ function describeUnmatchedChecks(checks) {
113656
+ return checks.filter((c) => c.state !== "match").map((c) => {
113657
+ const actual = c.state === "unread" ? "\uFF08\u672A\u8BFB\u5230\uFF09" : JSON.stringify(c.actual);
113658
+ return `${c.field}\uFF1A\u671F\u671B ${JSON.stringify(c.expected)}\uFF0C\u8BFB\u5230 ${actual}`;
113659
+ }).join("\uFF1B");
113660
+ }
113661
+ function writeVerificationHeadline(verification, detail) {
113662
+ if (verification === "verified") return `\u2705 ${detail}`;
113663
+ if (verification === "partial") return `\u26A0\uFE0F \u90E8\u5206\u751F\u6548\uFF1A${detail}`;
113664
+ return `\u26A0\uFE0F \u5DF2\u63D0\u4EA4\uFF0C\u5C1A\u672A\u6838\u9A8C\uFF1A${detail}`;
113665
+ }
113666
+ function readBudgetSharedFlag(campaign) {
113667
+ const raw = campaign["budgetShared"] ?? campaign["BudgetShared"];
113668
+ if (typeof raw === "boolean") return raw;
113669
+ return void 0;
113670
+ }
113671
+ function readCampaignBudgetCents(campaign) {
113672
+ const raw = campaign["budget"] ?? campaign["Budget"];
113673
+ if (typeof raw === "number" && Number.isFinite(raw)) return raw;
113674
+ return void 0;
113675
+ }
113676
+ function readKey(obj, keys) {
113677
+ for (const key of keys) {
113678
+ if (Object.prototype.hasOwnProperty.call(obj, key)) return { present: true, value: obj[key] };
113679
+ }
113680
+ return { present: false, value: void 0 };
113681
+ }
113682
+ function evaluateCampaignReadback(after, expected, readFailed) {
113683
+ if (readFailed || !after) return { verification: "unknown", checks: [] };
113684
+ const checks = [];
113685
+ const push = (field, expectedValue, keys) => {
113686
+ if (expectedValue === void 0) return;
113687
+ const found = readKey(after, keys);
113688
+ checks.push(fieldCheck(field, expectedValue, found.value, found.present));
113689
+ };
113690
+ push("name", expected.name, ["name", "Name"]);
113691
+ push("statusV2", expected.statusV2, ["statusV2", "StatusV2"]);
113692
+ push("biddingStrategyTypeV2", expected.biddingStrategyTypeV2, [
113693
+ "biddingStrategyTypeV2",
113694
+ "BiddingStrategyTypeV2"
113695
+ ]);
113696
+ push("targetSpend_BidCeilingAmount", expected.targetSpendBidCeilingCents, [
113697
+ "targetSpend_BidCeilingAmount",
113698
+ "TargetSpend_BidCeilingAmount"
113699
+ ]);
113700
+ push("targetCpa_BidingAmount", expected.targetCpaCents, [
113701
+ "targetCpa_BidingAmount",
113702
+ "TargetCpa_BidingAmount"
113703
+ ]);
113704
+ push("targetRoas", expected.targetRoas, ["targetRoas", "TargetRoas"]);
113705
+ push("manualCpc_EnhancedCpcEnabled", expected.manualCpcEnhanced, [
113706
+ "manualCpc_EnhancedCpcEnabled",
113707
+ "ManualCpc_EnhancedCpcEnabled"
113708
+ ]);
113709
+ push("targetSearchNetwork", expected.targetSearchNetwork, [
113710
+ "targetSearchNetwork",
113711
+ "TargetSearchNetwork"
113712
+ ]);
113713
+ push("targetContentNetwork", expected.targetContentNetwork, [
113714
+ "targetContentNetwork",
113715
+ "TargetContentNetwork"
113716
+ ]);
113717
+ if (expected.budgetWasRequested) {
113718
+ push("budget", expected.budgetCents, ["budget", "Budget"]);
113719
+ } else if (expected.budgetCentsBefore !== void 0) {
113720
+ const found = readKey(after, ["budget", "Budget"]);
113721
+ checks.push(fieldCheck("budget", expected.budgetCentsBefore, found.value, found.present));
113722
+ }
113723
+ return { verification: classifyFieldChecks(checks, false), checks };
113724
+ }
113725
+ function judgeBudgetAfterFailedPut(input) {
113726
+ if (input.readFailed || input.budgetCentsBefore === void 0 || input.budgetCentsAfter === void 0) {
113727
+ const present = !input.readFailed && input.budgetCentsAfter !== void 0;
113728
+ const checks = input.budgetCentsBefore === void 0 ? [] : [fieldCheck("budget", input.budgetCentsBefore, input.budgetCentsAfter, present)];
113729
+ return { kind: "unknown", verification: "unknown", checks };
113730
+ }
113731
+ if (input.budgetCentsBefore !== input.budgetCentsAfter) {
113732
+ return {
113733
+ kind: "partial",
113734
+ verification: "partial",
113735
+ checks: [fieldCheck("budget", input.budgetCentsBefore, input.budgetCentsAfter, true)]
113736
+ };
113737
+ }
113738
+ return { kind: "unchanged" };
113739
+ }
113740
+ function coreKeyword(text2) {
113741
+ const t = text2.trim();
113742
+ if (t.length >= 2 && (t.startsWith('"') && t.endsWith('"') || t.startsWith("[") && t.endsWith("]"))) {
113743
+ return t.slice(1, -1).trim();
113744
+ }
113745
+ return t;
113746
+ }
113747
+ function collectKeywordCores(value) {
113748
+ if (typeof value === "string") {
113749
+ const core = coreKeyword(value);
113750
+ return core ? [core] : [];
113751
+ }
113752
+ if (!Array.isArray(value)) return [];
113753
+ const cores = [];
113754
+ for (const item of value) {
113755
+ if (typeof item !== "string") continue;
113756
+ const core = coreKeyword(item);
113757
+ if (core) cores.push(core);
113758
+ }
113759
+ return cores;
113760
+ }
113761
+ function isRecord(value) {
113762
+ return !!value && typeof value === "object" && !Array.isArray(value);
113763
+ }
113764
+ function recordsFromResponse(response) {
113765
+ if (Array.isArray(response)) return response.filter(isRecord);
113766
+ if (!isRecord(response)) return [];
113767
+ for (const key of ["data", "items", "keywords"]) {
113768
+ const nested = response[key];
113769
+ if (Array.isArray(nested)) return nested.filter(isRecord);
113770
+ }
113771
+ const cores = [
113772
+ ...collectKeywordCores(response["keywordText"]),
113773
+ ...collectKeywordCores(response["KeywordText"]),
113774
+ ...collectKeywordCores(response["text"])
113775
+ ];
113776
+ if (cores.length > 0 || typeof response["id"] === "string") return [response];
113777
+ return [];
113778
+ }
113779
+ function summarizeNegativeKeywordCreate(requested, response, listItems) {
113780
+ const responseRecords = recordsFromResponse(response);
113781
+ const pools = listItems == null ? responseRecords : [...responseRecords, ...listItems];
113782
+ const matched = [];
113783
+ const pending = [];
113784
+ for (const text2 of requested) {
113785
+ const core = coreKeyword(text2);
113786
+ const hit = pools.find((item) => {
113787
+ const cores = [
113788
+ ...collectKeywordCores(item["keywordText"]),
113789
+ ...collectKeywordCores(item["KeywordText"]),
113790
+ ...collectKeywordCores(item["text"])
113791
+ ];
113792
+ return cores.includes(core);
113793
+ });
113794
+ if (!hit) {
113795
+ pending.push(text2);
113796
+ continue;
113797
+ }
113798
+ const idRaw = hit["id"] ?? hit["Id"];
113799
+ matched.push({ text: text2, id: typeof idRaw === "string" ? idRaw : void 0 });
113800
+ }
113801
+ let verification;
113802
+ if (requested.length > 0 && matched.length === requested.length) verification = "verified";
113803
+ else if (matched.length > 0) verification = "partial";
113804
+ else verification = "unknown";
113805
+ return { verification, matchedCount: matched.length, matched, pending };
113806
+ }
113807
+
113808
+ // src/commands/ad/keyword.ts
113623
113809
  function unwrapKeywordDisplayTextForEdit(raw) {
113624
113810
  const t = raw.trim();
113625
113811
  if (t.length >= 2 && t.startsWith('"') && t.endsWith('"')) {
@@ -113952,17 +114138,77 @@ async function runAdNegativeKeywordCreate(opts) {
113952
114138
  }
113953
114139
  const body = buildNegativeKeywordCreateBody(level, keywords, opts);
113954
114140
  const url = `${googleApiUrl}/negativekeywordmanagement/negativekeyword/${opts.account}`;
114141
+ let response;
113955
114142
  try {
113956
- await apiFetch2(url, config, { method: "POST", body: JSON.stringify(body) }, opts.verbose);
114143
+ response = await apiFetch2(
114144
+ url,
114145
+ config,
114146
+ { method: "POST", body: JSON.stringify(body) },
114147
+ opts.verbose
114148
+ );
113957
114149
  } catch (err) {
113958
114150
  console.error(`
113959
114151
  \u274C \u6DFB\u52A0\u5426\u5B9A\u5173\u952E\u8BCD\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
113960
114152
  `);
113961
114153
  process.exit(1);
113962
114154
  }
114155
+ const dateParams = new URLSearchParams();
114156
+ dateParams.set("startDate", toGoogleDate(void 0, -30));
114157
+ dateParams.set("endDate", toGoogleDate(void 0, 0));
114158
+ const listUrl = `${googleApiUrl}/negativekeywordmanagement/v2/list/${opts.account}?${dateParams}`;
114159
+ const readList = async () => {
114160
+ try {
114161
+ const listed = await apiFetch2(
114162
+ listUrl,
114163
+ config,
114164
+ {},
114165
+ opts.verbose
114166
+ );
114167
+ return listed.data ?? [];
114168
+ } catch {
114169
+ return null;
114170
+ }
114171
+ };
114172
+ let listItems = await readList();
114173
+ let summary = summarizeNegativeKeywordCreate(keywords, response, listItems);
114174
+ if (summary.verification !== "verified") {
114175
+ await new Promise((resolve25) => setTimeout(resolve25, 1e3));
114176
+ listItems = await readList();
114177
+ summary = summarizeNegativeKeywordCreate(keywords, response, listItems);
114178
+ }
114179
+ const nextQuery = `siluzan-tso ad keywords -a ${opts.account} --negative --json-out ./snap`;
114180
+ const detail = summary.verification === "verified" ? `\u5DF2\u6838\u9A8C\u6DFB\u52A0 ${summary.matchedCount} \u4E2A\u5426\u5B9A\u5173\u952E\u8BCD\uFF08${negativeKeywordLevelLabel[level]}\uFF09` : `\u8BF7\u6C42 ${keywords.length} \u4E2A\uFF0C\u5BF9\u4E0A ${summary.matchedCount} \u4E2A${summary.pending.length ? `\uFF0C\u5C1A\u672A\u6838\u9A8C\uFF1A${summary.pending.join("\u3001")}` : ""}`;
114181
+ const headline = writeVerificationHeadline(summary.verification, detail);
114182
+ if (summary.verification !== "verified") {
114183
+ console.error(`
114184
+ ${headline}
114185
+ \u8BF7\u518D\u67E5\uFF1A${nextQuery}
114186
+ `);
114187
+ }
114188
+ const payload = {
114189
+ verification: summary.verification,
114190
+ requestedCount: keywords.length,
114191
+ matchedCount: summary.matchedCount,
114192
+ matched: summary.matched,
114193
+ pending: summary.pending,
114194
+ nextQuery,
114195
+ level,
114196
+ response
114197
+ };
114198
+ if (await emitCliJsonOrSnapshot(opts, {
114199
+ section: `ad-keyword-negative-create-${opts.account}`,
114200
+ commandLabel: "ad keyword-negative-create",
114201
+ commandHint: opts.account,
114202
+ payload,
114203
+ idSuffix: opts.account,
114204
+ summaryExtras: { verification: summary.verification, matchedCount: summary.matchedCount }
114205
+ })) {
114206
+ return;
114207
+ }
113963
114208
  console.log(
113964
114209
  `
113965
- \u2705 \u5DF2\u5728${negativeKeywordLevelLabel[level]}\u5C42\u7EA7\u6DFB\u52A0 ${keywords.length} \u4E2A\u5426\u5B9A\u5173\u952E\u8BCD
114210
+ ${headline}${summary.verification === "verified" ? "" : `
114211
+ \u8BF7\u518D\u67E5\uFF1A${nextQuery}`}
113966
114212
  `
113967
114213
  );
113968
114214
  }
@@ -114058,7 +114304,7 @@ async function runAdKeywordEdit(opts) {
114058
114304
  currentId = returnedId;
114059
114305
  console.log(
114060
114306
  `
114061
- \u2139\uFE0F \u7F51\u5173\u8FD4\u56DE\u7684\u5173\u952E\u8BCD id \u5DF2\u53D8\u66F4\u4E3A ${returnedId}\uFF08\u8BF7\u6C42\u65F6 id \u4E3A ${opts.id}\uFF09\uFF0C\u540E\u7EED\u67E5\u8BE2\u8BF7\u4F7F\u7528\u65B0 id\u3002
114307
+ \u2139\uFE0F \u7F51\u5173\u8FD4\u56DE\u7684\u5173\u952E\u8BCD id \u5DF2\u53D8\u66F4\u4E3A ${returnedId}\uFF08\u8BF7\u6C42\u65F6 id \u4E3A ${opts.id}\uFF0CchangeKind=replace\uFF09\uFF0C\u540E\u7EED\u67E5\u8BE2\u8BF7\u4F7F\u7528\u65B0 id\u3002
114062
114308
  `
114063
114309
  );
114064
114310
  }
@@ -114107,9 +114353,28 @@ ${mismatches.map((m) => ` - ${m}`).join("\n")}
114107
114353
  process.exit(1);
114108
114354
  }
114109
114355
  }
114110
- console.log(`
114111
- \u2705 \u5173\u952E\u8BCD\u5DF2\u66F4\u65B0
114356
+ const verifiedFields = [];
114357
+ if (opts.finalUrl !== void 0) verifiedFields.push("finalURL");
114358
+ if (opts.maxCpc !== void 0) verifiedFields.push("maxCPC");
114359
+ if (opts.status !== void 0) verifiedFields.push("userStatusV2");
114360
+ const changeKind = currentId !== opts.id ? "replace" : "update";
114361
+ if (verifiedFields.length > 0) {
114362
+ console.log(
114363
+ `
114364
+ \u2705 \u5173\u952E\u8BCD\u5DF2\u6838\u9A8C\uFF08${verifiedFields.join("\u3001")}\uFF1BchangeKind=${changeKind}${changeKind === "replace" ? `\uFF0C\u65B0 id ${currentId}` : ""}\uFF09
114365
+ `
114366
+ );
114367
+ } else if (changeKind === "replace") {
114368
+ console.log(
114369
+ `
114370
+ \u2705 \u5173\u952E\u8BCD\u5DF2\u63D0\u4EA4\uFF08changeKind=replace\uFF0C\u65B0 id ${currentId}\uFF09\u3002\u8BCD\u9762\u6216\u5339\u914D\u7C7B\u578B\u4F1A\u65B0\u5EFA\u5E76\u79FB\u9664\u65E7\u8BCD\uFF0C\u8BF7\u7528 ad keywords \u6838\u5BF9\u65B0 id\u3002
114371
+ `
114372
+ );
114373
+ } else {
114374
+ console.log(`
114375
+ \u2705 \u5173\u952E\u8BCD\u5DF2\u63D0\u4EA4\uFF08changeKind=update\uFF09\u3002\u8BF7\u7528 ad keywords \u6838\u5BF9\u8BCD\u9762\u3002
114112
114376
  `);
114377
+ }
114113
114378
  }
114114
114379
  async function runAdKeywordStatus(opts) {
114115
114380
  opts = withResolvedAdAccount(opts);
@@ -115626,6 +115891,7 @@ async function resolveCampaignCustomerName(cfg, config, verbose) {
115626
115891
  }
115627
115892
 
115628
115893
  // src/commands/ad/campaign.ts
115894
+ var CAMPAIGN_READBACK_WAIT_MS = 1e3;
115629
115895
  function formatGoogleCampaignListStatus(row) {
115630
115896
  const raw = row.statusV2;
115631
115897
  if (raw == null || String(raw).trim() === "") return "-";
@@ -115735,6 +116001,108 @@ async function runAdCampaigns(opts) {
115735
116001
  printCliTable(rows, cols);
115736
116002
  console.log();
115737
116003
  }
116004
+ function unwrapCampaign(res) {
116005
+ if (res && typeof res === "object" && "data" in res && res.data && typeof res.data === "object") {
116006
+ return res.data;
116007
+ }
116008
+ return res;
116009
+ }
116010
+ async function fetchCampaignRecord(detailUrl, config, verbose) {
116011
+ const res = await apiFetch2(
116012
+ detailUrl,
116013
+ config,
116014
+ {},
116015
+ verbose
116016
+ );
116017
+ return unwrapCampaign(res);
116018
+ }
116019
+ async function verifyCampaignAfterPut(detailUrl, config, expected, verbose) {
116020
+ const readOnce = async () => {
116021
+ try {
116022
+ const after = await fetchCampaignRecord(detailUrl, config, verbose);
116023
+ return evaluateCampaignReadback(after, expected, false);
116024
+ } catch {
116025
+ return evaluateCampaignReadback(void 0, expected, true);
116026
+ }
116027
+ };
116028
+ let result = await readOnce();
116029
+ if (result.verification !== "verified") {
116030
+ await new Promise((resolve25) => setTimeout(resolve25, CAMPAIGN_READBACK_WAIT_MS));
116031
+ result = await readOnce();
116032
+ }
116033
+ return result;
116034
+ }
116035
+ async function reportFailedCampaignPut(opts) {
116036
+ const message = opts.err instanceof Error ? opts.err.message : String(opts.err);
116037
+ let budgetCentsAfter;
116038
+ let readFailed = false;
116039
+ try {
116040
+ const after = await fetchCampaignRecord(opts.detailUrl, opts.config, opts.verbose);
116041
+ budgetCentsAfter = readCampaignBudgetCents(after);
116042
+ } catch {
116043
+ readFailed = true;
116044
+ }
116045
+ const judged = judgeBudgetAfterFailedPut({
116046
+ readFailed,
116047
+ budgetCentsBefore: opts.budgetCentsBefore,
116048
+ budgetCentsAfter
116049
+ });
116050
+ if (judged.kind === "unchanged") {
116051
+ console.error(`
116052
+ \u274C ${opts.failureLabel}\uFF1A${message}
116053
+ `);
116054
+ process.exit(1);
116055
+ }
116056
+ const hint = judged.kind === "partial" ? "\u56DE\u8BFB\u9884\u7B97\u5DF2\u76F8\u5BF9\u63D0\u4EA4\u524D\u53D8\u5316\u3002\u9884\u7B97\u53EF\u80FD\u5DF2\u5199\u5165\uFF0C\u7CFB\u5217\u5176\u4F59\u66F4\u65B0\u672A\u5B8C\u6210\u3002\u4E0D\u8981\u6574\u5305\u91CD\u63D0\u3002" : "\u56DE\u8BFB\u5931\u8D25\u6216\u65E0\u6CD5\u5BF9\u6BD4\u9884\u7B97\uFF0C\u5199\u5165\u7ED3\u679C\u672A\u77E5\u3002\u4E0D\u8981\u6574\u5305\u91CD\u63D0\u3002";
116057
+ await reportCampaignWrite({
116058
+ jsonOut: opts.jsonOut,
116059
+ account: opts.account,
116060
+ id: opts.id,
116061
+ section: opts.section,
116062
+ commandLabel: opts.commandLabel,
116063
+ verification: judged.verification,
116064
+ checks: judged.checks,
116065
+ detail: `${opts.failureLabel}\uFF1A${message}\u3002${hint}`
116066
+ });
116067
+ }
116068
+ async function reportCampaignWrite(opts) {
116069
+ const nextQuery = `siluzan-tso ad campaigns -a ${opts.account} --json-out ./snap`;
116070
+ const unmatched = describeUnmatchedChecks(opts.checks);
116071
+ const text2 = opts.verification === "verified" ? opts.detail : unmatched || opts.detail;
116072
+ const headline = writeVerificationHeadline(opts.verification, text2);
116073
+ if (opts.verification !== "verified") {
116074
+ console.error(`
116075
+ ${headline}
116076
+ \u8BF7\u518D\u67E5\uFF1A${nextQuery}
116077
+ `);
116078
+ }
116079
+ const payload = {
116080
+ id: opts.id,
116081
+ writeScope: CAMPAIGN_WRITE_SCOPE,
116082
+ verification: opts.verification,
116083
+ nextQuery,
116084
+ checks: opts.checks
116085
+ };
116086
+ if (await emitCliJsonOrSnapshot(
116087
+ { jsonOut: opts.jsonOut },
116088
+ {
116089
+ section: opts.section,
116090
+ commandLabel: opts.commandLabel,
116091
+ commandHint: opts.account,
116092
+ payload,
116093
+ idSuffix: `${opts.account}-${opts.id}`,
116094
+ summaryExtras: { verification: opts.verification, writeScope: CAMPAIGN_WRITE_SCOPE }
116095
+ }
116096
+ )) {
116097
+ return;
116098
+ }
116099
+ console.log(
116100
+ `
116101
+ ${headline}${opts.verification === "verified" ? "" : `
116102
+ \u8BF7\u518D\u67E5\uFF1A${nextQuery}`}
116103
+ `
116104
+ );
116105
+ }
115738
116106
  async function runAdCampaignStatus(opts) {
115739
116107
  opts = withResolvedAdAccount(opts);
115740
116108
  const config = loadConfig(opts.token);
@@ -115761,14 +116129,41 @@ async function runAdCampaignStatus(opts) {
115761
116129
  try {
115762
116130
  await apiFetch2(detailUrl, config, { method: "PUT", body: JSON.stringify(body) }, opts.verbose);
115763
116131
  } catch (err) {
115764
- console.error(`
115765
- \u274C \u72B6\u6001\u66F4\u65B0\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
115766
- `);
115767
- process.exit(1);
116132
+ await reportFailedCampaignPut({
116133
+ err,
116134
+ detailUrl,
116135
+ config,
116136
+ verbose: opts.verbose,
116137
+ budgetCentsBefore: readCampaignBudgetCents(campaign),
116138
+ jsonOut: opts.jsonOut,
116139
+ account: opts.account,
116140
+ id: opts.id,
116141
+ section: "ad-campaign-status",
116142
+ commandLabel: "ad campaign-status",
116143
+ failureLabel: "\u72B6\u6001\u66F4\u65B0\u5931\u8D25"
116144
+ });
116145
+ return;
115768
116146
  }
115769
- console.log(`
115770
- \u2705 \u5E7F\u544A\u7CFB\u5217 ${opts.id} \u5DF2\u8BBE\u7F6E\u4E3A ${opts.status}
115771
- `);
116147
+ const judged = await verifyCampaignAfterPut(
116148
+ detailUrl,
116149
+ config,
116150
+ {
116151
+ statusV2: opts.status,
116152
+ budgetCentsBefore: readCampaignBudgetCents(campaign),
116153
+ budgetWasRequested: false
116154
+ },
116155
+ opts.verbose
116156
+ );
116157
+ await reportCampaignWrite({
116158
+ jsonOut: opts.jsonOut,
116159
+ account: opts.account,
116160
+ id: opts.id,
116161
+ section: "ad-campaign-status",
116162
+ commandLabel: "ad campaign-status",
116163
+ verification: judged.verification,
116164
+ checks: judged.checks,
116165
+ detail: `\u5E7F\u544A\u7CFB\u5217 ${opts.id} \u72B6\u6001\u5DF2\u6838\u9A8C\u4E3A ${opts.status}`
116166
+ });
115772
116167
  }
115773
116168
  async function runAdCampaignDelete(opts) {
115774
116169
  opts = withResolvedAdAccount(opts);
@@ -116069,13 +116464,29 @@ async function runAdCampaignEdit(opts) {
116069
116464
  console.error("\n\u274C \u8BF7\u81F3\u5C11\u6307\u5B9A\u4E00\u4E2A\u4FEE\u6539\u9879\uFF08--name / --budget / --bidding / \u2026\uFF09\n");
116070
116465
  process.exit(1);
116071
116466
  }
116467
+ if (opts.budget !== void 0 && readBudgetSharedFlag(campaign) === true) {
116468
+ console.error(
116469
+ "\n\u274C \u8BE5\u7CFB\u5217\u6807\u8BB0\u4E3A\u5171\u4EAB\u9884\u7B97\uFF08budgetShared=true\uFF09\u3002campaign-edit \u4E0D\u4F1A\u53EA\u6539\u672C\u7CFB\u5217\u7684\u5171\u4EAB\u9884\u7B97\u91D1\u989D\uFF0C\u672C\u6B21\u672A\u63D0\u4EA4\u3002\n"
116470
+ );
116471
+ process.exit(1);
116472
+ }
116072
116473
  try {
116073
116474
  await apiFetch2(detailUrl, config, { method: "PUT", body: JSON.stringify(body) }, opts.verbose);
116074
116475
  } catch (err) {
116075
- console.error(`
116076
- \u274C \u7F16\u8F91\u5E7F\u544A\u7CFB\u5217\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
116077
- `);
116078
- process.exit(1);
116476
+ await reportFailedCampaignPut({
116477
+ err,
116478
+ detailUrl,
116479
+ config,
116480
+ verbose: opts.verbose,
116481
+ budgetCentsBefore: readCampaignBudgetCents(campaign),
116482
+ jsonOut: opts.jsonOut,
116483
+ account: opts.account,
116484
+ id: opts.id,
116485
+ section: "ad-campaign-edit",
116486
+ commandLabel: "ad campaign-edit",
116487
+ failureLabel: "\u7F16\u8F91\u5E7F\u544A\u7CFB\u5217\u5931\u8D25"
116488
+ });
116489
+ return;
116079
116490
  }
116080
116491
  const parts = [];
116081
116492
  if (opts.name !== void 0) parts.push(`\u540D\u79F0\u2192${opts.name}`);
@@ -116085,9 +116496,34 @@ async function runAdCampaignEdit(opts) {
116085
116496
  if (opts.targetSpendBidCeiling !== void 0) parts.push(`CPC\u4E0A\u9650\u2192${opts.targetSpendBidCeiling}`);
116086
116497
  if (opts.targetCpa !== void 0) parts.push(`\u76EE\u6807CPA\u2192${opts.targetCpa}`);
116087
116498
  if (opts.targetRoas !== void 0) parts.push(`\u76EE\u6807ROAS\u2192${opts.targetRoas}`);
116088
- console.log(`
116089
- \u2705 \u5E7F\u544A\u7CFB\u5217 ${opts.id} \u5DF2\u66F4\u65B0${parts.length ? `\uFF08${parts.join("\uFF0C")}\uFF09` : ""}
116090
- `);
116499
+ const judged = await verifyCampaignAfterPut(
116500
+ detailUrl,
116501
+ config,
116502
+ {
116503
+ name: opts.name,
116504
+ budgetCents: opts.budget !== void 0 ? toCentAmount(opts.budget) : void 0,
116505
+ budgetWasRequested: opts.budget !== void 0,
116506
+ budgetCentsBefore: readCampaignBudgetCents(campaign),
116507
+ biddingStrategyTypeV2: opts.biddingStrategy?.toUpperCase(),
116508
+ targetSpendBidCeilingCents: opts.targetSpendBidCeiling !== void 0 ? toCentAmount(opts.targetSpendBidCeiling) : void 0,
116509
+ targetCpaCents: opts.targetCpa !== void 0 ? toCentAmount(opts.targetCpa) : void 0,
116510
+ targetRoas: opts.targetRoas,
116511
+ manualCpcEnhanced: opts.manualCpcEnhanced,
116512
+ targetSearchNetwork: opts.targetSearchNetwork,
116513
+ targetContentNetwork: opts.targetContentNetwork
116514
+ },
116515
+ opts.verbose
116516
+ );
116517
+ await reportCampaignWrite({
116518
+ jsonOut: opts.jsonOut,
116519
+ account: opts.account,
116520
+ id: opts.id,
116521
+ section: "ad-campaign-edit",
116522
+ commandLabel: "ad campaign-edit",
116523
+ verification: judged.verification,
116524
+ checks: judged.checks,
116525
+ detail: `\u5E7F\u544A\u7CFB\u5217 ${opts.id} \u5DF2\u6838\u9A8C${parts.length ? `\uFF08${parts.join("\uFF0C")}\uFF09` : ""}`
116526
+ });
116091
116527
  }
116092
116528
 
116093
116529
  // src/commands/ad/adgroup.ts
@@ -123928,7 +124364,7 @@ function register21(program2) {
123928
124364
  });
123929
124365
  }
123930
124366
  );
123931
- adCmd.command("campaign-status").description("\u5207\u6362\u5E7F\u544A\u7CFB\u5217\u72B6\u6001\uFF08Enabled / Paused\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <campaignId>", "\u5E7F\u544A\u7CFB\u5217 ID").requiredOption("--status <status>", "\u76EE\u6807\u72B6\u6001\uFF1AEnabled | Paused").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
124367
+ adCmd.command("campaign-status").description("\u5207\u6362\u5E7F\u544A\u7CFB\u5217\u72B6\u6001\uFF08Enabled / Paused\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <campaignId>", "\u5E7F\u544A\u7CFB\u5217 ID").requiredOption("--status <status>", "\u76EE\u6807\u72B6\u6001\uFF1AEnabled | Paused").option("--json-out <path>", "\u843D\u76D8\u6838\u9A8C\u7ED3\u679C\uFF08writeScope=gateway-full-put\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
123932
124368
  async (opts) => {
123933
124369
  if (opts.status !== "Enabled" && opts.status !== "Paused") {
123934
124370
  console.error("\n\u274C --status \u53EA\u63A5\u53D7 Enabled \u6216 Paused\n");
@@ -123939,6 +124375,7 @@ function register21(program2) {
123939
124375
  account: opts.account,
123940
124376
  id: opts.id,
123941
124377
  status: opts.status,
124378
+ jsonOut: opts.jsonOut,
123942
124379
  verbose: opts.verbose
123943
124380
  });
123944
124381
  }
@@ -124124,7 +124561,7 @@ function register21(program2) {
124124
124561
  ).option(
124125
124562
  "--adgroup-id <id>",
124126
124563
  "\u5E7F\u544A\u7EC4 ID\uFF08level=AdGroup \u5FC5\u4F20\uFF1B\u672A\u4F20 --level \u65F6\u6307\u5B9A\u5373\u89C6\u4E3A AdGroup \u5C42\u7EA7\uFF09"
124127
- ).option("--adgroup-name <name>", "\u5E7F\u544A\u7EC4\u540D\u79F0").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
124564
+ ).option("--adgroup-name <name>", "\u5E7F\u544A\u7EC4\u540D\u79F0").option("--json-out <path>", "\u843D\u76D8\u6838\u9A8C\u7ED3\u679C\uFF08\u6210\u529F\u6761\u6570\u6765\u81EA\u56DE\u8BFB\uFF0C\u4E0D\u662F\u8BF7\u6C42\u6761\u6570\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
124128
124565
  async (opts) => {
124129
124566
  if (opts.level && !["Account", "Campaign", "AdGroup"].includes(opts.level)) {
124130
124567
  console.error("\n\u274C --level \u53EA\u63A5\u53D7 Account | Campaign | AdGroup\n");
@@ -124141,6 +124578,7 @@ function register21(program2) {
124141
124578
  keywords: opts.keywords,
124142
124579
  keywordsFile: opts.keywordsFile,
124143
124580
  matchType: parseOptionalMatchType(opts.matchType),
124581
+ jsonOut: opts.jsonOut,
124144
124582
  verbose: opts.verbose
124145
124583
  });
124146
124584
  }
@@ -124455,7 +124893,7 @@ function register21(program2) {
124455
124893
  "--search-network <bool>",
124456
124894
  "\u6295\u653E\u5230 Google \u641C\u7D22\uFF1Atrue | false",
124457
124895
  (v) => v === "true"
124458
- ).option("--content-network <bool>", "\u6295\u653E\u5230\u5C55\u793A\u7F51\u7EDC\uFF1Atrue | false", (v) => v === "true").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
124896
+ ).option("--content-network <bool>", "\u6295\u653E\u5230\u5C55\u793A\u7F51\u7EDC\uFF1Atrue | false", (v) => v === "true").option("--json-out <path>", "\u843D\u76D8\u6838\u9A8C\u7ED3\u679C\uFF08writeScope=gateway-full-put\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
124459
124897
  async (opts) => {
124460
124898
  await runAdCampaignEdit({
124461
124899
  token: opts.token,
@@ -124470,6 +124908,7 @@ function register21(program2) {
124470
124908
  manualCpcEnhanced: opts.manualEcpc,
124471
124909
  targetSearchNetwork: opts.searchNetwork,
124472
124910
  targetContentNetwork: opts.contentNetwork,
124911
+ jsonOut: opts.jsonOut,
124473
124912
  verbose: opts.verbose
124474
124913
  });
124475
124914
  }
@@ -125193,13 +125632,14 @@ function failMetaAd(action, err) {
125193
125632
  `);
125194
125633
  process.exit(1);
125195
125634
  }
125196
- async function emitMetaJson(opts, section, commandLabel, payload, idSuffix) {
125635
+ async function emitMetaJson(opts, section, commandLabel, payload, idSuffix, summaryExtras) {
125197
125636
  return emitCliJsonOrSnapshot(opts, {
125198
125637
  section,
125199
125638
  commandLabel,
125200
125639
  commandHint: idSuffix,
125201
125640
  payload,
125202
- idSuffix
125641
+ idSuffix,
125642
+ summaryExtras
125203
125643
  });
125204
125644
  }
125205
125645
  function isHttpUrl(value) {
@@ -125480,6 +125920,10 @@ function buildUpdateAdSetBody(opts) {
125480
125920
  if (opts.startTime != null) body.start_time = opts.startTime;
125481
125921
  if (opts.endTime != null) body.end_time = opts.endTime;
125482
125922
  if (opts.status != null) body.status = opts.status;
125923
+ if (opts.targeting && Object.keys(opts.targeting).length > 0) {
125924
+ body.targeting = opts.targeting;
125925
+ return body;
125926
+ }
125483
125927
  const hasTargeting = opts.countries != null || opts.ageMin != null || opts.ageMax != null || opts.genders != null || opts.flexibleSpec != null || opts.advantageAudience != null;
125484
125928
  if (hasTargeting) {
125485
125929
  const targeting = {};
@@ -125490,17 +125934,221 @@ function buildUpdateAdSetBody(opts) {
125490
125934
  if (opts.ageMax != null) targeting.age_max = opts.ageMax;
125491
125935
  if (opts.genders) targeting.genders = opts.genders;
125492
125936
  if (opts.flexibleSpec != null) targeting.flexible_spec = opts.flexibleSpec;
125493
- applyLeadAdTargetingAutomation(
125494
- targeting,
125495
- resolveAdvantageAudience({
125496
- advantageAudience: opts.advantageAudience,
125497
- flexibleSpec: opts.flexibleSpec
125498
- })
125499
- );
125937
+ if (opts.advantageAudience === 0 || opts.advantageAudience === 1) {
125938
+ applyLeadAdTargetingAutomation(targeting, opts.advantageAudience);
125939
+ }
125500
125940
  body.targeting = targeting;
125501
125941
  }
125502
125942
  return body;
125503
125943
  }
125944
+ var ADSET_TARGETING_TOP_KEYS = /* @__PURE__ */ new Set([
125945
+ "geo_locations",
125946
+ "age_min",
125947
+ "age_max",
125948
+ "genders",
125949
+ "flexible_spec",
125950
+ "targeting_automation"
125951
+ ]);
125952
+ function isPlainObject(value) {
125953
+ return !!value && typeof value === "object" && !Array.isArray(value);
125954
+ }
125955
+ function readFiniteNumber(value) {
125956
+ if (typeof value === "number" && Number.isFinite(value)) return value;
125957
+ if (typeof value === "string" && value.trim() !== "") {
125958
+ const n = Number(value);
125959
+ if (Number.isFinite(n)) return n;
125960
+ }
125961
+ return void 0;
125962
+ }
125963
+ function upperUnique(list) {
125964
+ const seen = /* @__PURE__ */ new Set();
125965
+ const out = [];
125966
+ for (const raw of list) {
125967
+ const code = raw.trim().toUpperCase();
125968
+ if (!code || seen.has(code)) continue;
125969
+ seen.add(code);
125970
+ out.push(code);
125971
+ }
125972
+ return out;
125973
+ }
125974
+ function readCountries(targeting) {
125975
+ const geo = targeting?.["geo_locations"];
125976
+ if (!isPlainObject(geo) || !Array.isArray(geo["countries"])) return void 0;
125977
+ const countries = upperUnique(geo["countries"].map((c) => String(c)));
125978
+ return countries.length > 0 ? countries : void 0;
125979
+ }
125980
+ function readAdvantage(targeting) {
125981
+ const auto = targeting?.["targeting_automation"];
125982
+ if (!isPlainObject(auto)) return void 0;
125983
+ const value = readFiniteNumber(auto["advantage_audience"]);
125984
+ if (value === 0 || value === 1) return value;
125985
+ return void 0;
125986
+ }
125987
+ function unsupportedTargetingKeys(current) {
125988
+ if (!current) return [];
125989
+ const keys = [];
125990
+ for (const key of Object.keys(current)) {
125991
+ if (!ADSET_TARGETING_TOP_KEYS.has(key)) keys.push(key);
125992
+ }
125993
+ const geo = current["geo_locations"];
125994
+ if (isPlainObject(geo)) {
125995
+ for (const key of Object.keys(geo)) {
125996
+ if (key !== "countries") keys.push(`geo_locations.${key}`);
125997
+ }
125998
+ }
125999
+ const auto = current["targeting_automation"];
126000
+ if (isPlainObject(auto)) {
126001
+ for (const key of Object.keys(auto)) {
126002
+ if (key !== "advantage_audience") keys.push(`targeting_automation.${key}`);
126003
+ }
126004
+ }
126005
+ return keys;
126006
+ }
126007
+ function snapshotSupported(targeting) {
126008
+ const snap = {};
126009
+ if (!targeting) return snap;
126010
+ const countries = readCountries(targeting);
126011
+ if (countries) snap.countries = countries;
126012
+ const ageMin = readFiniteNumber(targeting["age_min"]);
126013
+ const ageMax = readFiniteNumber(targeting["age_max"]);
126014
+ if (ageMin !== void 0) snap.age_min = ageMin;
126015
+ if (ageMax !== void 0) snap.age_max = ageMax;
126016
+ if (Array.isArray(targeting["genders"])) snap.genders = targeting["genders"];
126017
+ if (targeting["flexible_spec"] !== void 0) snap.flexible_spec = targeting["flexible_spec"];
126018
+ const advantage = readAdvantage(targeting);
126019
+ if (advantage !== void 0) snap.advantage_audience = advantage;
126020
+ return snap;
126021
+ }
126022
+ function diffSnapshots(before, after) {
126023
+ const fields = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
126024
+ const changes = [];
126025
+ for (const field of fields) {
126026
+ const from = before[field];
126027
+ const to = after[field];
126028
+ const changed = JSON.stringify(from) !== JSON.stringify(to);
126029
+ changes.push({ field, from: from ?? null, to: to ?? null, changed });
126030
+ }
126031
+ return changes;
126032
+ }
126033
+ function mergeAdSetTargeting(current, patch, opts) {
126034
+ const source = current ?? void 0;
126035
+ const unsupported = unsupportedTargetingKeys(source);
126036
+ if (unsupported.length > 0 && !opts?.allowDropUnsupported) {
126037
+ return { ok: false, reason: "unsupported", unsupportedKeys: unsupported };
126038
+ }
126039
+ const before = snapshotSupported(source);
126040
+ const next = {};
126041
+ let countries = readCountries(source);
126042
+ if (patch.countries) {
126043
+ const incoming = upperUnique(patch.countries);
126044
+ countries = (patch.countriesMode ?? "replace") === "append" && countries ? upperUnique([...countries, ...incoming]) : incoming;
126045
+ }
126046
+ if (countries && countries.length > 0) {
126047
+ next.geo_locations = { countries };
126048
+ }
126049
+ const ageMin = patch.ageMin != null ? patch.ageMin : readFiniteNumber(source?.["age_min"]);
126050
+ const ageMax = patch.ageMax != null ? patch.ageMax : readFiniteNumber(source?.["age_max"]);
126051
+ if (ageMin !== void 0) next.age_min = ageMin;
126052
+ if (ageMax !== void 0) next.age_max = ageMax;
126053
+ const genders = patch.genders ?? source?.["genders"];
126054
+ if (Array.isArray(genders)) next.genders = genders;
126055
+ const flexible = patch.flexibleSpec ?? source?.["flexible_spec"];
126056
+ if (flexible !== void 0 && Array.isArray(flexible)) next.flexible_spec = flexible;
126057
+ const explicitAdvantage = patch.advantageAudience === 0 || patch.advantageAudience === 1 ? patch.advantageAudience : void 0;
126058
+ const advantage = explicitAdvantage ?? readAdvantage(source);
126059
+ if (advantage === void 0) {
126060
+ return { ok: false, reason: "advantage_unset" };
126061
+ }
126062
+ next.targeting_automation = { advantage_audience: advantage };
126063
+ return {
126064
+ ok: true,
126065
+ targeting: next,
126066
+ changes: diffSnapshots(before, snapshotSupported(next)),
126067
+ droppedKeys: opts?.allowDropUnsupported ? unsupported : []
126068
+ };
126069
+ }
126070
+ function sortedCountries(value) {
126071
+ if (!Array.isArray(value)) return [];
126072
+ return upperUnique(value.map((c) => String(c))).sort();
126073
+ }
126074
+ function targetingReadbackChecks(expected, actual) {
126075
+ const checks = [];
126076
+ const parentRead = actual !== void 0;
126077
+ if (expected["geo_locations"] !== void 0) {
126078
+ const exp = sortedCountries(
126079
+ isPlainObject(expected["geo_locations"]) ? expected["geo_locations"]["countries"] : []
126080
+ );
126081
+ const act = sortedCountries(
126082
+ actual && isPlainObject(actual["geo_locations"]) ? actual["geo_locations"]["countries"] : []
126083
+ );
126084
+ checks.push(fieldCheck("targeting.countries", exp, parentRead ? act : void 0, parentRead));
126085
+ }
126086
+ for (const key of ["age_min", "age_max"]) {
126087
+ if (expected[key] === void 0) continue;
126088
+ const raw = actual?.[key];
126089
+ checks.push(
126090
+ fieldCheck(
126091
+ `targeting.${key}`,
126092
+ expected[key],
126093
+ parentRead ? readFiniteNumber(raw) : void 0,
126094
+ parentRead
126095
+ )
126096
+ );
126097
+ }
126098
+ if (expected["genders"] !== void 0) {
126099
+ const exp = Array.isArray(expected["genders"]) ? [...expected["genders"]].sort() : [];
126100
+ const act = actual && Array.isArray(actual["genders"]) ? [...actual["genders"]].sort() : [];
126101
+ checks.push(fieldCheck("targeting.genders", exp, parentRead ? act : void 0, parentRead));
126102
+ }
126103
+ if (expected["flexible_spec"] !== void 0) {
126104
+ const exp = expected["flexible_spec"];
126105
+ const act = actual?.["flexible_spec"];
126106
+ const match = JSON.stringify(exp) === JSON.stringify(act);
126107
+ checks.push({
126108
+ field: "targeting.flexible_spec",
126109
+ expected: exp,
126110
+ actual: parentRead ? act : void 0,
126111
+ state: !parentRead ? "unread" : match ? "match" : "mismatch"
126112
+ });
126113
+ }
126114
+ if (isPlainObject(expected["targeting_automation"])) {
126115
+ const exp = expected["targeting_automation"]["advantage_audience"];
126116
+ const act = isPlainObject(actual?.["targeting_automation"]) ? readFiniteNumber(actual["targeting_automation"]["advantage_audience"]) : void 0;
126117
+ checks.push(fieldCheck("targeting.advantage_audience", exp, act, parentRead));
126118
+ }
126119
+ return checks;
126120
+ }
126121
+ var ADSET_SCALAR_READBACK_KEYS = [
126122
+ "name",
126123
+ "status",
126124
+ "daily_budget",
126125
+ "lifetime_budget",
126126
+ "optimization_goal",
126127
+ "bid_strategy",
126128
+ "bid_amount"
126129
+ ];
126130
+ function evaluateAdSetEditReadback(sentBody, actual, readFailed) {
126131
+ if (readFailed || !actual) return { verification: "unknown", checks: [] };
126132
+ const checks = [];
126133
+ for (const key of ADSET_SCALAR_READBACK_KEYS) {
126134
+ if (sentBody[key] === void 0) continue;
126135
+ const present = Object.prototype.hasOwnProperty.call(actual, key);
126136
+ const money2 = key === "daily_budget" || key === "lifetime_budget" || key === "bid_amount";
126137
+ const expected = money2 ? String(sentBody[key]) : sentBody[key];
126138
+ const actualValue = money2 && actual[key] != null && actual[key] !== "" ? String(actual[key]) : actual[key];
126139
+ checks.push(fieldCheck(key, expected, actualValue, present));
126140
+ }
126141
+ if (isPlainObject(sentBody["targeting"])) {
126142
+ const actualTargeting = isPlainObject(actual["targeting"]) ? actual["targeting"] : void 0;
126143
+ checks.push(
126144
+ ...targetingReadbackChecks(
126145
+ sentBody["targeting"],
126146
+ Object.prototype.hasOwnProperty.call(actual, "targeting") ? actualTargeting : void 0
126147
+ )
126148
+ );
126149
+ }
126150
+ return { verification: classifyFieldChecks(checks, false), checks };
126151
+ }
125504
126152
  function buildUpdateAdBody(opts) {
125505
126153
  const body = {};
125506
126154
  if (opts.name != null) body.name = opts.name;
@@ -125713,6 +126361,9 @@ function runMetaLeadValidation(cfg) {
125713
126361
  if (adset.bidAmount != null && !(Number.isFinite(adset.bidAmount) && adset.bidAmount > 0)) {
125714
126362
  errors.push("adset.bidAmount \u987B\u4E3A\u5927\u4E8E 0 \u7684\u6570\u5B57\uFF08\u4E3B\u5E01\u79CD\u5143\uFF09");
125715
126363
  }
126364
+ if (adset.bidAmountOnRequired != null && !(Number.isFinite(adset.bidAmountOnRequired) && adset.bidAmountOnRequired > 0)) {
126365
+ errors.push("adset.bidAmountOnRequired \u987B\u4E3A\u5927\u4E8E 0 \u7684\u6570\u5B57\uFF08\u4E3B\u5E01\u79CD\u5143\uFF09");
126366
+ }
125716
126367
  if (adset.advantageAudience != null && adset.advantageAudience !== 0 && adset.advantageAudience !== 1) {
125717
126368
  errors.push("adset.advantageAudience \u53EA\u5141\u8BB8 0 \u6216 1\uFF08targeting_automation.advantage_audience\uFF09");
125718
126369
  }
@@ -125845,6 +126496,46 @@ async function runMetaAdValidate(opts) {
125845
126496
  console.log("\n\u2705 Meta Lead \u914D\u7F6E\u6821\u9A8C\u901A\u8FC7\uFF08\u672C\u5730\uFF0C\u672A\u8C03\u7F51\u5173\uFF09\n");
125846
126497
  }
125847
126498
 
126499
+ // src/commands/meta-ad/bid-recovery.ts
126500
+ function decideBidAmountRecovery(input) {
126501
+ if (input.bidAmountAlreadySet) return { action: "abort" };
126502
+ const authorized = input.authorizedBidAmount;
126503
+ if (typeof authorized === "number" && Number.isFinite(authorized) && authorized > 0) {
126504
+ return {
126505
+ action: "retry",
126506
+ bidStrategy: "LOWEST_COST_WITH_BID_CAP",
126507
+ bidAmount: authorized,
126508
+ recovery: {
126509
+ applied: true,
126510
+ fromStrategy: input.originalBidStrategy,
126511
+ toStrategy: "LOWEST_COST_WITH_BID_CAP",
126512
+ bidAmount: authorized
126513
+ }
126514
+ };
126515
+ }
126516
+ const budgetNote = [
126517
+ input.dailyBudget != null ? `\u65E5\u9884\u7B97 ${input.dailyBudget}` : "",
126518
+ input.lifetimeBudget != null ? `\u603B\u9884\u7B97 ${input.lifetimeBudget}` : "",
126519
+ input.currency ? input.currency : ""
126520
+ ].filter(Boolean).join(" ");
126521
+ return {
126522
+ action: "needs_decision",
126523
+ needsDecision: {
126524
+ code: "BID_AMOUNT_REQUIRED",
126525
+ status: "needs_decision",
126526
+ campaignId: input.campaignId,
126527
+ originalBidStrategy: input.originalBidStrategy,
126528
+ proposedBidStrategy: "LOWEST_COST_WITH_BID_CAP",
126529
+ message: "\u8BE5\u6237\u8981\u6C42\u51FA\u4EF7\u4E0A\u9650\uFF08LOWEST_COST_WITH_BID_CAP\uFF09\u3002\u9884\u7B97\u91D1\u989D\u4E0D\u80FD\u5F53\u4F5C bid cap\u3002\u8BF7\u5355\u72EC\u586B\u5199\u51FA\u4EF7\u540E\u518D\u7EE7\u7EED\uFF1Acreate JSON \u7528 adset.bidAmountOnRequired\uFF0C\u6216 adset-create \u7528 --bid-amount-on-required\u3002" + (budgetNote ? ` \u5F53\u524D\u9884\u7B97\u4EC5\u4F5C\u4E0A\u4E0B\u6587\uFF08${budgetNote}\uFF09\uFF0C\u4E0D\u4F1A\u5199\u5165 bid_amount\u3002` : ""),
126530
+ budgetContext: {
126531
+ dailyBudget: input.dailyBudget,
126532
+ lifetimeBudget: input.lifetimeBudget,
126533
+ currency: input.currency
126534
+ }
126535
+ }
126536
+ };
126537
+ }
126538
+
125848
126539
  // src/commands/meta-ad/create.ts
125849
126540
  function resolveBesideConfig(configFile, p) {
125850
126541
  if (isAbsolute7(p)) return p;
@@ -126011,23 +126702,47 @@ async function runMetaAdCreate(opts) {
126011
126702
  );
126012
126703
  } catch (firstErr) {
126013
126704
  if (!isBidAmountRequired(firstErr) || cfg.adset.bidAmount != null) throw firstErr;
126014
- const retryMajor = cfg.campaign.dailyBudget ?? cfg.campaign.lifetimeBudget ?? cfg.adset.dailyBudget ?? cfg.adset.lifetimeBudget ?? 1;
126015
- if (!opts.jsonOut) {
126016
- console.warn(
126705
+ const decision = decideBidAmountRecovery({
126706
+ bidAmountAlreadySet: false,
126707
+ authorizedBidAmount: cfg.adset.bidAmountOnRequired,
126708
+ originalBidStrategy: baseAdset.bidStrategy,
126709
+ dailyBudget: cfg.campaign.dailyBudget ?? cfg.adset.dailyBudget,
126710
+ lifetimeBudget: cfg.campaign.lifetimeBudget ?? cfg.adset.lifetimeBudget,
126711
+ currency,
126712
+ campaignId: progress.campaignId
126713
+ });
126714
+ if (decision.action === "needs_decision") {
126715
+ progress.failedStep = "adset";
126716
+ progress.needsDecision = decision.needsDecision;
126717
+ progress.error = decision.needsDecision.message;
126718
+ await emitProgress();
126719
+ console.error(
126017
126720
  `
126018
- \u26A0\uFE0F \u8BE5\u6237\u8981\u6C42 bid cap\uFF0C\u6309\u65E5/\u603B\u9884\u7B97 ${retryMajor} ${currency} \u5E26 LOWEST_COST_WITH_BID_CAP \u91CD\u8BD5\u5EFA\u7EC4
126721
+ \u26A0\uFE0F ${decision.needsDecision.message}
126722
+ \u5DF2\u5EFA\u6210\uFF1A${summarizeProgress(progress)}
126723
+ \u539F\u7B56\u7565\uFF1A${decision.needsDecision.originalBidStrategy ?? "\uFF08\u672A\u6307\u5B9A\uFF09"}
126724
+ \u62DF\u8BAE\u7B56\u7565\uFF1A${decision.needsDecision.proposedBidStrategy}
126725
+ \u672C\u6B21\u672A\u6539\u51FA\u4EF7\uFF0C\u672A\u518D\u6B21\u5EFA\u7EC4\u3002
126019
126726
  `
126020
126727
  );
126728
+ process.exit(2);
126021
126729
  }
126730
+ if (decision.action !== "retry") throw firstErr;
126731
+ console.warn(
126732
+ `
126733
+ \u26A0\uFE0F \u5DF2\u6309\u4E8B\u524D\u6388\u6743\u7684 bid cap ${decision.bidAmount} ${currency} \u4EE5 LOWEST_COST_WITH_BID_CAP \u91CD\u8BD5\u5EFA\u7EC4\uFF08\u539F\u7B56\u7565 ${decision.recovery.fromStrategy ?? "\u672A\u6307\u5B9A"}\uFF09
126734
+ `
126735
+ );
126736
+ progress.recovery = decision.recovery;
126022
126737
  data = await postJson(
126023
126738
  adsetUrl,
126024
126739
  config,
126025
126740
  buildCreateAdSetBody({
126026
- adset: { ...baseAdset, bidStrategy: "LOWEST_COST_WITH_BID_CAP" },
126741
+ adset: { ...baseAdset, bidStrategy: decision.bidStrategy },
126027
126742
  campaignId: progress.campaignId,
126028
126743
  pageId,
126029
126744
  ...money2,
126030
- bidAmountMinor: toMinorUnitString(retryMajor, currency)
126745
+ bidAmountMinor: toMinorUnitString(decision.bidAmount, currency)
126031
126746
  }),
126032
126747
  opts.verbose
126033
126748
  );
@@ -126856,6 +127571,28 @@ async function runMetaAdCampaignStatus(opts) {
126856
127571
  // src/commands/meta-ad/adset.ts
126857
127572
  init_cli_table();
126858
127573
  init_auth();
127574
+ var READBACK_WAIT_MS = 1e3;
127575
+ function parseCountriesMode(raw) {
127576
+ if (!raw || raw === "replace") return "replace";
127577
+ if (raw === "append") return "append";
127578
+ throw new Error("--countries-mode \u53EA\u5141\u8BB8 append \u6216 replace");
127579
+ }
127580
+ function isRecord2(value) {
127581
+ return !!value && typeof value === "object" && !Array.isArray(value);
127582
+ }
127583
+ function formatTargetingChange(change) {
127584
+ const from = change.from == null ? "\uFF08\u65E0\uFF09" : JSON.stringify(change.from);
127585
+ const to = change.to == null ? "\uFF08\u65E0\uFF09" : JSON.stringify(change.to);
127586
+ return change.changed ? `${change.field}\uFF1A${from} \u2192 ${to}` : `${change.field}\uFF1A${to}\uFF08\u4FDD\u6301\uFF09`;
127587
+ }
127588
+ async function readAdSetForVerify(url, config, verbose) {
127589
+ try {
127590
+ const raw = await apiFetch2(url, config, {}, verbose);
127591
+ return { adset: raw, readFailed: false };
127592
+ } catch {
127593
+ return { readFailed: true };
127594
+ }
127595
+ }
126859
127596
  function parseGenders(raw) {
126860
127597
  if (!raw?.trim()) return void 0;
126861
127598
  const nums = raw.split(",").map((s) => Number(s.trim()));
@@ -127002,14 +127739,37 @@ async function runMetaAdAdsetCreate(opts) {
127002
127739
  if (!isBidAmountRequired(firstErr) || adset.bidAmount != null) {
127003
127740
  failMetaAd("\u5EFA\u5E7F\u544A\u7EC4\u5931\u8D25", firstErr);
127004
127741
  }
127005
- const retryMajor = daily ?? lifetime ?? 1;
127006
- if (!opts.jsonOut) {
127007
- console.warn(
127742
+ const decision = decideBidAmountRecovery({
127743
+ bidAmountAlreadySet: false,
127744
+ authorizedBidAmount: opts.bidAmountOnRequired,
127745
+ originalBidStrategy: adset.bidStrategy,
127746
+ dailyBudget: daily,
127747
+ lifetimeBudget: lifetime,
127748
+ currency,
127749
+ campaignId: opts.campaignId.trim()
127750
+ });
127751
+ if (decision.action === "needs_decision") {
127752
+ const payload3 = { ...decision.needsDecision, currency };
127753
+ console.error(
127008
127754
  `
127009
- \u26A0\uFE0F \u8BE5\u6237\u8981\u6C42 bid cap\uFF0C\u6309\u65E5/\u603B\u9884\u7B97 ${retryMajor} ${currency} \u5E26 LOWEST_COST_WITH_BID_CAP \u91CD\u8BD5\u5EFA\u7EC4
127755
+ \u26A0\uFE0F ${decision.needsDecision.message}
127756
+ \u539F\u7B56\u7565\uFF1A${decision.needsDecision.originalBidStrategy ?? "\uFF08\u672A\u6307\u5B9A\uFF09"}
127757
+ \u62DF\u8BAE\u7B56\u7565\uFF1A${decision.needsDecision.proposedBidStrategy}
127758
+ campaignId\uFF1A${opts.campaignId.trim()}
127759
+ \u672C\u6B21\u672A\u6539\u51FA\u4EF7\uFF0C\u672A\u518D\u6B21\u5EFA\u7EC4\u3002
127010
127760
  `
127011
127761
  );
127762
+ await emitMetaJson(opts, "meta-ad-adset-create", "meta-ad adset-create", payload3, digits, {
127763
+ status: "needs_decision"
127764
+ });
127765
+ process.exit(2);
127012
127766
  }
127767
+ if (decision.action !== "retry") failMetaAd("\u5EFA\u5E7F\u544A\u7EC4\u5931\u8D25", firstErr);
127768
+ console.warn(
127769
+ `
127770
+ \u26A0\uFE0F \u5DF2\u6309\u4E8B\u524D\u6388\u6743\u7684 bid cap ${decision.bidAmount} ${currency} \u4EE5 LOWEST_COST_WITH_BID_CAP \u91CD\u8BD5\u5EFA\u7EC4\uFF08\u539F\u7B56\u7565 ${decision.recovery.fromStrategy ?? "\u672A\u6307\u5B9A"}\uFF09
127771
+ `
127772
+ );
127013
127773
  try {
127014
127774
  data = await apiFetch2(
127015
127775
  adsetUrl,
@@ -127018,12 +127778,12 @@ async function runMetaAdAdsetCreate(opts) {
127018
127778
  method: "POST",
127019
127779
  body: JSON.stringify(
127020
127780
  buildCreateAdSetBody({
127021
- adset: { ...adset, bidStrategy: "LOWEST_COST_WITH_BID_CAP" },
127781
+ adset: { ...adset, bidStrategy: decision.bidStrategy },
127022
127782
  campaignId: opts.campaignId.trim(),
127023
127783
  pageId: opts.pageId.trim(),
127024
127784
  dailyBudgetMinor: daily != null ? toMinorUnitString(daily, currency) : void 0,
127025
127785
  lifetimeBudgetMinor: lifetime != null ? toMinorUnitString(lifetime, currency) : void 0,
127026
- bidAmountMinor: toMinorUnitString(retryMajor, currency)
127786
+ bidAmountMinor: toMinorUnitString(decision.bidAmount, currency)
127027
127787
  })
127028
127788
  )
127029
127789
  },
@@ -127032,6 +127792,15 @@ async function runMetaAdAdsetCreate(opts) {
127032
127792
  } catch (retryErr) {
127033
127793
  failMetaAd("\u5EFA\u5E7F\u544A\u7EC4\u5931\u8D25", retryErr);
127034
127794
  }
127795
+ const id2 = extractCreatedId(data);
127796
+ const payload2 = { ...data, id: id2, currency, recovery: decision.recovery };
127797
+ if (await emitMetaJson(opts, "meta-ad-adset-create", "meta-ad adset-create", payload2, digits)) {
127798
+ return;
127799
+ }
127800
+ console.log(`
127801
+ \u2705 \u5E7F\u544A\u7EC4\u5DF2\u521B\u5EFA\uFF1A${id2 ?? JSON.stringify(data)}
127802
+ `);
127803
+ return;
127035
127804
  }
127036
127805
  const id = extractCreatedId(data);
127037
127806
  const payload = { ...data, id, currency };
@@ -127053,10 +127822,12 @@ async function runMetaAdAdsetEdit(opts) {
127053
127822
  let countries;
127054
127823
  let genders;
127055
127824
  let advantageAudience;
127825
+ let countriesMode;
127056
127826
  try {
127057
127827
  if (opts.countries != null) countries = assertCountries(opts.countries);
127058
127828
  genders = parseGenders(opts.genders);
127059
127829
  advantageAudience = parseAdvantageAudience(opts.advantageAudience);
127830
+ countriesMode = parseCountriesMode(opts.countriesMode);
127060
127831
  } catch (err) {
127061
127832
  console.error(`
127062
127833
  \u274C ${err instanceof Error ? err.message : String(err)}
@@ -127070,26 +127841,87 @@ async function runMetaAdAdsetEdit(opts) {
127070
127841
  process.exit(1);
127071
127842
  }
127072
127843
  }
127844
+ const ageMin = parseOptionalNumber(opts.ageMin);
127845
+ const ageMax = parseOptionalNumber(opts.ageMax);
127846
+ const wantsTargeting = countries != null || ageMin != null || ageMax != null || genders != null || advantageAudience != null;
127073
127847
  const { config, facebookApiUrl } = metaAdContext(opts.token);
127074
127848
  const currency = await fetchAccountCurrency(facebookApiUrl, digits, config, opts.verbose);
127075
127849
  const bidAmount = parseOptionalNumber(opts.bidAmount);
127850
+ const adsetUrl = adsUrl(facebookApiUrl, digits, `/adsets/${encodeURIComponent(opts.id)}`);
127851
+ let targeting;
127852
+ let targetingChanges;
127853
+ let droppedKeys = [];
127854
+ if (wantsTargeting) {
127855
+ let currentRaw;
127856
+ try {
127857
+ currentRaw = await apiFetch2(adsetUrl, config, {}, opts.verbose);
127858
+ } catch (err) {
127859
+ console.error(
127860
+ `
127861
+ \u274C \u8BFB\u53D6\u5F53\u524D\u5B9A\u5411\u5931\u8D25\uFF0C\u672A\u63D0\u4EA4\u4FEE\u6539\uFF1A${err instanceof Error ? err.message : String(err)}
127862
+ `
127863
+ );
127864
+ process.exit(1);
127865
+ }
127866
+ const currentTargeting = isRecord2(currentRaw["targeting"]) ? currentRaw["targeting"] : void 0;
127867
+ const merged = mergeAdSetTargeting(
127868
+ currentTargeting,
127869
+ {
127870
+ countries,
127871
+ countriesMode: countries ? countriesMode : "replace",
127872
+ ageMin,
127873
+ ageMax,
127874
+ genders,
127875
+ advantageAudience
127876
+ },
127877
+ { allowDropUnsupported: opts.targetingReplace === true }
127878
+ );
127879
+ if (!merged.ok) {
127880
+ const payload2 = merged.reason === "advantage_unset" ? {
127881
+ status: "needs_decision",
127882
+ code: "ADVANTAGE_AUDIENCE_UNSET",
127883
+ id: opts.id,
127884
+ message: "\u5F53\u524D\u7EC4\u8BFB\u4E0D\u5230 advantage_audience\uFF080 \u6216 1\uFF09\u3002\u7F51\u5173\u4F1A\u5728\u8FD9\u6B21\u5B9A\u5411\u66F4\u65B0\u91CC\u628A\u5B83\u5199\u6210 1\u3002\u8BF7\u663E\u5F0F\u4F20\u5165 --advantage-audience 0 \u6216 1 \u540E\u518D\u63D0\u4EA4\u3002\u672C\u6B21\u672A\u63D0\u4EA4\u4FEE\u6539\u3002"
127885
+ } : {
127886
+ status: "needs_decision",
127887
+ code: "TARGETING_UNSUPPORTED",
127888
+ id: opts.id,
127889
+ unsupportedKeys: merged.unsupportedKeys,
127890
+ message: "\u5F53\u524D\u5B9A\u5411\u542B\u672C\u547D\u4EE4\u4E0D\u80FD\u5B89\u5168\u5199\u56DE\u7684\u5B57\u6BB5\u3002\u6574\u4EFD\u66FF\u6362\u4F1A\u4E22\u6389\u5B83\u4EEC\u3002\u786E\u8BA4\u53EA\u4FDD\u7559\u56FD\u5BB6/\u5E74\u9F84/\u6027\u522B/flexible_spec/advantage_audience \u540E\uFF0C\u52A0\u4E0A --targeting-replace \u518D\u63D0\u4EA4\u3002\u672C\u6B21\u672A\u63D0\u4EA4\u4FEE\u6539\u3002"
127891
+ };
127892
+ const extra = merged.reason === "unsupported" ? `
127893
+ \u4E0D\u80FD\u5199\u56DE\uFF1A${merged.unsupportedKeys.join("\u3001")}` : "";
127894
+ console.error(`
127895
+ \u26A0\uFE0F ${payload2.message}${extra}
127896
+ `);
127897
+ await emitMetaJson(opts, "meta-ad-adset-edit", "meta-ad adset-edit", payload2, digits, {
127898
+ status: "needs_decision"
127899
+ });
127900
+ process.exit(2);
127901
+ }
127902
+ targeting = merged.targeting;
127903
+ targetingChanges = merged.changes;
127904
+ droppedKeys = merged.droppedKeys;
127905
+ const lines = merged.changes.map(formatTargetingChange);
127906
+ if (droppedKeys.length > 0) lines.push(`\u5C06\u4E22\u5F03\uFF1A${droppedKeys.join("\u3001")}`);
127907
+ console.error(`
127908
+ \u5B9A\u5411\u5DEE\u5F02\uFF1A
127909
+ ${lines.join("\n ")}
127910
+ `);
127911
+ }
127076
127912
  let body;
127077
127913
  try {
127078
127914
  body = buildUpdateAdSetBody({
127079
127915
  name: opts.name?.trim() || void 0,
127080
127916
  dailyBudgetMinor: daily != null ? toMinorUnitString(daily, currency) : void 0,
127081
127917
  lifetimeBudgetMinor: lifetime != null ? toMinorUnitString(lifetime, currency) : void 0,
127082
- countries,
127083
- ageMin: parseOptionalNumber(opts.ageMin),
127084
- ageMax: parseOptionalNumber(opts.ageMax),
127085
- genders,
127918
+ targeting,
127086
127919
  optimizationGoal: opts.optimizationGoal?.trim().toUpperCase(),
127087
127920
  bidStrategy: opts.bidStrategy,
127088
127921
  bidAmountMinor: bidAmount != null ? toMinorUnitString(bidAmount, currency) : void 0,
127089
127922
  startTime: opts.startTime,
127090
127923
  endTime: opts.endTime,
127091
- status: opts.status,
127092
- advantageAudience
127924
+ status: opts.status
127093
127925
  });
127094
127926
  } catch (err) {
127095
127927
  console.error(`
@@ -127102,16 +127934,47 @@ async function runMetaAdAdsetEdit(opts) {
127102
127934
  process.exit(1);
127103
127935
  }
127104
127936
  const data = await metaAdPost(
127105
- adsUrl(facebookApiUrl, digits, `/adsets/${encodeURIComponent(opts.id)}`),
127937
+ adsetUrl,
127106
127938
  config,
127107
127939
  body,
127108
127940
  opts.verbose,
127109
127941
  "\u6539\u5E7F\u544A\u7EC4\u5931\u8D25"
127110
127942
  );
127111
- const payload = { id: extractCreatedId(data) ?? opts.id, ...data };
127112
- if (await emitMetaJson(opts, "meta-ad-adset-edit", "meta-ad adset-edit", payload, digits)) return;
127943
+ let read = await readAdSetForVerify(adsetUrl, config, opts.verbose);
127944
+ let judged = evaluateAdSetEditReadback(body, read.adset, read.readFailed);
127945
+ if (judged.verification !== "verified") {
127946
+ await new Promise((r) => setTimeout(r, READBACK_WAIT_MS));
127947
+ read = await readAdSetForVerify(adsetUrl, config, opts.verbose);
127948
+ judged = evaluateAdSetEditReadback(body, read.adset, read.readFailed);
127949
+ }
127950
+ const verification = judged.verification;
127951
+ const nextQuery = `siluzan-tso meta-ad adset -a ${digits} --id ${opts.id} --json-out ./snap`;
127952
+ const unmatched = describeUnmatchedChecks(judged.checks);
127953
+ const detail = verification === "verified" ? `\u5E7F\u544A\u7EC4 ${opts.id} \u5DF2\u6838\u9A8C` : unmatched || `\u5E7F\u544A\u7EC4 ${opts.id} \u7684\u5199\u5165\u7ED3\u679C\u5C1A\u672A\u6838\u9A8C`;
127954
+ const headline = writeVerificationHeadline(verification, detail);
127955
+ if (verification !== "verified") {
127956
+ console.error(`
127957
+ ${headline}
127958
+ \u8BF7\u518D\u67E5\uFF1A${nextQuery}
127959
+ `);
127960
+ }
127961
+ const payload = {
127962
+ id: extractCreatedId(data) ?? opts.id,
127963
+ verification,
127964
+ nextQuery,
127965
+ targetingDiff: targetingChanges,
127966
+ droppedKeys,
127967
+ checks: judged.checks,
127968
+ ...data
127969
+ };
127970
+ if (await emitMetaJson(opts, "meta-ad-adset-edit", "meta-ad adset-edit", payload, digits, {
127971
+ verification
127972
+ })) {
127973
+ return;
127974
+ }
127113
127975
  console.log(`
127114
- \u2705 \u5E7F\u544A\u7EC4 ${opts.id} \u5DF2\u66F4\u65B0
127976
+ ${headline}${verification === "verified" ? "" : `
127977
+ \u8BF7\u518D\u67E5\uFF1A${nextQuery}`}
127115
127978
  `);
127116
127979
  }
127117
127980
  async function runMetaAdAdsetStatus(opts) {
@@ -127408,7 +128271,11 @@ function register22(program2) {
127408
128271
  );
127409
128272
  addCommon(
127410
128273
  addAccount(
127411
- meta.command("adset-create").description("\u5EFA\u7EBF\u7D22\u5E7F\u544A\u7EC4\uFF08destination=ON_AD\uFF1B\u9ED8\u8BA4 LEAD_GENERATION\uFF09").requiredOption("--campaign-id <id>", "\u7CFB\u5217 ID").requiredOption("--page-id <id>", "\u6295\u653E\u4E3B\u9875 ID").requiredOption("--name <name>", "\u7EC4\u540D\u79F0").requiredOption("--countries <list>", "ISO \u56FD\u5BB6\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u5982 US,GB\uFF09").option("--daily-budget <amount>", "\u65E5\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143\uFF08ABO\uFF09", parseFloat).option("--lifetime-budget <amount>", "\u603B\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--age-min <n>", "\u6700\u5C0F\u5E74\u9F84", parseInt).option("--age-max <n>", "\u6700\u5927\u5E74\u9F84", parseInt).option("--genders <list>", "1=\u7537,2=\u5973\uFF0C\u9017\u53F7\u5206\u9694").option("--optimization-goal <g>", "LEAD_GENERATION | QUALITY_LEAD").option("--bid-strategy <s>", "\u51FA\u4EF7\u7B56\u7565").option("--bid-amount <amount>", "\u51FA\u4EF7\u4E0A\u9650\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--start-time <iso>", "\u5F00\u59CB\u65F6\u95F4").option("--end-time <iso>", "\u7ED3\u675F\u65F6\u95F4").option("--status <status>", "ACTIVE | PAUSED").option(
128274
+ meta.command("adset-create").description("\u5EFA\u7EBF\u7D22\u5E7F\u544A\u7EC4\uFF08destination=ON_AD\uFF1B\u9ED8\u8BA4 LEAD_GENERATION\uFF09").requiredOption("--campaign-id <id>", "\u7CFB\u5217 ID").requiredOption("--page-id <id>", "\u6295\u653E\u4E3B\u9875 ID").requiredOption("--name <name>", "\u7EC4\u540D\u79F0").requiredOption("--countries <list>", "ISO \u56FD\u5BB6\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u5982 US,GB\uFF09").option("--daily-budget <amount>", "\u65E5\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143\uFF08ABO\uFF09", parseFloat).option("--lifetime-budget <amount>", "\u603B\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--age-min <n>", "\u6700\u5C0F\u5E74\u9F84", parseInt).option("--age-max <n>", "\u6700\u5927\u5E74\u9F84", parseInt).option("--genders <list>", "1=\u7537,2=\u5973\uFF0C\u9017\u53F7\u5206\u9694").option("--optimization-goal <g>", "LEAD_GENERATION | QUALITY_LEAD").option("--bid-strategy <s>", "\u51FA\u4EF7\u7B56\u7565").option("--bid-amount <amount>", "\u51FA\u4EF7\u4E0A\u9650\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option(
128275
+ "--bid-amount-on-required <amount>",
128276
+ "\u4EC5\u5F53\u5EFA\u7EC4\u8FD4\u56DE BID_AMOUNT_REQUIRED \u65F6\u4F7F\u7528\u7684\u51FA\u4EF7\u4E0A\u9650\uFF08\u4E3B\u5E01\u79CD\u5143\uFF09\u3002\u4E0D\u4F20\u5219\u505C\u4E0B\uFF0C\u4E0D\u4F1A\u7528\u9884\u7B97\u4EE3\u66FF",
128277
+ parseFloat
128278
+ ).option("--start-time <iso>", "\u5F00\u59CB\u65F6\u95F4").option("--end-time <iso>", "\u7ED3\u675F\u65F6\u95F4").option("--status <status>", "ACTIVE | PAUSED").option(
127412
128279
  "--advantage-audience <n>",
127413
128280
  "0=\u7CBE\u786E\u5B9A\u5411 1=Advantage Audience\uFF08\u9ED8\u8BA4 1\uFF09",
127414
128281
  (v) => Number.parseInt(v, 10)
@@ -127421,11 +128288,13 @@ function register22(program2) {
127421
128288
  );
127422
128289
  addCommon(
127423
128290
  addAccount(
127424
- meta.command("adset-edit").description("\u6539\u5E7F\u544A\u7EC4\uFF08targeting \u6574\u4EFD\u66FF\u6362\uFF1A\u56FD\u5BB6/\u5E74\u9F84/\u6027\u522B/advantage_audience\uFF09").requiredOption("--id <adSetId>", "\u5E7F\u544A\u7EC4 ID").option("--name <name>", "\u65B0\u540D\u79F0").option("--daily-budget <amount>", "\u65E5\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--lifetime-budget <amount>", "\u603B\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--countries <list>", "ISO \u56FD\u5BB6\uFF0C\u9017\u53F7\u5206\u9694").option("--age-min <n>", "\u6700\u5C0F\u5E74\u9F84", parseInt).option("--age-max <n>", "\u6700\u5927\u5E74\u9F84", parseInt).option("--genders <list>", "1=\u7537,2=\u5973").option("--optimization-goal <g>", "LEAD_GENERATION | QUALITY_LEAD").option("--bid-strategy <s>", "\u51FA\u4EF7\u7B56\u7565").option("--bid-amount <amount>", "\u51FA\u4EF7\u4E0A\u9650\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--start-time <iso>", "\u5F00\u59CB\u65F6\u95F4").option("--end-time <iso>", "\u7ED3\u675F\u65F6\u95F4").option("--status <status>", "\u72B6\u6001").option(
128291
+ meta.command("adset-edit").description(
128292
+ "\u6539\u5E7F\u544A\u7EC4\u3002\u56FD\u5BB6\u9ED8\u8BA4\u6574\u8868\u66FF\u6362\uFF1B\u8FFD\u52A0\u7528 --countries-mode append\u3002\u672A\u4F20 --advantage-audience \u65F6\u4FDD\u7559\u5F53\u524D\u503C\uFF0C\u4E0D\u5957\u521B\u5EFA\u9ED8\u8BA4"
128293
+ ).requiredOption("--id <adSetId>", "\u5E7F\u544A\u7EC4 ID").option("--name <name>", "\u65B0\u540D\u79F0").option("--daily-budget <amount>", "\u65E5\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--lifetime-budget <amount>", "\u603B\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--countries <list>", "ISO \u56FD\u5BB6\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u9ED8\u8BA4\u66FF\u6362\u6574\u4EFD\u56FD\u5BB6\u5217\u8868\uFF09").option("--countries-mode <mode>", "replace\uFF08\u9ED8\u8BA4\uFF09| append\uFF08\u5E76\u4E0A\u5F53\u524D\u56FD\u5BB6\uFF09").option("--age-min <n>", "\u6700\u5C0F\u5E74\u9F84", parseInt).option("--age-max <n>", "\u6700\u5927\u5E74\u9F84", parseInt).option("--genders <list>", "1=\u7537,2=\u5973").option("--optimization-goal <g>", "LEAD_GENERATION | QUALITY_LEAD").option("--bid-strategy <s>", "\u51FA\u4EF7\u7B56\u7565").option("--bid-amount <amount>", "\u51FA\u4EF7\u4E0A\u9650\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--start-time <iso>", "\u5F00\u59CB\u65F6\u95F4").option("--end-time <iso>", "\u7ED3\u675F\u65F6\u95F4").option("--status <status>", "\u72B6\u6001").option(
127425
128294
  "--advantage-audience <n>",
127426
- "0=\u7CBE\u786E\u5B9A\u5411 1=Advantage Audience",
128295
+ "0 \u6216 1\u3002\u4E0D\u4F20\u5219\u4FDD\u7559\u5F53\u524D\u503C\uFF0C\u4E0D\u4F1A\u9ED8\u8BA4\u6210 1",
127427
128296
  (v) => Number.parseInt(v, 10)
127428
- )
128297
+ ).option("--targeting-replace", "\u786E\u8BA4\u4E22\u6389\u5F53\u524D\u5B9A\u5411\u91CC\u672C\u547D\u4EE4\u4E0D\u80FD\u5199\u56DE\u7684\u5B57\u6BB5\u540E\u518D\u63D0\u4EA4", false)
127429
128298
  )
127430
128299
  ).action(
127431
128300
  async (opts) => {