siluzan-tso-cli 1.1.21-beta.2 → 1.1.21-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.21-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.21-beta.4),供内部测试使用。正式发布后安装命令将改为 `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);
@@ -5066,6 +5066,20 @@ var init_normalize_money = __esm({
5066
5066
  }
5067
5067
  });
5068
5068
 
5069
+ // src/commands/google-analysis/normalize-overview.ts
5070
+ function normalizeOverviewPayload(payload) {
5071
+ const obj = payload;
5072
+ if (typeof obj.balance === "number" && obj.balance === 0) {
5073
+ delete obj.balance;
5074
+ }
5075
+ return payload;
5076
+ }
5077
+ var init_normalize_overview = __esm({
5078
+ "src/commands/google-analysis/normalize-overview.ts"() {
5079
+ "use strict";
5080
+ }
5081
+ });
5082
+
5069
5083
  // src/commands/google-analysis/match-type-en2zh.json
5070
5084
  var match_type_en2zh_default;
5071
5085
  var init_match_type_en2zh = __esm({
@@ -100587,8 +100601,8 @@ function rowsFromAccountDailyReportsEnvelope(raw, mediaCustomerId) {
100587
100601
  const list = block?.data;
100588
100602
  return Array.isArray(list) ? list : [];
100589
100603
  }
100590
- async function fetchGoogleAnalysisSectionJson(config, fullPath, verbose, name) {
100591
- switch (name) {
100604
+ async function fetchGoogleAnalysisSectionJson(config, fullPath, verbose, name2) {
100605
+ switch (name2) {
100592
100606
  case "overview":
100593
100607
  return fetchJson(config, fullPath, verbose);
100594
100608
  case "keywords":
@@ -100632,7 +100646,7 @@ async function fetchGoogleAnalysisSectionJson(config, fullPath, verbose, name) {
100632
100646
  case "campaign-types":
100633
100647
  return fetchJson(config, fullPath, verbose);
100634
100648
  default:
100635
- return assertNever(name, "google-analysis");
100649
+ return assertNever(name2, "google-analysis");
100636
100650
  }
100637
100651
  }
100638
100652
  function endpointHintFrom(def) {
@@ -100731,13 +100745,13 @@ async function fetchSectionPayload(def, opts, config, id) {
100731
100745
  !!opts.verbose,
100732
100746
  def.name
100733
100747
  );
100734
- return annotateZhFields(
100735
- normalizeMoneyScales(
100736
- normalizeRateScales(stripLegacyGoogleFieldsIfV2Present(data), def.name),
100737
- def.name
100738
- ),
100739
- def.name
100740
- );
100748
+ let payload = stripLegacyGoogleFieldsIfV2Present(data);
100749
+ payload = normalizeRateScales(payload, def.name);
100750
+ payload = normalizeMoneyScales(payload, def.name);
100751
+ if (def.name === "overview") {
100752
+ payload = normalizeOverviewPayload(payload);
100753
+ }
100754
+ return annotateZhFields(payload, def.name);
100741
100755
  }
100742
100756
  function endpointHintForSection(def) {
100743
100757
  if (def.name === "materials") return "CampaignAssetView+Videos";
@@ -100779,6 +100793,7 @@ var init_fetch = __esm({
100779
100793
  init_sections();
100780
100794
  init_normalize_rates();
100781
100795
  init_normalize_money();
100796
+ init_normalize_overview();
100782
100797
  init_translate_fields();
100783
100798
  }
100784
100799
  });
@@ -105684,8 +105699,8 @@ async function readReportManifestIfExists(absDir, accountId) {
105684
105699
  if (accountId && candidates[0] !== REPORT_MANIFEST_FILE) {
105685
105700
  candidates.push(REPORT_MANIFEST_FILE);
105686
105701
  }
105687
- for (const name of candidates) {
105688
- const manifestPath = path12.join(absDir, name);
105702
+ for (const name2 of candidates) {
105703
+ const manifestPath = path12.join(absDir, name2);
105689
105704
  try {
105690
105705
  const raw = await fs8.readFile(manifestPath, "utf8");
105691
105706
  const parsed = JSON.parse(raw);
@@ -106265,11 +106280,11 @@ async function runReportPushHistory(opts) {
106265
106280
  const setting = d?.["setting"] ?? {};
106266
106281
  const mediaEx = d?.["mediaAccountEx"] ?? {};
106267
106282
  const reportEx = d?.["accountReportEx"] ?? {};
106268
- const name = String(setting["name"] ?? "");
106283
+ const name2 = String(setting["name"] ?? "");
106269
106284
  const acct = mediaEx["mediaCustomerId"] != null && mediaEx["mediaCustomerName"] != null ? `${mediaEx["mediaCustomerId"]}-${mediaEx["mediaCustomerName"]}` : "";
106270
106285
  const cycle = reportEx["reportStartDate"] != null ? String(reportEx["reportStartDate"]).slice(0, 10) : "";
106271
106286
  const rid = String(row["entityId"] ?? row["id"] ?? "");
106272
- return { name, account: acct, cycle, id: rid };
106287
+ return { name: name2, account: acct, cycle, id: rid };
106273
106288
  });
106274
106289
  printCliTable(rows, [
106275
106290
  { key: "name", header: "\u63A8\u9001\u540D\u79F0" },
@@ -106698,8 +106713,8 @@ function registerReportTiktokCommands(reportCmd) {
106698
106713
  ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
106699
106714
  await runReportTikTokOverview(opts);
106700
106715
  });
106701
- const bindTake = (name, segmentLabel, runner) => {
106702
- const cmd = reportCmd.command(name).description(`${desc} \u2014 ${segmentLabel}`);
106716
+ const bindTake = (name2, segmentLabel, runner) => {
106717
+ const cmd = reportCmd.command(name2).description(`${desc} \u2014 ${segmentLabel}`);
106703
106718
  addDateAndFetchOptions(cmd);
106704
106719
  cmd.action(async (opts) => {
106705
106720
  await runner(opts);
@@ -107184,8 +107199,8 @@ function addBingDateOptions(c) {
107184
107199
  }
107185
107200
  function registerReportBingCommands(reportCmd) {
107186
107201
  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}`);
107202
+ const bindSimple = (name2, label, runner) => {
107203
+ const cmd = reportCmd.command(name2).description(`${desc} \u2014 ${label}`);
107189
107204
  addBingDateOptions(cmd);
107190
107205
  cmd.action(async (opts) => {
107191
107206
  await runner(opts);
@@ -107205,8 +107220,8 @@ function registerReportBingCommands(reportCmd) {
107205
107220
  ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107206
107221
  await runReportBingAudienceMerged(opts);
107207
107222
  });
107208
- const bindLimit = (name, label, runner) => {
107209
- const cmd = reportCmd.command(name).description(`${desc} \u2014 ${label}`);
107223
+ const bindLimit = (name2, label, runner) => {
107224
+ const cmd = reportCmd.command(name2).description(`${desc} \u2014 ${label}`);
107210
107225
  addBingDateOptions(cmd);
107211
107226
  cmd.option(
107212
107227
  "--limit <n>",
@@ -109307,10 +109322,10 @@ TikTok \u7EBF\u7D22\u8868\u5355\uFF08\u8D26\u6237 ${opts.account}\uFF0C\u7B2C 1
109307
109322
  for (const lead of leads) {
109308
109323
  const sys = lead.system_fields ?? {};
109309
109324
  const custom = lead.custom_fields ?? {};
109310
- const name = custom["name"] ?? custom["full_name"] ?? "\u2014";
109325
+ const name2 = custom["name"] ?? custom["full_name"] ?? "\u2014";
109311
109326
  const email = custom["email"] ?? "\u2014";
109312
109327
  const phone = custom["phone number"] ?? custom["phone"] ?? "\u2014";
109313
- console.log(` [${sys.region ?? "\u2014"}] ${name} ${email} ${phone}`);
109328
+ console.log(` [${sys.region ?? "\u2014"}] ${name2} ${email} ${phone}`);
109314
109329
  console.log(
109315
109330
  ` \u8868\u5355: ${sys.form_name ?? "\u2014"} \u5E7F\u544A: ${sys.ad_name ?? "\u2014"} \u65F6\u95F4: ${sys.created_time ?? "\u2014"}`
109316
109331
  );
@@ -109371,11 +109386,11 @@ Meta \u7EBF\u7D22\u8868\u5355\uFF08\u8D26\u6237 ${opts.account}\uFF0C\u7B2C 1 \u
109371
109386
  if (f.name) acc[f.name] = (f.values ?? []).join(", ");
109372
109387
  return acc;
109373
109388
  }, {});
109374
- const name = fields["full_name"] ?? fields["name"] ?? "\u2014";
109389
+ const name2 = fields["full_name"] ?? fields["name"] ?? "\u2014";
109375
109390
  const email = fields["email"] ?? "\u2014";
109376
109391
  const phone = fields["phone_number"] ?? fields["phone"] ?? "\u2014";
109377
109392
  const formName = lead.form?.name ?? "\u2014";
109378
- console.log(` ${name} ${email} ${phone}`);
109393
+ console.log(` ${name2} ${email} ${phone}`);
109379
109394
  console.log(` \u8868\u5355: ${formName} \u65F6\u95F4: ${lead.created_Time ?? "\u2014"}`);
109380
109395
  console.log();
109381
109396
  }
@@ -110842,13 +110857,13 @@ function validateCampaignCreateConfigCore(cfg) {
110842
110857
  "campaign.TargetContentNetwork \u5FC5\u987B\u4E3A false\uFF08\u641C\u7D22\u4E13\u5C5E\u65B9\u6848\uFF1A\u7981\u6B62\u5C55\u793A\u5E7F\u544A\u7F51\u7EDC\uFF09"
110843
110858
  );
110844
110859
  }
110845
- const name = campaign["Name"];
110846
- if (typeof name !== "string" || !name.trim()) {
110860
+ const name2 = campaign["Name"];
110861
+ if (typeof name2 !== "string" || !name2.trim()) {
110847
110862
  pushErr2(errors, "campaign.Name \u4E0D\u80FD\u4E3A\u7A7A");
110848
- } else if (cfg.name?.trim() && cfg.name.trim() !== name.trim()) {
110863
+ } else if (cfg.name?.trim() && cfg.name.trim() !== name2.trim()) {
110849
110864
  pushWarn2(
110850
110865
  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`
110866
+ `\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
110867
  );
110853
110868
  }
110854
110869
  const budget = campaign["Budget"];
@@ -111375,8 +111390,8 @@ async function runAdCampaignCreate(opts) {
111375
111390
  let adCount = 0;
111376
111391
  const groupNames = [];
111377
111392
  for (const g of adGroups) {
111378
- const name = g["Name"];
111379
- if (typeof name === "string" && name) groupNames.push(name);
111393
+ const name2 = g["Name"];
111394
+ if (typeof name2 === "string" && name2) groupNames.push(name2);
111380
111395
  const kws = g["KeywordsForBatchJob"] ?? [];
111381
111396
  for (const block of kws) kwCount += block.KeywordText?.length ?? 0;
111382
111397
  const ads = g["AdsForBatchJob"] ?? [];
@@ -112592,9 +112607,9 @@ async function runAdGeoSearch(opts) {
112592
112607
  }
112593
112608
  for (const item of items) {
112594
112609
  const id = String(item["id"] ?? "");
112595
- const name = String(item["locationName"] ?? item["canonicalName"] ?? item["name"] ?? "");
112610
+ const name2 = String(item["locationName"] ?? item["canonicalName"] ?? item["name"] ?? "");
112596
112611
  const type = String(item["targetType"] ?? item["typeV2"] ?? "");
112597
- console.log(` id:${id} ${name} [${type}]`);
112612
+ console.log(` id:${id} ${name2} [${type}]`);
112598
112613
  }
112599
112614
  console.log();
112600
112615
  }
@@ -112650,14 +112665,14 @@ async function runAdGeoList(opts) {
112650
112665
  }
112651
112666
  for (const item of items) {
112652
112667
  const id = String(item["id"] ?? item["countryCriteriaId"] ?? "");
112653
- const name = String(
112668
+ const name2 = String(
112654
112669
  item["name"] ?? item["countryOrRegion"] ?? item["country"] ?? item["location"] ?? item["canonicalName"] ?? ""
112655
112670
  );
112656
112671
  const campaign = item["campaignName"] ? ` campaign:${item["campaignName"]}` : "";
112657
112672
  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
112673
  const spend = item["spend"] != null ? ` \u6D88\u8017:${item["spend"]}` : "";
112659
112674
  const clicks = item["clicks"] != null ? ` \u70B9\u51FB:${item["clicks"]}` : "";
112660
- console.log(` id:${id} ${name}${campaign}${bid}${spend}${clicks}`);
112675
+ console.log(` id:${id} ${name2}${campaign}${bid}${spend}${clicks}`);
112661
112676
  }
112662
112677
  console.log();
112663
112678
  }
@@ -112772,12 +112787,12 @@ async function runAdGeoSetBid(opts) {
112772
112787
  process.exit(1);
112773
112788
  }
112774
112789
  const row = rows.find((r) => String(r["id"] ?? "") === criterionId);
112775
- const name = String(
112790
+ const name2 = String(
112776
112791
  row?.["locationName"] ?? row?.["name"] ?? row?.["countryOrRegion"] ?? criterionId
112777
112792
  );
112778
112793
  console.log(
112779
112794
  `
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
112795
+ \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
112796
  `
112782
112797
  );
112783
112798
  }
@@ -113744,8 +113759,11 @@ function nonEmptyStrings(list) {
113744
113759
  }
113745
113760
  function hasImageSource(cfg, kind) {
113746
113761
  const paths = cfg.imagePaths;
113762
+ const assetIdKey = kind === "marketing" ? "marketingImageAssetId" : kind === "square" ? "squareMarketingImageAssetId" : "logoImageAssetId";
113747
113763
  const base64Key = kind === "marketing" ? "marketingImageBase64" : kind === "square" ? "squareMarketingImageBase64" : "logoImageBase64";
113748
113764
  const pathKey = kind;
113765
+ const assetId = cfg[assetIdKey];
113766
+ if (typeof assetId === "string" && assetId.trim().length > 0) return true;
113749
113767
  const b64 = cfg[base64Key];
113750
113768
  if (typeof b64 === "string" && b64.trim().length > 0) return true;
113751
113769
  const p = paths?.[pathKey];
@@ -113822,13 +113840,22 @@ function runPmaxCreateValidation(cfg) {
113822
113840
  pushErr3(errors, "businessName\uFF08\u5546\u5BB6\u540D\u79F0\uFF09\u5FC5\u586B");
113823
113841
  }
113824
113842
  if (!hasImageSource(cfg, "marketing")) {
113825
- pushErr3(errors, "\u6A2A\u56FE\u5FC5\u586B\uFF1AimagePaths.marketing \u6216 marketingImageBase64");
113843
+ pushErr3(
113844
+ errors,
113845
+ "\u6A2A\u56FE\u5FC5\u586B\uFF1AimagePaths.marketing\uFF08\u63A8\u8350\uFF0C\u63D0\u4EA4\u65F6\u81EA\u52A8\u4E0A\u4F20\uFF09\u6216 marketingImageAssetId / marketingImageBase64"
113846
+ );
113826
113847
  }
113827
113848
  if (!hasImageSource(cfg, "square")) {
113828
- pushErr3(errors, "\u65B9\u56FE\u5FC5\u586B\uFF1AimagePaths.square \u6216 squareMarketingImageBase64");
113849
+ pushErr3(
113850
+ errors,
113851
+ "\u65B9\u56FE\u5FC5\u586B\uFF1AimagePaths.square \u6216 squareMarketingImageAssetId / squareMarketingImageBase64"
113852
+ );
113829
113853
  }
113830
113854
  if (!hasImageSource(cfg, "logo")) {
113831
- pushErr3(errors, "Logo \u5FC5\u586B\uFF1AimagePaths.logo \u6216 logoImageBase64");
113855
+ pushErr3(
113856
+ errors,
113857
+ "Logo \u5FC5\u586B\uFF1AimagePaths.logo \u6216 logoImageAssetId / logoImageBase64"
113858
+ );
113832
113859
  }
113833
113860
  const bidding = normalizeBidding(cfg.biddingStrategyTypeV2);
113834
113861
  if (!bidding) {
@@ -114054,9 +114081,205 @@ async function runPmaxImageValidation(configFile, cfg) {
114054
114081
  return { errors, warnings };
114055
114082
  }
114056
114083
 
114057
- // src/commands/ad/pmax-load.ts
114084
+ // src/commands/ad/pmax-image-resolve.ts
114085
+ import { basename as basename4, dirname as dirname8, isAbsolute as isAbsolute3, resolve as resolve7 } from "path";
114058
114086
  import { readFileSync as readFileSync5 } from "fs";
114059
- import { dirname as dirname8, isAbsolute as isAbsolute3, resolve as resolve7 } from "path";
114087
+
114088
+ // src/commands/ad/pmax-urls.ts
114089
+ function pmaxChannelTypesUrl(googleApiUrl) {
114090
+ const base = googleApiUrl.replace(/\/$/, "");
114091
+ return `${base}/campaign/pmax/channel-types`;
114092
+ }
114093
+ function pmaxCampaignUrl(googleApiUrl, accountId, campaignId) {
114094
+ const base = googleApiUrl.replace(/\/$/, "");
114095
+ const path22 = `${base}/accounts/${accountId}/campaign/pmax`;
114096
+ return campaignId ? `${path22}/${campaignId}` : path22;
114097
+ }
114098
+ function pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, suffix) {
114099
+ const base = googleApiUrl.replace(/\/$/, "");
114100
+ const path22 = `${base}/accounts/${accountId}/campaign/pmax/asset-group/${assetGroupId}`;
114101
+ return suffix ? `${path22}/${suffix.replace(/^\//, "")}` : path22;
114102
+ }
114103
+ function pmaxCampaignAssetGroupUrl(googleApiUrl, accountId, campaignId) {
114104
+ const base = googleApiUrl.replace(/\/$/, "");
114105
+ return `${base}/accounts/${accountId}/campaign/pmax/${campaignId}/asset-group`;
114106
+ }
114107
+ function pmaxUserlistsUrl(googleApiUrl, accountId) {
114108
+ const base = googleApiUrl.replace(/\/$/, "");
114109
+ return `${base}/campaignmanagement/userlists/${accountId}`;
114110
+ }
114111
+ function pmaxAudiencesUrl(googleApiUrl, accountId, limit) {
114112
+ const base = googleApiUrl.replace(/\/$/, "");
114113
+ const url = `${base}/campaignmanagement/audiences/${accountId}`;
114114
+ if (limit != null && Number.isFinite(limit)) {
114115
+ return `${url}?limit=${Math.floor(limit)}`;
114116
+ }
114117
+ return url;
114118
+ }
114119
+ function pmaxImageAssetUrl(googleApiUrl, accountId, name2) {
114120
+ const base = googleApiUrl.replace(/\/$/, "");
114121
+ const url = `${base}/mediamanagement/imageasset/${accountId}`;
114122
+ if (name2?.trim()) {
114123
+ return `${url}?name=${encodeURIComponent(name2.trim())}`;
114124
+ }
114125
+ return url;
114126
+ }
114127
+ function pmaxAssetGroupReportUrl(googleApiUrl, accountId, query) {
114128
+ const base = googleApiUrl.replace(/\/$/, "");
114129
+ const qs = query.toString();
114130
+ return `${base}/reporting/media-account/${accountId}/pmax/asset-groups/reports${qs ? `?${qs}` : ""}`;
114131
+ }
114132
+ function pmaxGeoReportUrl(googleApiUrl, accountId, query) {
114133
+ const base = googleApiUrl.replace(/\/$/, "");
114134
+ const qs = query.toString();
114135
+ return `${base}/reporting/media-account/${accountId}/pmax/campaigns/reports/geo${qs ? `?${qs}` : ""}`;
114136
+ }
114137
+
114138
+ // src/commands/ad/pmax-image-resolve.ts
114139
+ function resolvePmaxImagePath(configFile, relOrAbs) {
114140
+ const trimmed = relOrAbs.trim();
114141
+ if (isAbsolute3(trimmed)) return trimmed;
114142
+ return resolve7(dirname8(configFile), trimmed);
114143
+ }
114144
+ var SLOT_META = {
114145
+ marketing: {
114146
+ assetIdKey: "marketingImageAssetId",
114147
+ base64Key: "marketingImageBase64",
114148
+ pathKey: "marketing",
114149
+ configAssetIdKey: "marketingImageAssetId",
114150
+ configBase64Key: "marketingImageBase64",
114151
+ label: "\u6A2A\u56FE (MARKETING_IMAGE)"
114152
+ },
114153
+ square: {
114154
+ assetIdKey: "squareMarketingImageAssetId",
114155
+ base64Key: "squareMarketingImageBase64",
114156
+ pathKey: "square",
114157
+ configAssetIdKey: "squareMarketingImageAssetId",
114158
+ configBase64Key: "squareMarketingImageBase64",
114159
+ label: "\u65B9\u56FE (SQUARE_MARKETING_IMAGE)"
114160
+ },
114161
+ logo: {
114162
+ assetIdKey: "logoImageAssetId",
114163
+ base64Key: "logoImageBase64",
114164
+ pathKey: "logo",
114165
+ configAssetIdKey: "logoImageAssetId",
114166
+ configBase64Key: "logoImageBase64",
114167
+ label: "Logo (LOGO)"
114168
+ }
114169
+ };
114170
+ function guessContentType(fileName) {
114171
+ const lower = fileName.toLowerCase();
114172
+ if (lower.endsWith(".png")) return "image/png";
114173
+ if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
114174
+ if (lower.endsWith(".gif")) return "image/gif";
114175
+ if (lower.endsWith(".webp")) return "image/webp";
114176
+ return "application/octet-stream";
114177
+ }
114178
+ function normalizePmaxAssetId(value) {
114179
+ if (value == null) return "";
114180
+ if (typeof value === "number" && Number.isFinite(value)) return String(Math.trunc(value));
114181
+ const s = String(value).trim();
114182
+ if (!s) return "";
114183
+ const m = s.match(/\/assets\/(\d+)\s*$/);
114184
+ return m ? m[1] : s;
114185
+ }
114186
+ async function uploadPmaxImageFile(config, googleApiUrl, accountId, absImagePath, name2, verbose) {
114187
+ const fileName = basename4(absImagePath);
114188
+ const assetName = (name2 ?? fileName).trim() || fileName;
114189
+ const fileBuffer = readFileSync5(absImagePath);
114190
+ const mimeType = guessContentType(fileName);
114191
+ const form = new FormData();
114192
+ form.append("file", new Blob([fileBuffer], { type: mimeType }), fileName);
114193
+ const url = pmaxImageAssetUrl(googleApiUrl, accountId, assetName);
114194
+ const headers = {
114195
+ "Accept-Language": "zh-CN",
114196
+ Datapermission: config.dataPermission ?? ""
114197
+ };
114198
+ if (config.apiKey) headers["x-api-key"] = config.apiKey;
114199
+ else headers.Authorization = `Bearer ${config.authToken}`;
114200
+ const res = await fetch(url, { method: "POST", headers, body: form });
114201
+ const text = await res.text();
114202
+ if (!res.ok) {
114203
+ const detail = verbose ? `\uFF1A${text.slice(0, 300)}` : "";
114204
+ throw new Error(`\u4E0A\u4F20\u56FE\u7247\u8D44\u4EA7 HTTP ${res.status}${detail}\uFF08${absImagePath}\uFF09`);
114205
+ }
114206
+ let data;
114207
+ try {
114208
+ data = JSON.parse(text);
114209
+ } catch {
114210
+ throw new Error(`\u4E0A\u4F20\u56FE\u7247\u8D44\u4EA7\u54CD\u5E94\u975E JSON\uFF08${absImagePath}\uFF09`);
114211
+ }
114212
+ const id = normalizePmaxAssetId(data["id"]);
114213
+ if (!id) {
114214
+ throw new Error(`\u4E0A\u4F20\u56FE\u7247\u8D44\u4EA7\u672A\u8FD4\u56DE id\uFF08${absImagePath}\uFF09`);
114215
+ }
114216
+ return id;
114217
+ }
114218
+ async function resolveOneSlot(configFile, cfg, kind, accountId, config, googleApiUrl, verbose) {
114219
+ const meta = SLOT_META[kind];
114220
+ const presetId = String(cfg[meta.configAssetIdKey] ?? "").trim();
114221
+ if (presetId) {
114222
+ return { [meta.assetIdKey]: normalizePmaxAssetId(presetId) };
114223
+ }
114224
+ const inlineB64 = String(cfg[meta.configBase64Key] ?? "").trim();
114225
+ const pathRel = cfg.imagePaths?.[meta.pathKey]?.trim();
114226
+ if (pathRel) {
114227
+ const abs = resolvePmaxImagePath(configFile, pathRel);
114228
+ const id = await uploadPmaxImageFile(
114229
+ config,
114230
+ googleApiUrl,
114231
+ accountId,
114232
+ abs,
114233
+ basename4(abs),
114234
+ verbose
114235
+ );
114236
+ return { [meta.assetIdKey]: id };
114237
+ }
114238
+ if (inlineB64) {
114239
+ return { [meta.base64Key]: inlineB64 };
114240
+ }
114241
+ throw new Error(
114242
+ `${meta.label} \u672A\u914D\u7F6E\uFF1A\u8BF7\u63D0\u4F9B imagePaths.${String(meta.pathKey)}\u3001${String(meta.configBase64Key)} \u6216 ${String(meta.configAssetIdKey)}`
114243
+ );
114244
+ }
114245
+ async function resolvePmaxImageSlots(configFile, cfg, accountId, config, googleApiUrl, opts) {
114246
+ const kinds = ["marketing", "square", "logo"];
114247
+ const willUpload = kinds.some((k) => {
114248
+ const meta = SLOT_META[k];
114249
+ if (String(cfg[meta.configAssetIdKey] ?? "").trim()) return false;
114250
+ if (String(cfg[meta.configBase64Key] ?? "").trim()) return false;
114251
+ return Boolean(cfg.imagePaths?.[meta.pathKey]?.trim());
114252
+ });
114253
+ if (willUpload && opts?.logUploads !== false) {
114254
+ console.log("\n\u{1F4E4} \u6B63\u5728\u4E0A\u4F20 PMax \u56FE\u7247\u7D20\u6750\uFF08imagePaths \u2192 assetId\uFF09\u2026");
114255
+ }
114256
+ const parts = await Promise.all(
114257
+ kinds.map(
114258
+ (kind) => resolveOneSlot(configFile, cfg, kind, accountId, config, googleApiUrl, opts?.verbose)
114259
+ )
114260
+ );
114261
+ const merged = {};
114262
+ for (const part of parts) Object.assign(merged, part);
114263
+ if (willUpload && opts?.logUploads !== false) {
114264
+ console.log(
114265
+ ` \u6A2A\u56FE id\uFF1A${merged.marketingImageAssetId ?? "(base64)"} \u65B9\u56FE id\uFF1A${merged.squareMarketingImageAssetId ?? "(base64)"} Logo id\uFF1A${merged.logoImageAssetId ?? "(base64)"}`
114266
+ );
114267
+ }
114268
+ return merged;
114269
+ }
114270
+ function assertPmaxImageSlotsResolved(slots) {
114271
+ for (const kind of ["marketing", "square", "logo"]) {
114272
+ const meta = SLOT_META[kind];
114273
+ const id = slots[meta.assetIdKey];
114274
+ const b64 = slots[meta.base64Key];
114275
+ if (!id && !b64) {
114276
+ throw new Error(`${meta.label} \u89E3\u6790\u540E\u4E3A\u7A7A`);
114277
+ }
114278
+ }
114279
+ }
114280
+
114281
+ // src/commands/ad/pmax-load.ts
114282
+ import { readFileSync as readFileSync6 } from "fs";
114060
114283
  function loadPmaxCreateConfig(configFile) {
114061
114284
  const cfg = tryLoadPmaxCreateConfig(configFile);
114062
114285
  if (!cfg) {
@@ -114070,49 +114293,20 @@ function loadPmaxCreateConfig(configFile) {
114070
114293
  function tryLoadPmaxCreateConfig(configFile) {
114071
114294
  let raw;
114072
114295
  try {
114073
- raw = JSON.parse(readFileSync5(configFile, "utf8"));
114296
+ raw = JSON.parse(readFileSync6(configFile, "utf8"));
114074
114297
  } catch {
114075
114298
  return null;
114076
114299
  }
114077
114300
  return stripMetaKeys(raw);
114078
114301
  }
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
114302
  function parseLocationLanguageIds(items) {
114095
114303
  if (!items?.length) return void 0;
114096
114304
  return items.map((item) => ({
114097
114305
  id: Number(item.id)
114098
114306
  }));
114099
114307
  }
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
- }
114308
+ function buildPmaxCreateApiBody(cfg, imageSlots) {
114309
+ assertPmaxImageSlotsResolved(imageSlots);
114116
114310
  const body = {
114117
114311
  name: cfg.name.trim(),
114118
114312
  budget: toCentAmount(Number(cfg.budget)),
@@ -114121,9 +114315,7 @@ function buildPmaxCreateApiBody(configFile, cfg) {
114121
114315
  longHeadlines: cfg.longHeadlines.map((h) => h.trim()).filter(Boolean),
114122
114316
  descriptions: cfg.descriptions.map((d) => d.trim()).filter(Boolean),
114123
114317
  businessName: cfg.businessName.trim(),
114124
- marketingImageBase64,
114125
- squareMarketingImageBase64,
114126
- logoImageBase64
114318
+ ...imageSlots
114127
114319
  };
114128
114320
  if (cfg.budgetName?.trim()) body.budgetName = cfg.budgetName.trim();
114129
114321
  if (cfg.assetGroupName?.trim()) body.assetGroupName = cfg.assetGroupName.trim();
@@ -114171,9 +114363,25 @@ async function runAdPmaxCreate(opts) {
114171
114363
  const config = await ensureDataPermission(loadConfig(opts.token));
114172
114364
  const googleApiUrl = requireGoogleApi(config);
114173
114365
  const accountId = cfg.account.toString().trim();
114366
+ let imageSlots;
114367
+ try {
114368
+ imageSlots = await resolvePmaxImageSlots(
114369
+ opts.configFile,
114370
+ cfg,
114371
+ accountId,
114372
+ config,
114373
+ googleApiUrl,
114374
+ { verbose: opts.verbose, logUploads: !opts.json }
114375
+ );
114376
+ } catch (err) {
114377
+ console.error(`
114378
+ \u274C \u56FE\u7247\u4E0A\u4F20/\u89E3\u6790\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114379
+ `);
114380
+ process.exit(1);
114381
+ }
114174
114382
  let body;
114175
114383
  try {
114176
- body = buildPmaxCreateApiBody(opts.configFile, cfg);
114384
+ body = buildPmaxCreateApiBody(cfg, imageSlots);
114177
114385
  } catch (err) {
114178
114386
  console.error(`
114179
114387
  \u274C \u6784\u5EFA\u8BF7\u6C42\u4F53\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
@@ -114272,18 +114480,18 @@ async function runAdPmaxValidate(opts) {
114272
114480
  }
114273
114481
 
114274
114482
  // src/commands/ad/pmax-mgmt.ts
114275
- import { readFileSync as readFileSync7 } from "fs";
114483
+ import { basename as basename5, isAbsolute as isAbsolute5 } from "path";
114276
114484
 
114277
114485
  // src/commands/ad/pmax-shared.ts
114278
114486
  init_auth();
114279
114487
  init_cli_json_snapshot();
114280
- import { readFileSync as readFileSync6 } from "fs";
114488
+ import { readFileSync as readFileSync7 } from "fs";
114281
114489
  import { dirname as dirname9, isAbsolute as isAbsolute4, resolve as resolve8 } from "path";
114282
114490
  var PMAX_MONEY_KEYS = /* @__PURE__ */ new Set(["budget", "targetCpa_BidingAmount"]);
114283
114491
  function loadPmaxJsonFile(configFile) {
114284
114492
  let raw;
114285
114493
  try {
114286
- raw = JSON.parse(readFileSync6(configFile, "utf8"));
114494
+ raw = JSON.parse(readFileSync7(configFile, "utf8"));
114287
114495
  } catch (e) {
114288
114496
  const msg = e instanceof Error ? e.message : String(e);
114289
114497
  console.error(`
@@ -114293,21 +114501,11 @@ function loadPmaxJsonFile(configFile) {
114293
114501
  }
114294
114502
  return stripMetaKeys(raw);
114295
114503
  }
114296
- function resolveImagePath3(configFile, relOrAbs) {
114504
+ function resolvePmaxImagePath2(configFile, relOrAbs) {
114297
114505
  const trimmed = relOrAbs.trim();
114298
114506
  if (isAbsolute4(trimmed)) return trimmed;
114299
114507
  return resolve8(dirname9(configFile), trimmed);
114300
114508
  }
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
114509
  function convertPmaxMoneyInObject(body) {
114312
114510
  const out = { ...body };
114313
114511
  for (const key of PMAX_MONEY_KEYS) {
@@ -114318,22 +114516,8 @@ function convertPmaxMoneyInObject(body) {
114318
114516
  }
114319
114517
  return out;
114320
114518
  }
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
- }
114519
+ function buildPmaxAssetGroupApiBody(cfg, imageSlots) {
114520
+ assertPmaxImageSlotsResolved(imageSlots);
114337
114521
  return {
114338
114522
  name: cfg.name.trim(),
114339
114523
  finalUrls: cfg.finalUrls.map((u) => u.trim()).filter(Boolean),
@@ -114341,26 +114525,34 @@ function buildPmaxAssetGroupApiBody(configFile, cfg) {
114341
114525
  longHeadlines: cfg.longHeadlines.map((h) => h.trim()).filter(Boolean),
114342
114526
  descriptions: cfg.descriptions.map((d) => d.trim()).filter(Boolean),
114343
114527
  businessName: cfg.businessName.trim(),
114344
- marketingImageBase64,
114345
- squareMarketingImageBase64,
114346
- logoImageBase64
114528
+ ...imageSlots
114347
114529
  };
114348
114530
  }
114349
- function processAssetsUpdateBody(configFile, body) {
114531
+ async function processAssetsUpdateBodyWithUpload(configFile, body, accountId, config, googleApiUrl, verbose) {
114350
114532
  const out = { ...body };
114351
114533
  const links = out["assetsToLink"];
114352
114534
  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
- });
114535
+ out["assetsToLink"] = await Promise.all(
114536
+ links.map(async (item) => {
114537
+ if (!item || typeof item !== "object") return item;
114538
+ const row = { ...item };
114539
+ const imagePath = row["imagePath"];
114540
+ if (typeof imagePath === "string" && imagePath.trim()) {
114541
+ const abs = resolvePmaxImagePath2(configFile, imagePath);
114542
+ row["assetId"] = await uploadPmaxImageFile(
114543
+ config,
114544
+ googleApiUrl,
114545
+ accountId,
114546
+ abs,
114547
+ void 0,
114548
+ verbose
114549
+ );
114550
+ delete row["imagePath"];
114551
+ delete row["imageBase64"];
114552
+ }
114553
+ return row;
114554
+ })
114555
+ );
114364
114556
  return out;
114365
114557
  }
114366
114558
  async function withGoogleApi(opts, fn) {
@@ -114391,56 +114583,6 @@ function requireAccountId(account, label = "-a, --account") {
114391
114583
  return id;
114392
114584
  }
114393
114585
 
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
114586
  // src/commands/ad/pmax-mgmt.ts
114445
114587
  function buildReportQuery(opts) {
114446
114588
  const params = new URLSearchParams();
@@ -114598,27 +114740,49 @@ async function runAdPmaxAssetGroupCreate(opts) {
114598
114740
  console.error("\n\u274C JSON \u987B\u5305\u542B campaignId\n");
114599
114741
  process.exit(1);
114600
114742
  }
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(`
114743
+ const assetFields = {
114744
+ name: String(cfg["name"] ?? ""),
114745
+ finalUrls: cfg["finalUrls"] ?? [],
114746
+ businessName: String(cfg["businessName"] ?? ""),
114747
+ headlines: cfg["headlines"] ?? [],
114748
+ longHeadlines: cfg["longHeadlines"] ?? [],
114749
+ descriptions: cfg["descriptions"] ?? [],
114750
+ imagePaths: cfg["imagePaths"],
114751
+ marketingImageAssetId: cfg["marketingImageAssetId"],
114752
+ squareMarketingImageAssetId: cfg["squareMarketingImageAssetId"],
114753
+ logoImageAssetId: cfg["logoImageAssetId"],
114754
+ marketingImageBase64: cfg["marketingImageBase64"],
114755
+ squareMarketingImageBase64: cfg["squareMarketingImageBase64"],
114756
+ logoImageBase64: cfg["logoImageBase64"]
114757
+ };
114758
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
114759
+ let imageSlots;
114760
+ try {
114761
+ imageSlots = await resolvePmaxImageSlots(
114762
+ opts.configFile,
114763
+ assetFields,
114764
+ accountId,
114765
+ config,
114766
+ googleApiUrl,
114767
+ { verbose: opts.verbose, logUploads: !opts.json }
114768
+ );
114769
+ } catch (err) {
114770
+ console.error(
114771
+ `
114772
+ \u274C \u56FE\u7247\u4E0A\u4F20/\u89E3\u6790\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114773
+ `
114774
+ );
114775
+ process.exit(1);
114776
+ }
114777
+ let body;
114778
+ try {
114779
+ body = buildPmaxAssetGroupApiBody(assetFields, imageSlots);
114780
+ } catch (err) {
114781
+ console.error(`
114617
114782
  \u274C \u6784\u5EFA\u8BF7\u6C42\u4F53\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114618
114783
  `);
114619
- process.exit(1);
114620
- }
114621
- await withGoogleApi(opts, async (config, googleApiUrl) => {
114784
+ process.exit(1);
114785
+ }
114622
114786
  const url = pmaxCampaignAssetGroupUrl(googleApiUrl, accountId, campaignId);
114623
114787
  let data;
114624
114788
  try {
@@ -114716,17 +114880,24 @@ async function runAdPmaxAssetsUpdate(opts) {
114716
114880
  console.error("\n\u274C JSON \u987B\u5305\u542B campaignId\uFF08PUT assets body \u5FC5\u586B\uFF09\n");
114717
114881
  process.exit(1);
114718
114882
  }
114719
- let body;
114720
- try {
114721
- const { account: _a, ...rest } = cfg;
114722
- body = processAssetsUpdateBody(opts.configFile, rest);
114723
- } catch (err) {
114724
- console.error(`
114883
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
114884
+ let body;
114885
+ try {
114886
+ const { account: _a, ...rest } = cfg;
114887
+ body = await processAssetsUpdateBodyWithUpload(
114888
+ opts.configFile,
114889
+ rest,
114890
+ accountId,
114891
+ config,
114892
+ googleApiUrl,
114893
+ opts.verbose
114894
+ );
114895
+ } catch (err) {
114896
+ console.error(`
114725
114897
  \u274C \u5904\u7406 assets JSON \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
114726
114898
  `);
114727
- process.exit(1);
114728
- }
114729
- await withGoogleApi(opts, async (config, googleApiUrl) => {
114899
+ process.exit(1);
114900
+ }
114730
114901
  const url = pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, "assets");
114731
114902
  try {
114732
114903
  await pmaxApiFetch(
@@ -114967,43 +115138,55 @@ PMax \u53D7\u4F17\u6570\u636E\u6E90\uFF08\u8D26\u6237 ${accountId}\uFF09
114967
115138
  }
114968
115139
  async function runAdPmaxImageUpload(opts) {
114969
115140
  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
115141
  await withGoogleApi(opts, async (config, googleApiUrl) => {
114994
- const url = pmaxImageAssetUrl(googleApiUrl, accountId, name);
114995
115142
  let data;
114996
115143
  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 };
115144
+ if (opts.imagePath?.trim()) {
115145
+ const abs = isAbsolute5(opts.imagePath) ? opts.imagePath.trim() : resolvePmaxImagePath2(process.cwd(), opts.imagePath);
115146
+ const name2 = (opts.name ?? basename5(abs)).trim();
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 if (opts.bodyFile) {
115157
+ const cfg = loadPmaxJsonFile(opts.bodyFile);
115158
+ const name2 = String(cfg["name"] ?? opts.name ?? "cli-image").trim();
115159
+ const imagePath = String(cfg["imagePath"] ?? "").trim();
115160
+ if (imagePath) {
115161
+ const abs = resolvePmaxImagePath2(opts.bodyFile, imagePath);
115162
+ const id = await uploadPmaxImageFile(
115163
+ config,
115164
+ googleApiUrl,
115165
+ accountId,
115166
+ abs,
115167
+ name2,
115168
+ opts.verbose
115169
+ );
115170
+ data = { id, name: name2 };
115171
+ } else {
115172
+ const base64String = String(cfg["base64String"] ?? cfg["Base64String"] ?? "").trim() || "";
115173
+ if (!base64String) {
115174
+ console.error("\n\u274C body JSON \u987B\u542B imagePath \u6216 base64String\n");
115175
+ process.exit(1);
115176
+ }
115177
+ const url = pmaxImageAssetUrl(googleApiUrl, accountId, name2);
115178
+ const raw = await pmaxApiFetch(
115179
+ url,
115180
+ config,
115181
+ { method: "POST", body: JSON.stringify({ name: name2, base64String }) },
115182
+ opts.verbose
115183
+ );
115184
+ data = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : { data: raw };
115185
+ }
115186
+ } else {
115187
+ console.error("\n\u274C \u8BF7\u6307\u5B9A --image-path \u6216 --body-file\n");
115188
+ process.exit(1);
115189
+ }
115007
115190
  } catch (err) {
115008
115191
  console.error(
115009
115192
  `
@@ -115150,7 +115333,7 @@ async function runAdCampaignValidate(opts) {
115150
115333
 
115151
115334
  // src/commands/ad/pmax-image-convert.ts
115152
115335
  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";
115336
+ import { resolve as resolve9, dirname as dirname10, basename as basename6, extname } from "path";
115154
115337
  var SPECS = [
115155
115338
  { kind: "marketing", width: 1200, height: 628, fit: "cover", suffix: "_marketing" },
115156
115339
  { kind: "square", width: 1200, height: 1200, fit: "cover", suffix: "_square" },
@@ -115199,7 +115382,7 @@ async function runAdPmaxImageConvert(opts) {
115199
115382
  const firstInput = input ?? inputMarketing ?? inputSquare ?? inputLogo;
115200
115383
  const outputDir = opts.outputDir ? resolve9(opts.outputDir) : dirname10(resolve9(firstInput));
115201
115384
  mkdirSync3(outputDir, { recursive: true });
115202
- const prefix = opts.prefix ?? basename4(firstInput, extname(firstInput));
115385
+ const prefix = opts.prefix ?? basename6(firstInput, extname(firstInput));
115203
115386
  const quality = opts.quality ?? 85;
115204
115387
  const sharpMod = await loadSharp();
115205
115388
  const sharpFn = (p) => sharpMod(p);
@@ -115662,7 +115845,7 @@ function register20(program2) {
115662
115845
  }
115663
115846
  );
115664
115847
  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'
115848
+ '\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
115849
  ).requiredOption(
115667
115850
  "--config-file <path>",
115668
115851
  "JSON \u914D\u7F6E\u6587\u4EF6\uFF08\u6A21\u677F\u89C1 assets/siluzan-ads/assets/pmax-create-*.json|md\uFF09"
@@ -116780,9 +116963,9 @@ function splitQueryWhitespaceToKeywords(query) {
116780
116963
  function ragItemDedupeKey(item) {
116781
116964
  const id = String(item.id ?? "").trim();
116782
116965
  if (id) return `id:${id}`;
116783
- const name = String(item.fields?.name ?? "").trim();
116966
+ const name2 = String(item.fields?.name ?? "").trim();
116784
116967
  const content = String(item.fields?.content ?? "").slice(0, 160);
116785
- return `fb:${name}\0${content}`;
116968
+ return `fb:${name2}\0${content}`;
116786
116969
  }
116787
116970
  function mergeRagResultsByBestScore(batches) {
116788
116971
  const best = /* @__PURE__ */ new Map();
@@ -116870,12 +117053,12 @@ function formatRagResultsMarkdown(query, items, subQueries) {
116870
117053
  items.forEach((item, itemIndex) => {
116871
117054
  const fields = item.fields;
116872
117055
  if (!fields) return;
116873
- const name = fields.name ?? "\u672A\u77E5\u6587\u4EF6";
117056
+ const name2 = fields.name ?? "\u672A\u77E5\u6587\u4EF6";
116874
117057
  const content = fields.content ?? "";
116875
117058
  const sourceUrl = fields.source_url ?? "";
116876
117059
  const createTime = fields.create_time ?? "";
116877
117060
  const usageCount = fields.usage_count ?? 0;
116878
- parts.push(`### ${itemIndex + 1}. ${name}
117061
+ parts.push(`### ${itemIndex + 1}. ${name2}
116879
117062
  `);
116880
117063
  if (item.id) {
116881
117064
  parts.push(`**\u7247\u6BB5 ID\uFF1A** \`${item.id}\`
@@ -118926,7 +119109,7 @@ init_cli_table();
118926
119109
  async function uploadAttachment(filePath, apiBaseUrl, config, verbose) {
118927
119110
  const fileName = path14.basename(filePath);
118928
119111
  const fileBuffer = fs9.readFileSync(filePath);
118929
- const mimeType = guessContentType(fileName);
119112
+ const mimeType = guessContentType2(fileName);
118930
119113
  const form = new FormData();
118931
119114
  form.append("file", new Blob([fileBuffer], { type: mimeType }), fileName);
118932
119115
  const uploadUrl = `${apiBaseUrl}/command/attachment`;
@@ -118952,7 +119135,7 @@ async function uploadAttachment(filePath, apiBaseUrl, config, verbose) {
118952
119135
  }
118953
119136
  return { id: data.id, fileName, storageProvider: data.storageProvider ?? "StorageAccount" };
118954
119137
  }
118955
- function guessContentType(fileName) {
119138
+ function guessContentType2(fileName) {
118956
119139
  const ext = path14.extname(fileName).toLowerCase();
118957
119140
  const map = {
118958
119141
  ".jpg": "image/jpeg",
@@ -119227,10 +119410,10 @@ async function runOpenAccountGoogleTimezones(opts) {
119227
119410
  if (kw) {
119228
119411
  rows = rows.filter((r) => {
119229
119412
  const code = String(r.Code ?? "").toLowerCase();
119230
- const name = String(r.Name ?? "").toLowerCase();
119413
+ const name2 = String(r.Name ?? "").toLowerCase();
119231
119414
  const time = String(r.Time ?? "").toLowerCase();
119232
119415
  const label = `(${r.Time ?? ""})${r.Name ?? ""}`.toLowerCase();
119233
- return code.includes(kw) || name.includes(kw) || time.includes(kw) || label.includes(kw);
119416
+ return code.includes(kw) || name2.includes(kw) || time.includes(kw) || label.includes(kw);
119234
119417
  });
119235
119418
  }
119236
119419
  const n = rows.length;
@@ -119652,9 +119835,9 @@ async function runOpenAccountTikTokTimezones(opts) {
119652
119835
  if (kw) {
119653
119836
  rows = rows.filter((r) => {
119654
119837
  const code = String(r.Code ?? "").toLowerCase();
119655
- const name = String(r.Name ?? "").toLowerCase();
119838
+ const name2 = String(r.Name ?? "").toLowerCase();
119656
119839
  const time = String(r.Time ?? "").toLowerCase();
119657
- return code.includes(kw) || name.includes(kw) || time.includes(kw);
119840
+ return code.includes(kw) || name2.includes(kw) || time.includes(kw);
119658
119841
  });
119659
119842
  }
119660
119843
  const n = rows.length;
@@ -119741,12 +119924,12 @@ TikTok \u884C\u4E1A\u5217\u8868\uFF08\u7B2C 1 \u9875\uFF0C\u672C\u9875 ${rows.le
119741
119924
  }
119742
119925
  for (const r of rows) {
119743
119926
  const id = String(r.IndustryId ?? "");
119744
- const name = String(r.IndustryName ?? "");
119927
+ const name2 = String(r.IndustryName ?? "");
119745
119928
  const isTop = id.length >= 6 && id.slice(4, 6) === "01";
119746
119929
  if (isTop) {
119747
- console.log(` \u25B8 ${name} (${id})`);
119930
+ console.log(` \u25B8 ${name2} (${id})`);
119748
119931
  } else {
119749
- console.log(` ${name} (${id})`);
119932
+ console.log(` ${name2} (${id})`);
119750
119933
  }
119751
119934
  }
119752
119935
  console.log();
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: siluzan-tso
3
3
  description: >-
4
- Siluzan TSO 广告 skill(siluzan-tso-cli):Google/Bing/Yandex/TikTok/Kwai 账户开户与管理、Google Ads CRUD、数据分析、优化报告、智能预警、TikTok/Meta 线索、发票财务,并提供 RAG 知识库查询,google广告关键词推荐,
4
+ Siluzan TSO 广告 skill(siluzan-tso-cli):Google/Bing/Yandex/TikTok/Kwai 账户开户与管理、Google Ads CRUD、数据分析、优化报告、智能预警、TikTok/Meta 线索、发票财务,并提供 RAG 知识库查询,google广告关键词推荐;当你发现你的上下文被压缩过后,你需要根据用户的需求,重新阅读本skill相关的文件,保证skill功能正常。
5
5
  license: MIT
6
6
  metadata:
7
7
  requires: nodejs,siluzan-tso-cli
@@ -153,7 +153,7 @@ Windows 注意:部分 Agent 客户端通过 PowerShell / cmd 代执行命令
153
153
  - **常用字段**:
154
154
  - `ad campaigns --json/--json-out` → `budget`(元,与 `campaign-edit --budget` 同口径)
155
155
  - `ad groups --json` → `maxCPCAmountYuan` / `targetCpaAmountYuan`(元)
156
- - `google-analysis overview-*.json` → **余额取 `remainingAccountBudget`,禁止取 `balance`**(`balance` Google 原始字段,多数账户恒为 0)
156
+ - `google-analysis overview-*.json` → **余额只认 `remainingAccountBudget`**(`balance` 0 CLI 已剔除该字段)
157
157
  - `google-analysis campaigns-*.json` → `budgetAmountYuan` / `campaignTargetCpaYuan` / `maximizeConversionsTargetCpaYuan` / `spend` / `averageCpc` / `costPerConversion`(均元)
158
158
  - `keyword --json` → `averageCpc` / `lowTopOfPageBid` / `highTopOfPageBid` + 根级与每条 `bidAmountCurrency`(无 `-a` 为 USD;有 `-a` 为账户 `currencyCode`);`-a <mediaCustomerId>` 走账户级推荐接口;限定市场用 `keyword geo-list` + `--geo <id>`(**多 id = 汇总指标**;分市场须多次调用、每次一个 `--geo`,见 `references/keyword-planner-workflows.md`)
159
159
  - **品牌名优先级**:(1) 用户明确提供 → (2) `list-accounts.mag.advertiserName` → (3) 用户提供网址 → 域名占位并标注 `[待确认品牌名]`。**严禁**把英文域名翻译为虚构中文品牌。
@@ -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.4",
4
+ "publishedAt": 1779697477475
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` |
@@ -84,7 +84,7 @@ CLI 出口的所有 JSON / 表格金额已统一为**元**,关键字段:
84
84
  | `ad groups --json` | `maxCPCAmountYuan`、`targetCpaAmountYuan` |
85
85
  | `google-analysis campaigns` 落盘 `campaigns-*.json` | `budgetAmountYuan`、`campaignTargetCpaYuan`、`maximizeConversionsTargetCpaYuan`;同行 `spend` / `averageCpc` / `costPerConversion` 也是元 |
86
86
  | `keyword suggest --json` | `averageCpc`、`lowTopOfPageBid`、`highTopOfPageBid`;根级与每条 `bidAmountCurrency`(有 `-a` 为账户币;无 `-a` 为 USD) |
87
- | `balance` 等账户余额接口 | `remainingAccountBudget`(元);**注意:`google-analysis overview` 同时返回 `balance`(Google 原始字段,多数账户恒为 0)和 `remainingAccountBudget`(真实余额),余额取值必须用后者** |
87
+ | `balance` 等账户余额接口 | 余额字段为`remainingAccountBudget`(元) |
88
88
 
89
89
  旧字段 `budgetAmount`(分)、`maxCPCAmountDisplay`、`*Micros`(微元)**已不再落盘**,下游脚本无需做单位换算。金额保留 2 位小数,带货币代码(如 `¥50.00 CNY`、`$50.00 USD`),`currencyCode` 从响应读取,跨币种账户分表;细则见 `references/currency.md`。
90
90
 
@@ -43,7 +43,7 @@ siluzan-tso balance -m Google --accounts <mediaCustomerId> --json
43
43
  mkdir -p ./snap-monitor && siluzan-tso google-analysis -a <mediaCustomerId> --sections overview --start <YYYY-MM-DD> --end <YYYY-MM-DD> --json-out ./snap-monitor
44
44
  ```
45
45
 
46
- 读 **`./snap-monitor/overview-<accountId>.json`**(具体路径见 stdout 摘要的 `writtenFiles[0]` 或 `manifest-<accountId>.json` 的 `artifacts`)。根对象常见字段:**`remainingAccountBudget`**、**`averageDailyCost`**、**`totalCost`**、**`activeDays`**、**`currencyCode`**、**`accountId`**;**`currentPeriod`** / **`previousPeriod`** 为对象块,内含 **`spend`**、`impressions`、`clicks`、`conversions` 等与 `references/account-analytics.md` 总览口径一致的指标(块内还可能出现 **`currencyCode`**)。
46
+ 读 **`./snap-monitor/overview-<accountId>.json`**(具体路径见 stdout 摘要的 `writtenFiles[0]` 或 `manifest-<accountId>.json` 的 `artifacts`)。根对象常见字段:**`remainingAccountBudget`**(余额)、**`averageDailyCost`**、**`totalCost`**、**`activeDays`**、**`currencyCode`**、**`accountId`**(无 **`balance`**,网关为 0 时 CLI 已剔除);**`currentPeriod`** / **`previousPeriod`** 为对象块,内含 **`spend`**、`impressions`、`clicks`、`conversions` 等与 `references/account-analytics.md` 总览口径一致的指标(块内还可能出现 **`currencyCode`**)。
47
47
 
48
48
  ---
49
49
 
@@ -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.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.21-beta.2"
12
+ readonly PKG_VERSION="1.1.21-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.21-beta.2",
3
+ "version": "1.1.21-beta.4",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",