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

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.21-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.21-beta.3),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -12,8 +12,8 @@ var __commonJS = (cb, mod) => function __require() {
12
12
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
13
13
  };
14
14
  var __export = (target, all) => {
15
- for (var name in all)
16
- __defProp(target, name, { get: all[name], enumerable: true });
15
+ for (var name2 in all)
16
+ __defProp(target, name2, { get: all[name2], enumerable: true });
17
17
  };
18
18
  var __copyProps = (to, from, except, desc) => {
19
19
  if (from && typeof from === "object" || typeof from === "function") {
@@ -103,11 +103,11 @@ var require_re = __commonJS({
103
103
  }
104
104
  return value;
105
105
  };
106
- var createToken = (name, value, isGlobal) => {
106
+ var createToken = (name2, value, isGlobal) => {
107
107
  const safe = makeSafeRegex(value);
108
108
  const index = R++;
109
- debug(name, index, value);
110
- t[name] = index;
109
+ debug(name2, index, value);
110
+ t[name2] = index;
111
111
  src[index] = value;
112
112
  safeSrc[index] = safe;
113
113
  re[index] = new RegExp(value, isGlobal ? "g" : void 0);
@@ -2776,8 +2776,8 @@ function addCalendarDaysToYmd(ymd, deltaDays) {
2776
2776
  const anchor = new Date(Date.UTC(y, mo - 1, d, 4, 0, 0));
2777
2777
  return formatBeijingYmd(new Date(anchor.getTime() + deltaDays * 864e5));
2778
2778
  }
2779
- function parseAuditLogFilename(name) {
2780
- const m = AUDIT_LOG_FILENAME_RE.exec(name);
2779
+ function parseAuditLogFilename(name2) {
2780
+ const m = AUDIT_LOG_FILENAME_RE.exec(name2);
2781
2781
  if (!m) return null;
2782
2782
  return { day: m[1], segment: m[2] ? Number.parseInt(m[2], 10) : 0 };
2783
2783
  }
@@ -3809,8 +3809,8 @@ async function readCliManifestIfExists(absDir, idSuffix) {
3809
3809
  if (idSuffix && candidates[0] !== CLI_SNAPSHOT_MANIFEST_FILE) {
3810
3810
  candidates.push(CLI_SNAPSHOT_MANIFEST_FILE);
3811
3811
  }
3812
- for (const name of candidates) {
3813
- const manifestPath = path7.join(absDir, name);
3812
+ for (const name2 of candidates) {
3813
+ const manifestPath = path7.join(absDir, name2);
3814
3814
  try {
3815
3815
  const raw = await fs4.readFile(manifestPath, "utf8");
3816
3816
  const parsed = JSON.parse(raw);
@@ -4637,8 +4637,8 @@ async function readManifestIfExists(absDir, accountId) {
4637
4637
  if (suffixed !== LEGACY_MANIFEST_FILE) {
4638
4638
  candidates.push(LEGACY_MANIFEST_FILE);
4639
4639
  }
4640
- for (const name of candidates) {
4641
- const manifestPath = path11.join(absDir, name);
4640
+ for (const name2 of candidates) {
4641
+ const manifestPath = path11.join(absDir, name2);
4642
4642
  try {
4643
4643
  const raw = await fs7.readFile(manifestPath, "utf8");
4644
4644
  const parsed = JSON.parse(raw);
@@ -100587,8 +100587,8 @@ function rowsFromAccountDailyReportsEnvelope(raw, mediaCustomerId) {
100587
100587
  const list = block?.data;
100588
100588
  return Array.isArray(list) ? list : [];
100589
100589
  }
100590
- async function fetchGoogleAnalysisSectionJson(config, fullPath, verbose, name) {
100591
- switch (name) {
100590
+ async function fetchGoogleAnalysisSectionJson(config, fullPath, verbose, name2) {
100591
+ switch (name2) {
100592
100592
  case "overview":
100593
100593
  return fetchJson(config, fullPath, verbose);
100594
100594
  case "keywords":
@@ -100632,7 +100632,7 @@ async function fetchGoogleAnalysisSectionJson(config, fullPath, verbose, name) {
100632
100632
  case "campaign-types":
100633
100633
  return fetchJson(config, fullPath, verbose);
100634
100634
  default:
100635
- return assertNever(name, "google-analysis");
100635
+ return assertNever(name2, "google-analysis");
100636
100636
  }
100637
100637
  }
100638
100638
  function endpointHintFrom(def) {
@@ -105684,8 +105684,8 @@ async function readReportManifestIfExists(absDir, accountId) {
105684
105684
  if (accountId && candidates[0] !== REPORT_MANIFEST_FILE) {
105685
105685
  candidates.push(REPORT_MANIFEST_FILE);
105686
105686
  }
105687
- for (const name of candidates) {
105688
- const manifestPath = path12.join(absDir, name);
105687
+ for (const name2 of candidates) {
105688
+ const manifestPath = path12.join(absDir, name2);
105689
105689
  try {
105690
105690
  const raw = await fs8.readFile(manifestPath, "utf8");
105691
105691
  const parsed = JSON.parse(raw);
@@ -106265,11 +106265,11 @@ async function runReportPushHistory(opts) {
106265
106265
  const setting = d?.["setting"] ?? {};
106266
106266
  const mediaEx = d?.["mediaAccountEx"] ?? {};
106267
106267
  const reportEx = d?.["accountReportEx"] ?? {};
106268
- const name = String(setting["name"] ?? "");
106268
+ const name2 = String(setting["name"] ?? "");
106269
106269
  const acct = mediaEx["mediaCustomerId"] != null && mediaEx["mediaCustomerName"] != null ? `${mediaEx["mediaCustomerId"]}-${mediaEx["mediaCustomerName"]}` : "";
106270
106270
  const cycle = reportEx["reportStartDate"] != null ? String(reportEx["reportStartDate"]).slice(0, 10) : "";
106271
106271
  const rid = String(row["entityId"] ?? row["id"] ?? "");
106272
- return { name, account: acct, cycle, id: rid };
106272
+ return { name: name2, account: acct, cycle, id: rid };
106273
106273
  });
106274
106274
  printCliTable(rows, [
106275
106275
  { key: "name", header: "\u63A8\u9001\u540D\u79F0" },
@@ -106698,8 +106698,8 @@ function registerReportTiktokCommands(reportCmd) {
106698
106698
  ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
106699
106699
  await runReportTikTokOverview(opts);
106700
106700
  });
106701
- const bindTake = (name, segmentLabel, runner) => {
106702
- const cmd = reportCmd.command(name).description(`${desc} \u2014 ${segmentLabel}`);
106701
+ const bindTake = (name2, segmentLabel, runner) => {
106702
+ const cmd = reportCmd.command(name2).description(`${desc} \u2014 ${segmentLabel}`);
106703
106703
  addDateAndFetchOptions(cmd);
106704
106704
  cmd.action(async (opts) => {
106705
106705
  await runner(opts);
@@ -107184,8 +107184,8 @@ function addBingDateOptions(c) {
107184
107184
  }
107185
107185
  function registerReportBingCommands(reportCmd) {
107186
107186
  const desc = "Bing\uFF08BingV2\uFF09\u8D26\u6237\u5206\u6790\uFF08TSO reporting/media-account/BingV2/...\uFF1B\u65E5\u671F\u4E0D\u53EF\u542B\u4ECA\u5929/\u6628\u5929\uFF09";
107187
- const bindSimple = (name, label, runner) => {
107188
- const cmd = reportCmd.command(name).description(`${desc} \u2014 ${label}`);
107187
+ const bindSimple = (name2, label, runner) => {
107188
+ const cmd = reportCmd.command(name2).description(`${desc} \u2014 ${label}`);
107189
107189
  addBingDateOptions(cmd);
107190
107190
  cmd.action(async (opts) => {
107191
107191
  await runner(opts);
@@ -107205,8 +107205,8 @@ function registerReportBingCommands(reportCmd) {
107205
107205
  ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107206
107206
  await runReportBingAudienceMerged(opts);
107207
107207
  });
107208
- const bindLimit = (name, label, runner) => {
107209
- const cmd = reportCmd.command(name).description(`${desc} \u2014 ${label}`);
107208
+ const bindLimit = (name2, label, runner) => {
107209
+ const cmd = reportCmd.command(name2).description(`${desc} \u2014 ${label}`);
107210
107210
  addBingDateOptions(cmd);
107211
107211
  cmd.option(
107212
107212
  "--limit <n>",
@@ -109307,10 +109307,10 @@ TikTok \u7EBF\u7D22\u8868\u5355\uFF08\u8D26\u6237 ${opts.account}\uFF0C\u7B2C 1
109307
109307
  for (const lead of leads) {
109308
109308
  const sys = lead.system_fields ?? {};
109309
109309
  const custom = lead.custom_fields ?? {};
109310
- const name = custom["name"] ?? custom["full_name"] ?? "\u2014";
109310
+ const name2 = custom["name"] ?? custom["full_name"] ?? "\u2014";
109311
109311
  const email = custom["email"] ?? "\u2014";
109312
109312
  const phone = custom["phone number"] ?? custom["phone"] ?? "\u2014";
109313
- console.log(` [${sys.region ?? "\u2014"}] ${name} ${email} ${phone}`);
109313
+ console.log(` [${sys.region ?? "\u2014"}] ${name2} ${email} ${phone}`);
109314
109314
  console.log(
109315
109315
  ` \u8868\u5355: ${sys.form_name ?? "\u2014"} \u5E7F\u544A: ${sys.ad_name ?? "\u2014"} \u65F6\u95F4: ${sys.created_time ?? "\u2014"}`
109316
109316
  );
@@ -109371,11 +109371,11 @@ Meta \u7EBF\u7D22\u8868\u5355\uFF08\u8D26\u6237 ${opts.account}\uFF0C\u7B2C 1 \u
109371
109371
  if (f.name) acc[f.name] = (f.values ?? []).join(", ");
109372
109372
  return acc;
109373
109373
  }, {});
109374
- const name = fields["full_name"] ?? fields["name"] ?? "\u2014";
109374
+ const name2 = fields["full_name"] ?? fields["name"] ?? "\u2014";
109375
109375
  const email = fields["email"] ?? "\u2014";
109376
109376
  const phone = fields["phone_number"] ?? fields["phone"] ?? "\u2014";
109377
109377
  const formName = lead.form?.name ?? "\u2014";
109378
- console.log(` ${name} ${email} ${phone}`);
109378
+ console.log(` ${name2} ${email} ${phone}`);
109379
109379
  console.log(` \u8868\u5355: ${formName} \u65F6\u95F4: ${lead.created_Time ?? "\u2014"}`);
109380
109380
  console.log();
109381
109381
  }
@@ -110842,13 +110842,13 @@ function validateCampaignCreateConfigCore(cfg) {
110842
110842
  "campaign.TargetContentNetwork \u5FC5\u987B\u4E3A false\uFF08\u641C\u7D22\u4E13\u5C5E\u65B9\u6848\uFF1A\u7981\u6B62\u5C55\u793A\u5E7F\u544A\u7F51\u7EDC\uFF09"
110843
110843
  );
110844
110844
  }
110845
- const name = campaign["Name"];
110846
- if (typeof name !== "string" || !name.trim()) {
110845
+ const name2 = campaign["Name"];
110846
+ if (typeof name2 !== "string" || !name2.trim()) {
110847
110847
  pushErr2(errors, "campaign.Name \u4E0D\u80FD\u4E3A\u7A7A");
110848
- } else if (cfg.name?.trim() && cfg.name.trim() !== name.trim()) {
110848
+ } else if (cfg.name?.trim() && cfg.name.trim() !== name2.trim()) {
110849
110849
  pushWarn2(
110850
110850
  warnings,
110851
- `\u5916\u5C42 name\uFF08${cfg.name}\uFF09\u4E0E campaign.Name\uFF08${name}\uFF09\u4E0D\u4E00\u81F4\uFF1B\u63D0\u4EA4\u65F6 campaignName \u4F18\u5148\u53D6\u5916\u5C42 name`
110851
+ `\u5916\u5C42 name\uFF08${cfg.name}\uFF09\u4E0E campaign.Name\uFF08${name2}\uFF09\u4E0D\u4E00\u81F4\uFF1B\u63D0\u4EA4\u65F6 campaignName \u4F18\u5148\u53D6\u5916\u5C42 name`
110852
110852
  );
110853
110853
  }
110854
110854
  const budget = campaign["Budget"];
@@ -111375,8 +111375,8 @@ async function runAdCampaignCreate(opts) {
111375
111375
  let adCount = 0;
111376
111376
  const groupNames = [];
111377
111377
  for (const g of adGroups) {
111378
- const name = g["Name"];
111379
- if (typeof name === "string" && name) groupNames.push(name);
111378
+ const name2 = g["Name"];
111379
+ if (typeof name2 === "string" && name2) groupNames.push(name2);
111380
111380
  const kws = g["KeywordsForBatchJob"] ?? [];
111381
111381
  for (const block of kws) kwCount += block.KeywordText?.length ?? 0;
111382
111382
  const ads = g["AdsForBatchJob"] ?? [];
@@ -112592,9 +112592,9 @@ async function runAdGeoSearch(opts) {
112592
112592
  }
112593
112593
  for (const item of items) {
112594
112594
  const id = String(item["id"] ?? "");
112595
- const name = String(item["locationName"] ?? item["canonicalName"] ?? item["name"] ?? "");
112595
+ const name2 = String(item["locationName"] ?? item["canonicalName"] ?? item["name"] ?? "");
112596
112596
  const type = String(item["targetType"] ?? item["typeV2"] ?? "");
112597
- console.log(` id:${id} ${name} [${type}]`);
112597
+ console.log(` id:${id} ${name2} [${type}]`);
112598
112598
  }
112599
112599
  console.log();
112600
112600
  }
@@ -112650,14 +112650,14 @@ async function runAdGeoList(opts) {
112650
112650
  }
112651
112651
  for (const item of items) {
112652
112652
  const id = String(item["id"] ?? item["countryCriteriaId"] ?? "");
112653
- const name = String(
112653
+ const name2 = String(
112654
112654
  item["name"] ?? item["countryOrRegion"] ?? item["country"] ?? item["location"] ?? item["canonicalName"] ?? ""
112655
112655
  );
112656
112656
  const campaign = item["campaignName"] ? ` campaign:${item["campaignName"]}` : "";
112657
112657
  const bid = item["bidModifier"] != null && typeof item["bidModifier"] === "number" ? ` \u51FA\u4EF7\u8C03\u6574:${formatBidModifierPercent(item["bidModifier"])}` : item["bidModifier"] != null ? ` \u51FA\u4EF7\u8C03\u6574:${item["bidModifier"]}` : "";
112658
112658
  const spend = item["spend"] != null ? ` \u6D88\u8017:${item["spend"]}` : "";
112659
112659
  const clicks = item["clicks"] != null ? ` \u70B9\u51FB:${item["clicks"]}` : "";
112660
- console.log(` id:${id} ${name}${campaign}${bid}${spend}${clicks}`);
112660
+ console.log(` id:${id} ${name2}${campaign}${bid}${spend}${clicks}`);
112661
112661
  }
112662
112662
  console.log();
112663
112663
  }
@@ -112772,12 +112772,12 @@ async function runAdGeoSetBid(opts) {
112772
112772
  process.exit(1);
112773
112773
  }
112774
112774
  const row = rows.find((r) => String(r["id"] ?? "") === criterionId);
112775
- const name = String(
112775
+ const name2 = String(
112776
112776
  row?.["locationName"] ?? row?.["name"] ?? row?.["countryOrRegion"] ?? criterionId
112777
112777
  );
112778
112778
  console.log(
112779
112779
  `
112780
- \u2705 \u5DF2\u66F4\u65B0\u5730\u7406\u4F4D\u7F6E\u51FA\u4EF7\uFF08\u7CFB\u5217 ${opts.campaignId} / ${name} / id ${criterionId} \u2192 \u500D\u7387 ${opts.bidModifier}\uFF0C${formatBidModifierPercent(opts.bidModifier)}\uFF09
112780
+ \u2705 \u5DF2\u66F4\u65B0\u5730\u7406\u4F4D\u7F6E\u51FA\u4EF7\uFF08\u7CFB\u5217 ${opts.campaignId} / ${name2} / id ${criterionId} \u2192 \u500D\u7387 ${opts.bidModifier}\uFF0C${formatBidModifierPercent(opts.bidModifier)}\uFF09
112781
112781
  `
112782
112782
  );
112783
112783
  }
@@ -113744,8 +113744,11 @@ function nonEmptyStrings(list) {
113744
113744
  }
113745
113745
  function hasImageSource(cfg, kind) {
113746
113746
  const paths = cfg.imagePaths;
113747
+ const assetIdKey = kind === "marketing" ? "marketingImageAssetId" : kind === "square" ? "squareMarketingImageAssetId" : "logoImageAssetId";
113747
113748
  const base64Key = kind === "marketing" ? "marketingImageBase64" : kind === "square" ? "squareMarketingImageBase64" : "logoImageBase64";
113748
113749
  const pathKey = kind;
113750
+ const assetId = cfg[assetIdKey];
113751
+ if (typeof assetId === "string" && assetId.trim().length > 0) return true;
113749
113752
  const b64 = cfg[base64Key];
113750
113753
  if (typeof b64 === "string" && b64.trim().length > 0) return true;
113751
113754
  const p = paths?.[pathKey];
@@ -113822,13 +113825,22 @@ function runPmaxCreateValidation(cfg) {
113822
113825
  pushErr3(errors, "businessName\uFF08\u5546\u5BB6\u540D\u79F0\uFF09\u5FC5\u586B");
113823
113826
  }
113824
113827
  if (!hasImageSource(cfg, "marketing")) {
113825
- pushErr3(errors, "\u6A2A\u56FE\u5FC5\u586B\uFF1AimagePaths.marketing \u6216 marketingImageBase64");
113828
+ pushErr3(
113829
+ errors,
113830
+ "\u6A2A\u56FE\u5FC5\u586B\uFF1AimagePaths.marketing\uFF08\u63A8\u8350\uFF0C\u63D0\u4EA4\u65F6\u81EA\u52A8\u4E0A\u4F20\uFF09\u6216 marketingImageAssetId / marketingImageBase64"
113831
+ );
113826
113832
  }
113827
113833
  if (!hasImageSource(cfg, "square")) {
113828
- pushErr3(errors, "\u65B9\u56FE\u5FC5\u586B\uFF1AimagePaths.square \u6216 squareMarketingImageBase64");
113834
+ pushErr3(
113835
+ errors,
113836
+ "\u65B9\u56FE\u5FC5\u586B\uFF1AimagePaths.square \u6216 squareMarketingImageAssetId / squareMarketingImageBase64"
113837
+ );
113829
113838
  }
113830
113839
  if (!hasImageSource(cfg, "logo")) {
113831
- pushErr3(errors, "Logo \u5FC5\u586B\uFF1AimagePaths.logo \u6216 logoImageBase64");
113840
+ pushErr3(
113841
+ errors,
113842
+ "Logo \u5FC5\u586B\uFF1AimagePaths.logo \u6216 logoImageAssetId / logoImageBase64"
113843
+ );
113832
113844
  }
113833
113845
  const bidding = normalizeBidding(cfg.biddingStrategyTypeV2);
113834
113846
  if (!bidding) {
@@ -114054,9 +114066,205 @@ async function runPmaxImageValidation(configFile, cfg) {
114054
114066
  return { errors, warnings };
114055
114067
  }
114056
114068
 
114057
- // src/commands/ad/pmax-load.ts
114069
+ // src/commands/ad/pmax-image-resolve.ts
114070
+ import { basename as basename4, dirname as dirname8, isAbsolute as isAbsolute3, resolve as resolve7 } from "path";
114058
114071
  import { readFileSync as readFileSync5 } from "fs";
114059
- import { dirname as dirname8, isAbsolute as isAbsolute3, resolve as resolve7 } from "path";
114072
+
114073
+ // src/commands/ad/pmax-urls.ts
114074
+ function pmaxChannelTypesUrl(googleApiUrl) {
114075
+ const base = googleApiUrl.replace(/\/$/, "");
114076
+ return `${base}/campaign/pmax/channel-types`;
114077
+ }
114078
+ function pmaxCampaignUrl(googleApiUrl, accountId, campaignId) {
114079
+ const base = googleApiUrl.replace(/\/$/, "");
114080
+ const path22 = `${base}/accounts/${accountId}/campaign/pmax`;
114081
+ return campaignId ? `${path22}/${campaignId}` : path22;
114082
+ }
114083
+ function pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, suffix) {
114084
+ const base = googleApiUrl.replace(/\/$/, "");
114085
+ const path22 = `${base}/accounts/${accountId}/campaign/pmax/asset-group/${assetGroupId}`;
114086
+ return suffix ? `${path22}/${suffix.replace(/^\//, "")}` : path22;
114087
+ }
114088
+ function pmaxCampaignAssetGroupUrl(googleApiUrl, accountId, campaignId) {
114089
+ const base = googleApiUrl.replace(/\/$/, "");
114090
+ return `${base}/accounts/${accountId}/campaign/pmax/${campaignId}/asset-group`;
114091
+ }
114092
+ function pmaxUserlistsUrl(googleApiUrl, accountId) {
114093
+ const base = googleApiUrl.replace(/\/$/, "");
114094
+ return `${base}/campaignmanagement/userlists/${accountId}`;
114095
+ }
114096
+ function pmaxAudiencesUrl(googleApiUrl, accountId, limit) {
114097
+ const base = googleApiUrl.replace(/\/$/, "");
114098
+ const url = `${base}/campaignmanagement/audiences/${accountId}`;
114099
+ if (limit != null && Number.isFinite(limit)) {
114100
+ return `${url}?limit=${Math.floor(limit)}`;
114101
+ }
114102
+ return url;
114103
+ }
114104
+ function pmaxImageAssetUrl(googleApiUrl, accountId, name2) {
114105
+ const base = googleApiUrl.replace(/\/$/, "");
114106
+ const url = `${base}/mediamanagement/imageasset/${accountId}`;
114107
+ if (name2?.trim()) {
114108
+ return `${url}?name=${encodeURIComponent(name2.trim())}`;
114109
+ }
114110
+ return url;
114111
+ }
114112
+ function pmaxAssetGroupReportUrl(googleApiUrl, accountId, query) {
114113
+ const base = googleApiUrl.replace(/\/$/, "");
114114
+ const qs = query.toString();
114115
+ return `${base}/reporting/media-account/${accountId}/pmax/asset-groups/reports${qs ? `?${qs}` : ""}`;
114116
+ }
114117
+ function pmaxGeoReportUrl(googleApiUrl, accountId, query) {
114118
+ const base = googleApiUrl.replace(/\/$/, "");
114119
+ const qs = query.toString();
114120
+ return `${base}/reporting/media-account/${accountId}/pmax/campaigns/reports/geo${qs ? `?${qs}` : ""}`;
114121
+ }
114122
+
114123
+ // src/commands/ad/pmax-image-resolve.ts
114124
+ function resolvePmaxImagePath(configFile, relOrAbs) {
114125
+ const trimmed = relOrAbs.trim();
114126
+ if (isAbsolute3(trimmed)) return trimmed;
114127
+ return resolve7(dirname8(configFile), trimmed);
114128
+ }
114129
+ var SLOT_META = {
114130
+ marketing: {
114131
+ assetIdKey: "marketingImageAssetId",
114132
+ base64Key: "marketingImageBase64",
114133
+ pathKey: "marketing",
114134
+ configAssetIdKey: "marketingImageAssetId",
114135
+ configBase64Key: "marketingImageBase64",
114136
+ label: "\u6A2A\u56FE (MARKETING_IMAGE)"
114137
+ },
114138
+ square: {
114139
+ assetIdKey: "squareMarketingImageAssetId",
114140
+ base64Key: "squareMarketingImageBase64",
114141
+ pathKey: "square",
114142
+ configAssetIdKey: "squareMarketingImageAssetId",
114143
+ configBase64Key: "squareMarketingImageBase64",
114144
+ label: "\u65B9\u56FE (SQUARE_MARKETING_IMAGE)"
114145
+ },
114146
+ logo: {
114147
+ assetIdKey: "logoImageAssetId",
114148
+ base64Key: "logoImageBase64",
114149
+ pathKey: "logo",
114150
+ configAssetIdKey: "logoImageAssetId",
114151
+ configBase64Key: "logoImageBase64",
114152
+ label: "Logo (LOGO)"
114153
+ }
114154
+ };
114155
+ function guessContentType(fileName) {
114156
+ const lower = fileName.toLowerCase();
114157
+ if (lower.endsWith(".png")) return "image/png";
114158
+ if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
114159
+ if (lower.endsWith(".gif")) return "image/gif";
114160
+ if (lower.endsWith(".webp")) return "image/webp";
114161
+ return "application/octet-stream";
114162
+ }
114163
+ function normalizePmaxAssetId(value) {
114164
+ if (value == null) return "";
114165
+ if (typeof value === "number" && Number.isFinite(value)) return String(Math.trunc(value));
114166
+ const s = String(value).trim();
114167
+ if (!s) return "";
114168
+ const m = s.match(/\/assets\/(\d+)\s*$/);
114169
+ return m ? m[1] : s;
114170
+ }
114171
+ async function uploadPmaxImageFile(config, googleApiUrl, accountId, absImagePath, name2, verbose) {
114172
+ const fileName = basename4(absImagePath);
114173
+ const assetName = (name2 ?? fileName).trim() || fileName;
114174
+ const fileBuffer = readFileSync5(absImagePath);
114175
+ const mimeType = guessContentType(fileName);
114176
+ const form = new FormData();
114177
+ form.append("file", new Blob([fileBuffer], { type: mimeType }), fileName);
114178
+ const url = pmaxImageAssetUrl(googleApiUrl, accountId, assetName);
114179
+ const headers = {
114180
+ "Accept-Language": "zh-CN",
114181
+ Datapermission: config.dataPermission ?? ""
114182
+ };
114183
+ if (config.apiKey) headers["x-api-key"] = config.apiKey;
114184
+ else headers.Authorization = `Bearer ${config.authToken}`;
114185
+ const res = await fetch(url, { method: "POST", headers, body: form });
114186
+ const text = await res.text();
114187
+ if (!res.ok) {
114188
+ const detail = verbose ? `\uFF1A${text.slice(0, 300)}` : "";
114189
+ throw new Error(`\u4E0A\u4F20\u56FE\u7247\u8D44\u4EA7 HTTP ${res.status}${detail}\uFF08${absImagePath}\uFF09`);
114190
+ }
114191
+ let data;
114192
+ try {
114193
+ data = JSON.parse(text);
114194
+ } catch {
114195
+ throw new Error(`\u4E0A\u4F20\u56FE\u7247\u8D44\u4EA7\u54CD\u5E94\u975E JSON\uFF08${absImagePath}\uFF09`);
114196
+ }
114197
+ const id = normalizePmaxAssetId(data["id"]);
114198
+ if (!id) {
114199
+ throw new Error(`\u4E0A\u4F20\u56FE\u7247\u8D44\u4EA7\u672A\u8FD4\u56DE id\uFF08${absImagePath}\uFF09`);
114200
+ }
114201
+ return id;
114202
+ }
114203
+ async function resolveOneSlot(configFile, cfg, kind, accountId, config, googleApiUrl, verbose) {
114204
+ const meta = SLOT_META[kind];
114205
+ const presetId = String(cfg[meta.configAssetIdKey] ?? "").trim();
114206
+ if (presetId) {
114207
+ return { [meta.assetIdKey]: normalizePmaxAssetId(presetId) };
114208
+ }
114209
+ const inlineB64 = String(cfg[meta.configBase64Key] ?? "").trim();
114210
+ const pathRel = cfg.imagePaths?.[meta.pathKey]?.trim();
114211
+ if (pathRel) {
114212
+ const abs = resolvePmaxImagePath(configFile, pathRel);
114213
+ const id = await uploadPmaxImageFile(
114214
+ config,
114215
+ googleApiUrl,
114216
+ accountId,
114217
+ abs,
114218
+ basename4(abs),
114219
+ verbose
114220
+ );
114221
+ return { [meta.assetIdKey]: id };
114222
+ }
114223
+ if (inlineB64) {
114224
+ return { [meta.base64Key]: inlineB64 };
114225
+ }
114226
+ throw new Error(
114227
+ `${meta.label} \u672A\u914D\u7F6E\uFF1A\u8BF7\u63D0\u4F9B imagePaths.${String(meta.pathKey)}\u3001${String(meta.configBase64Key)} \u6216 ${String(meta.configAssetIdKey)}`
114228
+ );
114229
+ }
114230
+ async function resolvePmaxImageSlots(configFile, cfg, accountId, config, googleApiUrl, opts) {
114231
+ const kinds = ["marketing", "square", "logo"];
114232
+ const willUpload = kinds.some((k) => {
114233
+ const meta = SLOT_META[k];
114234
+ if (String(cfg[meta.configAssetIdKey] ?? "").trim()) return false;
114235
+ if (String(cfg[meta.configBase64Key] ?? "").trim()) return false;
114236
+ return Boolean(cfg.imagePaths?.[meta.pathKey]?.trim());
114237
+ });
114238
+ if (willUpload && opts?.logUploads !== false) {
114239
+ console.log("\n\u{1F4E4} \u6B63\u5728\u4E0A\u4F20 PMax \u56FE\u7247\u7D20\u6750\uFF08imagePaths \u2192 assetId\uFF09\u2026");
114240
+ }
114241
+ const parts = await Promise.all(
114242
+ kinds.map(
114243
+ (kind) => resolveOneSlot(configFile, cfg, kind, accountId, config, googleApiUrl, opts?.verbose)
114244
+ )
114245
+ );
114246
+ const merged = {};
114247
+ for (const part of parts) Object.assign(merged, part);
114248
+ if (willUpload && opts?.logUploads !== false) {
114249
+ console.log(
114250
+ ` \u6A2A\u56FE id\uFF1A${merged.marketingImageAssetId ?? "(base64)"} \u65B9\u56FE id\uFF1A${merged.squareMarketingImageAssetId ?? "(base64)"} Logo id\uFF1A${merged.logoImageAssetId ?? "(base64)"}`
114251
+ );
114252
+ }
114253
+ return merged;
114254
+ }
114255
+ function assertPmaxImageSlotsResolved(slots) {
114256
+ for (const kind of ["marketing", "square", "logo"]) {
114257
+ const meta = SLOT_META[kind];
114258
+ const id = slots[meta.assetIdKey];
114259
+ const b64 = slots[meta.base64Key];
114260
+ if (!id && !b64) {
114261
+ throw new Error(`${meta.label} \u89E3\u6790\u540E\u4E3A\u7A7A`);
114262
+ }
114263
+ }
114264
+ }
114265
+
114266
+ // src/commands/ad/pmax-load.ts
114267
+ import { readFileSync as readFileSync6 } from "fs";
114060
114268
  function loadPmaxCreateConfig(configFile) {
114061
114269
  const cfg = tryLoadPmaxCreateConfig(configFile);
114062
114270
  if (!cfg) {
@@ -114070,49 +114278,20 @@ function loadPmaxCreateConfig(configFile) {
114070
114278
  function tryLoadPmaxCreateConfig(configFile) {
114071
114279
  let raw;
114072
114280
  try {
114073
- raw = JSON.parse(readFileSync5(configFile, "utf8"));
114281
+ raw = JSON.parse(readFileSync6(configFile, "utf8"));
114074
114282
  } catch {
114075
114283
  return null;
114076
114284
  }
114077
114285
  return stripMetaKeys(raw);
114078
114286
  }
114079
- function resolveImagePath2(configFile, relOrAbs) {
114080
- const trimmed = relOrAbs.trim();
114081
- if (isAbsolute3(trimmed)) return trimmed;
114082
- return resolve7(dirname8(configFile), trimmed);
114083
- }
114084
- function readImageBase64(configFile, pathOrUndefined) {
114085
- if (!pathOrUndefined?.trim()) return void 0;
114086
- const abs = resolveImagePath2(configFile, pathOrUndefined);
114087
- try {
114088
- return readFileSync5(abs).toString("base64");
114089
- } catch (e) {
114090
- const msg = e instanceof Error ? e.message : String(e);
114091
- throw new Error(`\u8BFB\u53D6\u56FE\u7247\u5931\u8D25\uFF08${abs}\uFF09\uFF1A${msg}`);
114092
- }
114093
- }
114094
114287
  function parseLocationLanguageIds(items) {
114095
114288
  if (!items?.length) return void 0;
114096
114289
  return items.map((item) => ({
114097
114290
  id: Number(item.id)
114098
114291
  }));
114099
114292
  }
114100
- function buildPmaxCreateApiBody(configFile, cfg) {
114101
- const paths = cfg.imagePaths;
114102
- const marketingImageBase64 = cfg.marketingImageBase64?.trim() || readImageBase64(configFile, paths?.marketing) || "";
114103
- const squareMarketingImageBase64 = cfg.squareMarketingImageBase64?.trim() || readImageBase64(configFile, paths?.square) || "";
114104
- const logoImageBase64 = cfg.logoImageBase64?.trim() || readImageBase64(configFile, paths?.logo) || "";
114105
- if (!marketingImageBase64) {
114106
- throw new Error("marketingImageBase64 \u4E3A\u7A7A\uFF08\u8BF7\u914D\u7F6E imagePaths.marketing \u6216 marketingImageBase64\uFF09");
114107
- }
114108
- if (!squareMarketingImageBase64) {
114109
- throw new Error(
114110
- "squareMarketingImageBase64 \u4E3A\u7A7A\uFF08\u8BF7\u914D\u7F6E imagePaths.square \u6216 squareMarketingImageBase64\uFF09"
114111
- );
114112
- }
114113
- if (!logoImageBase64) {
114114
- throw new Error("logoImageBase64 \u4E3A\u7A7A\uFF08\u8BF7\u914D\u7F6E imagePaths.logo \u6216 logoImageBase64\uFF09");
114115
- }
114293
+ function buildPmaxCreateApiBody(cfg, imageSlots) {
114294
+ assertPmaxImageSlotsResolved(imageSlots);
114116
114295
  const body = {
114117
114296
  name: cfg.name.trim(),
114118
114297
  budget: toCentAmount(Number(cfg.budget)),
@@ -114121,9 +114300,7 @@ function buildPmaxCreateApiBody(configFile, cfg) {
114121
114300
  longHeadlines: cfg.longHeadlines.map((h) => h.trim()).filter(Boolean),
114122
114301
  descriptions: cfg.descriptions.map((d) => d.trim()).filter(Boolean),
114123
114302
  businessName: cfg.businessName.trim(),
114124
- marketingImageBase64,
114125
- squareMarketingImageBase64,
114126
- logoImageBase64
114303
+ ...imageSlots
114127
114304
  };
114128
114305
  if (cfg.budgetName?.trim()) body.budgetName = cfg.budgetName.trim();
114129
114306
  if (cfg.assetGroupName?.trim()) body.assetGroupName = cfg.assetGroupName.trim();
@@ -114171,9 +114348,25 @@ async function runAdPmaxCreate(opts) {
114171
114348
  const config = await ensureDataPermission(loadConfig(opts.token));
114172
114349
  const googleApiUrl = requireGoogleApi(config);
114173
114350
  const accountId = cfg.account.toString().trim();
114351
+ let imageSlots;
114352
+ try {
114353
+ imageSlots = await resolvePmaxImageSlots(
114354
+ opts.configFile,
114355
+ cfg,
114356
+ accountId,
114357
+ config,
114358
+ googleApiUrl,
114359
+ { verbose: opts.verbose, logUploads: !opts.json }
114360
+ );
114361
+ } catch (err) {
114362
+ console.error(`
114363
+ \u274C \u56FE\u7247\u4E0A\u4F20/\u89E3\u6790\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114364
+ `);
114365
+ process.exit(1);
114366
+ }
114174
114367
  let body;
114175
114368
  try {
114176
- body = buildPmaxCreateApiBody(opts.configFile, cfg);
114369
+ body = buildPmaxCreateApiBody(cfg, imageSlots);
114177
114370
  } catch (err) {
114178
114371
  console.error(`
114179
114372
  \u274C \u6784\u5EFA\u8BF7\u6C42\u4F53\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
@@ -114272,18 +114465,18 @@ async function runAdPmaxValidate(opts) {
114272
114465
  }
114273
114466
 
114274
114467
  // src/commands/ad/pmax-mgmt.ts
114275
- import { readFileSync as readFileSync7 } from "fs";
114468
+ import { basename as basename5, isAbsolute as isAbsolute5 } from "path";
114276
114469
 
114277
114470
  // src/commands/ad/pmax-shared.ts
114278
114471
  init_auth();
114279
114472
  init_cli_json_snapshot();
114280
- import { readFileSync as readFileSync6 } from "fs";
114473
+ import { readFileSync as readFileSync7 } from "fs";
114281
114474
  import { dirname as dirname9, isAbsolute as isAbsolute4, resolve as resolve8 } from "path";
114282
114475
  var PMAX_MONEY_KEYS = /* @__PURE__ */ new Set(["budget", "targetCpa_BidingAmount"]);
114283
114476
  function loadPmaxJsonFile(configFile) {
114284
114477
  let raw;
114285
114478
  try {
114286
- raw = JSON.parse(readFileSync6(configFile, "utf8"));
114479
+ raw = JSON.parse(readFileSync7(configFile, "utf8"));
114287
114480
  } catch (e) {
114288
114481
  const msg = e instanceof Error ? e.message : String(e);
114289
114482
  console.error(`
@@ -114293,21 +114486,11 @@ function loadPmaxJsonFile(configFile) {
114293
114486
  }
114294
114487
  return stripMetaKeys(raw);
114295
114488
  }
114296
- function resolveImagePath3(configFile, relOrAbs) {
114489
+ function resolvePmaxImagePath2(configFile, relOrAbs) {
114297
114490
  const trimmed = relOrAbs.trim();
114298
114491
  if (isAbsolute4(trimmed)) return trimmed;
114299
114492
  return resolve8(dirname9(configFile), trimmed);
114300
114493
  }
114301
- function readImageBase642(configFile, pathOrUndefined) {
114302
- if (!pathOrUndefined?.trim()) return void 0;
114303
- const abs = resolveImagePath3(configFile, pathOrUndefined);
114304
- try {
114305
- return readFileSync6(abs).toString("base64");
114306
- } catch (e) {
114307
- const msg = e instanceof Error ? e.message : String(e);
114308
- throw new Error(`\u8BFB\u53D6\u56FE\u7247\u5931\u8D25\uFF08${abs}\uFF09\uFF1A${msg}`);
114309
- }
114310
- }
114311
114494
  function convertPmaxMoneyInObject(body) {
114312
114495
  const out = { ...body };
114313
114496
  for (const key of PMAX_MONEY_KEYS) {
@@ -114318,22 +114501,8 @@ function convertPmaxMoneyInObject(body) {
114318
114501
  }
114319
114502
  return out;
114320
114503
  }
114321
- function buildPmaxAssetGroupApiBody(configFile, cfg) {
114322
- const paths = cfg.imagePaths;
114323
- const marketingImageBase64 = cfg.marketingImageBase64?.trim() || readImageBase642(configFile, paths?.marketing) || "";
114324
- const squareMarketingImageBase64 = cfg.squareMarketingImageBase64?.trim() || readImageBase642(configFile, paths?.square) || "";
114325
- const logoImageBase64 = cfg.logoImageBase64?.trim() || readImageBase642(configFile, paths?.logo) || "";
114326
- if (!marketingImageBase64) {
114327
- throw new Error("marketingImageBase64 \u4E3A\u7A7A\uFF08\u8BF7\u914D\u7F6E imagePaths.marketing \u6216 marketingImageBase64\uFF09");
114328
- }
114329
- if (!squareMarketingImageBase64) {
114330
- throw new Error(
114331
- "squareMarketingImageBase64 \u4E3A\u7A7A\uFF08\u8BF7\u914D\u7F6E imagePaths.square \u6216 squareMarketingImageBase64\uFF09"
114332
- );
114333
- }
114334
- if (!logoImageBase64) {
114335
- throw new Error("logoImageBase64 \u4E3A\u7A7A\uFF08\u8BF7\u914D\u7F6E imagePaths.logo \u6216 logoImageBase64\uFF09");
114336
- }
114504
+ function buildPmaxAssetGroupApiBody(cfg, imageSlots) {
114505
+ assertPmaxImageSlotsResolved(imageSlots);
114337
114506
  return {
114338
114507
  name: cfg.name.trim(),
114339
114508
  finalUrls: cfg.finalUrls.map((u) => u.trim()).filter(Boolean),
@@ -114341,26 +114510,34 @@ function buildPmaxAssetGroupApiBody(configFile, cfg) {
114341
114510
  longHeadlines: cfg.longHeadlines.map((h) => h.trim()).filter(Boolean),
114342
114511
  descriptions: cfg.descriptions.map((d) => d.trim()).filter(Boolean),
114343
114512
  businessName: cfg.businessName.trim(),
114344
- marketingImageBase64,
114345
- squareMarketingImageBase64,
114346
- logoImageBase64
114513
+ ...imageSlots
114347
114514
  };
114348
114515
  }
114349
- function processAssetsUpdateBody(configFile, body) {
114516
+ async function processAssetsUpdateBodyWithUpload(configFile, body, accountId, config, googleApiUrl, verbose) {
114350
114517
  const out = { ...body };
114351
114518
  const links = out["assetsToLink"];
114352
114519
  if (!Array.isArray(links)) return out;
114353
- out["assetsToLink"] = links.map((item) => {
114354
- if (!item || typeof item !== "object") return item;
114355
- const row = { ...item };
114356
- const imagePath = row["imagePath"];
114357
- if (typeof imagePath === "string" && imagePath.trim()) {
114358
- const b64 = readImageBase642(configFile, imagePath);
114359
- if (b64) row["imageBase64"] = b64;
114360
- delete row["imagePath"];
114361
- }
114362
- return row;
114363
- });
114520
+ out["assetsToLink"] = await Promise.all(
114521
+ links.map(async (item) => {
114522
+ if (!item || typeof item !== "object") return item;
114523
+ const row = { ...item };
114524
+ const imagePath = row["imagePath"];
114525
+ if (typeof imagePath === "string" && imagePath.trim()) {
114526
+ const abs = resolvePmaxImagePath2(configFile, imagePath);
114527
+ row["assetId"] = await uploadPmaxImageFile(
114528
+ config,
114529
+ googleApiUrl,
114530
+ accountId,
114531
+ abs,
114532
+ void 0,
114533
+ verbose
114534
+ );
114535
+ delete row["imagePath"];
114536
+ delete row["imageBase64"];
114537
+ }
114538
+ return row;
114539
+ })
114540
+ );
114364
114541
  return out;
114365
114542
  }
114366
114543
  async function withGoogleApi(opts, fn) {
@@ -114391,56 +114568,6 @@ function requireAccountId(account, label = "-a, --account") {
114391
114568
  return id;
114392
114569
  }
114393
114570
 
114394
- // src/commands/ad/pmax-urls.ts
114395
- function pmaxChannelTypesUrl(googleApiUrl) {
114396
- const base = googleApiUrl.replace(/\/$/, "");
114397
- return `${base}/campaign/pmax/channel-types`;
114398
- }
114399
- function pmaxCampaignUrl(googleApiUrl, accountId, campaignId) {
114400
- const base = googleApiUrl.replace(/\/$/, "");
114401
- const path22 = `${base}/accounts/${accountId}/campaign/pmax`;
114402
- return campaignId ? `${path22}/${campaignId}` : path22;
114403
- }
114404
- function pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, suffix) {
114405
- const base = googleApiUrl.replace(/\/$/, "");
114406
- const path22 = `${base}/accounts/${accountId}/campaign/pmax/asset-group/${assetGroupId}`;
114407
- return suffix ? `${path22}/${suffix.replace(/^\//, "")}` : path22;
114408
- }
114409
- function pmaxCampaignAssetGroupUrl(googleApiUrl, accountId, campaignId) {
114410
- const base = googleApiUrl.replace(/\/$/, "");
114411
- return `${base}/accounts/${accountId}/campaign/pmax/${campaignId}/asset-group`;
114412
- }
114413
- function pmaxUserlistsUrl(googleApiUrl, accountId) {
114414
- const base = googleApiUrl.replace(/\/$/, "");
114415
- return `${base}/campaignmanagement/userlists/${accountId}`;
114416
- }
114417
- function pmaxAudiencesUrl(googleApiUrl, accountId, limit) {
114418
- const base = googleApiUrl.replace(/\/$/, "");
114419
- const url = `${base}/campaignmanagement/audiences/${accountId}`;
114420
- if (limit != null && Number.isFinite(limit)) {
114421
- return `${url}?limit=${Math.floor(limit)}`;
114422
- }
114423
- return url;
114424
- }
114425
- function pmaxImageAssetUrl(googleApiUrl, accountId, name) {
114426
- const base = googleApiUrl.replace(/\/$/, "");
114427
- const url = `${base}/mediamanagement/imageasset/${accountId}`;
114428
- if (name?.trim()) {
114429
- return `${url}?name=${encodeURIComponent(name.trim())}`;
114430
- }
114431
- return url;
114432
- }
114433
- function pmaxAssetGroupReportUrl(googleApiUrl, accountId, query) {
114434
- const base = googleApiUrl.replace(/\/$/, "");
114435
- const qs = query.toString();
114436
- return `${base}/reporting/media-account/${accountId}/pmax/asset-groups/reports${qs ? `?${qs}` : ""}`;
114437
- }
114438
- function pmaxGeoReportUrl(googleApiUrl, accountId, query) {
114439
- const base = googleApiUrl.replace(/\/$/, "");
114440
- const qs = query.toString();
114441
- return `${base}/reporting/media-account/${accountId}/pmax/campaigns/reports/geo${qs ? `?${qs}` : ""}`;
114442
- }
114443
-
114444
114571
  // src/commands/ad/pmax-mgmt.ts
114445
114572
  function buildReportQuery(opts) {
114446
114573
  const params = new URLSearchParams();
@@ -114598,27 +114725,49 @@ async function runAdPmaxAssetGroupCreate(opts) {
114598
114725
  console.error("\n\u274C JSON \u987B\u5305\u542B campaignId\n");
114599
114726
  process.exit(1);
114600
114727
  }
114601
- let body;
114602
- try {
114603
- body = buildPmaxAssetGroupApiBody(opts.configFile, {
114604
- name: String(cfg["name"] ?? ""),
114605
- finalUrls: cfg["finalUrls"] ?? [],
114606
- businessName: String(cfg["businessName"] ?? ""),
114607
- headlines: cfg["headlines"] ?? [],
114608
- longHeadlines: cfg["longHeadlines"] ?? [],
114609
- descriptions: cfg["descriptions"] ?? [],
114610
- imagePaths: cfg["imagePaths"],
114611
- marketingImageBase64: cfg["marketingImageBase64"],
114612
- squareMarketingImageBase64: cfg["squareMarketingImageBase64"],
114613
- logoImageBase64: cfg["logoImageBase64"]
114614
- });
114615
- } catch (err) {
114616
- console.error(`
114728
+ const assetFields = {
114729
+ name: String(cfg["name"] ?? ""),
114730
+ finalUrls: cfg["finalUrls"] ?? [],
114731
+ businessName: String(cfg["businessName"] ?? ""),
114732
+ headlines: cfg["headlines"] ?? [],
114733
+ longHeadlines: cfg["longHeadlines"] ?? [],
114734
+ descriptions: cfg["descriptions"] ?? [],
114735
+ imagePaths: cfg["imagePaths"],
114736
+ marketingImageAssetId: cfg["marketingImageAssetId"],
114737
+ squareMarketingImageAssetId: cfg["squareMarketingImageAssetId"],
114738
+ logoImageAssetId: cfg["logoImageAssetId"],
114739
+ marketingImageBase64: cfg["marketingImageBase64"],
114740
+ squareMarketingImageBase64: cfg["squareMarketingImageBase64"],
114741
+ logoImageBase64: cfg["logoImageBase64"]
114742
+ };
114743
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
114744
+ let imageSlots;
114745
+ try {
114746
+ imageSlots = await resolvePmaxImageSlots(
114747
+ opts.configFile,
114748
+ assetFields,
114749
+ accountId,
114750
+ config,
114751
+ googleApiUrl,
114752
+ { verbose: opts.verbose, logUploads: !opts.json }
114753
+ );
114754
+ } catch (err) {
114755
+ console.error(
114756
+ `
114757
+ \u274C \u56FE\u7247\u4E0A\u4F20/\u89E3\u6790\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114758
+ `
114759
+ );
114760
+ process.exit(1);
114761
+ }
114762
+ let body;
114763
+ try {
114764
+ body = buildPmaxAssetGroupApiBody(assetFields, imageSlots);
114765
+ } catch (err) {
114766
+ console.error(`
114617
114767
  \u274C \u6784\u5EFA\u8BF7\u6C42\u4F53\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114618
114768
  `);
114619
- process.exit(1);
114620
- }
114621
- await withGoogleApi(opts, async (config, googleApiUrl) => {
114769
+ process.exit(1);
114770
+ }
114622
114771
  const url = pmaxCampaignAssetGroupUrl(googleApiUrl, accountId, campaignId);
114623
114772
  let data;
114624
114773
  try {
@@ -114716,17 +114865,24 @@ async function runAdPmaxAssetsUpdate(opts) {
114716
114865
  console.error("\n\u274C JSON \u987B\u5305\u542B campaignId\uFF08PUT assets body \u5FC5\u586B\uFF09\n");
114717
114866
  process.exit(1);
114718
114867
  }
114719
- let body;
114720
- try {
114721
- const { account: _a, ...rest } = cfg;
114722
- body = processAssetsUpdateBody(opts.configFile, rest);
114723
- } catch (err) {
114724
- console.error(`
114868
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
114869
+ let body;
114870
+ try {
114871
+ const { account: _a, ...rest } = cfg;
114872
+ body = await processAssetsUpdateBodyWithUpload(
114873
+ opts.configFile,
114874
+ rest,
114875
+ accountId,
114876
+ config,
114877
+ googleApiUrl,
114878
+ opts.verbose
114879
+ );
114880
+ } catch (err) {
114881
+ console.error(`
114725
114882
  \u274C \u5904\u7406 assets JSON \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114726
114883
  `);
114727
- process.exit(1);
114728
- }
114729
- await withGoogleApi(opts, async (config, googleApiUrl) => {
114884
+ process.exit(1);
114885
+ }
114730
114886
  const url = pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, "assets");
114731
114887
  try {
114732
114888
  await pmaxApiFetch(
@@ -114967,43 +115123,55 @@ PMax \u53D7\u4F17\u6570\u636E\u6E90\uFF08\u8D26\u6237 ${accountId}\uFF09
114967
115123
  }
114968
115124
  async function runAdPmaxImageUpload(opts) {
114969
115125
  const accountId = requireAccountId(opts.account);
114970
- let name;
114971
- let base64String;
114972
- if (opts.bodyFile) {
114973
- const cfg = loadPmaxJsonFile(opts.bodyFile);
114974
- name = String(cfg["name"] ?? opts.name ?? "cli-image").trim();
114975
- base64String = String(cfg["base64String"] ?? cfg["Base64String"] ?? "").trim() || readImageBase642(opts.bodyFile, String(cfg["imagePath"] ?? "")) || "";
114976
- } else {
114977
- name = (opts.name ?? "cli-image").trim();
114978
- try {
114979
- base64String = readFileSync7(opts.imagePath).toString("base64");
114980
- } catch (err) {
114981
- console.error(
114982
- `
114983
- \u274C \u8BFB\u53D6\u56FE\u7247\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114984
- `
114985
- );
114986
- process.exit(1);
114987
- }
114988
- }
114989
- if (!base64String) {
114990
- console.error("\n\u274C \u7F3A\u5C11\u56FE\u7247 Base64\uFF08--image-path \u6216 body JSON\uFF09\n");
114991
- process.exit(1);
114992
- }
114993
115126
  await withGoogleApi(opts, async (config, googleApiUrl) => {
114994
- const url = pmaxImageAssetUrl(googleApiUrl, accountId, name);
114995
115127
  let data;
114996
115128
  try {
114997
- const raw = await pmaxApiFetch(
114998
- url,
114999
- config,
115000
- {
115001
- method: "POST",
115002
- body: JSON.stringify({ name, base64String })
115003
- },
115004
- opts.verbose
115005
- );
115006
- data = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : { data: raw };
115129
+ if (opts.imagePath?.trim()) {
115130
+ const abs = isAbsolute5(opts.imagePath) ? opts.imagePath.trim() : resolvePmaxImagePath2(process.cwd(), opts.imagePath);
115131
+ const name2 = (opts.name ?? basename5(abs)).trim();
115132
+ const id = await uploadPmaxImageFile(
115133
+ config,
115134
+ googleApiUrl,
115135
+ accountId,
115136
+ abs,
115137
+ name2,
115138
+ opts.verbose
115139
+ );
115140
+ data = { id, name: name2 };
115141
+ } else if (opts.bodyFile) {
115142
+ const cfg = loadPmaxJsonFile(opts.bodyFile);
115143
+ const name2 = String(cfg["name"] ?? opts.name ?? "cli-image").trim();
115144
+ const imagePath = String(cfg["imagePath"] ?? "").trim();
115145
+ if (imagePath) {
115146
+ const abs = resolvePmaxImagePath2(opts.bodyFile, imagePath);
115147
+ const id = await uploadPmaxImageFile(
115148
+ config,
115149
+ googleApiUrl,
115150
+ accountId,
115151
+ abs,
115152
+ name2,
115153
+ opts.verbose
115154
+ );
115155
+ data = { id, name: name2 };
115156
+ } else {
115157
+ const base64String = String(cfg["base64String"] ?? cfg["Base64String"] ?? "").trim() || "";
115158
+ if (!base64String) {
115159
+ console.error("\n\u274C body JSON \u987B\u542B imagePath \u6216 base64String\n");
115160
+ process.exit(1);
115161
+ }
115162
+ const url = pmaxImageAssetUrl(googleApiUrl, accountId, name2);
115163
+ const raw = await pmaxApiFetch(
115164
+ url,
115165
+ config,
115166
+ { method: "POST", body: JSON.stringify({ name: name2, base64String }) },
115167
+ opts.verbose
115168
+ );
115169
+ data = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : { data: raw };
115170
+ }
115171
+ } else {
115172
+ console.error("\n\u274C \u8BF7\u6307\u5B9A --image-path \u6216 --body-file\n");
115173
+ process.exit(1);
115174
+ }
115007
115175
  } catch (err) {
115008
115176
  console.error(
115009
115177
  `
@@ -115150,7 +115318,7 @@ async function runAdCampaignValidate(opts) {
115150
115318
 
115151
115319
  // src/commands/ad/pmax-image-convert.ts
115152
115320
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5, readFileSync as readFileSync8, existsSync as existsSync3 } from "fs";
115153
- import { resolve as resolve9, dirname as dirname10, basename as basename4, extname } from "path";
115321
+ import { resolve as resolve9, dirname as dirname10, basename as basename6, extname } from "path";
115154
115322
  var SPECS = [
115155
115323
  { kind: "marketing", width: 1200, height: 628, fit: "cover", suffix: "_marketing" },
115156
115324
  { kind: "square", width: 1200, height: 1200, fit: "cover", suffix: "_square" },
@@ -115199,7 +115367,7 @@ async function runAdPmaxImageConvert(opts) {
115199
115367
  const firstInput = input ?? inputMarketing ?? inputSquare ?? inputLogo;
115200
115368
  const outputDir = opts.outputDir ? resolve9(opts.outputDir) : dirname10(resolve9(firstInput));
115201
115369
  mkdirSync3(outputDir, { recursive: true });
115202
- const prefix = opts.prefix ?? basename4(firstInput, extname(firstInput));
115370
+ const prefix = opts.prefix ?? basename6(firstInput, extname(firstInput));
115203
115371
  const quality = opts.quality ?? 85;
115204
115372
  const sharpMod = await loadSharp();
115205
115373
  const sharpFn = (p) => sharpMod(p);
@@ -115662,7 +115830,7 @@ function register20(program2) {
115662
115830
  }
115663
115831
  );
115664
115832
  adCmd.command("pmax-create").description(
115665
- '\u65B0\u5EFA Performance Max \u5E7F\u544A\u7CFB\u5217\uFF08\u540C\u6B65 API\uFF1B\u4EC5\u652F\u6301 JSON \u914D\u7F6E\u6587\u4EF6\uFF09\n\n \u7528\u6CD5\uFF1A\n 1. \u590D\u5236 pmax-create-template.json\uFF0C\u5B57\u6BB5\u8BF4\u660E\u89C1 pmax-create-template.md\n 2. siluzan-tso ad geo search -a <accountId> -q "United States"\n 3. siluzan-tso ad pmax-validate --config-file ./pmax.json\n 4. siluzan-tso ad pmax-create --config-file ./pmax.json\n 5. siluzan-tso ad campaigns -a <accountId> --json # \u786E\u8BA4 PERFORMANCE_MAX'
115833
+ '\u65B0\u5EFA Performance Max \u5E7F\u544A\u7CFB\u5217\uFF08\u540C\u6B65 API\uFF1B\u4EC5\u652F\u6301 JSON \u914D\u7F6E\u6587\u4EF6\uFF09\n\n \u7528\u6CD5\uFF1A\n 1. \u590D\u5236 pmax-create-template.json\uFF0C\u5B57\u6BB5\u8BF4\u660E\u89C1 pmax-create-template.md\n 2. siluzan-tso ad geo search -a <accountId> -q "United States"\n 3. siluzan-tso ad pmax-validate --config-file ./pmax.json\n 4. siluzan-tso ad pmax-create --config-file ./pmax.json # imagePaths \u81EA\u52A8\u4E0A\u4F20\n 5. siluzan-tso ad campaigns -a <accountId> --json # \u786E\u8BA4 PERFORMANCE_MAX'
115666
115834
  ).requiredOption(
115667
115835
  "--config-file <path>",
115668
115836
  "JSON \u914D\u7F6E\u6587\u4EF6\uFF08\u6A21\u677F\u89C1 assets/siluzan-ads/assets/pmax-create-*.json|md\uFF09"
@@ -116780,9 +116948,9 @@ function splitQueryWhitespaceToKeywords(query) {
116780
116948
  function ragItemDedupeKey(item) {
116781
116949
  const id = String(item.id ?? "").trim();
116782
116950
  if (id) return `id:${id}`;
116783
- const name = String(item.fields?.name ?? "").trim();
116951
+ const name2 = String(item.fields?.name ?? "").trim();
116784
116952
  const content = String(item.fields?.content ?? "").slice(0, 160);
116785
- return `fb:${name}\0${content}`;
116953
+ return `fb:${name2}\0${content}`;
116786
116954
  }
116787
116955
  function mergeRagResultsByBestScore(batches) {
116788
116956
  const best = /* @__PURE__ */ new Map();
@@ -116870,12 +117038,12 @@ function formatRagResultsMarkdown(query, items, subQueries) {
116870
117038
  items.forEach((item, itemIndex) => {
116871
117039
  const fields = item.fields;
116872
117040
  if (!fields) return;
116873
- const name = fields.name ?? "\u672A\u77E5\u6587\u4EF6";
117041
+ const name2 = fields.name ?? "\u672A\u77E5\u6587\u4EF6";
116874
117042
  const content = fields.content ?? "";
116875
117043
  const sourceUrl = fields.source_url ?? "";
116876
117044
  const createTime = fields.create_time ?? "";
116877
117045
  const usageCount = fields.usage_count ?? 0;
116878
- parts.push(`### ${itemIndex + 1}. ${name}
117046
+ parts.push(`### ${itemIndex + 1}. ${name2}
116879
117047
  `);
116880
117048
  if (item.id) {
116881
117049
  parts.push(`**\u7247\u6BB5 ID\uFF1A** \`${item.id}\`
@@ -118926,7 +119094,7 @@ init_cli_table();
118926
119094
  async function uploadAttachment(filePath, apiBaseUrl, config, verbose) {
118927
119095
  const fileName = path14.basename(filePath);
118928
119096
  const fileBuffer = fs9.readFileSync(filePath);
118929
- const mimeType = guessContentType(fileName);
119097
+ const mimeType = guessContentType2(fileName);
118930
119098
  const form = new FormData();
118931
119099
  form.append("file", new Blob([fileBuffer], { type: mimeType }), fileName);
118932
119100
  const uploadUrl = `${apiBaseUrl}/command/attachment`;
@@ -118952,7 +119120,7 @@ async function uploadAttachment(filePath, apiBaseUrl, config, verbose) {
118952
119120
  }
118953
119121
  return { id: data.id, fileName, storageProvider: data.storageProvider ?? "StorageAccount" };
118954
119122
  }
118955
- function guessContentType(fileName) {
119123
+ function guessContentType2(fileName) {
118956
119124
  const ext = path14.extname(fileName).toLowerCase();
118957
119125
  const map = {
118958
119126
  ".jpg": "image/jpeg",
@@ -119227,10 +119395,10 @@ async function runOpenAccountGoogleTimezones(opts) {
119227
119395
  if (kw) {
119228
119396
  rows = rows.filter((r) => {
119229
119397
  const code = String(r.Code ?? "").toLowerCase();
119230
- const name = String(r.Name ?? "").toLowerCase();
119398
+ const name2 = String(r.Name ?? "").toLowerCase();
119231
119399
  const time = String(r.Time ?? "").toLowerCase();
119232
119400
  const label = `(${r.Time ?? ""})${r.Name ?? ""}`.toLowerCase();
119233
- return code.includes(kw) || name.includes(kw) || time.includes(kw) || label.includes(kw);
119401
+ return code.includes(kw) || name2.includes(kw) || time.includes(kw) || label.includes(kw);
119234
119402
  });
119235
119403
  }
119236
119404
  const n = rows.length;
@@ -119652,9 +119820,9 @@ async function runOpenAccountTikTokTimezones(opts) {
119652
119820
  if (kw) {
119653
119821
  rows = rows.filter((r) => {
119654
119822
  const code = String(r.Code ?? "").toLowerCase();
119655
- const name = String(r.Name ?? "").toLowerCase();
119823
+ const name2 = String(r.Name ?? "").toLowerCase();
119656
119824
  const time = String(r.Time ?? "").toLowerCase();
119657
- return code.includes(kw) || name.includes(kw) || time.includes(kw);
119825
+ return code.includes(kw) || name2.includes(kw) || time.includes(kw);
119658
119826
  });
119659
119827
  }
119660
119828
  const n = rows.length;
@@ -119741,12 +119909,12 @@ TikTok \u884C\u4E1A\u5217\u8868\uFF08\u7B2C 1 \u9875\uFF0C\u672C\u9875 ${rows.le
119741
119909
  }
119742
119910
  for (const r of rows) {
119743
119911
  const id = String(r.IndustryId ?? "");
119744
- const name = String(r.IndustryName ?? "");
119912
+ const name2 = String(r.IndustryName ?? "");
119745
119913
  const isTop = id.length >= 6 && id.slice(4, 6) === "01";
119746
119914
  if (isTop) {
119747
- console.log(` \u25B8 ${name} (${id})`);
119915
+ console.log(` \u25B8 ${name2} (${id})`);
119748
119916
  } else {
119749
- console.log(` ${name} (${id})`);
119917
+ console.log(` ${name2} (${id})`);
119750
119918
  }
119751
119919
  }
119752
119920
  console.log();
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.21-beta.2",
4
- "publishedAt": 1779679832469
3
+ "version": "1.1.21-beta.3",
4
+ "publishedAt": 1779689350731
5
5
  }
@@ -6,7 +6,7 @@
6
6
  "agentPitfalls": [
7
7
  "勿与 Search campaign-create-template.json 混用(PMax 走 POST .../campaign/pmax,非 batch-asyncs)",
8
8
  "budget / targetCpa_BidingAmount 填主币种「元」,CLI 提交前 ×100 转 API 整型",
9
- "三张图:用 imagePaths 指向本地 PNG/JPEG,或 JSON 内直接写 marketingImageBase64 等",
9
+ "三张图:只填 imagePaths(pmax-create 会自动上传为 assetId);勿把 Base64 写入 JSON",
10
10
  "提交前:ad pmax-validate → 用户确认 → ad pmax-create"
11
11
  ]
12
12
  },
@@ -14,7 +14,7 @@
14
14
  |------|----------|
15
15
  | 创建 PMax | `pmax-validate` → 用户确认 → `pmax-create` |
16
16
  | 金额 | JSON 填**主币种「元」**;CLI 提交前 `budget`、`targetCpa_BidingAmount` ×100 |
17
- | 图片 | 优先 `imagePaths` 指向本地 PNG/JPEG;勿把大图 Base64 提交进 Git |
17
+ | 图片 | **只填 `imagePaths`** 指向本地 PNG/JPEG;`pmax-create` 会自动 multipart 上传并用 assetId 创建(勿把 Base64 提交进 Git |
18
18
  | 改已上线 PMax | `ad pmax-get` / `pmax-edit` / `pmax-assets-update` 等(见 `references/pmax-api.md`) |
19
19
  | 列表复核 | `ad campaigns -a <id> --json`,`channelTypeV2` 应为 `PERFORMANCE_MAX` |
20
20
 
@@ -46,12 +46,15 @@ siluzan-tso ad campaigns -a <accountId> --json
46
46
  | `headlines` | string[] | ✅ | ≥3 条标题 |
47
47
  | `longHeadlines` | string[] | ✅ | ≥1 条长标题 |
48
48
  | `descriptions` | string[] | ✅ | ≥2 条描述 |
49
- | `imagePaths.marketing` | string | ✅* | 横图 1.91:1(建议 1200×628)路径,相对配置文件目录 |
50
- | `imagePaths.square` | string | ✅* | 方图 1:1(建议 1200×1200 |
51
- | `imagePaths.logo` | string | ✅* | Logo 1:1 |
52
- | `marketingImageBase64` | string | ✅* | `imagePaths.marketing` 二选一 |
53
- | `squareMarketingImageBase64` | string | ✅* | `imagePaths.square` 二选一 |
54
- | `logoImageBase64` | string | ✅* | `imagePaths.logo` 二选一 |
49
+ | `imagePaths.marketing` | string | ✅* | 横图 1.91:1(建议 1200×628)路径,相对配置文件目录;**提交时自动上传** |
50
+ | `imagePaths.square` | string | ✅* | 方图 1:1(建议 1200×1200);自动上传 |
51
+ | `imagePaths.logo` | string | ✅* | Logo 1:1;自动上传 |
52
+ | `marketingImageAssetId` | string | ✅* | 已上传横图 asset.id(与 path/base64 三选一) |
53
+ | `squareMarketingImageAssetId` | string | ✅* | 已上传方图 asset.id |
54
+ | `logoImageAssetId` | string | ✅* | 已上传 Logo asset.id |
55
+ | `marketingImageBase64` | string | ✅* | 内联 Base64(不推荐;无 path 时用) |
56
+ | `squareMarketingImageBase64` | string | ✅* | 内联方图 Base64 |
57
+ | `logoImageBase64` | string | ✅* | 内联 Logo Base64 |
55
58
  | `targetedLocations` | `{ id }[]` | | 地理 criterion ID,如 `{ "id": "2840" }`(美国) |
56
59
  | `targetedLanguages` | `{ id }[]` | | 语言 ID,如 `{ "id": 1000 }`(英语) |
57
60
  | `biddingStrategyTypeV2` | string | | 默认 `MAXIMIZE_CONVERSIONS` |
@@ -38,6 +38,12 @@
38
38
 
39
39
  模板:`assets/pmax-create-template.json`、`pmax-asset-group-template.json`、`pmax-assets-update-template.json`、`pmax-signals-template.json`、`pmax-patch-campaign-template.json`。
40
40
 
41
+ ### 图片(Agent 推荐)
42
+
43
+ 1. JSON 中只配置 `imagePaths`(三张本地图路径)。
44
+ 2. `ad pmax-validate` → 用户确认 → `ad pmax-create`:**CLI 自动** `POST /mediamanagement/imageasset/{accountId}`(multipart)×3,再以 `*ImageAssetId` 调用 `POST .../campaign/pmax`(创建 Body **不含** 大图 Base64)。
45
+ 3. 单张预上传(可选):`ad pmax-image-upload -a <id> --image-path ./x.png`,将返回的 `id` 写入 `marketingImageAssetId` 等。
46
+
41
47
  ---
42
48
 
43
49
  ## 编辑流程(无整包 PUT)
@@ -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.21-beta.2'
12
+ $PKG_VERSION = '1.1.21-beta.3'
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.21-beta.2"
12
+ readonly PKG_VERSION="1.1.21-beta.3"
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.21-beta.2",
3
+ "version": "1.1.21-beta.3",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",