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 +1 -1
- package/dist/index.js +96 -13
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/analytics/account-analytics.md +2 -0
- package/dist/skill/references/operations/hosted-automation-tiktok.md +2 -2
- package/dist/skill/scripts/install.ps1 +1 -1
- package/dist/skill/scripts/install.sh +1 -1
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
134787
|
-
const metricKeys = metrics
|
|
134788
|
-
const keys = [...dimKeys, ...metricKeys, "
|
|
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\
|
|
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\
|
|
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);
|
package/dist/skill/_meta.json
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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"
|