siluzan-tso-cli 1.1.47-beta.1 → 1.1.47-beta.2

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.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -134230,6 +134230,87 @@ function parseTikTokNumber(value) {
134230
134230
  const n = Number(value);
134231
134231
  return Number.isFinite(n) ? n : void 0;
134232
134232
  }
134233
+ var TIKTOK_VALUE_METRICS = ["total_purchase_value"];
134234
+ var TIKTOK_OFFICIAL_ROAS_METRICS = [
134235
+ "complete_payment_roas",
134236
+ "total_active_pay_roas"
134237
+ ];
134238
+ var TIKTOK_ROAS_COMPANION_METRICS = [
134239
+ "total_purchase_value",
134240
+ "complete_payment_roas",
134241
+ "total_active_pay_roas"
134242
+ ];
134243
+ function splitTikTokCsv(raw) {
134244
+ return raw.split(",").map((x) => x.trim()).filter(Boolean);
134245
+ }
134246
+ function pickFirstMetric(row, keys) {
134247
+ let fallback;
134248
+ for (const key of keys) {
134249
+ const value = parseTikTokNumber(row[key]);
134250
+ if (value === void 0) continue;
134251
+ if (value > 0) return { key, value };
134252
+ fallback ??= { key, value };
134253
+ }
134254
+ return fallback;
134255
+ }
134256
+ function expandTikTokOfficialReportMetrics(metrics) {
134257
+ const parts = splitTikTokCsv(metrics);
134258
+ const needCompanions = parts.includes("complete_payment") || TIKTOK_ROAS_COMPANION_METRICS.some((k) => parts.includes(k));
134259
+ if (!needCompanions) return parts.join(",");
134260
+ for (const extra of TIKTOK_ROAS_COMPANION_METRICS) {
134261
+ if (!parts.includes(extra)) parts.push(extra);
134262
+ }
134263
+ return parts.join(",");
134264
+ }
134265
+ function pickTikTokOfficialRoas(row) {
134266
+ return pickFirstMetric(row, TIKTOK_OFFICIAL_ROAS_METRICS);
134267
+ }
134268
+ function pickTikTokRevenue(row) {
134269
+ const picked = pickFirstMetric(row, TIKTOK_VALUE_METRICS);
134270
+ if (!picked) return { revenue: void 0, revenueSource: void 0 };
134271
+ return { revenue: picked.value, revenueSource: picked.key };
134272
+ }
134273
+ function attachTikTokRoasFields(row) {
134274
+ const spend = parseTikTokNumber(row.spend);
134275
+ const official = pickTikTokOfficialRoas(row);
134276
+ const purchase = pickTikTokRevenue(row);
134277
+ if (purchase.revenue !== void 0) {
134278
+ row.revenue = purchase.revenue;
134279
+ row.revenueSource = purchase.revenueSource;
134280
+ } else if (official && official.value > 0 && spend !== void 0 && spend > 0) {
134281
+ row.revenue = official.value * spend;
134282
+ row.revenueSource = `${official.key}*spend`;
134283
+ }
134284
+ const revenue = parseTikTokNumber(row.revenue);
134285
+ if (official && official.value > 0) {
134286
+ row.roas = official.value;
134287
+ row.roasSource = official.key;
134288
+ } else if (spend !== void 0 && spend > 0 && revenue !== void 0) {
134289
+ row.roas = revenue / spend;
134290
+ row.roasSource = "computed";
134291
+ } else if (official) {
134292
+ row.roas = official.value;
134293
+ row.roasSource = official.key;
134294
+ }
134295
+ }
134296
+ function summarizeTikTokOfficialReport(items2) {
134297
+ let spend = 0;
134298
+ let revenue = 0;
134299
+ let hasRevenue = false;
134300
+ for (const row of items2) {
134301
+ spend += parseTikTokNumber(row.spend) ?? 0;
134302
+ const rowRevenue = parseTikTokNumber(row.revenue);
134303
+ if (rowRevenue === void 0) continue;
134304
+ revenue += rowRevenue;
134305
+ hasRevenue = true;
134306
+ }
134307
+ return {
134308
+ spend,
134309
+ revenue: hasRevenue ? revenue : 0,
134310
+ roas: spend > 0 && hasRevenue ? revenue / spend : void 0,
134311
+ itemCount: items2.length
134312
+ };
134313
+ }
134233
134314
  function flattenTikTokReportRow(row) {
134234
134315
  const dims = row.dimensions && typeof row.dimensions === "object" && !Array.isArray(row.dimensions) ? row.dimensions : {};
134235
134316
  const metrics = row.metrics && typeof row.metrics === "object" && !Array.isArray(row.metrics) ? row.metrics : {};
@@ -134238,11 +134319,7 @@ function flattenTikTokReportRow(row) {
134238
134319
  const n = parseTikTokNumber(v);
134239
134320
  out[k] = n === void 0 ? v : n;
134240
134321
  }
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
- }
134322
+ attachTikTokRoasFields(out);
134246
134323
  return out;
134247
134324
  }
134248
134325
  function pickAdGroupBidField(bidType) {
@@ -134450,6 +134527,7 @@ async function fetchAdEntities(config, opts) {
134450
134527
  );
134451
134528
  }
134452
134529
  async function fetchOfficialReport(config, opts) {
134530
+ const metrics = expandTikTokOfficialReportMetrics(opts.metrics);
134453
134531
  const base = requireTikTokApiUrl(config);
134454
134532
  const raw = await fetchTikTokPagedList(
134455
134533
  config,
@@ -134461,7 +134539,7 @@ async function fetchOfficialReport(config, opts) {
134461
134539
  endDate: opts.endDate,
134462
134540
  dataLevel: opts.dataLevel,
134463
134541
  dimensions: opts.dimensions,
134464
- metrics: opts.metrics,
134542
+ metrics,
134465
134543
  serviceType: opts.serviceType,
134466
134544
  pageNum,
134467
134545
  pageSize
@@ -134471,7 +134549,8 @@ async function fetchOfficialReport(config, opts) {
134471
134549
  );
134472
134550
  return {
134473
134551
  items: raw.items.map(flattenTikTokReportRow),
134474
- totalNumber: raw.totalNumber
134552
+ totalNumber: raw.totalNumber,
134553
+ metrics
134475
134554
  };
134476
134555
  }
134477
134556
 
@@ -134725,7 +134804,7 @@ async function runTikTokOfficialReport(opts) {
134725
134804
  const id = assertTikTokAccountId(opts.account);
134726
134805
  const dataLevel = opts.dataLevel.trim();
134727
134806
  const dimensions = opts.dimensions.trim();
134728
- const metrics = opts.metrics.trim();
134807
+ const metrics = expandTikTokOfficialReportMetrics(opts.metrics.trim());
134729
134808
  if (!dataLevel || !dimensions || !metrics) {
134730
134809
  console.error("\n\u274C --data-level / --dimensions / --metrics \u5747\u4E3A\u5FC5\u586B\u3002\n");
134731
134810
  process.exit(1);
@@ -134750,6 +134829,7 @@ async function runTikTokOfficialReport(opts) {
134750
134829
  } catch (err) {
134751
134830
  fail("\u67E5\u8BE2 TikTok \u5B98\u65B9\u62A5\u8868\u5931\u8D25", err);
134752
134831
  }
134832
+ const totals = summarizeTikTokOfficialReport(result.items);
134753
134833
  const payload = {
134754
134834
  account: id,
134755
134835
  dataLevel,
@@ -134758,6 +134838,7 @@ async function runTikTokOfficialReport(opts) {
134758
134838
  metrics,
134759
134839
  startDate,
134760
134840
  endDate,
134841
+ totals,
134761
134842
  ...wrapListJson({
134762
134843
  page: 1,
134763
134844
  pageSize,
@@ -134783,9 +134864,11 @@ TikTok \u5B98\u65B9\u62A5\u8868 ${dataLevel} ${startDate}~${endDate}\uFF08${id}\
134783
134864
  console.log(" \u65E0\u62A5\u8868\u884C\u3002\n");
134784
134865
  return;
134785
134866
  }
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);
134867
+ const dimKeys = splitTikTokCsv(dimensions);
134868
+ const metricKeys = splitTikTokCsv(metrics);
134869
+ const keys = [...dimKeys, ...metricKeys, "revenue", "revenueSource", "roas", "roasSource"].filter(
134870
+ (k, i, a) => a.indexOf(k) === i
134871
+ );
134789
134872
  printCliTable(
134790
134873
  result.items.map((r) => {
134791
134874
  const row = {};
@@ -134800,7 +134883,7 @@ TikTok \u5B98\u65B9\u62A5\u8868 ${dataLevel} ${startDate}~${endDate}\uFF08${id}\
134800
134883
  const sampleSpend = parseTikTokNumber(result.items[0]?.spend);
134801
134884
  if (sampleSpend !== void 0) {
134802
134885
  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"
134886
+ " 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
134887
  );
134805
134888
  } else {
134806
134889
  console.log();
@@ -134953,7 +135036,7 @@ function registerTikTokAnalysisCommands(program2) {
134953
135036
  "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
135037
  ).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
135038
  "--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"
135039
+ "\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
135040
  ).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
135041
  async (opts) => {
134959
135042
  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.2",
4
+ "publishedAt": 1787276448425
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.2'
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.2"
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.2",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",