siluzan-tso-cli 1.1.47-beta.1 → 1.1.47-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.47-beta.1),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.47-beta.3),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -105841,7 +105841,15 @@ function register5(program2) {
105841
105841
  // src/commands/list-accounts/_shared.ts
105842
105842
  init_dist();
105843
105843
  init_auth();
105844
- function shouldExposeScopeActivatedSources() {
105844
+ var SCOPE_ACTIVATION_BYPASSED_HEADER = "x-scope-activation-bypassed";
105845
+ function isScopeActivationBypassed(headers) {
105846
+ if (!headers) return false;
105847
+ const raw = headers[SCOPE_ACTIVATION_BYPASSED_HEADER] ?? headers["X-Scope-Activation-Bypassed"];
105848
+ const v = raw?.trim().toLowerCase();
105849
+ return v === "1" || v === "true" || v === "yes";
105850
+ }
105851
+ function shouldExposeScopeActivatedSources(bypassed = false) {
105852
+ if (bypassed) return false;
105845
105853
  return resolveCompanySourceType() === "Ctaiad";
105846
105854
  }
105847
105855
  var VALID_MEDIA_TYPES = [
@@ -105966,7 +105974,7 @@ async function fetchTikTokAccountByMediaCustomerId(config, mediaCustomerId, verb
105966
105974
  const match = items2.find((it) => String(it.ma?.mediaCustomerId ?? "") === id);
105967
105975
  return match ?? null;
105968
105976
  }
105969
- function stripListAccountsEnrichmentFields(item) {
105977
+ function stripListAccountsEnrichmentFields(item, exposeScopeActivatedSources = shouldExposeScopeActivatedSources()) {
105970
105978
  delete item.ma.remainingAccountBudget;
105971
105979
  delete item.ma.platformStatus;
105972
105980
  delete item.ma.aritScore;
@@ -105982,7 +105990,7 @@ function stripListAccountsEnrichmentFields(item) {
105982
105990
  ]) {
105983
105991
  delete maExt[key];
105984
105992
  }
105985
- if (!shouldExposeScopeActivatedSources()) {
105993
+ if (!exposeScopeActivatedSources) {
105986
105994
  delete item.ma.scopeActivatedSources;
105987
105995
  delete maExt["scopeActivatedSources"];
105988
105996
  }
@@ -106087,8 +106095,8 @@ var COL_GOOGLE_BASE = [
106087
106095
  { key: "shared", header: "\u5206\u4EAB" }
106088
106096
  ];
106089
106097
  var COL_GOOGLE_PACKAGE = { key: "packageStatus", header: "\u5957\u9910\u6FC0\u6D3B" };
106090
- function googleTableColumns() {
106091
- if (!shouldExposeScopeActivatedSources()) return COL_GOOGLE_BASE;
106098
+ function googleTableColumns(exposePackage = shouldExposeScopeActivatedSources()) {
106099
+ if (!exposePackage) return COL_GOOGLE_BASE;
106092
106100
  const authIdx = COL_GOOGLE_BASE.findIndex((c) => c.key === "authStatus");
106093
106101
  const cols = [...COL_GOOGLE_BASE];
106094
106102
  cols.splice(authIdx + 1, 0, COL_GOOGLE_PACKAGE);
@@ -106262,8 +106270,7 @@ function printTikTokTable(items2, tableOpts) {
106262
106270
  });
106263
106271
  printCliTable(rows, COL_TIKTOK, tableOpts);
106264
106272
  }
106265
- function printGoogleTable(items2, tableOpts) {
106266
- const exposePackage = shouldExposeScopeActivatedSources();
106273
+ function printGoogleTable(items2, tableOpts, exposePackage = shouldExposeScopeActivatedSources()) {
106267
106274
  const rows = items2.map((item) => {
106268
106275
  const ma = item.ma;
106269
106276
  const platformStatus = ma.platformStatus ?? "-";
@@ -106289,7 +106296,7 @@ function printGoogleTable(items2, tableOpts) {
106289
106296
  }
106290
106297
  return row;
106291
106298
  });
106292
- printCliTable(rows, googleTableColumns(), tableOpts);
106299
+ printCliTable(rows, googleTableColumns(exposePackage), tableOpts);
106293
106300
  }
106294
106301
  var COL_DEFAULT_WITH_MEDIA = [
106295
106302
  { key: "mediaType", header: "\u5A92\u4F53\u7C7B\u578B" },
@@ -106364,6 +106371,7 @@ async function runListAccounts(opts) {
106364
106371
  const platformCfg = opts.media ? PLATFORM_CONFIG[opts.media] : null;
106365
106372
  let items2;
106366
106373
  let total;
106374
+ let scopeActivationBypassed = false;
106367
106375
  if (platformCfg) {
106368
106376
  const params = new URLSearchParams();
106369
106377
  for (const [k, v] of Object.entries(platformCfg.fixedParams)) {
@@ -106380,6 +106388,7 @@ async function runListAccounts(opts) {
106380
106388
  try {
106381
106389
  const res = await apiFetchWithHeaders2(url, config, {}, opts.verbose);
106382
106390
  items2 = res.data ?? [];
106391
+ scopeActivationBypassed = isScopeActivationBypassed(res.headers);
106383
106392
  const hit = res.headers["s-total-hits"];
106384
106393
  if (hit !== void 0) total = parseInt(hit, 10) || void 0;
106385
106394
  } catch (err) {
@@ -106389,22 +106398,22 @@ async function runListAccounts(opts) {
106389
106398
  process.exit(1);
106390
106399
  }
106391
106400
  } else {
106392
- let res;
106393
106401
  try {
106394
- res = await apiFetch2(url, config, {}, opts.verbose);
106402
+ const res = await apiFetchWithHeaders2(url, config, {}, opts.verbose);
106403
+ items2 = Array.isArray(res.data?.mas) ? res.data.mas : [];
106404
+ total = typeof res.data?.total === "number" ? res.data.total : void 0;
106405
+ scopeActivationBypassed = isScopeActivationBypassed(res.headers);
106406
+ if (opts.media === "TikTok") {
106407
+ mergeTikTokAdList(items2, res.data?.adList);
106408
+ } else if (opts.media === "MetaAd") {
106409
+ mergeMetaFacebookAdList(items2, res.data?.adList);
106410
+ }
106395
106411
  } catch (err) {
106396
106412
  console.error(`
106397
106413
  \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
106398
106414
  `);
106399
106415
  process.exit(1);
106400
106416
  }
106401
- items2 = Array.isArray(res?.mas) ? res.mas : [];
106402
- total = typeof res?.total === "number" ? res.total : void 0;
106403
- if (opts.media === "TikTok") {
106404
- mergeTikTokAdList(items2, res.adList);
106405
- } else if (opts.media === "MetaAd") {
106406
- mergeMetaFacebookAdList(items2, res.adList);
106407
- }
106408
106417
  }
106409
106418
  } else {
106410
106419
  const params = new URLSearchParams();
@@ -106416,6 +106425,7 @@ async function runListAccounts(opts) {
106416
106425
  try {
106417
106426
  const res = await apiFetchWithHeaders2(url, config, {}, opts.verbose);
106418
106427
  items2 = res.data ?? [];
106428
+ scopeActivationBypassed = isScopeActivationBypassed(res.headers);
106419
106429
  const hit = res.headers["s-total-hits"];
106420
106430
  if (hit !== void 0) total = parseInt(hit, 10) || void 0;
106421
106431
  } catch (err) {
@@ -106425,9 +106435,10 @@ async function runListAccounts(opts) {
106425
106435
  process.exit(1);
106426
106436
  }
106427
106437
  }
106438
+ const exposeScopeActivated = shouldExposeScopeActivatedSources(scopeActivationBypassed);
106428
106439
  if (items2.length > 0) {
106429
106440
  for (const item of items2) {
106430
- stripListAccountsEnrichmentFields(item);
106441
+ stripListAccountsEnrichmentFields(item, exposeScopeActivated);
106431
106442
  }
106432
106443
  }
106433
106444
  const oauthFailWarning = (() => {
@@ -106469,7 +106480,7 @@ async function runListAccounts(opts) {
106469
106480
  const isKwai = opts.media === "Kwai";
106470
106481
  const tableBorderOpts = opts.unicode ? { plain: false } : void 0;
106471
106482
  if (isGoogle) {
106472
- printGoogleTable(items2, tableBorderOpts);
106483
+ printGoogleTable(items2, tableBorderOpts, exposeScopeActivated);
106473
106484
  } else if (isYandex) {
106474
106485
  printYandexTable(items2, tableBorderOpts);
106475
106486
  } else if (isTikTok) {
@@ -134230,6 +134241,87 @@ function parseTikTokNumber(value) {
134230
134241
  const n = Number(value);
134231
134242
  return Number.isFinite(n) ? n : void 0;
134232
134243
  }
134244
+ var TIKTOK_VALUE_METRICS = ["total_purchase_value"];
134245
+ var TIKTOK_OFFICIAL_ROAS_METRICS = [
134246
+ "complete_payment_roas",
134247
+ "total_active_pay_roas"
134248
+ ];
134249
+ var TIKTOK_ROAS_COMPANION_METRICS = [
134250
+ "total_purchase_value",
134251
+ "complete_payment_roas",
134252
+ "total_active_pay_roas"
134253
+ ];
134254
+ function splitTikTokCsv(raw) {
134255
+ return raw.split(",").map((x) => x.trim()).filter(Boolean);
134256
+ }
134257
+ function pickFirstMetric(row, keys) {
134258
+ let fallback;
134259
+ for (const key of keys) {
134260
+ const value = parseTikTokNumber(row[key]);
134261
+ if (value === void 0) continue;
134262
+ if (value > 0) return { key, value };
134263
+ fallback ??= { key, value };
134264
+ }
134265
+ return fallback;
134266
+ }
134267
+ function expandTikTokOfficialReportMetrics(metrics) {
134268
+ const parts = splitTikTokCsv(metrics);
134269
+ const needCompanions = parts.includes("complete_payment") || TIKTOK_ROAS_COMPANION_METRICS.some((k) => parts.includes(k));
134270
+ if (!needCompanions) return parts.join(",");
134271
+ for (const extra of TIKTOK_ROAS_COMPANION_METRICS) {
134272
+ if (!parts.includes(extra)) parts.push(extra);
134273
+ }
134274
+ return parts.join(",");
134275
+ }
134276
+ function pickTikTokOfficialRoas(row) {
134277
+ return pickFirstMetric(row, TIKTOK_OFFICIAL_ROAS_METRICS);
134278
+ }
134279
+ function pickTikTokRevenue(row) {
134280
+ const picked = pickFirstMetric(row, TIKTOK_VALUE_METRICS);
134281
+ if (!picked) return { revenue: void 0, revenueSource: void 0 };
134282
+ return { revenue: picked.value, revenueSource: picked.key };
134283
+ }
134284
+ function attachTikTokRoasFields(row) {
134285
+ const spend = parseTikTokNumber(row.spend);
134286
+ const official = pickTikTokOfficialRoas(row);
134287
+ const purchase = pickTikTokRevenue(row);
134288
+ if (purchase.revenue !== void 0) {
134289
+ row.revenue = purchase.revenue;
134290
+ row.revenueSource = purchase.revenueSource;
134291
+ } else if (official && official.value > 0 && spend !== void 0 && spend > 0) {
134292
+ row.revenue = official.value * spend;
134293
+ row.revenueSource = `${official.key}*spend`;
134294
+ }
134295
+ const revenue = parseTikTokNumber(row.revenue);
134296
+ if (official && official.value > 0) {
134297
+ row.roas = official.value;
134298
+ row.roasSource = official.key;
134299
+ } else if (spend !== void 0 && spend > 0 && revenue !== void 0) {
134300
+ row.roas = revenue / spend;
134301
+ row.roasSource = "computed";
134302
+ } else if (official) {
134303
+ row.roas = official.value;
134304
+ row.roasSource = official.key;
134305
+ }
134306
+ }
134307
+ function summarizeTikTokOfficialReport(items2) {
134308
+ let spend = 0;
134309
+ let revenue = 0;
134310
+ let hasRevenue = false;
134311
+ for (const row of items2) {
134312
+ spend += parseTikTokNumber(row.spend) ?? 0;
134313
+ const rowRevenue = parseTikTokNumber(row.revenue);
134314
+ if (rowRevenue === void 0) continue;
134315
+ revenue += rowRevenue;
134316
+ hasRevenue = true;
134317
+ }
134318
+ return {
134319
+ spend,
134320
+ revenue: hasRevenue ? revenue : 0,
134321
+ roas: spend > 0 && hasRevenue ? revenue / spend : void 0,
134322
+ itemCount: items2.length
134323
+ };
134324
+ }
134233
134325
  function flattenTikTokReportRow(row) {
134234
134326
  const dims = row.dimensions && typeof row.dimensions === "object" && !Array.isArray(row.dimensions) ? row.dimensions : {};
134235
134327
  const metrics = row.metrics && typeof row.metrics === "object" && !Array.isArray(row.metrics) ? row.metrics : {};
@@ -134238,11 +134330,7 @@ function flattenTikTokReportRow(row) {
134238
134330
  const n = parseTikTokNumber(v);
134239
134331
  out[k] = n === void 0 ? v : n;
134240
134332
  }
134241
- const spend = parseTikTokNumber(out.spend);
134242
- const payment = parseTikTokNumber(out.complete_payment);
134243
- if (spend !== void 0 && spend > 0 && payment !== void 0) {
134244
- out.roas = payment / spend;
134245
- }
134333
+ attachTikTokRoasFields(out);
134246
134334
  return out;
134247
134335
  }
134248
134336
  function pickAdGroupBidField(bidType) {
@@ -134450,6 +134538,7 @@ async function fetchAdEntities(config, opts) {
134450
134538
  );
134451
134539
  }
134452
134540
  async function fetchOfficialReport(config, opts) {
134541
+ const metrics = expandTikTokOfficialReportMetrics(opts.metrics);
134453
134542
  const base = requireTikTokApiUrl(config);
134454
134543
  const raw = await fetchTikTokPagedList(
134455
134544
  config,
@@ -134461,7 +134550,7 @@ async function fetchOfficialReport(config, opts) {
134461
134550
  endDate: opts.endDate,
134462
134551
  dataLevel: opts.dataLevel,
134463
134552
  dimensions: opts.dimensions,
134464
- metrics: opts.metrics,
134553
+ metrics,
134465
134554
  serviceType: opts.serviceType,
134466
134555
  pageNum,
134467
134556
  pageSize
@@ -134471,7 +134560,8 @@ async function fetchOfficialReport(config, opts) {
134471
134560
  );
134472
134561
  return {
134473
134562
  items: raw.items.map(flattenTikTokReportRow),
134474
- totalNumber: raw.totalNumber
134563
+ totalNumber: raw.totalNumber,
134564
+ metrics
134475
134565
  };
134476
134566
  }
134477
134567
 
@@ -134725,7 +134815,7 @@ async function runTikTokOfficialReport(opts) {
134725
134815
  const id = assertTikTokAccountId(opts.account);
134726
134816
  const dataLevel = opts.dataLevel.trim();
134727
134817
  const dimensions = opts.dimensions.trim();
134728
- const metrics = opts.metrics.trim();
134818
+ const metrics = expandTikTokOfficialReportMetrics(opts.metrics.trim());
134729
134819
  if (!dataLevel || !dimensions || !metrics) {
134730
134820
  console.error("\n\u274C --data-level / --dimensions / --metrics \u5747\u4E3A\u5FC5\u586B\u3002\n");
134731
134821
  process.exit(1);
@@ -134750,6 +134840,7 @@ async function runTikTokOfficialReport(opts) {
134750
134840
  } catch (err) {
134751
134841
  fail("\u67E5\u8BE2 TikTok \u5B98\u65B9\u62A5\u8868\u5931\u8D25", err);
134752
134842
  }
134843
+ const totals = summarizeTikTokOfficialReport(result.items);
134753
134844
  const payload = {
134754
134845
  account: id,
134755
134846
  dataLevel,
@@ -134758,6 +134849,7 @@ async function runTikTokOfficialReport(opts) {
134758
134849
  metrics,
134759
134850
  startDate,
134760
134851
  endDate,
134852
+ totals,
134761
134853
  ...wrapListJson({
134762
134854
  page: 1,
134763
134855
  pageSize,
@@ -134783,9 +134875,11 @@ TikTok \u5B98\u65B9\u62A5\u8868 ${dataLevel} ${startDate}~${endDate}\uFF08${id}\
134783
134875
  console.log(" \u65E0\u62A5\u8868\u884C\u3002\n");
134784
134876
  return;
134785
134877
  }
134786
- const dimKeys = dimensions.split(",").map((x) => x.trim()).filter(Boolean);
134787
- const metricKeys = metrics.split(",").map((x) => x.trim()).filter(Boolean);
134788
- const keys = [...dimKeys, ...metricKeys, "roas"].filter((k, i, a) => a.indexOf(k) === i);
134878
+ const dimKeys = splitTikTokCsv(dimensions);
134879
+ const metricKeys = splitTikTokCsv(metrics);
134880
+ const keys = [...dimKeys, ...metricKeys, "revenue", "revenueSource", "roas", "roasSource"].filter(
134881
+ (k, i, a) => a.indexOf(k) === i
134882
+ );
134789
134883
  printCliTable(
134790
134884
  result.items.map((r) => {
134791
134885
  const row = {};
@@ -134800,7 +134894,7 @@ TikTok \u5B98\u65B9\u62A5\u8868 ${dataLevel} ${startDate}~${endDate}\uFF08${id}\
134800
134894
  const sampleSpend = parseTikTokNumber(result.items[0]?.spend);
134801
134895
  if (sampleSpend !== void 0) {
134802
134896
  console.log(
134803
- " spend \u5DF2\u8F6C\u6570\u5B57\uFF1BROAS = complete_payment / spend\uFF08spend=0 \u4E0D\u9664\uFF09\u3002\u52FF\u6DF7\u52A0\u4E0D\u540C DataLevel\u3002\n"
134897
+ " spend \u5DF2\u8F6C\u6570\u5B57\u3002ROAS \u4F18\u5148\u5B98\u65B9\u5217 complete_payment_roas / total_active_pay_roas\uFF0C\u5426\u5219\u7528 total_purchase_value/spend\uFF08\u89C1 roasSource\uFF09\u3002complete_payment \u662F\u7F51\u9875\u5B8C\u4ED8\u6B21\u6570\uFF0C\u4E0D\u662F\u91D1\u989D\u3002\u591A\u884C\u6C47\u603B\u89C1 totals\uFF08\u5148\u52A0\u603B\u518D\u9664\uFF0C\u52FF\u5E73\u5747\u5404\u7EC4 roas\uFF09\u3002\u52FF\u6DF7\u52A0\u4E0D\u540C DataLevel\u3002\n"
134804
134898
  );
134805
134899
  } else {
134806
134900
  console.log();
@@ -134953,7 +135047,7 @@ function registerTikTokAnalysisCommands(program2) {
134953
135047
  "TikTokAPI \u5B98\u65B9\u62A5\u8868\uFF08GET \u2026/query/report/Tiktok/search\uFF1B\u53EF\u542B\u4ECA\u5929/\u5C0F\u65F6\uFF1B\u4E0E `run` \u7684 TSO \u770B\u677F\u5468\u671F\u62A5\u8868\u4E0D\u540C\u3002ServiceType/DataLevel/Dimensions/Metrics/\u65E5\u671F/PageSize \u5FC5\u586B\uFF09"
134954
135048
  ).requiredOption(...ACCOUNT_OPTION2).requiredOption("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u6309\u5E7F\u544A\u4E3B timezone \u81EA\u7136\u65E5\uFF09").requiredOption("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").requiredOption("--data-level <level>", "AUCTION_CAMPAIGN / AUCTION_ADGROUP / AUCTION_AD").requiredOption("--dimensions <list>", "\u9017\u53F7\u5206\u9694\uFF0C\u5982 campaign_id \u6216 adgroup_id,stat_time_hour").requiredOption(
134955
135049
  "--metrics <list>",
134956
- "\u9017\u53F7\u5206\u9694\uFF0C\u82B1\u8D39\u987B\u7528 spend\uFF08\u4E0D\u8981 stat_cost\uFF09\uFF1B\u53EF\u542B conversion,cost_per_conversion,ctr,complete_payment"
135050
+ "\u9017\u53F7\u5206\u9694\uFF0C\u82B1\u8D39\u987B\u7528 spend\uFF08\u4E0D\u8981 stat_cost\uFF09\u3002complete_payment \u662F\u7F51\u9875\u5B8C\u4ED8\u6B21\u6570\u3002\u542B\u5B8C\u4ED8/ROAS \u65F6\u81EA\u52A8\u9644\u5E26 total_purchase_value,complete_payment_roas,total_active_pay_roas"
134957
135051
  ).option("--service-type <type>", "AUCTION\uFF08\u9ED8\u8BA4\uFF09\u6216 RESERVATION", "AUCTION").option("--page-size <n>", "\u6BCF\u9875\u6761\u6570\uFF0C\u9ED8\u8BA4 200\uFF0C\u4E0A\u9650 1000", (v) => parseInt(v, 10)).option("-t, --token <token>", "Auth Token").option("--json-out <path>", JSON_OUT_OPTION_DESC2).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
134958
135052
  async (opts) => {
134959
135053
  await runTikTokOfficialReport(opts);
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.47-beta.1",
4
- "publishedAt": 1787219174166
3
+ "version": "1.1.47-beta.3",
4
+ "publishedAt": 1787282369500
5
5
  }
@@ -351,6 +351,8 @@ siluzan-tso tiktok-analysis official-report -a <mediaCustomerId> --json-out ./sn
351
351
  --data-level AUCTION_CAMPAIGN --dimensions campaign_id --metrics spend
352
352
  ```
353
353
 
354
+ **ROAS 口径**(对齐 [网页事件](https://ads.tiktok.com/help/article/page-events?lang=zh) / [应用内事件](https://ads.tiktok.com/help/article/in-app-events?lang=zh)):`--metrics` 含完付或 ROAS 时 CLI 自动附带 `total_purchase_value,complete_payment_roas,total_active_pay_roas`。落盘行有 `revenue` / `revenueSource` / `roas` / `roasSource`,多行汇总在 `totals`(先加总 spend/revenue 再除)。`complete_payment` 是网页支付完成**次数**;金额用 `total_purchase_value`(总付费价值)。ROAS 优先官方列 `complete_payment_roas` / `total_active_pay_roas`。**禁止** `complete_payment/spend`,**禁止**把完付=0 判成「像素没回传」,**禁止**对各组 `roas` 取平均。不确定时先 `adgroup-entities` 看 `optimization_event`。
355
+
354
356
  ---
355
357
 
356
358
  ## Bing(BingV2)账户分析
@@ -107,7 +107,7 @@ siluzan-tso tiktok-analysis official-report -a <mediaCustomerId> --json-out ./sn
107
107
  --data-level AUCTION_CAMPAIGN --dimensions campaign_id,stat_time_hour --metrics spend
108
108
  ```
109
109
 
110
- 广告效果(CPA 用 `cost_per_conversion` 或 `spend/conversion`;ROAS 落盘里已自算 `complete_payment/spend`):
110
+ 广告效果(CPA 用 `cost_per_conversion` 或 `spend/conversion`;**ROAS 用落盘 `roas` / `totals.roas`**):
111
111
 
112
112
  ```bash
113
113
  siluzan-tso tiktok-analysis official-report -a <mediaCustomerId> --json-out ./snap-tt \
@@ -116,7 +116,7 @@ siluzan-tso tiktok-analysis official-report -a <mediaCustomerId> --json-out ./sn
116
116
  --metrics spend,conversion,cost_per_conversion,complete_payment,ctr
117
117
  ```
118
118
 
119
- 非完付目标的 `complete_payment` 常为 0,ROAS 不能当通用门槛。出价在组,广告层只能关停(本批 CLI **不**关停)。
119
+ `--metrics` 含完付/ROAS 时 CLI 自动附带 `total_purchase_value,complete_payment_roas,total_active_pay_roas`。口径对齐 Ads Manager:`complete_payment` 是网页支付完成**次数**(不是金额);ROAS 优先官方列 `complete_payment_roas`(网页)/ `total_active_pay_roas`(App 付费),否则 `total_purchase_value/spend`。多行用 `totals`(先加总再除),**禁止**对各组 `roas` 取平均。**`complete_payment=0` 不是像素坏了**——App / `ACTIVE_PAY` 看 `total_purchase_value`。不确定优化目标时先 `adgroup-entities` 看 `optimization_event`。出价在组,广告层只能关停(本批 CLI **不**关停)。
120
120
 
121
121
  根因分解(含小时时起止仍须同一天,必须翻完页;CLI 已自动翻):
122
122
 
@@ -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.47-beta.1'
12
+ $PKG_VERSION = '1.1.47-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.47-beta.1"
12
+ readonly PKG_VERSION="1.1.47-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.47-beta.1",
3
+ "version": "1.1.47-beta.3",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",