siluzan-tso-cli 1.1.49-beta.2 → 1.1.49-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.49-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.49-beta.3),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -136024,6 +136024,7 @@ var BING_SECTIONS = [
136024
136024
  }
136025
136025
  ];
136026
136026
  var BING_SECTION_NAMES = BING_SECTIONS.map((s) => s.name);
136027
+ var BING_DEFAULT_SECTION_NAMES = BING_SECTION_NAMES.filter((name2) => name2 !== "audience-merged");
136027
136028
  var BING_SECTION_ALIASES = {
136028
136029
  devices: "device",
136029
136030
  geo: "geographic",
@@ -136031,6 +136032,19 @@ var BING_SECTION_ALIASES = {
136031
136032
  };
136032
136033
 
136033
136034
  // src/commands/bing-analysis/resolve-sections.ts
136035
+ var AUDIENCE_SPLIT_NAMES = [
136036
+ "age-audience",
136037
+ "gender-audience"
136038
+ ];
136039
+ function dropMergedWhenSplitPresent(defs) {
136040
+ const names = new Set(defs.map((d) => d.name));
136041
+ const hasSplit = AUDIENCE_SPLIT_NAMES.some((n) => names.has(n));
136042
+ if (!hasSplit || !names.has("audience-merged")) return defs;
136043
+ console.error(
136044
+ "\u5DF2\u8DF3\u8FC7 audience-merged\uFF08\u4E0E age-audience/gender-audience \u5E76\u884C\u4F1A\u89E6\u53D1 Bing SDK \u53D7\u4F17 csv \u6587\u4EF6\u9501\uFF09\u3002"
136045
+ );
136046
+ return defs.filter((d) => d.name !== "audience-merged");
136047
+ }
136034
136048
  function normalizeSectionToken2(raw) {
136035
136049
  const t = raw.trim();
136036
136050
  if (!t) return null;
@@ -136056,8 +136070,9 @@ function resolveSectionList3(sections, exclude) {
136056
136070
  }
136057
136071
  const includeCanonical = new Set(include.map((n) => normalizeSectionToken2(n)).filter(Boolean));
136058
136072
  const dropCanonical = new Set(dropRaw.map((n) => normalizeSectionToken2(n)).filter(Boolean));
136059
- const base = include.length > 0 ? BING_SECTIONS.filter((s) => includeCanonical.has(s.name)) : [...BING_SECTIONS];
136060
- return base.filter((s) => !dropCanonical.has(s.name));
136073
+ const defaultSet = new Set(BING_DEFAULT_SECTION_NAMES);
136074
+ const base = include.length > 0 ? BING_SECTIONS.filter((s) => includeCanonical.has(s.name)) : BING_SECTIONS.filter((s) => defaultSet.has(s.name));
136075
+ return dropMergedWhenSplitPresent(base.filter((s) => !dropCanonical.has(s.name)));
136061
136076
  }
136062
136077
 
136063
136078
  // src/commands/bing-analysis/fetch.ts
@@ -136333,10 +136348,8 @@ async function fetchBingAudienceMergedPayload(config, id, startDate, endDate, ve
136333
136348
  const params = new URLSearchParams({ startDate, endDate });
136334
136349
  const ageUrl = reportingUrl(config, id, "AgeAudienceData", params.toString());
136335
136350
  const genderUrl = reportingUrl(config, id, "GenderAudienceData", params.toString());
136336
- const [ageData, genderData] = await Promise.all([
136337
- fetchBingJson(config, ageUrl, verbose),
136338
- fetchBingJson(config, genderUrl, verbose)
136339
- ]);
136351
+ const ageData = await fetchBingJson(config, ageUrl, verbose);
136352
+ const genderData = await fetchBingJson(config, genderUrl, verbose);
136340
136353
  return normalizeBingSectionPayload(
136341
136354
  {
136342
136355
  mergedParts: ["AgeAudienceData", "GenderAudienceData"],
@@ -136916,22 +136929,29 @@ function mapGeographicRows(payload) {
136916
136929
  (r) => pickMetrics(r, ["countryOrRegion", "countryCriteriaId", "regionName", "cityName"])
136917
136930
  );
136918
136931
  }
136919
- function mapAudienceRows2(payload) {
136932
+ function mapAudienceLabelRows(payload, labelKey) {
136920
136933
  const obj = asRecord23(payload);
136921
- const data = asRecord23(obj?.data);
136922
- const ageAudience = asRecord23(data?.ageAudience);
136923
- const genderAudience = asRecord23(data?.genderAudience);
136924
- const audienceAge = asRows2(ageAudience?.audience).map((r) => {
136925
- const m = pickMetrics(r, ["audience", "bidModifier"]);
136926
- m.ageRange = r.audience;
136927
- return m;
136928
- });
136929
- const audienceGender = asRows2(genderAudience?.audience).map((r) => {
136934
+ return asRows2(obj?.audience).map((r) => {
136930
136935
  const m = pickMetrics(r, ["audience", "bidModifier"]);
136931
- m.gender = r.audience;
136936
+ m[labelKey] = r.audience;
136932
136937
  return m;
136933
136938
  });
136934
- return { audienceAge, audienceGender };
136939
+ }
136940
+ function mapAudienceFromMerged(payload) {
136941
+ const data = asRecord23(asRecord23(payload)?.data);
136942
+ return {
136943
+ audienceAge: mapAudienceLabelRows(data?.ageAudience, "ageRange"),
136944
+ audienceGender: mapAudienceLabelRows(data?.genderAudience, "gender")
136945
+ };
136946
+ }
136947
+ function resolveAudienceTables(sectionMap) {
136948
+ const fromMerged = mapAudienceFromMerged(sectionMap.get("audience-merged"));
136949
+ const fromAge = mapAudienceLabelRows(sectionMap.get("age-audience"), "ageRange");
136950
+ const fromGender = mapAudienceLabelRows(sectionMap.get("gender-audience"), "gender");
136951
+ return {
136952
+ audienceAge: fromMerged.audienceAge.length > 0 ? fromMerged.audienceAge : fromAge,
136953
+ audienceGender: fromMerged.audienceGender.length > 0 ? fromMerged.audienceGender : fromGender
136954
+ };
136935
136955
  }
136936
136956
  function mapCampaignRows2(payload) {
136937
136957
  return asRows2(payload).map((r) => ({
@@ -137082,7 +137102,7 @@ async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
137082
137102
  const rows = mapGeographicRows(sectionMap.get("geographic"));
137083
137103
  if (rows.length > 0) tables.geographic = rows;
137084
137104
  }
137085
- const audienceMapped = mapAudienceRows2(sectionMap.get("audience-merged"));
137105
+ const audienceMapped = resolveAudienceTables(sectionMap);
137086
137106
  if (audienceMapped.audienceAge.length > 0) tables.audienceAge = audienceMapped.audienceAge;
137087
137107
  if (audienceMapped.audienceGender.length > 0) {
137088
137108
  tables.audienceGender = audienceMapped.audienceGender;
@@ -137340,7 +137360,7 @@ function registerBatchCommand2(sectionHelp, aliasHelp) {
137340
137360
  "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u4E0E --end \u540C\u4F20\u6216\u540C\u7701\u7565\uFF1B\u53EF\u542B\u6628\u5929/\u4ECA\u5929\uFF0C\u6570\u636E\u53EF\u80FD\u4E0D\u5B8C\u6574\uFF1B\u7701\u7565=\u622A\u81F3\u6628\u5929\u8FD1 7 \u5929\uFF09"
137341
137361
  ).option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option(
137342
137362
  "--sections <list>",
137343
- `\u4EC5\u6267\u884C\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u5982 overview,campaigns,keywords\uFF1B\u7701\u7565=\u5168\u90E8 11 \u4E2A\u3002\u53EF\u9009\uFF1A${sectionHelp}\uFF1B\u522B\u540D\uFF1A${aliasHelp}`
137363
+ `\u4EC5\u6267\u884C\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u5982 overview,campaigns,keywords\uFF1B\u7701\u7565=\u9ED8\u8BA4 10 \u4E2A\uFF08\u4E0D\u542B audience-merged\uFF0C\u907F\u514D\u4E0E\u5E74\u9F84/\u6027\u522B\u7EF4\u62A2 Bing SDK \u53D7\u4F17 csv\uFF09\u3002\u53EF\u9009\uFF1A${sectionHelp}\uFF1B\u522B\u540D\uFF1A${aliasHelp}`
137344
137364
  ).option("--exclude <list>", "\u6392\u9664\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u4E0E --sections \u53EF\u53E0\u52A0").option(
137345
137365
  "--limit <n>",
137346
137366
  "\u4EC5 keywords / search-terms\uFF1A\u6761\u6570\u4E0A\u9650\uFF08\u9ED8\u8BA4 100\uFF0C\u4E0E\u524D\u7AEF KeywordReport/SearchQueryReport \u4E00\u81F4\uFF09",
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.49-beta.2",
4
- "publishedAt": 1788233809334
3
+ "version": "1.1.49-beta.3",
4
+ "publishedAt": 1788247766399
5
5
  }
@@ -368,7 +368,7 @@ siluzan-tso tiktok-analysis official-report -a <mediaCustomerId> --json-out ./sn
368
368
 
369
369
  | 选项 | 说明 |
370
370
  | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
371
- | `--sections` / `--exclude` | `overview` `device` `geographic` `age-audience` `gender-audience` `audience-merged` `campaigns` `ad-groups` `ads` `keywords` `search-terms`(11 个);**别名**:`devices`→`device`,`geo`→`geographic`,`search-queries`→`search-terms` |
371
+ | `--sections` / `--exclude` | 可选 11 个:`overview` `device` `geographic` `age-audience` `gender-audience` `audience-merged` `campaigns` `ad-groups` `ads` `keywords` `search-terms`;**默认 10 个**(不含 `audience-merged`,勿与年龄/性别维同时拉);**别名**:`devices`→`device`,`geo`→`geographic`,`search-queries`→`search-terms` |
372
372
  | `--limit` | 仅 `keywords` / `search-terms`:条数上限,默认 100 |
373
373
  | `--start` / `--end` | 同传或同省略;可含昨天/今天(可能不完整);省略=截至昨天的近 7 天 |
374
374
  | `--concurrency` | 默认 5,上限 16 |
@@ -376,7 +376,7 @@ siluzan-tso tiktok-analysis official-report -a <mediaCustomerId> --json-out ./sn
376
376
  ```bash
377
377
  mkdir -p ./snap-bing
378
378
 
379
- # 11
379
+ # 默认 10 维(含年龄/性别受众,不含 audience-merged)
380
380
  siluzan-tso bing-analysis -a <mediaCustomerId> --json-out ./snap-bing
381
381
 
382
382
  # 仅拉总览 + 关键词(limit 50)
@@ -13,7 +13,7 @@
13
13
  | `overview` | 对象:`currentPeriod` / `previousPeriod` / `balance` / `averageDailyCost` … | → `meta` + `kpis`(环比、余额、日均) |
14
14
  | `device` | `{ devices: Row[] }` | → `tables.devices[]` |
15
15
  | `geographic` | `{ countries: Row[] }` | → `tables.geographic[]` |
16
- | `audience-merged`(或 `age-audience`/`gender-audience`) | `{ data: { ageAudience.audience[], genderAudience.audience[] } }` | → `tables.audienceAge[]` / `audienceGender[]` |
16
+ | `age-audience` / `gender-audience`(默认)或 `audience-merged` | 拆分维 `{ audience: [] }`;merged `{ data: { ageAudience, genderAudience } }` | → `tables.audienceAge[]` / `audienceGender[]`(merged 空则回退拆分维) |
17
17
  | `campaigns` | **根节点数组** | → `tables.campaigns[]`;**本期 KPI 优先由此累加** |
18
18
  | `ad-groups` | **根节点数组** | → `tables.adGroups[]`(含 `qualityScore`) |
19
19
  | `ads` | **根节点数组** | → `tables.ads[]` |
@@ -28,7 +28,7 @@
28
28
 
29
29
  | 步骤 | 执行者 | 动作 |
30
30
  | ---------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
31
- | **1. 拉数** | Agent 调 CLI | `bing-analysis -a <id> --start <s> --end <e> --json-out ./snap-bing`(见下方「日期规则」,全 11 维或 `--sections` 指定子集) |
31
+ | **1. 拉数** | Agent 调 CLI | `bing-analysis -a <id> --start <s> --end <e> --json-out ./snap-bing`(见下方「日期规则」,默认 10 维含年龄/性别、不含 `audience-merged`) |
32
32
  | **2. 分析** | Agent | 用 **node/python 脚本**读落盘 JSON(勿用 Read 打开业务 `*.json`),把网关原始字段映射为下方 `tables.*` 契约,完成聚合与洞察 |
33
33
  | **3. 写 JSON** | Agent | 撰写 `bing-period-report.json`:仅 `meta.accountId` + `narrative`(9 个分析小节)为必填;`kpis`/`tables` 可省略由 `--snapshot-dir` 自动合并 |
34
34
  | **4. 渲染 HTML** | CLI | `bing-analysis render` — **校验 narrative 9 个分析小节必含字段**,缺项报错不生成 HTML;**禁止** Agent 手写/拼接 HTML |
@@ -55,7 +55,7 @@ siluzan-tso bing-analysis render \
55
55
  - `audienceGender[]`:`{gender, spend, impressions, clicks, ctr, averageCpc, conversions}`
56
56
  - `campaigns[]`:`{campaignName, campaignStatus, campaignStatusDisplay, spend, impressions, clicks, ctr, conversions, costPerConversion}`
57
57
  - `adGroups[]`:`{adGroupName, campaignName, qualityScore, spend, clicks, ctr, conversions, costPerConversion}`
58
- - `ads[]`:`{adTitle, adGroupName, adType, spend, clicks, ctr, conversions}`
58
+ - `ads[]`:`{adTitle, adGroupName, adType, spend, clicks, ctr, conversions}`(RSA 常 `adTitle=""`,HTML 整列为空则不渲染该列并在表上方提醒)
59
59
  - `keywords[]`:`{keyword, matchType, qualityScore, spend, ctr, averageCpc, conversions, costPerConversion}`
60
60
  - `searchTerms[]`:`{searchQuery, keyword, deliveredMatchType, spend, ctr, conversions, costPerConversion}`
61
61
  - `narrative`(**Agent 必填,唯一由 Agent 撰写的叙事内容**):
@@ -107,13 +107,13 @@ mkdir -p ./snap-bing
107
107
 
108
108
  siluzan-tso list-accounts -m BingV2 -k <mediaCustomerId> --json-out ./snap-bing
109
109
 
110
- # 一次批跑全部 11 维(等价于逐个 --sections 拉取,见下方速查表)
110
+ # 一次批跑默认 10 维(含 age-audience / gender-audience,不含 audience-merged)
111
111
  siluzan-tso bing-analysis -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
112
112
  siluzan-tso balance -m BingV2 --accounts <mediaCustomerId> --json-out ./snap-bing
113
113
 
114
- # 或仅拉本次报告所需维度(--sections 逗号分隔)
114
+ # 或仅拉本次报告所需维度(--sections 逗号分隔;不要同时写拆分维和 audience-merged)
115
115
  siluzan-tso bing-analysis -a <mediaCustomerId> --start <S> --end <E> \
116
- --sections overview,campaigns,device,geographic,audience-merged,ad-groups,ads,keywords,search-terms \
116
+ --sections overview,campaigns,device,geographic,age-audience,gender-audience,ad-groups,ads,keywords,search-terms \
117
117
  --limit 100 --json-out ./snap-bing
118
118
  ```
119
119
 
@@ -206,9 +206,9 @@ Bing 网关常不返回 `averageDailyCost`、`activeDays`(或为 0)。CLI
206
206
 
207
207
  ## 4. 受众
208
208
 
209
- - **CLI**:推荐 `bing-analysis --sections audience-merged`(年龄+性别合并 JSON);或分别 `--sections age-audience` / `--sections gender-audience`
209
+ - **CLI**:默认已含 `--sections age-audience,gender-audience`。不要与 `audience-merged` 同时拉(Bing SDK 会抢同一份受众 csv,merged 400)。`render --snapshot-dir` 优先 merged,空则回退拆分维。
210
210
 
211
- **数据呈现**:年龄段、性别的展示、点击、消耗、CTR、CPC(按 `data.ageAudience.audience` / `data.genderAudience.audience`)。写入 JSON `tables.audienceAge[]` / `tables.audienceGender[]`。
211
+ **数据呈现**:年龄段、性别的展示、点击、消耗、CTR、CPC。拆分维读 `age-audience-*.json` / `gender-audience-*.json` 的 `audience[]`(标签字段 `audience`)。写入 JSON `tables.audienceAge[]`(`ageRange`)/ `tables.audienceGender[]`(`gender`)。
212
212
 
213
213
  **分析(必写)** → 写入 `narrative.sections.audience.{analysis,suggestions}`:
214
214
 
@@ -225,7 +225,7 @@ Bing 网关常不返回 `averageDailyCost`、`activeDays`(或为 0)。CLI
225
225
  | 广告组 | `bing-analysis --sections ad-groups` | `ad-groups-*.json` |
226
226
  | 广告 | `bing-analysis --sections ads` | `ads-*.json` |
227
227
 
228
- **数据呈现**:各表按消耗降序;系列含 `campaignStatus`;广告组可含质量分相关字段(以 outline 为准)。写入 JSON `tables.campaigns[]` / `tables.adGroups[]` / `tables.ads[]`。
228
+ **数据呈现**:各表按消耗降序;系列含 `campaignStatus`;广告组可含质量分相关字段(以 outline 为准)。写入 JSON `tables.campaigns[]` / `tables.adGroups[]` / `tables.ads[]`。RSA 的 `adTitle` 常为空串,HTML 不展示空标题列。
229
229
 
230
230
  **分析(必写,三个子块各写一段,不可合并为一句带过)**:
231
231
 
@@ -472,6 +472,9 @@
472
472
  color: var(--muted);
473
473
  flex: none;
474
474
  }
475
+ .empty-note + .table-wrap {
476
+ margin-top: 10px;
477
+ }
475
478
 
476
479
  @keyframes fadeInUp {
477
480
  from {
@@ -1022,10 +1025,14 @@
1022
1025
  const currency = currencyOf(data);
1023
1026
  const section = data.narrative?.sections?.ads;
1024
1027
  const sorted = topBySpend(rows, 10);
1028
+ const showTitle = sorted.some((r) => String(r.adTitle || "").trim() !== "");
1029
+ const titleNotice = !showTitle && sorted.length > 0
1030
+ ? `<div class="empty-note">Bing 响应式搜索广告的报表不返回单条标题,本表不展示「广告标题」列。</div>`
1031
+ : "";
1025
1032
  const body = sorted
1026
1033
  .map(
1027
1034
  (r) => `<tr>
1028
- <td>${escapeHtml(r.adTitle || "—")}</td>
1035
+ ${showTitle ? `<td>${escapeHtml(r.adTitle || "—")}</td>` : ""}
1029
1036
  <td>${escapeHtml(r.adGroupName || "—")}</td>
1030
1037
  <td>${escapeHtml(r.adType || "—")}</td>
1031
1038
  <td class="num">${money(r.spend, currency)}</td>
@@ -1038,8 +1045,9 @@
1038
1045
  const table =
1039
1046
  sorted.length > 0
1040
1047
  ? `<div class="card-title-row"><h3>广告 Top 10</h3><span class="hint">共 ${rows.length} 条广告,按消耗排序</span></div>
1048
+ ${titleNotice}
1041
1049
  <div class="table-wrap"><table>
1042
- <thead><tr><th>广告标题</th><th>广告组</th><th>广告类型</th><th class="num">消耗</th><th class="num">点击</th><th class="num">CTR</th><th class="num">转化</th></tr></thead>
1050
+ <thead><tr>${showTitle ? "<th>广告标题</th>" : ""}<th>广告组</th><th>广告类型</th><th class="num">消耗</th><th class="num">点击</th><th class="num">CTR</th><th class="num">转化</th></tr></thead>
1043
1051
  <tbody>${body}</tbody>
1044
1052
  </table></div>`
1045
1053
  : emptyNoteHtml();
@@ -13,7 +13,7 @@
13
13
  | `overview` | 对象:`currentPeriod` / `previousPeriod` / `balance` / `averageDailyCost` … | → `meta` + `kpis`(环比、余额、日均) |
14
14
  | `device` | `{ devices: Row[] }` | → `tables.devices[]` |
15
15
  | `geographic` | `{ countries: Row[] }` | → `tables.geographic[]` |
16
- | `audience-merged`(或 `age-audience`/`gender-audience`) | `{ data: { ageAudience.audience[], genderAudience.audience[] } }` | → `tables.audienceAge[]` / `audienceGender[]` |
16
+ | `age-audience` / `gender-audience`(默认)或 `audience-merged` | 拆分维 `{ audience: [] }`;merged `{ data: { ageAudience, genderAudience } }` | → `tables.audienceAge[]` / `audienceGender[]`(merged 空则回退拆分维) |
17
17
  | `campaigns` | **根节点数组** | → `tables.campaigns[]`;**本期 KPI 优先由此累加** |
18
18
  | `ad-groups` | **根节点数组** | → `tables.adGroups[]`(含 `qualityScore`) |
19
19
  | `ads` | **根节点数组** | → `tables.ads[]` |
@@ -28,7 +28,7 @@
28
28
 
29
29
  | 步骤 | 执行者 | 动作 |
30
30
  | ---------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
31
- | **1. 拉数** | Agent 调 CLI | `bing-analysis -a <id> --start <s> --end <e> --json-out ./snap-bing`(见下方「日期规则」,全 11 维或 `--sections` 指定子集) |
31
+ | **1. 拉数** | Agent 调 CLI | `bing-analysis -a <id> --start <s> --end <e> --json-out ./snap-bing`(见下方「日期规则」,默认 10 维含年龄/性别、不含 `audience-merged`) |
32
32
  | **2. 分析** | Agent | 用 **node/python 脚本**读落盘 JSON(勿用 Read 打开业务 `*.json`),把网关原始字段映射为下方 `tables.*` 契约,完成聚合与洞察 |
33
33
  | **3. 写 JSON** | Agent | 撰写 `bing-period-report.json`:仅 `meta.accountId` + `narrative`(9 个分析小节)为必填;`kpis`/`tables` 可省略由 `--snapshot-dir` 自动合并 |
34
34
  | **4. 渲染 HTML** | CLI | `bing-analysis render` — **校验 narrative 9 个分析小节必含字段**,缺项报错不生成 HTML;**禁止** Agent 手写/拼接 HTML |
@@ -55,7 +55,7 @@ siluzan-tso bing-analysis render \
55
55
  - `audienceGender[]`:`{gender, spend, impressions, clicks, ctr, averageCpc, conversions}`
56
56
  - `campaigns[]`:`{campaignName, campaignStatus, campaignStatusDisplay, spend, impressions, clicks, ctr, conversions, costPerConversion}`
57
57
  - `adGroups[]`:`{adGroupName, campaignName, qualityScore, spend, clicks, ctr, conversions, costPerConversion}`
58
- - `ads[]`:`{adTitle, adGroupName, adType, spend, clicks, ctr, conversions}`
58
+ - `ads[]`:`{adTitle, adGroupName, adType, spend, clicks, ctr, conversions}`(RSA 常 `adTitle=""`,HTML 整列为空则不渲染该列并在表上方提醒)
59
59
  - `keywords[]`:`{keyword, matchType, qualityScore, spend, ctr, averageCpc, conversions, costPerConversion}`
60
60
  - `searchTerms[]`:`{searchQuery, keyword, deliveredMatchType, spend, ctr, conversions, costPerConversion}`
61
61
  - `narrative`(**Agent 必填,唯一由 Agent 撰写的叙事内容**):
@@ -107,13 +107,13 @@ mkdir -p ./snap-bing
107
107
 
108
108
  siluzan-tso list-accounts -m BingV2 -k <mediaCustomerId> --json-out ./snap-bing
109
109
 
110
- # 一次批跑全部 11 维(等价于逐个 --sections 拉取,见下方速查表)
110
+ # 一次批跑默认 10 维(含 age-audience / gender-audience,不含 audience-merged)
111
111
  siluzan-tso bing-analysis -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
112
112
  siluzan-tso balance -m BingV2 --accounts <mediaCustomerId> --json-out ./snap-bing
113
113
 
114
- # 或仅拉本次报告所需维度(--sections 逗号分隔)
114
+ # 或仅拉本次报告所需维度(--sections 逗号分隔;不要同时写拆分维和 audience-merged)
115
115
  siluzan-tso bing-analysis -a <mediaCustomerId> --start <S> --end <E> \
116
- --sections overview,campaigns,device,geographic,audience-merged,ad-groups,ads,keywords,search-terms \
116
+ --sections overview,campaigns,device,geographic,age-audience,gender-audience,ad-groups,ads,keywords,search-terms \
117
117
  --limit 100 --json-out ./snap-bing
118
118
  ```
119
119
 
@@ -206,9 +206,9 @@ Bing 网关常不返回 `averageDailyCost`、`activeDays`(或为 0)。CLI
206
206
 
207
207
  ## 4. 受众
208
208
 
209
- - **CLI**:推荐 `bing-analysis --sections audience-merged`(年龄+性别合并 JSON);或分别 `--sections age-audience` / `--sections gender-audience`
209
+ - **CLI**:默认已含 `--sections age-audience,gender-audience`。不要与 `audience-merged` 同时拉(Bing SDK 会抢同一份受众 csv,merged 400)。`render --snapshot-dir` 优先 merged,空则回退拆分维。
210
210
 
211
- **数据呈现**:年龄段、性别的展示、点击、消耗、CTR、CPC(按 `data.ageAudience.audience` / `data.genderAudience.audience`)。写入 JSON `tables.audienceAge[]` / `tables.audienceGender[]`。
211
+ **数据呈现**:年龄段、性别的展示、点击、消耗、CTR、CPC。拆分维读 `age-audience-*.json` / `gender-audience-*.json` 的 `audience[]`(标签字段 `audience`)。写入 JSON `tables.audienceAge[]`(`ageRange`)/ `tables.audienceGender[]`(`gender`)。
212
212
 
213
213
  **分析(必写)** → 写入 `narrative.sections.audience.{analysis,suggestions}`:
214
214
 
@@ -225,7 +225,7 @@ Bing 网关常不返回 `averageDailyCost`、`activeDays`(或为 0)。CLI
225
225
  | 广告组 | `bing-analysis --sections ad-groups` | `ad-groups-*.json` |
226
226
  | 广告 | `bing-analysis --sections ads` | `ads-*.json` |
227
227
 
228
- **数据呈现**:各表按消耗降序;系列含 `campaignStatus`;广告组可含质量分相关字段(以 outline 为准)。写入 JSON `tables.campaigns[]` / `tables.adGroups[]` / `tables.ads[]`。
228
+ **数据呈现**:各表按消耗降序;系列含 `campaignStatus`;广告组可含质量分相关字段(以 outline 为准)。写入 JSON `tables.campaigns[]` / `tables.adGroups[]` / `tables.ads[]`。RSA 的 `adTitle` 常为空串,HTML 不展示空标题列。
229
229
 
230
230
  **分析(必写,三个子块各写一段,不可合并为一句带过)**:
231
231
 
@@ -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.49-beta.2'
12
+ $PKG_VERSION = '1.1.49-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.49-beta.2"
12
+ readonly PKG_VERSION="1.1.49-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.49-beta.2",
3
+ "version": "1.1.49-beta.3",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",