siluzan-tso-cli 1.1.43-beta.2 → 1.1.43-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.43-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.43-beta.3),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -4816,6 +4816,19 @@ function normalizeNumericMediaCustomerId(raw) {
4816
4816
  if (!looksLikeNumericMediaCustomerId(raw)) return null;
4817
4817
  return stripMediaCustomerIdDecorators(raw);
4818
4818
  }
4819
+ function requireNumericMediaCustomerId(raw, label = "-a, --account") {
4820
+ const trimmed = raw?.toString().trim() ?? "";
4821
+ if (!trimmed) {
4822
+ throw new Error(`\u8BF7\u6307\u5B9A\u5A92\u4F53\u8D26\u6237 ID\uFF08${label}\uFF09`);
4823
+ }
4824
+ const id = normalizeNumericMediaCustomerId(trimmed);
4825
+ if (!id) {
4826
+ throw new Error(
4827
+ `\u5A92\u4F53\u8D26\u6237 ID \u5FC5\u987B\u4E3A\u7EAF\u6570\u5B57\uFF08Google CID \u53EF\u542B\u8FDE\u5B57\u7B26\uFF0C\u5982 123-456-7890\uFF1B\u52FF\u4F20 entityId/UUID\uFF09\uFF1A${trimmed}`
4828
+ );
4829
+ }
4830
+ return id;
4831
+ }
4819
4832
  function looksLikeEntityIdUuid(raw) {
4820
4833
  return ENTITY_ID_UUID_RE.test(raw.trim());
4821
4834
  }
@@ -107265,6 +107278,20 @@ init_list_fetch();
107265
107278
 
107266
107279
  // src/commands/ad/_shared.ts
107267
107280
  init_auth();
107281
+ init_media_customer_id();
107282
+ function resolveAdAccountId(account, label = "-a, --account") {
107283
+ try {
107284
+ return requireNumericMediaCustomerId(account, label);
107285
+ } catch (e) {
107286
+ console.error(`
107287
+ \u274C ${e instanceof Error ? e.message : String(e)}
107288
+ `);
107289
+ process.exit(1);
107290
+ }
107291
+ }
107292
+ function withResolvedAdAccount(opts) {
107293
+ return { ...opts, account: resolveAdAccountId(opts.account) };
107294
+ }
107268
107295
  async function findItemInList(listUrl, config, id, verbose) {
107269
107296
  const res = await apiFetch2(listUrl, config, {}, verbose);
107270
107297
  const item = (res.data ?? []).find((x) => x["id"] === id);
@@ -111394,6 +111421,9 @@ init_cli_json_snapshot();
111394
111421
  init_strip_legacy_google_fields();
111395
111422
  init_cli_table();
111396
111423
 
111424
+ // src/commands/ad/campaign-create-validate.ts
111425
+ init_media_customer_id();
111426
+
111397
111427
  // src/commands/ad/campaign-length-violations.ts
111398
111428
  function pushLengthViolation(violations, item) {
111399
111429
  violations.push({ ...item, excess: item.actual - item.limit });
@@ -111791,6 +111821,7 @@ function keywordRowToYuan(item) {
111791
111821
  return { ...rest, maxCPCYuan: toDisplayMoney(rawMaxCpc) };
111792
111822
  }
111793
111823
  async function runAdKeywords(opts) {
111824
+ opts = withResolvedAdAccount(opts);
111794
111825
  const config = loadConfig(opts.token);
111795
111826
  const googleApiUrl = requireGoogleApi(config);
111796
111827
  const params = new URLSearchParams();
@@ -111894,6 +111925,7 @@ ${label}\uFF08\u8D26\u6237\uFF1A${opts.account}\uFF0C\u7B2C 1 \u9875\uFF0C\u672C
111894
111925
  console.log();
111895
111926
  }
111896
111927
  async function runAdKeywordCreate(opts) {
111928
+ opts = withResolvedAdAccount(opts);
111897
111929
  const config = loadConfig(opts.token);
111898
111930
  const googleApiUrl = requireGoogleApi(config);
111899
111931
  let keywords;
@@ -111946,6 +111978,7 @@ async function runAdKeywordCreate(opts) {
111946
111978
  `);
111947
111979
  }
111948
111980
  async function runAdNegativeKeywordCreate(opts) {
111981
+ opts = withResolvedAdAccount(opts);
111949
111982
  const config = loadConfig(opts.token);
111950
111983
  const googleApiUrl = requireGoogleApi(config);
111951
111984
  let keywords;
@@ -111985,6 +112018,7 @@ async function runAdNegativeKeywordCreate(opts) {
111985
112018
  `);
111986
112019
  }
111987
112020
  async function runAdKeywordDelete(opts) {
112021
+ opts = withResolvedAdAccount(opts);
111988
112022
  const config = await ensureDataPermission(loadConfig(opts.token));
111989
112023
  const googleApiUrl = requireGoogleApi(config);
111990
112024
  const body = [{ adGroupId: opts.adgroupId, id: opts.id }];
@@ -112002,6 +112036,7 @@ async function runAdKeywordDelete(opts) {
112002
112036
  `);
112003
112037
  }
112004
112038
  async function runAdKeywordEdit(opts) {
112039
+ opts = withResolvedAdAccount(opts);
112005
112040
  const config = await ensureDataPermission(loadConfig(opts.token));
112006
112041
  const googleApiUrl = requireGoogleApi(config);
112007
112042
  const hasChange = opts.text !== void 0 || opts.matchType !== void 0 || opts.maxCpc !== void 0 || opts.finalUrl !== void 0 || opts.status !== void 0;
@@ -112067,6 +112102,7 @@ async function runAdKeywordEdit(opts) {
112067
112102
  `);
112068
112103
  }
112069
112104
  async function runAdKeywordStatus(opts) {
112105
+ opts = withResolvedAdAccount(opts);
112070
112106
  const config = await ensureDataPermission(loadConfig(opts.token));
112071
112107
  const googleApiUrl = requireGoogleApi(config);
112072
112108
  const params = new URLSearchParams();
@@ -112102,6 +112138,7 @@ async function runAdKeywordStatus(opts) {
112102
112138
  `);
112103
112139
  }
112104
112140
  async function runAdNegativeKeywordEdit(opts) {
112141
+ opts = withResolvedAdAccount(opts);
112105
112142
  const config = loadConfig(opts.token);
112106
112143
  const googleApiUrl = requireGoogleApi(config);
112107
112144
  const dateParams = new URLSearchParams();
@@ -112143,6 +112180,7 @@ async function runAdNegativeKeywordEdit(opts) {
112143
112180
  `);
112144
112181
  }
112145
112182
  async function runAdNegativeKeywordDelete(opts) {
112183
+ opts = withResolvedAdAccount(opts);
112146
112184
  const config = loadConfig(opts.token);
112147
112185
  const googleApiUrl = requireGoogleApi(config);
112148
112186
  const dateParams = new URLSearchParams();
@@ -112985,9 +113023,13 @@ function validateCampaignCreateConfigCore(cfg) {
112985
113023
  "customerName \u4E3A\u7A7A\uFF1A\u63D0\u4EA4 campaign-create \u65F6\u5C06\u6309 account \u4ECE list-accounts \u81EA\u52A8\u586B\u5145 mediaCustomerName"
112986
113024
  );
112987
113025
  }
112988
- const accountNum = Number(cfg.account);
112989
- if (!Number.isFinite(accountNum) || accountNum <= 0) {
112990
- pushErr2(errors, "account \u5FC5\u987B\u662F\u53EF\u89E3\u6790\u4E3A\u6B63\u6570\u7684\u5A92\u4F53\u8D26\u6237 ID\uFF08\u63D0\u4EA4\u65F6 customerId \u4E3A number\uFF09");
113026
+ const normalizedAccount = normalizeNumericMediaCustomerId(String(cfg.account ?? "").trim());
113027
+ const accountNum = normalizedAccount ? Number(normalizedAccount) : NaN;
113028
+ if (!normalizedAccount || !Number.isFinite(accountNum) || accountNum <= 0) {
113029
+ pushErr2(
113030
+ errors,
113031
+ "account \u5FC5\u987B\u662F\u53EF\u89E3\u6790\u4E3A\u6B63\u6570\u7684\u5A92\u4F53\u8D26\u6237 ID\uFF08\u53EF\u542B\u8FDE\u5B57\u7B26\uFF1B\u63D0\u4EA4\u65F6 customerId \u4E3A number\uFF09"
113032
+ );
112991
113033
  }
112992
113034
  if (!cfg.campaign || typeof cfg.campaign !== "object" || Array.isArray(cfg.campaign)) {
112993
113035
  pushErr2(errors, "campaign \u5BF9\u8C61\u4E0D\u80FD\u4E3A\u7A7A\uFF08\u540E\u7AEF\uFF1Acampaign is null\uFF09");
@@ -113353,6 +113395,7 @@ function parseCampaignTimeMs(v) {
113353
113395
  return Number.isFinite(t) ? t : null;
113354
113396
  }
113355
113397
  async function runAdCampaigns(opts) {
113398
+ opts = withResolvedAdAccount(opts);
113356
113399
  const config = loadConfig(opts.token);
113357
113400
  const googleApiUrl = requireGoogleApi(config);
113358
113401
  const params = new URLSearchParams();
@@ -113435,6 +113478,7 @@ async function runAdCampaigns(opts) {
113435
113478
  console.log();
113436
113479
  }
113437
113480
  async function runAdCampaignStatus(opts) {
113481
+ opts = withResolvedAdAccount(opts);
113438
113482
  const config = loadConfig(opts.token);
113439
113483
  const googleApiUrl = requireGoogleApi(config);
113440
113484
  const detailUrl = `${googleApiUrl}/campaignmanagement/campaign/${opts.account}/${opts.id}`;
@@ -113469,6 +113513,7 @@ async function runAdCampaignStatus(opts) {
113469
113513
  `);
113470
113514
  }
113471
113515
  async function runAdCampaignDelete(opts) {
113516
+ opts = withResolvedAdAccount(opts);
113472
113517
  const config = loadConfig(opts.token);
113473
113518
  const googleApiUrl = requireGoogleApi(config);
113474
113519
  const detailUrl = `${googleApiUrl}/campaignmanagement/campaign/${opts.account}/${opts.id}`;
@@ -113506,6 +113551,7 @@ async function runAdCampaignDelete(opts) {
113506
113551
  }
113507
113552
  async function runAdCampaignCreate(opts) {
113508
113553
  const cfg = loadCampaignCreateConfig(opts.configFile);
113554
+ cfg.account = resolveAdAccountId(cfg.account, "account");
113509
113555
  const { errors, warnings } = runCampaignCreateValidation(cfg);
113510
113556
  if (warnings.length > 0) {
113511
113557
  console.warn("\n\u26A0\uFE0F \u6295\u653E\u914D\u7F6E\u8B66\u544A\uFF08\u4E0D\u963B\u65AD\u63D0\u4EA4\uFF0C\u4F46\u5EFA\u8BAE\u4FEE\u6B63\uFF09\uFF1A");
@@ -113712,6 +113758,7 @@ function validateCampaignEditBidding(opts) {
113712
113758
  }
113713
113759
  }
113714
113760
  async function runAdCampaignEdit(opts) {
113761
+ opts = withResolvedAdAccount(opts);
113715
113762
  validateCampaignEditBidding(opts);
113716
113763
  const config = loadConfig(opts.token);
113717
113764
  const googleApiUrl = requireGoogleApi(config);
@@ -113781,6 +113828,7 @@ init_cli_json_snapshot();
113781
113828
  init_strip_legacy_google_fields();
113782
113829
  init_cli_table();
113783
113830
  async function runAdGroups(opts) {
113831
+ opts = withResolvedAdAccount(opts);
113784
113832
  const config = loadConfig(opts.token);
113785
113833
  const googleApiUrl = requireGoogleApi(config);
113786
113834
  const params = new URLSearchParams();
@@ -113872,6 +113920,7 @@ function adGroupRowToYuan(item) {
113872
113920
  };
113873
113921
  }
113874
113922
  async function runAdGroupCreate(opts) {
113923
+ opts = withResolvedAdAccount(opts);
113875
113924
  const config = loadConfig(opts.token);
113876
113925
  const googleApiUrl = requireGoogleApi(config);
113877
113926
  const body = {
@@ -113923,6 +113972,7 @@ async function runAdGroupCreate(opts) {
113923
113972
  }
113924
113973
  }
113925
113974
  async function runAdGroupStatus(opts) {
113975
+ opts = withResolvedAdAccount(opts);
113926
113976
  const config = loadConfig(opts.token);
113927
113977
  const googleApiUrl = requireGoogleApi(config);
113928
113978
  const listUrl = adgroupListUrl(googleApiUrl, opts.account, opts.startDate, opts.endDate);
@@ -113950,6 +114000,7 @@ async function runAdGroupStatus(opts) {
113950
114000
  `);
113951
114001
  }
113952
114002
  async function runAdGroupDelete(opts) {
114003
+ opts = withResolvedAdAccount(opts);
113953
114004
  const config = loadConfig(opts.token);
113954
114005
  const googleApiUrl = requireGoogleApi(config);
113955
114006
  const listUrl = adgroupListUrl(googleApiUrl, opts.account, opts.startDate, opts.endDate);
@@ -113976,6 +114027,7 @@ async function runAdGroupDelete(opts) {
113976
114027
  `);
113977
114028
  }
113978
114029
  async function runAdGroupRename(opts) {
114030
+ opts = withResolvedAdAccount(opts);
113979
114031
  const config = loadConfig(opts.token);
113980
114032
  const googleApiUrl = requireGoogleApi(config);
113981
114033
  const listUrl = adgroupListUrl(googleApiUrl, opts.account, opts.startDate, opts.endDate);
@@ -114003,6 +114055,7 @@ async function runAdGroupRename(opts) {
114003
114055
  `);
114004
114056
  }
114005
114057
  async function runAdGroupEdit(opts) {
114058
+ opts = withResolvedAdAccount(opts);
114006
114059
  const hasName = opts.name !== void 0 && opts.name.trim() !== "";
114007
114060
  const hasMax = opts.maxCPCAmount !== void 0;
114008
114061
  const hasTcpa = opts.targetCpaAmount !== void 0;
@@ -114125,6 +114178,7 @@ function formatRsaCliValidationError(errors) {
114125
114178
 
114126
114179
  // src/commands/ad/ad-creative.ts
114127
114180
  async function runAdList(opts) {
114181
+ opts = withResolvedAdAccount(opts);
114128
114182
  const config = loadConfig(opts.token);
114129
114183
  const googleApiUrl = requireGoogleApi(config);
114130
114184
  const params = new URLSearchParams();
@@ -114201,6 +114255,7 @@ async function runAdList(opts) {
114201
114255
  console.log();
114202
114256
  }
114203
114257
  async function runAdCreate(opts) {
114258
+ opts = withResolvedAdAccount(opts);
114204
114259
  const config = loadConfig(opts.token);
114205
114260
  const googleApiUrl = requireGoogleApi(config);
114206
114261
  const rsaErrors = validateRsaCliCopy({
@@ -114267,6 +114322,7 @@ async function runAdCreate(opts) {
114267
114322
  }
114268
114323
  }
114269
114324
  async function runAdStatus(opts) {
114325
+ opts = withResolvedAdAccount(opts);
114270
114326
  const config = loadConfig(opts.token);
114271
114327
  const googleApiUrl = requireGoogleApi(config);
114272
114328
  const params = new URLSearchParams();
@@ -114297,6 +114353,7 @@ async function runAdStatus(opts) {
114297
114353
  `);
114298
114354
  }
114299
114355
  async function runAdDelete(opts) {
114356
+ opts = withResolvedAdAccount(opts);
114300
114357
  const config = loadConfig(opts.token);
114301
114358
  const googleApiUrl = requireGoogleApi(config);
114302
114359
  const params = new URLSearchParams();
@@ -114326,6 +114383,7 @@ async function runAdDelete(opts) {
114326
114383
  `);
114327
114384
  }
114328
114385
  async function runAdEdit(opts) {
114386
+ opts = withResolvedAdAccount(opts);
114329
114387
  const config = await ensureDataPermission(loadConfig(opts.token));
114330
114388
  const googleApiUrl = requireGoogleApi(config);
114331
114389
  const hasTextOrUrl = opts.headlines || opts.descriptions || opts.finalUrl !== void 0 || opts.path1 !== void 0 || opts.path2 !== void 0;
@@ -114629,6 +114687,7 @@ function buildBusinessMessageBodyForCampaign(accountId, campaignId, bm) {
114629
114687
 
114630
114688
  // src/commands/ad/extension.ts
114631
114689
  async function runAdExtensionList(opts) {
114690
+ opts = withResolvedAdAccount(opts);
114632
114691
  const config = loadConfig(opts.token);
114633
114692
  const googleApiUrl = requireGoogleApi(config);
114634
114693
  const url = `${googleApiUrl}/extensionmanagement/v2/list/${opts.account}`;
@@ -114705,6 +114764,7 @@ async function runAdExtensionList(opts) {
114705
114764
  console.log();
114706
114765
  }
114707
114766
  async function runAdExtensionCreate(opts) {
114767
+ opts = withResolvedAdAccount(opts);
114708
114768
  const config = loadConfig(opts.token);
114709
114769
  const googleApiUrl = requireGoogleApi(config);
114710
114770
  const body = {
@@ -114782,6 +114842,7 @@ async function runAdExtensionCreate(opts) {
114782
114842
  }
114783
114843
  }
114784
114844
  async function runAdExtensionDelete(opts) {
114845
+ opts = withResolvedAdAccount(opts);
114785
114846
  const config = loadConfig(opts.token);
114786
114847
  const googleApiUrl = requireGoogleApi(config);
114787
114848
  const listUrl = `${googleApiUrl}/extensionmanagement/v2/list/${opts.account}`;
@@ -114883,6 +114944,7 @@ async function runAdExtensionSnippetHeaders(opts) {
114883
114944
  console.log();
114884
114945
  }
114885
114946
  async function runAdExtensionLeadFormCreate(opts) {
114947
+ opts = withResolvedAdAccount(opts);
114886
114948
  const config = loadConfig(opts.token);
114887
114949
  const googleApiUrl = requireGoogleApi(config);
114888
114950
  let cfg;
@@ -114941,6 +115003,7 @@ async function runAdExtensionLeadFormCreate(opts) {
114941
115003
  );
114942
115004
  }
114943
115005
  async function runAdExtensionWhatsappCreate(opts) {
115006
+ opts = withResolvedAdAccount(opts);
114944
115007
  const config = loadConfig(opts.token);
114945
115008
  const googleApiUrl = requireGoogleApi(config);
114946
115009
  if (!opts.configFile) {
@@ -115003,6 +115066,7 @@ function detectExtensionUpdateKind(raw) {
115003
115066
  process.exit(1);
115004
115067
  }
115005
115068
  async function runAdExtensionUpdate(opts) {
115069
+ opts = withResolvedAdAccount(opts);
115006
115070
  const config = loadConfig(opts.token);
115007
115071
  const googleApiUrl = requireGoogleApi(config);
115008
115072
  let raw;
@@ -115077,6 +115141,7 @@ init_cli_json_snapshot();
115077
115141
  init_strip_legacy_google_fields();
115078
115142
  init_cli_table();
115079
115143
  async function runAdSearchTerms(opts) {
115144
+ opts = withResolvedAdAccount(opts);
115080
115145
  const config = loadConfig(opts.token);
115081
115146
  const googleApiUrl = requireGoogleApi(config);
115082
115147
  const params = new URLSearchParams();
@@ -115205,6 +115270,7 @@ function resolveCriterionId(rows, opts) {
115205
115270
  return String(match.id);
115206
115271
  }
115207
115272
  async function runAdDeviceBidList(opts) {
115273
+ opts = withResolvedAdAccount(opts);
115208
115274
  const config = loadConfig(opts.token);
115209
115275
  const googleApiUrl = requireGoogleApi(config);
115210
115276
  const level = opts.level ?? "campaign";
@@ -115301,6 +115367,7 @@ async function runAdDeviceBidList(opts) {
115301
115367
  console.log();
115302
115368
  }
115303
115369
  async function runAdDeviceBidSet(opts) {
115370
+ opts = withResolvedAdAccount(opts);
115304
115371
  const config = loadConfig(opts.token);
115305
115372
  const googleApiUrl = requireGoogleApi(config);
115306
115373
  const level = opts.level ?? "campaign";
@@ -115431,6 +115498,7 @@ function pickGeoResolveCandidate(query, items2) {
115431
115498
  return items2[0] ?? null;
115432
115499
  }
115433
115500
  async function runAdGeoSearch(opts) {
115501
+ opts = withResolvedAdAccount(opts);
115434
115502
  const config = loadConfig(opts.token);
115435
115503
  const googleApiUrl = requireGoogleApi(config);
115436
115504
  let items2;
@@ -115503,6 +115571,7 @@ function parseGeoResolveQueries(opts) {
115503
115571
  return unique;
115504
115572
  }
115505
115573
  async function runAdGeoResolve(opts) {
115574
+ opts = withResolvedAdAccount(opts);
115506
115575
  let queries;
115507
115576
  try {
115508
115577
  queries = parseGeoResolveQueries(opts);
@@ -115614,6 +115683,7 @@ async function runAdGeoResolve(opts) {
115614
115683
  }
115615
115684
  }
115616
115685
  async function runAdGeoList(opts) {
115686
+ opts = withResolvedAdAccount(opts);
115617
115687
  const config = loadConfig(opts.token);
115618
115688
  const googleApiUrl = requireGoogleApi(config);
115619
115689
  let url;
@@ -115677,6 +115747,7 @@ async function runAdGeoList(opts) {
115677
115747
  console.log();
115678
115748
  }
115679
115749
  async function runAdGeoAdd(opts) {
115750
+ opts = withResolvedAdAccount(opts);
115680
115751
  const config = loadConfig(opts.token);
115681
115752
  const googleApiUrl = requireGoogleApi(config);
115682
115753
  const body = {
@@ -115703,6 +115774,7 @@ async function runAdGeoAdd(opts) {
115703
115774
  `);
115704
115775
  }
115705
115776
  async function runAdGeoRemove(opts) {
115777
+ opts = withResolvedAdAccount(opts);
115706
115778
  const config = loadConfig(opts.token);
115707
115779
  const googleApiUrl = requireGoogleApi(config);
115708
115780
  const body = { campaignId: opts.campaignId, id: opts.locationId };
@@ -115743,6 +115815,7 @@ function resolveGeoCriterionId(rows, opts) {
115743
115815
  return String(match["id"]);
115744
115816
  }
115745
115817
  async function runAdGeoSetBid(opts) {
115818
+ opts = withResolvedAdAccount(opts);
115746
115819
  const config = loadConfig(opts.token);
115747
115820
  const googleApiUrl = requireGoogleApi(config);
115748
115821
  if (opts.bidModifier < 0) {
@@ -116599,11 +116672,12 @@ async function runCampaignBatchDiff(opts) {
116599
116672
  `);
116600
116673
  process.exit(1);
116601
116674
  }
116602
- const accountId = pickString2(opts.account, cfg.account);
116603
- if (!accountId) {
116675
+ const rawAccountId = pickString2(opts.account, cfg.account);
116676
+ if (!rawAccountId) {
116604
116677
  console.error("\n\u274C \u7F3A\u5C11\u8D26\u6237 ID\uFF1A\u8BF7\u5728 JSON \u4E2D\u586B\u5199 account \u6216\u4F7F\u7528 -a\n");
116605
116678
  process.exit(1);
116606
116679
  }
116680
+ const accountId = resolveAdAccountId(rawAccountId, "account / -a");
116607
116681
  cfg.account = accountId;
116608
116682
  const config = loadConfig(opts.token);
116609
116683
  let campaignId = pickString2(opts.campaignId);
@@ -118624,14 +118698,7 @@ async function emitPmaxResult(opts, section, commandLabel, payload, idSuffix) {
118624
118698
  });
118625
118699
  }
118626
118700
  function requireAccountId(account, label = "-a, --account") {
118627
- const id = account?.toString().trim();
118628
- if (!id) {
118629
- console.error(`
118630
- \u274C \u8BF7\u6307\u5B9A\u5A92\u4F53\u8D26\u6237 ID\uFF08${label}\uFF09
118631
- `);
118632
- process.exit(1);
118633
- }
118634
- return id;
118701
+ return resolveAdAccountId(account, label);
118635
118702
  }
118636
118703
 
118637
118704
  // src/commands/ad/pmax-video-upload.ts
@@ -118994,7 +119061,8 @@ async function runAdPmaxCreate(opts) {
118994
119061
  }
118995
119062
  const config = await ensureDataPermission(loadConfig(opts.token));
118996
119063
  const googleApiUrl = requireGoogleApi(config);
118997
- const accountId = cfg.account.toString().trim();
119064
+ const accountId = resolveAdAccountId(cfg.account.toString(), "account");
119065
+ cfg.account = accountId;
118998
119066
  let imageSlots;
118999
119067
  try {
119000
119068
  imageSlots = await resolvePmaxImageSlots(
@@ -122803,6 +122871,7 @@ async function runAccountMe(opts) {
122803
122871
  init_dist();
122804
122872
  init_auth();
122805
122873
  init_cli_json_snapshot();
122874
+ init_media_customer_id();
122806
122875
 
122807
122876
  // src/commands/account-manage/google-mcc.ts
122808
122877
  init_auth();
@@ -122978,18 +123047,14 @@ function hintGoogleOAuth(kind) {
122978
123047
  return "\u6267\u884C siluzan-tso account auth -m Google \u5B8C\u6210 OAuth \u7ED1\u5B9A\u3002";
122979
123048
  }
122980
123049
  function assertGoogleMediaCustomerId(raw) {
122981
- const id = raw.trim();
122982
- if (!id) {
122983
- console.error("\n\u274C --account \u4E0D\u80FD\u4E3A\u7A7A\u3002\n");
122984
- process.exit(1);
122985
- }
122986
- if (!/^\d+$/.test(id)) {
122987
- console.error(
122988
- "\n\u274C --account \u987B\u4E3A Google mediaCustomerId\uFF08\u7EAF\u6570\u5B57\uFF0C\u4E0E list-accounts -m Google \u4E00\u81F4\uFF09\u3002\n"
122989
- );
123050
+ try {
123051
+ return requireNumericMediaCustomerId(raw, "--account");
123052
+ } catch (e) {
123053
+ console.error(`
123054
+ \u274C ${e instanceof Error ? e.message : String(e)}
123055
+ `);
122990
123056
  process.exit(1);
122991
123057
  }
122992
- return id;
122993
123058
  }
122994
123059
  function parseCustomerAccessPermissionBody(text) {
122995
123060
  const trimmed = text.trim();
@@ -38,7 +38,9 @@ siluzan-tso -h
38
38
 
39
39
  ## 即时规范
40
40
 
41
- - `entityId`(UUID)≠ `mediaCustomerId`(`list-accounts` 的 `ma.mediaCustomerId`:Google/TikTok/Bing 多为数字;**Yandex=`porg-…`**;Meta 常带 `act_`)。`stats`/`balance`/`accounts-digest` 的 `-a` **只传 mediaCustomerId**;空结果或 verbose 打出 403 时**先核验 ID**,禁止把 UUID/`entityId` 传给 `-a`,也**禁止**据此直接 `reauth`。
41
+ - `entityId`(UUID)≠ `mediaCustomerId`(`list-accounts` 的 `ma.mediaCustomerId`:Google/TikTok/Bing 多为数字;**Yandex=`porg-…`**;Meta 常带 `act_`)。`stats`/`balance`/`accounts-digest`/`ad *` 的 `-a` **只传 mediaCustomerId**;空结果或 verbose 打出 403 时**先核验 ID**,禁止把 UUID/`entityId` 传给 `-a`,也**禁止**据此直接 `reauth`。
42
+ - **Google CID 特殊性**:UI 常写 `123-456-7890`(带连字符),平台 `mediaCustomerId` 为纯数字 `1234567890`。CLI 会自动去连字符;**勿**把带横杠 403 误判为 OAuth 失效。优先用 `list-accounts` 的纯数字 ID。
43
+ - **禁止臆测授权过期**:见 403/`HTTP 403`/拉数失败/「可能 OAuth 失效」时,**禁止**口头推断授权过期或直接 `reauth`。须先核验 ID(及 Google 连字符),再跑 `account check-access -a <mediaCustomerId>`(套餐已激活前提下),以返回的 `accessible` / `reauth_required` / `no_permission` 为准;细则见 `accounts-permissions.md`。
42
44
  - `list-accounts` 无余额/消耗;列全部用 `--page-size 999`。
43
45
  - 多户余额用 `balance-scan`(P2),多户消耗用 `accounts-digest`(P3);禁止外层 for-loop 逐户拉数。
44
46
  - `stats` 的 `spend` = **区间合计**,不是日消耗。
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.43-beta.2",
4
- "publishedAt": 1785994362313
3
+ "version": "1.1.43-beta.3",
4
+ "publishedAt": 1785999491310
5
5
  }
@@ -35,7 +35,9 @@ siluzan-tso account me --check-phone 15130150466 --json-out ./snap-me
35
35
 
36
36
  校验当前丝路赞凭据是否对指定 **Google** 广告账户有访问权限。应在拉数/诊断前调用,避免误用他户 ID。
37
37
 
38
- **前提(查授权是否过期时)**:须先确认该户 **套餐已激活**(`list-accounts` `ma.scopeActivatedSources` 有未过期条目,或 Google 表格「套餐激活」= ✅ 已激活)。在已激活前提下,可用本命令检查授权是否可用:
38
+ > **硬约束**:**禁止**凭 403、空结果或经验臆测「授权/OAuth 过期」。要对用户下「授权不可用 / 需重授权」结论前,**必须**先跑本命令(并满足下方套餐已激活前提),以 CLI 返回的 `status` 为准;**禁止**跳过本命令直接 `reauth` 或口头推断。
39
+
40
+ **前提(查授权是否过期时)**:须先确认该户 **套餐已激活**(`list-accounts` 的 `ma.scopeActivatedSources` 有未过期条目,或 Google 表格「套餐激活」= ✅ 已激活)。在已激活前提下,**必须**用本命令检查授权是否可用:
39
41
 
40
42
  | 结果 | 含义(套餐已激活时) |
41
43
  | ---- | -------------------- |
@@ -19,6 +19,8 @@
19
19
  - `list-accounts` **不含**余额/消耗;列全部账户用 `--page-size 999`,禁止默认 20 再翻页。
20
20
  - `entityId`(UUID,分享/delink/账单/`reauth`)≠ `mediaCustomerId`(`balance`/`stats`/`accounts-digest`/`ad` 的 `-a`)。**Yandex 的 mediaCustomerId 形如 `porg-…`,不是 UUID。**
21
21
  - MetaAd OAuth 户的 `mediaCustomerId` **须带 `act_` 前缀**。
22
- - `stats`/`balance` 空结果 + verbose `HTTP 403`:先确认 `-a` 是否为 `ma.mediaCustomerId`;**禁止**把 `entityId` / tokenId `-a`,也**禁止**未确认 `invalidOAuthToken=true` 就 `reauth`。
22
+ - **Google CID**:广告后台展示常为 `XXX-XXX-XXXX`(连字符),`list-accounts` / 网关要的是**纯数字**(无横杠)。`stats`/`balance`/`ad *`/`google-analysis` `-a` 可带连字符(CLI 会去掉);若仍见 `HTTP 403:123-456-7890` 这类**回显带横杠 ID**,优先改成纯数字重试,**不要**直接当 OAuth 失效去 `reauth`。
23
+ - **禁止臆测授权过期**:403 / 拉数失败时**禁止**口头说「可能授权过期」。须 ID 核验后,对 Google 在套餐已激活前提下执行 `account check-access -a <mediaCustomerId>`,以返回 `status` 为准(见 [`accounts-permissions.md`](accounts-permissions.md));非 Google 看 `list-accounts` 的 `invalidOAuthToken`。
24
+ - `stats`/`balance` 空结果 + verbose `HTTP 403`:先确认 `-a` 是否为 `ma.mediaCustomerId`(且 Google 已去连字符);**禁止**把 `entityId` / tokenId 当 `-a`;未跑 `account check-access`(或未见 `invalidOAuthToken=true`)**禁止** `reauth`。
23
25
  - 多账户余额预警用 `balance-scan`(P2),多账户消耗汇总用 `accounts-digest`(P3);**禁止**外层 for-loop 逐户 `balance`/`stats`。
24
26
  - `stats` 默认 `spend` = **区间合计**,不是日消耗;日均看 `balance-scan.dailySpend` 或合计÷天数。
@@ -126,7 +126,8 @@
126
126
 
127
127
  - **账户状态 ≠ 系列状态**:`stats` / `balance` / `list-accounts` 的 `status` 只表示账户是否可用;系列状态必须来自 `ad campaigns`。
128
128
  - **数据时效性**:涉及「今天/当天/今日消耗」「实时消耗排行」前,必读 `references/analytics/account-analytics.md` 顶部「数据时效性」表。TikTok / Yandex / BingV2 / Kwai 是 `accountsoverview` 同步昨天数据,**不能查今天**。
129
- - **先查账户再操作**(拉数 / 改账户 / 报告 / **创建广告**):`list-accounts -m [mediaType] -k [mediaCustomerId]`;用户给出的 `mediaCustomerId` 必须 `-k` 核验,无结果则告知用户并停止,**禁止**翻页 grep 自行换 ID(会导致报告错户);拉数、脚本、报告文件名全链路用同一 ID(以 stdout `accountId` 为准)。**Google 额外建议**:`list-accounts` 命中后,若 `scopeActivatedSources` 显示套餐**已激活**,拉数前可用 `account check-access -a <mediaCustomerId>` 检查授权是否仍可用(`reauth_required` / `no_permission`);**未激活套餐时勿用 check-access 判断授权过期**。`no_permission`(403)也可能表示账户不在当前丝路赞账号下。详见 `accounts-permissions.md`。
129
+ - **先查账户再操作**(拉数 / 改账户 / 报告 / **创建广告**):`list-accounts -m [mediaType] -k [mediaCustomerId]`;用户给出的 `mediaCustomerId` 必须 `-k` 核验,无结果则告知用户并停止,**禁止**翻页 grep 自行换 ID(会导致报告错户);拉数、脚本、报告文件名全链路用同一 ID(以 stdout `accountId` 为准)。
130
+ - **禁止臆测授权过期(硬约束)**:任何 403、空结果、「可能授权/OAuth 过期」话术,**禁止**凭感觉下结论或直接 `reauth`。Google:在 `list-accounts` 确认套餐**已激活**后,**必须**执行 `account check-access -a <mediaCustomerId>`,仅当结果为 `reauth_required`(或列表 `invalidOAuthToken=true` 与之交叉确认)才谈重授权;`no_permission` 也可能是账户不在当前丝路赞账号下。**未激活套餐时勿用 check-access 判断授权过期**。非 Google 媒体以 `list-accounts` 的 `invalidOAuthToken` 为准,同样禁止臆测。详见 `accounts-permissions.md`。
130
131
  - **一律走 CLI,禁止自拼网关请求**:查余额 / 拉数 / 写操作只用 `siluzan-tso …`。**禁止**用 curl、自写脚本或改请求头直连 TSO/Google 网关「另辟蹊径」取数;`balance` 等无数据时按 CLI/`list-accounts` 字段向用户说明(如套餐未激活、授权失效),**禁止**尝试任何绕过平台门禁的取数方式。
131
132
  - **W3 仅出方案例外(覆盖上条)**:用户只要「投放方案 / 规划 / 表格 / 先别创建·开户·投钱」,或未给账户且未要求创建/发布时——**禁止**把「请先提供 Google 广告账户」当作第一步;按 `google-ads-campaign-plan.md` §「仅出方案 vs 创建」落盘 JSON 后 **写代码**投影完整审查稿(默认 MD;用户指定则 Excel 等;`account`=`[PENDING_ACCOUNT]`),跳过 `list-accounts` / `geo resolve` / validate / create。**禁止**只交概览表。用户确认要创建后再要账户并续跑创建流水线。
132
133
  - **W3 审查稿(搜索与 PMax)**:JSON 落盘后、创建前,必须 **写代码**读取 JSON,按 `google-ads-launch-plan-template.md`(搜索)或 `google-ads-pmax-launch-plan-template.md`(PMax)生成完整审查文件交给用户;须含全部关键词/RSA 或全部 PMax 文案与附加资产。**禁止**用「方案总结」条数勾选代替。用户要求其他格式时改脚本输出,数据仍只从 JSON 来。
@@ -276,7 +277,7 @@
276
277
 
277
278
  - **400**:参数错误,查看对应 reference 或 `-h`
278
279
  - **401 / OAuth 失效**:仅当 `list-accounts` 的 `invalidOAuthToken=true`(或表格「授权状态」为失效)且用户确认后——Siluzan Agent **优先**用平台重新授权工具(如 `present_reauth`);备选 `account reauth -m <媒体> --id <entityId> --i-confirm --commit "…"`(内置 delink→OAuth,见 W9 / `accounts-permissions.md`)。走 CLI 时**必须把 stdout 中的完整授权 URL 原样贴给用户**,禁止只说「链接已生成」,也**禁止**自行改写/补全该 URL。解绑后若列表已无该户,恢复用平台授权工具或 `account auth -m <媒体>`。**丝路赞登录凭据失效** → `send-login-code` + `login --phone --code`,见 `references/core/setup.md`
279
- - **403(拉数空结果)**:`stats`/`balance`/`accounts-digest` `--verbose` 下打出异常被吞的 `HTTP 403` 且无数据时:① **优先核验 `-a` 是否为 `ma.mediaCustomerId`**(勿传 `entityId`/UUID;Yandex 形如 `porg-…`);② Google 再看套餐是否激活(`scopeActivatedSources`),未激活则说明需先激活,**禁止**非 CLI 绕过取数;③ **禁止**在 `invalidOAuthToken=false` 时把 403 当成 OAuth 过期并自行 `reauth`。
280
+ - **403(拉数空结果 / ad 网关)**:① **优先核验 `-a` 是否为 `ma.mediaCustomerId`**(勿传 `entityId`/UUID;Google 注意连字符 CID;Yandex 形如 `porg-…`);② Google 看套餐是否激活(`scopeActivatedSources`),未激活则说明需先激活,**禁止**非 CLI 绕过取数;③ **禁止臆测授权过期**:套餐已激活时**必须**跑 `account check-access -a <mediaCustomerId>`,以 `status` 为准后再决定是否 `reauth`;**禁止**仅凭 403 文案对用户说「授权过期」。
280
281
  - **500**:服务可能正在部署/升级,建议反馈 Siluzan 相关人员
281
282
 
282
283
  ---
@@ -26,12 +26,14 @@
26
26
 
27
27
  | 需要的 ID | 获取命令 |
28
28
  | ------------------- | ----------------------------------------------------------------------- |
29
- | `accountId`(`-a`) | `siluzan-tso list-accounts --json-out ./snap` → `mediaCustomerId` |
29
+ | `accountId`(`-a`) | `siluzan-tso list-accounts -m Google --json-out ./snap` → `mediaCustomerId`(**纯数字**;UI 的 `123-456-7890` 可原样传入,CLI 去连字符) |
30
30
  | 广告系列 `id` | `siluzan-tso ad campaigns -a <accountId> --json-out ./snap` → `id` |
31
31
  | 广告组 `id`、`name` | `siluzan-tso ad groups -a <accountId> --json-out ./snap` → `id`、`name` |
32
32
  | 广告 `id` | `siluzan-tso ad list -a <accountId> --json-out ./snap` → `id` |
33
33
  | 关键词 `id` | `siluzan-tso ad keywords -a <accountId> --json-out ./snap` → `id` |
34
34
 
35
+ > **Google CID**:带连字符进网关曾导致 `HTTP 403:123-456-7890`;现 CLI 已归一化。仍失败时用纯数字重试;**禁止臆测授权过期**,须 `account check-access -a <mediaCustomerId>`(详见 [google-ads.md](google-ads.md) Gotchas / `accounts-permissions.md`)。
36
+
35
37
  ---
36
38
 
37
39
  ## ad campaigns — 广告系列管理
@@ -32,7 +32,7 @@
32
32
 
33
33
  | 需要的 ID | 获取命令 |
34
34
  | ------------------- | ----------------------------------------------------------------------- |
35
- | `accountId`(`-a`) | `siluzan-tso list-accounts --json-out ./snap` → `mediaCustomerId` |
35
+ | `accountId`(`-a`) / JSON `account` | `list-accounts -m Google` → `mediaCustomerId`(纯数字;UI 带连字符 CID 可传入,CLI 去横杠,见 [google-ads.md](google-ads.md) Gotchas) |
36
36
  | 广告系列 `id` | `siluzan-tso ad campaigns -a <accountId> --json-out ./snap` → `id` |
37
37
  | 广告组 `id`、`name` | `siluzan-tso ad groups -a <accountId> --json-out ./snap` → `id`、`name` |
38
38
  | 广告 `id` | `siluzan-tso ad list -a <accountId> --json-out ./snap` → `id` |
@@ -15,6 +15,10 @@
15
15
 
16
16
  ## Gotchas
17
17
 
18
+ - **Google 客户 ID(CID)带连字符**:UI / 用户口述常为 `123-456-7890`,平台 `mediaCustomerId` 与 Google 网关路径需要 `1234567890`。`ad campaigns` / `ad groups` / `ad list` 等 `-a` 与 JSON 顶层 `account` **可带连字符**(CLI 自动去掉)。若错误为 `HTTP 403:123-456-7890`(回显带横杠),先换纯数字重试,**禁止**据此直接判断 OAuth 过期或 `reauth`。
19
+ - **禁止臆测授权过期**:`ad *` / 拉数出现 403 或失败时,**禁止**对用户说「授权可能过期」。须核验 ID 后,在套餐已激活前提下执行 `account check-access -a <mediaCustomerId>`,仅当 `reauth_required`(或与 `invalidOAuthToken=true` 交叉确认)才走重授权。见 [`accounts-permissions.md`](../accounts/accounts-permissions.md)。
20
+ - `-a` 必须是 `list-accounts -m Google` 的 `ma.mediaCustomerId`,**禁止**传 `entityId`(UUID)。
21
+
18
22
  ## 金额单位(全局重要)
19
23
 
20
24
  > **所有 CLI 金额参数均按「主币种金额」传入**(如 `1.5` = ¥1.50 / $1.50);CLI 写入网关前对「分」字段 ×100(含 `ad keyword-edit --max-cpc` → `maxCPC`)。
@@ -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.43-beta.2'
12
+ $PKG_VERSION = '1.1.43-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.43-beta.2"
12
+ readonly PKG_VERSION="1.1.43-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.43-beta.2",
3
+ "version": "1.1.43-beta.3",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",