siluzan-tso-cli 1.1.29-beta.3 → 1.1.29-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -51,7 +51,7 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
51
51
  siluzan-tso init --force # 强制覆盖已存在文件
52
52
  ```
53
53
 
54
- > **注意**:当前为测试版(1.1.29-beta.3),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.29-beta.4),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -112491,6 +112491,7 @@ async function runAdEdit(opts) {
112491
112491
 
112492
112492
  // src/commands/ad/extension.ts
112493
112493
  init_auth();
112494
+ import { readFileSync as readFileSync7 } from "fs";
112494
112495
  init_cli_json_snapshot();
112495
112496
  init_strip_legacy_google_fields();
112496
112497
 
@@ -112606,6 +112607,101 @@ function buildLeadFormExtensionBody(cfg, extensionId) {
112606
112607
  return body;
112607
112608
  }
112608
112609
 
112610
+ // src/commands/ad/extension-whatsapp.ts
112611
+ import { readFileSync as readFileSync6 } from "fs";
112612
+ var VALID_WHATSAPP_CTA_SELECTIONS = [
112613
+ "CONTACT_US",
112614
+ "LEARN_MORE",
112615
+ "GET_QUOTE",
112616
+ "BOOK_NOW",
112617
+ "SHOP_NOW",
112618
+ "SIGN_UP"
112619
+ ];
112620
+ function loadWhatsappExtensionConfig(configFile) {
112621
+ let raw;
112622
+ try {
112623
+ raw = JSON.parse(readFileSync6(configFile, "utf8"));
112624
+ } catch {
112625
+ console.error(`
112626
+ \u274C \u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6\u5931\u8D25\uFF08${configFile}\uFF09
112627
+ `);
112628
+ process.exit(1);
112629
+ }
112630
+ const cfg = stripMetaKeys(raw);
112631
+ const result = validateWhatsappExtensionConfig(cfg);
112632
+ if (!result.ok) {
112633
+ console.error("\n\u274C WhatsApp \u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A\n");
112634
+ for (const e of result.errors) console.error(` - ${e}`);
112635
+ console.error();
112636
+ process.exit(1);
112637
+ }
112638
+ return cfg;
112639
+ }
112640
+ function validateBusinessMessagePayload(bm, prefix = "businessMessage") {
112641
+ const errors = [];
112642
+ if (!bm || typeof bm !== "object") {
112643
+ errors.push(`${prefix} \u5BF9\u8C61\u4E0D\u80FD\u4E3A\u7A7A`);
112644
+ return errors;
112645
+ }
112646
+ if (!bm.starterMessage?.trim()) {
112647
+ errors.push(`${prefix}.starterMessage \u4E0D\u80FD\u4E3A\u7A7A`);
112648
+ }
112649
+ const provider = (bm.messageProvider?.trim() || "WHATSAPP").toUpperCase();
112650
+ if (provider !== "WHATSAPP") {
112651
+ errors.push(`${prefix}.messageProvider \u5F53\u524D\u4EC5\u652F\u6301 WHATSAPP`);
112652
+ }
112653
+ const cta = bm.callToActionSelection?.trim();
112654
+ if (cta && !VALID_WHATSAPP_CTA_SELECTIONS.includes(cta.toUpperCase())) {
112655
+ errors.push(
112656
+ `${prefix}.callToActionSelection \u65E0\u6548\uFF1A${cta}\uFF1B\u652F\u6301 ${VALID_WHATSAPP_CTA_SELECTIONS.join(", ")}`
112657
+ );
112658
+ }
112659
+ const wa = bm.whatsapp;
112660
+ if (!wa?.countryCode?.trim()) {
112661
+ errors.push(`${prefix}.whatsapp.countryCode \u4E0D\u80FD\u4E3A\u7A7A\uFF08\u5982 US\u3001CN\uFF09`);
112662
+ }
112663
+ if (!wa?.phoneNumber?.trim()) {
112664
+ errors.push(`${prefix}.whatsapp.phoneNumber \u4E0D\u80FD\u4E3A\u7A7A\uFF08WhatsApp Business \u6CE8\u518C\u53F7\u7801\uFF09`);
112665
+ }
112666
+ return errors;
112667
+ }
112668
+ function validateWhatsappExtensionConfig(cfg) {
112669
+ const errors = [];
112670
+ if (!cfg.account?.trim()) errors.push("account\uFF08\u5A92\u4F53\u5BA2\u6237 ID\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
112671
+ if (!cfg.campaignId?.trim()) errors.push("campaignId\uFF08PMax \u6D3B\u52A8 ID\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
112672
+ errors.push(...validateBusinessMessagePayload(cfg.businessMessage, "businessMessage"));
112673
+ return { ok: errors.length === 0, errors };
112674
+ }
112675
+ function buildBusinessMessageExtensionBody(cfg, extensionId) {
112676
+ const bm = cfg.businessMessage;
112677
+ const body = {
112678
+ activeuseridg: cfg.account,
112679
+ campaignId: cfg.campaignId,
112680
+ level: "Campaign",
112681
+ typeV2: "BUSINESS_MESSAGE",
112682
+ assetFieldType: "BUSINESS_MESSAGE",
112683
+ businessMessage: {
112684
+ messageProvider: (bm.messageProvider?.trim() || "WHATSAPP").toUpperCase(),
112685
+ starterMessage: bm.starterMessage.trim(),
112686
+ callToActionSelection: (bm.callToActionSelection?.trim() || "CONTACT_US").toUpperCase(),
112687
+ callToActionDescription: bm.callToActionDescription?.trim() || "Message us on WhatsApp",
112688
+ whatsapp: {
112689
+ countryCode: bm.whatsapp.countryCode.trim(),
112690
+ phoneNumber: bm.whatsapp.phoneNumber.trim()
112691
+ }
112692
+ }
112693
+ };
112694
+ if (extensionId) body["id"] = extensionId;
112695
+ return body;
112696
+ }
112697
+ function buildBusinessMessageBodyForCampaign(accountId, campaignId, bm) {
112698
+ return buildBusinessMessageExtensionBody({
112699
+ account: accountId,
112700
+ campaignId,
112701
+ businessMessage: bm
112702
+ });
112703
+ }
112704
+
112609
112705
  // src/commands/ad/extension.ts
112610
112706
  async function runAdExtensionList(opts) {
112611
112707
  const config = loadConfig(opts.token);
@@ -112671,6 +112767,12 @@ async function runAdExtensionList(opts) {
112671
112767
  } else if (type === "LEAD_FORM") {
112672
112768
  const lf = item["leadForm"];
112673
112769
  if (lf) detail = `\u300C${lf["headline"] ?? ""}\u300D business=${lf["businessName"] ?? ""}`;
112770
+ } else if (type === "BUSINESS_MESSAGE") {
112771
+ const bm = item["businessMessage"];
112772
+ if (bm) {
112773
+ const wa = bm["whatsapp"];
112774
+ detail = `\u300C${bm["starterMessage"] ?? ""}\u300D +${wa?.["countryCode"] ?? ""}${wa?.["phoneNumber"] ?? ""}`;
112775
+ }
112674
112776
  }
112675
112777
  const camp = item["campaignId"] ? ` campaign:${item["campaignId"]}` : "";
112676
112778
  console.log(` [${type}] id:${id} level:${level}${camp} ${detail}`);
@@ -112808,11 +112910,13 @@ async function runAdExtensionPmaxTypes(opts) {
112808
112910
  }
112809
112911
  const levels = obj["supportedLevels"]?.join(", ") ?? "";
112810
112912
  const lfLevels = obj["leadFormSupportedLevels"]?.join(", ") ?? "";
112913
+ const bmLevels = obj["businessMessageSupportedLevels"]?.join(", ") ?? "";
112811
112914
  console.log(`
112812
112915
  \u652F\u6301\u5C42\u7EA7\uFF1A${levels}`);
112813
112916
  console.log(` Lead Form \u5C42\u7EA7\uFF1A${lfLevels}`);
112917
+ console.log(` WhatsApp\uFF08BUSINESS_MESSAGE\uFF09\u5C42\u7EA7\uFF1A${bmLevels}`);
112814
112918
  console.log(" \u4E0D\u652F\u6301\uFF1AAd Group\uFF08PMax \u4F1A 400\uFF09");
112815
- console.log(" WhatsApp\uFF1A\u540E\u7AEF\u5C1A\u672A\u5F00\u653E\uFF08\u540E\u7EED\u9636\u6BB5\uFF09\n");
112919
+ console.log(" WhatsApp \u9700 Google API \u767D\u540D\u5355\uFF08\u672A\u5F00\u901A\u4F1A CUSTOMER_NOT_ON_ALLOWLIST_FOR_MESSAGE_ASSETS\uFF09\n");
112816
112920
  }
112817
112921
  async function runAdExtensionSnippetHeaders(opts) {
112818
112922
  const config = loadConfig(opts.token);
@@ -112905,15 +113009,98 @@ async function runAdExtensionLeadFormCreate(opts) {
112905
113009
  "\u63D0\u793A\uFF1A\u521B\u5EFA\u6210\u529F\u540E\u540E\u7AEF\u4F1A\u81EA\u52A8\u5C1D\u8BD5\u5F00\u542F SUBMIT_LEAD_FORM + GOOGLE_HOSTED \u8F6C\u5316\u76EE\u6807\uFF08biddable=true\uFF09\u3002\n"
112906
113010
  );
112907
113011
  }
112908
- async function runAdExtensionUpdate(opts) {
113012
+ async function runAdExtensionWhatsappCreate(opts) {
112909
113013
  const config = loadConfig(opts.token);
112910
113014
  const googleApiUrl = requireGoogleApi(config);
112911
- const cfg = loadLeadFormExtensionConfig(opts.configFile);
112912
- if (opts.account !== cfg.account) {
113015
+ if (!opts.configFile) {
113016
+ console.error(
113017
+ "\n\u274C WhatsApp \u987B\u4F7F\u7528 --config-file\uFF08\u6A21\u677F\u89C1 assets/pmax-whatsapp-template.json\uFF09\n"
113018
+ );
113019
+ process.exit(1);
113020
+ }
113021
+ const cfg = loadWhatsappExtensionConfig(opts.configFile);
113022
+ if (opts.account && opts.account !== cfg.account) {
112913
113023
  console.error("\n\u274C --account \u4E0E\u914D\u7F6E\u6587\u4EF6 account \u4E0D\u4E00\u81F4\n");
112914
113024
  process.exit(1);
112915
113025
  }
112916
- const body = buildLeadFormExtensionBody(cfg, opts.id);
113026
+ if (opts.campaignId && opts.campaignId !== cfg.campaignId) {
113027
+ console.error("\n\u274C --campaign-id \u4E0E\u914D\u7F6E\u6587\u4EF6 campaignId \u4E0D\u4E00\u81F4\n");
113028
+ process.exit(1);
113029
+ }
113030
+ const body = buildBusinessMessageExtensionBody(cfg);
113031
+ const url = `${googleApiUrl}/extensionmanagement/extension/${cfg.account}`;
113032
+ let response;
113033
+ try {
113034
+ response = await apiFetch2(
113035
+ url,
113036
+ config,
113037
+ { method: "POST", body: JSON.stringify(body) },
113038
+ opts.verbose
113039
+ );
113040
+ } catch (err) {
113041
+ console.error(`
113042
+ \u274C \u521B\u5EFA WhatsApp \u6269\u5C55\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
113043
+ `);
113044
+ process.exit(1);
113045
+ }
113046
+ if (await emitCliJsonOrSnapshot(opts, {
113047
+ section: `ad-extension-whatsapp-${cfg.account}`,
113048
+ commandLabel: "ad extension whatsapp",
113049
+ commandHint: `${cfg.account}:${cfg.campaignId}`,
113050
+ payload: response,
113051
+ idSuffix: cfg.campaignId
113052
+ })) {
113053
+ return;
113054
+ }
113055
+ const resObj = response ?? {};
113056
+ const extId = resObj["id"];
113057
+ console.log(
113058
+ `
113059
+ \u2705 PMax WhatsApp \u5DF2\u521B\u5EFA\uFF08campaignId: ${cfg.campaignId}${extId ? `\uFF0Cid: ${extId}` : ""}\uFF09
113060
+ `
113061
+ );
113062
+ console.log(
113063
+ "\u63D0\u793A\uFF1A\u8D26\u6237\u987B\u5DF2\u5F00\u901A Google Business Message API \u767D\u540D\u5355\uFF1B\u540C Campaign \u4EC5 1 \u4E2A ENABLED WhatsApp\u3002\n"
113064
+ );
113065
+ }
113066
+ function detectExtensionUpdateKind(raw) {
113067
+ if (raw["businessMessage"] != null) return "whatsapp";
113068
+ if (raw["leadForm"] != null) return "lead-form";
113069
+ console.error("\n\u274C \u914D\u7F6E\u6587\u4EF6\u987B\u542B leadForm \u6216 businessMessage \u5BF9\u8C61\n");
113070
+ process.exit(1);
113071
+ }
113072
+ async function runAdExtensionUpdate(opts) {
113073
+ const config = loadConfig(opts.token);
113074
+ const googleApiUrl = requireGoogleApi(config);
113075
+ let raw;
113076
+ try {
113077
+ raw = stripMetaKeys(JSON.parse(readFileSync7(opts.configFile, "utf8")));
113078
+ } catch {
113079
+ console.error(`
113080
+ \u274C \u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6\u5931\u8D25\uFF08${opts.configFile}\uFF09
113081
+ `);
113082
+ process.exit(1);
113083
+ }
113084
+ const kind = detectExtensionUpdateKind(raw);
113085
+ let body;
113086
+ let commandLabel;
113087
+ if (kind === "whatsapp") {
113088
+ const cfg = loadWhatsappExtensionConfig(opts.configFile);
113089
+ if (opts.account !== cfg.account) {
113090
+ console.error("\n\u274C --account \u4E0E\u914D\u7F6E\u6587\u4EF6 account \u4E0D\u4E00\u81F4\n");
113091
+ process.exit(1);
113092
+ }
113093
+ body = buildBusinessMessageExtensionBody(cfg, opts.id);
113094
+ commandLabel = "ad extension update (whatsapp)";
113095
+ } else {
113096
+ const cfg = loadLeadFormExtensionConfig(opts.configFile);
113097
+ if (opts.account !== cfg.account) {
113098
+ console.error("\n\u274C --account \u4E0E\u914D\u7F6E\u6587\u4EF6 account \u4E0D\u4E00\u81F4\n");
113099
+ process.exit(1);
113100
+ }
113101
+ body = buildLeadFormExtensionBody(cfg, opts.id);
113102
+ commandLabel = "ad extension update (lead-form)";
113103
+ }
112917
113104
  const url = `${googleApiUrl}/extensionmanagement/extension/${opts.account}/${opts.id}`;
112918
113105
  let response;
112919
113106
  try {
@@ -112931,16 +113118,24 @@ async function runAdExtensionUpdate(opts) {
112931
113118
  }
112932
113119
  if (await emitCliJsonOrSnapshot(opts, {
112933
113120
  section: `ad-extension-update-${opts.account}`,
112934
- commandLabel: "ad extension update",
113121
+ commandLabel,
112935
113122
  commandHint: `${opts.account}:${opts.id}`,
112936
113123
  payload: response,
112937
113124
  idSuffix: opts.id
112938
113125
  })) {
112939
113126
  return;
112940
113127
  }
112941
- console.log(`
113128
+ const resObj = response ?? {};
113129
+ const newId = resObj["id"];
113130
+ if (kind === "whatsapp" && newId && newId !== opts.id) {
113131
+ console.log(`
113132
+ \u2705 WhatsApp \u5DF2\u66F4\u65B0\uFF08\u65B0 asset id: ${newId}\uFF0C\u65E7 id ${opts.id} \u5DF2\u5931\u6548\uFF09
113133
+ `);
113134
+ } else {
113135
+ console.log(`
112942
113136
  \u2705 \u9644\u52A0\u4FE1\u606F ${opts.id} \u5DF2\u66F4\u65B0
112943
113137
  `);
113138
+ }
112944
113139
  }
112945
113140
 
112946
113141
  // src/commands/ad/search-terms.ts
@@ -114890,7 +115085,7 @@ async function runPmaxImageValidation(configFile, cfg) {
114890
115085
  // src/commands/ad/pmax-image-resolve.ts
114891
115086
  init_auth();
114892
115087
  import { basename as basename4, dirname as dirname8, isAbsolute as isAbsolute3, resolve as resolve7 } from "path";
114893
- import { readFileSync as readFileSync6 } from "fs";
115088
+ import { readFileSync as readFileSync8 } from "fs";
114894
115089
 
114895
115090
  // src/commands/ad/pmax-urls.ts
114896
115091
  function pmaxChannelTypesUrl(googleApiUrl) {
@@ -115047,7 +115242,7 @@ async function uploadPmaxImageFileBase64(config, googleApiUrl, accountId, absIma
115047
115242
  async function uploadPmaxImageFile(config, googleApiUrl, accountId, absImagePath, name2, verbose) {
115048
115243
  const fileName = basename4(absImagePath);
115049
115244
  const assetName = (name2 ?? fileName).trim() || fileName;
115050
- const fileBuffer = readFileSync6(absImagePath);
115245
+ const fileBuffer = readFileSync8(absImagePath);
115051
115246
  if (fileBuffer.length <= MAX_BASE64_UPLOAD_BYTES) {
115052
115247
  return uploadPmaxImageFileBase64(
115053
115248
  config,
@@ -115133,7 +115328,7 @@ function assertPmaxImageSlotsResolved(slots) {
115133
115328
  }
115134
115329
 
115135
115330
  // src/commands/ad/pmax-load.ts
115136
- import { readFileSync as readFileSync7 } from "fs";
115331
+ import { readFileSync as readFileSync9 } from "fs";
115137
115332
  function loadPmaxCreateConfig(configFile) {
115138
115333
  const cfg = tryLoadPmaxCreateConfig(configFile);
115139
115334
  if (!cfg) {
@@ -115204,7 +115399,7 @@ function detectDeprecatedPmaxVideoKeys(raw) {
115204
115399
  function tryLoadPmaxCreateConfig(configFile) {
115205
115400
  let raw;
115206
115401
  try {
115207
- raw = JSON.parse(readFileSync7(configFile, "utf8"));
115402
+ raw = JSON.parse(readFileSync9(configFile, "utf8"));
115208
115403
  } catch {
115209
115404
  return null;
115210
115405
  }
@@ -115212,7 +115407,7 @@ function tryLoadPmaxCreateConfig(configFile) {
115212
115407
  }
115213
115408
  function loadPmaxCreateConfigRaw(configFile) {
115214
115409
  try {
115215
- const raw = JSON.parse(readFileSync7(configFile, "utf8"));
115410
+ const raw = JSON.parse(readFileSync9(configFile, "utf8"));
115216
115411
  return stripMetaKeys(raw);
115217
115412
  } catch {
115218
115413
  return null;
@@ -115260,19 +115455,19 @@ function buildPmaxCreateUrl(googleApiUrl, accountId) {
115260
115455
  }
115261
115456
 
115262
115457
  // src/commands/ad/pmax-video-upload.ts
115263
- import { existsSync as existsSync3, readFileSync as readFileSync9 } from "fs";
115458
+ import { existsSync as existsSync3, readFileSync as readFileSync11 } from "fs";
115264
115459
  import { basename as basename5, dirname as dirname10, isAbsolute as isAbsolute5, resolve as resolve9 } from "path";
115265
115460
 
115266
115461
  // src/commands/ad/pmax-shared.ts
115267
115462
  init_auth();
115268
115463
  init_cli_json_snapshot();
115269
- import { readFileSync as readFileSync8 } from "fs";
115464
+ import { readFileSync as readFileSync10 } from "fs";
115270
115465
  import { dirname as dirname9, isAbsolute as isAbsolute4, resolve as resolve8 } from "path";
115271
115466
  var PMAX_MONEY_KEYS = /* @__PURE__ */ new Set(["budget", "targetCpa_BidingAmount"]);
115272
115467
  function loadPmaxJsonFile(configFile) {
115273
115468
  let raw;
115274
115469
  try {
115275
- raw = JSON.parse(readFileSync8(configFile, "utf8"));
115470
+ raw = JSON.parse(readFileSync10(configFile, "utf8"));
115276
115471
  } catch (e) {
115277
115472
  const msg = e instanceof Error ? e.message : String(e);
115278
115473
  console.error(`
@@ -115459,7 +115654,7 @@ function runPmaxVideoValidation(configFile, cfg) {
115459
115654
  return { errors, warnings };
115460
115655
  }
115461
115656
  try {
115462
- const buf = readFileSync9(abs);
115657
+ const buf = readFileSync11(abs);
115463
115658
  if (buf.length === 0) errors.push(`videoPath \u4E3A\u7A7A\u6587\u4EF6\uFF1A${abs}`);
115464
115659
  } catch (e) {
115465
115660
  const msg = e instanceof Error ? e.message : String(e);
@@ -115522,7 +115717,7 @@ async function uploadPmaxLocalVideo(opts) {
115522
115717
  const fileName = basename5(opts.absVideoPath);
115523
115718
  const title = (opts.title ?? fileName).trim() || fileName;
115524
115719
  const description = (opts.description ?? "Uploaded via siluzan-tso PMax video upload").trim() || "Uploaded via siluzan-tso";
115525
- const fileBuffer = readFileSync9(opts.absVideoPath);
115720
+ const fileBuffer = readFileSync11(opts.absVideoPath);
115526
115721
  const form = new FormData();
115527
115722
  form.append("media_account_id", opts.accountId.replace(/-/g, ""));
115528
115723
  form.append("title", title);
@@ -115578,7 +115773,7 @@ function validatePmaxVideoPathQuick(absPath) {
115578
115773
  return `\u89C6\u9891\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${absPath}`;
115579
115774
  }
115580
115775
  try {
115581
- if (readFileSync9(absPath).length === 0) return `\u89C6\u9891\u6587\u4EF6\u4E3A\u7A7A\uFF1A${absPath}`;
115776
+ if (readFileSync11(absPath).length === 0) return `\u89C6\u9891\u6587\u4EF6\u4E3A\u7A7A\uFF1A${absPath}`;
115582
115777
  } catch (e) {
115583
115778
  const msg = e instanceof Error ? e.message : String(e);
115584
115779
  return `\u65E0\u6CD5\u8BFB\u53D6\u89C6\u9891\u6587\u4EF6\uFF08${absPath}\uFF09\uFF1A${msg}`;
@@ -115677,9 +115872,17 @@ function validatePmaxCampaignExtensions(ext) {
115677
115872
  const lfErrors = validateLeadFormPayload(ext.leadForm, "campaignExtensions.leadForm");
115678
115873
  errors.push(...lfErrors);
115679
115874
  }
115680
- if (callouts.length === 0 && snippets.length === 0 && !ext.leadForm) {
115875
+ if (ext.businessMessage) {
115876
+ errors.push(
115877
+ ...validateBusinessMessagePayload(ext.businessMessage, "campaignExtensions.businessMessage")
115878
+ );
115879
+ warnings.push(
115880
+ "WhatsApp\uFF08BUSINESS_MESSAGE\uFF09\u9700 Google API \u767D\u540D\u5355\uFF1B\u672A\u5F00\u901A\u4F1A\u8FD4\u56DE CUSTOMER_NOT_ON_ALLOWLIST_FOR_MESSAGE_ASSETS"
115881
+ );
115882
+ }
115883
+ if (callouts.length === 0 && snippets.length === 0 && !ext.leadForm && !ext.businessMessage) {
115681
115884
  warnings.push(
115682
- "campaignExtensions \u5DF2\u58F0\u660E\u4F46 callouts / structuredSnippets / leadForm \u5747\u4E3A\u7A7A\uFF0C\u521B\u5EFA\u65F6\u5C06\u8DF3\u8FC7\u9644\u52A0\u8D44\u4EA7"
115885
+ "campaignExtensions \u5DF2\u58F0\u660E\u4F46 callouts / structuredSnippets / leadForm / businessMessage \u5747\u4E3A\u7A7A\uFF0C\u521B\u5EFA\u65F6\u5C06\u8DF3\u8FC7\u9644\u52A0\u8D44\u4EA7"
115683
115886
  );
115684
115887
  }
115685
115888
  return { errors, warnings };
@@ -115690,7 +115893,7 @@ function hasExtensionsToAttach(ext) {
115690
115893
  const hasSnippets = (ext.structuredSnippets ?? []).some(
115691
115894
  (s) => s?.header?.trim() && (s.values ?? []).some((v) => v?.trim())
115692
115895
  );
115693
- return hasCallouts || hasSnippets || Boolean(ext.leadForm);
115896
+ return hasCallouts || hasSnippets || Boolean(ext.leadForm) || Boolean(ext.businessMessage);
115694
115897
  }
115695
115898
  async function postExtension(config, googleApiUrl, accountId, body, verbose) {
115696
115899
  const url = `${googleApiUrl}/extensionmanagement/extension/${accountId}`;
@@ -115817,6 +116020,32 @@ async function attachPmaxCampaignExtensions(opts) {
115817
116020
  result.leadForm = { ok: false, error: posted.error, label: headline };
115818
116021
  }
115819
116022
  }
116023
+ if (ext.businessMessage) {
116024
+ const label = ext.businessMessage.starterMessage?.trim().slice(0, 40) || "WhatsApp";
116025
+ if (opts.logProgress) console.log(`
116026
+ \u6302\u8F7D WhatsApp \u79C1\u4FE1\uFF1A${label}`);
116027
+ const posted = await postExtension(
116028
+ opts.config,
116029
+ opts.googleApiUrl,
116030
+ opts.accountId,
116031
+ buildBusinessMessageBodyForCampaign(
116032
+ opts.accountId,
116033
+ opts.campaignId,
116034
+ ext.businessMessage
116035
+ ),
116036
+ opts.verbose
116037
+ );
116038
+ if (posted.ok) {
116039
+ result.whatsapp = {
116040
+ ok: true,
116041
+ id: String(posted.data["id"] ?? ""),
116042
+ label
116043
+ };
116044
+ } else {
116045
+ result.allOk = false;
116046
+ result.whatsapp = { ok: false, error: posted.error, label };
116047
+ }
116048
+ }
115820
116049
  return result;
115821
116050
  }
115822
116051
  function formatPmaxExtensionsAttachErrors(result) {
@@ -115830,6 +116059,9 @@ function formatPmaxExtensionsAttachErrors(result) {
115830
116059
  if (result.leadForm && !result.leadForm.ok) {
115831
116060
  msgs.push(`\u6F5C\u5728\u5BA2\u6237\u8868\u5355\u300C${result.leadForm.label}\u300D\u5931\u8D25\uFF1A${result.leadForm.error}`);
115832
116061
  }
116062
+ if (result.whatsapp && !result.whatsapp.ok) {
116063
+ msgs.push(`WhatsApp\u300C${result.whatsapp.label}\u300D\u5931\u8D25\uFF1A${result.whatsapp.error}`);
116064
+ }
115833
116065
  return msgs;
115834
116066
  }
115835
116067
 
@@ -115993,7 +116225,7 @@ async function runAdPmaxCreate(opts) {
115993
116225
  ` \u53EF\u624B\u52A8\u8865\u6302\uFF1Asiluzan-tso ad extension callout|snippet|lead-form -a ${accountId} --campaign-id ${campaignId} \u2026
115994
116226
  `
115995
116227
  );
115996
- } else if (extensionsResult?.allOk && extensionsResult.callouts.length + extensionsResult.structuredSnippets.length + (extensionsResult.leadForm ? 1 : 0) > 0) {
116228
+ } else if (extensionsResult?.allOk && extensionsResult.callouts.length + extensionsResult.structuredSnippets.length + (extensionsResult.leadForm ? 1 : 0) + (extensionsResult.whatsapp ? 1 : 0) > 0) {
115997
116229
  if (!opts.jsonOut) {
115998
116230
  console.log("\n\u2705 Campaign \u9644\u52A0\u8D44\u4EA7\u5DF2\u6302\u8F7D");
115999
116231
  }
@@ -116895,7 +117127,7 @@ async function runAdCampaignValidate(opts) {
116895
117127
  }
116896
117128
 
116897
117129
  // src/commands/ad/pmax-image-convert.ts
116898
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5, readFileSync as readFileSync10, existsSync as existsSync4 } from "fs";
117130
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5, readFileSync as readFileSync12, existsSync as existsSync4 } from "fs";
116899
117131
  import { resolve as resolve10, dirname as dirname11, basename as basename7, extname } from "path";
116900
117132
  var SPECS = [
116901
117133
  { kind: "marketing", width: 1200, height: 628, fit: "cover", suffix: "_marketing" },
@@ -116986,7 +117218,7 @@ async function runAdPmaxImageConvert(opts) {
116986
117218
  if (opts.updateConfig) {
116987
117219
  const configPath = resolve10(opts.updateConfig);
116988
117220
  try {
116989
- const raw = JSON.parse(readFileSync10(configPath, "utf8"));
117221
+ const raw = JSON.parse(readFileSync12(configPath, "utf8"));
116990
117222
  const imgPaths = raw["imagePaths"] ?? {};
116991
117223
  if (outputPaths.marketing) imgPaths["marketing"] = outputPaths.marketing;
116992
117224
  if (outputPaths.square) imgPaths["square"] = outputPaths.square;
@@ -117834,7 +118066,7 @@ function register20(program2) {
117834
118066
  );
117835
118067
  extensionCmd.command("list").description("\u67E5\u8BE2\u9644\u52A0\u4FE1\u606F\u5217\u8868").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").option(
117836
118068
  "--type <type>",
117837
- "\u6309\u7C7B\u578B\u7B5B\u9009\uFF1ASITELINK | CALL | CALLOUT | STRUCTURED_SNIPPET | LEAD_FORM"
118069
+ "\u6309\u7C7B\u578B\u7B5B\u9009\uFF1ASITELINK | CALL | CALLOUT | STRUCTURED_SNIPPET | LEAD_FORM | BUSINESS_MESSAGE"
117838
118070
  ).option("--campaign-id <id>", "\u6309\u5E7F\u544A\u7CFB\u5217 ID \u7B5B\u9009\uFF08PMax Campaign \u7EA7\u6269\u5C55\uFF09").option("-t, --token <token>", "Auth Token").option(
117839
118071
  "--json-out <path>",
117840
118072
  "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
@@ -117869,7 +118101,27 @@ function register20(program2) {
117869
118101
  });
117870
118102
  }
117871
118103
  );
117872
- extensionCmd.command("update").description("\u66F4\u65B0\u9644\u52A0\u4FE1\u606F\uFF08\u5F53\u524D\u652F\u6301 LEAD_FORM\uFF1BPUT \u5168\u91CF\u66FF\u6362 leadForm \u5BF9\u8C61\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <extensionId>", "\u9644\u52A0\u4FE1\u606F ID\uFF08\u5148 ad extension list \u67E5\u8BE2\uFF09").requiredOption("--config-file <path>", "Lead Form JSON \u914D\u7F6E").option("-t, --token <token>", "Auth Token").option(
118104
+ extensionCmd.command("whatsapp").description(
118105
+ "\u4E3A PMax \u6D3B\u52A8\u6DFB\u52A0 WhatsApp \u79C1\u4FE1\uFF08BUSINESS_MESSAGE\uFF1B\u987B --config-file\uFF1B\u9700 Google API \u767D\u540D\u5355\uFF09\n \u6A21\u677F\uFF1Aassets/pmax-whatsapp-template.json"
118106
+ ).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--config-file <path>", "WhatsApp JSON \u914D\u7F6E\uFF08\u89C1 pmax-whatsapp-template.json\uFF09").option("--campaign-id <id>", "\u8986\u76D6\u914D\u7F6E\u6587\u4EF6\u4E2D\u7684 campaignId").option("-t, --token <token>", "Auth Token").option(
118107
+ "--json-out <path>",
118108
+ "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json",
118109
+ void 0
118110
+ ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
118111
+ async (opts) => {
118112
+ await runAdExtensionWhatsappCreate({
118113
+ token: opts.token,
118114
+ account: opts.account,
118115
+ campaignId: opts.campaignId,
118116
+ configFile: opts.configFile,
118117
+ jsonOut: opts.jsonOut,
118118
+ verbose: opts.verbose
118119
+ });
118120
+ }
118121
+ );
118122
+ extensionCmd.command("update").description(
118123
+ "\u66F4\u65B0\u9644\u52A0\u4FE1\u606F\uFF08LEAD_FORM \u6216 WhatsApp\uFF1B\u914D\u7F6E\u6587\u4EF6\u542B leadForm / businessMessage\uFF1BWhatsApp PUT \u540E id \u4F1A\u53D8\uFF09"
118124
+ ).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <extensionId>", "\u9644\u52A0\u4FE1\u606F ID\uFF08\u5148 ad extension list \u67E5\u8BE2\uFF09").requiredOption("--config-file <path>", "Lead Form JSON \u914D\u7F6E").option("-t, --token <token>", "Auth Token").option(
117873
118125
  "--json-out <path>",
117874
118126
  "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json",
117875
118127
  void 0
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.29-beta.3",
4
- "publishedAt": 1781073977455
3
+ "version": "1.1.29-beta.4",
4
+ "publishedAt": 1781076395156
5
5
  }
@@ -61,6 +61,15 @@
61
61
  { "inputType": "EMAIL" },
62
62
  { "inputType": "PHONE_NUMBER" }
63
63
  ]
64
+ },
65
+ "businessMessage": {
66
+ "starterMessage": "Hi! How can we help you today?",
67
+ "callToActionSelection": "CONTACT_US",
68
+ "callToActionDescription": "Message us on WhatsApp",
69
+ "whatsapp": {
70
+ "countryCode": "US",
71
+ "phoneNumber": "2125550100"
72
+ }
64
73
  }
65
74
  }
66
75
  }
@@ -81,8 +81,9 @@ siluzan-tso ad campaigns -a <accountId> --json-out ./snap
81
81
  | `callouts` | string[] | 宣传信息(CALLOUT),每条 ≤25 字符,各创建 1 个扩展 |
82
82
  | `structuredSnippets` | object[] | `{ header, values }`;`values` 至少 3 项 |
83
83
  | `leadForm` | object | 潜在客户表单;字段同 `pmax-lead-form-template.json` 的 `leadForm` |
84
+ | `businessMessage` | object | WhatsApp 私信;字段同 `pmax-whatsapp-template.json` 的 `businessMessage` |
84
85
 
85
- 标头可选值:`ad extension snippet-headers`。WhatsApp 尚未支持。
86
+ 标头可选值:`ad extension snippet-headers`。WhatsApp 需 Google API 白名单(见 `pmax-whatsapp-template.md`)。
86
87
 
87
88
  若挂载失败,活动仍已创建;`--json-out` 的 `campaignExtensions` 段含各条 `ok` / `error`,可手动 `ad extension *` 补挂。
88
89
 
@@ -8,7 +8,7 @@
8
8
  "勿在 pmax-create 流程内创建;先 pmax-create 再 ad extension lead-form",
9
9
  "每个 PMax 活动通常仅允许 1 个 Lead Form;创建前用 ad extension list --type LEAD_FORM --campaign-id 检查",
10
10
  "账户须在 Google Ads UI 接受 Lead Form ToS",
11
- "WhatsApp 私信扩展后端尚未开放"
11
+ "WhatsApp 见 pmax-whatsapp-template.json 或 campaignExtensions.businessMessage"
12
12
  ]
13
13
  },
14
14
  "account": "REPLACE_mediaCustomerId",
@@ -54,7 +54,7 @@ siluzan-tso ad extension update -a <accountId> --id <extensionId> --config-file
54
54
  | 潜在客户表单 | `LEAD_FORM` | `ad extension lead-form` |
55
55
  | 附加链接 | `SITELINK` | `ad extension sitelink` |
56
56
  | 附加电话 | `CALL` | `ad extension call` |
57
- | 私信 WhatsApp | | **后端尚未开放** |
57
+ | 私信 WhatsApp | `BUSINESS_MESSAGE` | `ad extension whatsapp` 或 `campaignExtensions.businessMessage` |
58
58
 
59
59
  **层级**:PMax 仅 `Account` / `Campaign`;`Ad Group` 会 **400**。
60
60
 
@@ -0,0 +1,26 @@
1
+ {
2
+ "_meta": {
3
+ "schema": "pmax-whatsapp/v1",
4
+ "doc": "pmax-whatsapp-template.md",
5
+ "note": "以 _ 开头的键仅作说明,CLI 提交前会剥离",
6
+ "agentPitfalls": [
7
+ "BUSINESS_MESSAGE 仅支持 Campaign / Account 级;PMax 用 Campaign + campaignId",
8
+ "每 Campaign 仅 1 个 ENABLED WhatsApp;重复 POST 会 400,须 DELETE 或 PUT 更新",
9
+ "PUT 成功后 asset id 会变化,后续删除/更新须用响应中的新 id",
10
+ "账户须已开通 Google Business Message API 白名单,否则 CUSTOMER_NOT_ON_ALLOWLIST_FOR_MESSAGE_ASSETS",
11
+ "勿与 pmax-create 同时挂 businessMessage 若活动上已有 WhatsApp"
12
+ ]
13
+ },
14
+ "account": "REPLACE_mediaCustomerId",
15
+ "campaignId": "REPLACE_pmaxCampaignId",
16
+ "businessMessage": {
17
+ "messageProvider": "WHATSAPP",
18
+ "starterMessage": "Hi! How can we help you today?",
19
+ "callToActionSelection": "CONTACT_US",
20
+ "callToActionDescription": "Message us on WhatsApp",
21
+ "whatsapp": {
22
+ "countryCode": "US",
23
+ "phoneNumber": "2125550100"
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,45 @@
1
+ # `ad extension whatsapp` JSON 配置说明
2
+
3
+ 为**已创建的 PMax 活动**挂载 WhatsApp 私信(`BUSINESS_MESSAGE`)。也可写入 `pmax-create` JSON 的 `campaignExtensions.businessMessage` 由 CLI 自动编排。
4
+
5
+ 模板:`pmax-whatsapp-template.json`。
6
+
7
+ ---
8
+
9
+ ## 命令
10
+
11
+ ```bash
12
+ # 单独挂载
13
+ siluzan-tso ad extension whatsapp -a <accountId> --config-file ./whatsapp.json --json-out ./snap
14
+
15
+ # 更新(PUT 后 id 会变,用响应新 id)
16
+ siluzan-tso ad extension update -a <accountId> --id <extensionId> --config-file ./whatsapp.json
17
+
18
+ # 或在 pmax-create 一并挂载
19
+ # campaignExtensions.businessMessage 见 pmax-create-template.md
20
+ ```
21
+
22
+ ---
23
+
24
+ ## 字段
25
+
26
+ | 字段 | 必填 | 说明 |
27
+ |------|:----:|------|
28
+ | `account` | ✅ | Google 媒体客户 ID |
29
+ | `campaignId` | ✅ | PMax 活动 ID |
30
+ | `businessMessage.starterMessage` | ✅ | 欢迎语 |
31
+ | `businessMessage.callToActionDescription` | | 默认 `Message us on WhatsApp` |
32
+ | `businessMessage.callToActionSelection` | | 默认 `CONTACT_US`;可选 `LEARN_MORE`、`GET_QUOTE` 等 |
33
+ | `businessMessage.messageProvider` | | 默认 `WHATSAPP` |
34
+ | `businessMessage.whatsapp.countryCode` | ✅ | 两位国家码(如 `US`) |
35
+ | `businessMessage.whatsapp.phoneNumber` | ✅ | WhatsApp Business 注册号码 |
36
+
37
+ ---
38
+
39
+ ## 前置条件
40
+
41
+ 1. **Google API 白名单**(阻塞项):未开通返回 `CUSTOMER_NOT_ON_ALLOWLIST_FOR_MESSAGE_ASSETS`;须 MCC 运营联系 Google AM 申请。
42
+ 2. 号码已在 **WhatsApp Business** 注册。
43
+ 3. 同 Campaign **仅 1 个** ENABLED WhatsApp;已有则 POST 400,须 DELETE 或 PUT。
44
+
45
+ 详见 Sammamish `pmax-frontend-api.md` §3.2.4。
@@ -68,8 +68,9 @@
68
68
  | 命令 | 选项 | 合法值 |
69
69
  | ---- | ---- | ------ |
70
70
  | ad extension sitelink, callout, snippet, … | --level | Account \| Campaign \| AdGroup |
71
- | ad extension list | --type | SITELINK \| CALL \| CALLOUT \| STRUCTURED_SNIPPET \| LEAD_FORM |
71
+ | ad extension list | --type | SITELINK \| CALL \| CALLOUT \| STRUCTURED_SNIPPET \| LEAD_FORM \| BUSINESS_MESSAGE |
72
72
  | ad extension lead-form / update | --config-file | JSON(见 `assets/pmax-lead-form-template.json`) |
73
+ | ad extension whatsapp / update | --config-file | JSON(见 `assets/pmax-whatsapp-template.json`) |
73
74
 
74
75
 
75
76
  ### 搜索系列 `BiddingStrategyTypeV2`
@@ -64,7 +64,7 @@
64
64
  | 改资产 | `ad pmax-assets-update --config-file …` |
65
65
  | YouTube 追加 | `ad pmax-youtube-link`(单条 `--youtube` / `--video-path`);批量见 `ad pmax-assets-update` |
66
66
  | 信号 | `ad pmax-signals-get` / `ad pmax-signals-set`;受众下拉 `ad pmax-audiences` |
67
- | 附加资产 | `ad extension pmax-types`;`callout` / `snippet` / `lead-form`(见 `pmax-api.md` § 附加资产) |
67
+ | 附加资产 | `ad extension pmax-types`;`callout` / `snippet` / `lead-form` / `whatsapp`(见 `pmax-api.md` § 附加资产) |
68
68
  | 图片库 | `ad pmax-image-upload` |
69
69
  | 报表 | `ad pmax-report-asset-groups` / `ad pmax-report-geo` |
70
70
  | 删活动 | `ad campaign-delete -a <id> --id <campaignId>`(与 Search 共用;勿用 `ad campaign-edit`) |
@@ -513,7 +513,7 @@ siluzan-tso ad keyword-negative-edit \
513
513
 
514
514
  ## ad extension — 附加信息管理
515
515
 
516
- Callout / Snippet / Sitelink / Call 等类型修改可**先删后建**;**Lead Form** 支持 `ad extension update`(PUT 全量替换 `leadForm`)。所有 `extension <type>` 子命令均支持 `--json-out`,输出网关返回的扩展对象(含 `id`),批量脚本可据此回填。
516
+ Callout / Snippet / Sitelink / Call 等类型修改可**先删后建**;**Lead Form / WhatsApp** 支持 `ad extension update`(PUT;WhatsApp 成功后 **id 会变**)。所有 `extension <type>` 子命令均支持 `--json-out`,输出网关返回的扩展对象(含 `id`),批量脚本可据此回填。
517
517
 
518
518
  ```bash
519
519
  # PMax 支持的类型与层级(含 LEAD_FORM)
@@ -524,7 +524,7 @@ siluzan-tso ad extension snippet-headers [--json-out ./snap]
524
524
 
525
525
  # 查询
526
526
  siluzan-tso ad extension list -a <accountId> \
527
- [--type SITELINK|CALL|CALLOUT|STRUCTURED_SNIPPET|LEAD_FORM] \
527
+ [--type SITELINK|CALL|CALLOUT|STRUCTURED_SNIPPET|LEAD_FORM|BUSINESS_MESSAGE] \
528
528
  [--campaign-id <campaignId>] [--json-out ./snap]
529
529
 
530
530
  # 附加链接
@@ -544,14 +544,17 @@ siluzan-tso ad extension snippet -a <accountId> --header "Brands" --values "A,B,
544
544
  # PMax 潜在客户表单(仅 Campaign 级;模板 assets/pmax-lead-form-template.json)
545
545
  siluzan-tso ad extension lead-form -a <accountId> --config-file ./lead-form.json [--json-out ./snap]
546
546
 
547
- # 更新 Lead Form
547
+ # PMax WhatsApp 私信(BUSINESS_MESSAGE;需 Google API 白名单;模板 assets/pmax-whatsapp-template.json)
548
+ siluzan-tso ad extension whatsapp -a <accountId> --config-file ./whatsapp.json [--json-out ./snap]
549
+
550
+ # 更新 Lead Form 或 WhatsApp(配置文件含 leadForm / businessMessage;WhatsApp PUT 后 id 会变)
548
551
  siluzan-tso ad extension update -a <accountId> --id <extensionId> --config-file ./lead-form.json
549
552
 
550
553
  # 删除
551
554
  siluzan-tso ad extension delete -a <accountId> --id <extensionId>
552
555
  ```
553
556
 
554
- **PMax 约束**:仅 `Account` / `Campaign` 层级;`Ad Group` 会 400。`LEAD_FORM` 仅挂 Campaign。WhatsApp 私信扩展后端尚未开放。
557
+ **PMax 约束**:仅 `Account` / `Campaign` 层级;`Ad Group` 会 400。`LEAD_FORM` Campaign。WhatsApp 每 Campaign 仅 1 个 ENABLED,须 Google API 白名单。
555
558
 
556
559
  `--header` 常用值:`Brands`/`Services`/`Amenities`/`Types`/`Styles`/`Courses`/`Models` 等(完整列表:`ad extension snippet-headers`)。
557
560
 
@@ -30,7 +30,7 @@
30
30
  | `ad pmax-report-asset-groups` / `ad pmax-report-geo` | 报表 |
31
31
  | `ad campaigns` | 列表(筛 `PERFORMANCE_MAX`) |
32
32
  | `ad extension pmax-types` | PMax 支持的附加资产类型与层级 |
33
- | `ad extension callout` / `snippet` / `lead-form` | 宣传信息 / 结构化摘要 / 潜在客户表单 |
33
+ | `ad extension callout` / `snippet` / `lead-form` / `whatsapp` | 宣传信息 / 结构化摘要 / 潜在客户表单 / WhatsApp |
34
34
  | `ad extension list` | 查询已挂载扩展(`--type` / `--campaign-id`) |
35
35
 
36
36
  模板:`assets/pmax-create-template.json`、`pmax-asset-group-template.json`、`pmax-assets-update-template.json`、`pmax-signals-template.json`、`pmax-patch-campaign-template.json`、`pmax-lead-form-template.json`。
@@ -46,7 +46,8 @@
46
46
  "campaignExtensions": {
47
47
  "callouts": ["Free shipping"],
48
48
  "structuredSnippets": [{ "header": "Services", "values": ["A", "B", "C"] }],
49
- "leadForm": { "businessName": "...", "headline": "...", "description": "...", "privacyPolicyUrl": "...", "finalUrl": "...", "fields": [{ "inputType": "EMAIL" }] }
49
+ "leadForm": { "businessName": "...", "headline": "...", "description": "...", "privacyPolicyUrl": "...", "finalUrl": "...", "fields": [{ "inputType": "EMAIL" }] },
50
+ "businessMessage": { "starterMessage": "Hi!", "whatsapp": { "countryCode": "US", "phoneNumber": "2125550100" } }
50
51
  }
51
52
  }
52
53
  ```
@@ -61,7 +62,7 @@
61
62
  | 结构化摘要 | `STRUCTURED_SNIPPET` | `ad extension snippet -a <id> --header Brands --values "A,B,C" --level Campaign --campaign-id <cid>` |
62
63
  | 潜在客户表单 | `LEAD_FORM` | `ad extension lead-form -a <id> --config-file ./lead-form.json` |
63
64
  | 附加链接 / 电话 | `SITELINK` / `CALL` | `ad extension sitelink` / `call`(同上 `--level Campaign`) |
64
- | 私信 WhatsApp | | **后端尚未开放**(后续阶段) |
65
+ | 私信 WhatsApp | `BUSINESS_MESSAGE` | `ad extension whatsapp` 或 `campaignExtensions.businessMessage` |
65
66
 
66
67
  ```bash
67
68
  # 查 PMax 支持类型
@@ -73,15 +74,22 @@ siluzan-tso ad extension snippet-headers --json-out ./snap
73
74
  # Lead Form(模板见 assets/pmax-lead-form-template.md)
74
75
  siluzan-tso ad extension lead-form -a <accountId> --config-file ./lead-form.json --json-out ./snap
75
76
 
77
+ # WhatsApp(模板见 assets/pmax-whatsapp-template.md;需 Google API 白名单)
78
+ siluzan-tso ad extension whatsapp -a <accountId> --config-file ./whatsapp.json --json-out ./snap
79
+
76
80
  # 复核 / 更新 / 删除
77
81
  siluzan-tso ad extension list -a <accountId> --type LEAD_FORM --campaign-id <cid> --json-out ./snap
82
+ siluzan-tso ad extension list -a <accountId> --type BUSINESS_MESSAGE --campaign-id <cid> --json-out ./snap
78
83
  siluzan-tso ad extension update -a <accountId> --id <extId> --config-file ./lead-form.json
84
+ siluzan-tso ad extension update -a <accountId> --id <extId> --config-file ./whatsapp.json # id 会变
79
85
  siluzan-tso ad extension delete -a <accountId> --id <extId>
80
86
  ```
81
87
 
82
88
  **约束**:
83
89
  - PMax **不支持** `level: Ad Group`(会 400)
84
90
  - `LEAD_FORM` **仅** `Campaign` 级;每活动通常 1 个
91
+ - `BUSINESS_MESSAGE`(WhatsApp)每 Campaign **仅 1 个** ENABLED;须 **Google API 白名单**
92
+ - WhatsApp **PUT 后 asset id 会变**;后续操作须用响应中的新 `id`
85
93
  - 创建 Lead Form 后后端自动尝试开启 `SUBMIT_LEAD_FORM` + `GOOGLE_HOSTED` 转化目标
86
94
 
87
95
  **网关**:`GET /extensionmanagement/pmaxSupportedTypeList`;`POST|PUT|DELETE /extensionmanagement/extension/{accountId}[/{id}]`(Sammamish `ExtensionManagementController.cs`)。
@@ -9,7 +9,7 @@ $ErrorActionPreference = 'Stop'
9
9
  # -- Package info (injected at build time) ------------------------------------
10
10
  $PKG_NAME = 'siluzan-tso-cli'
11
11
  # PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
12
- $PKG_VERSION = '1.1.29-beta.3'
12
+ $PKG_VERSION = '1.1.29-beta.4'
13
13
  $CLI_BIN = 'siluzan-tso'
14
14
  $SKILL_LABEL = 'Siluzan TSO'
15
15
  $INSTALL_CMD = 'npm install -g siluzan-tso-cli@beta'
@@ -9,7 +9,7 @@ set -euo pipefail
9
9
  # -- Package info (injected at build time) ------------------------------------
10
10
  readonly PKG_NAME="siluzan-tso-cli"
11
11
  # PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
12
- readonly PKG_VERSION="1.1.29-beta.3"
12
+ readonly PKG_VERSION="1.1.29-beta.4"
13
13
  readonly CLI_BIN="siluzan-tso"
14
14
  readonly SKILL_LABEL="Siluzan TSO"
15
15
  readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.29-beta.3",
3
+ "version": "1.1.29-beta.4",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",