siluzan-tso-cli 1.1.49-beta.3 → 1.1.49-beta.4
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 +88 -23
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/report-templates/bing-period-report.md +2 -2
- package/dist/skill/report-templates/bing-period-report.html +5 -5
- package/dist/skill/report-templates/bing-period-report.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.49-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.49-beta.4),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -117511,6 +117511,35 @@ function liveAdHasHeadline(ad, headline) {
|
|
|
117511
117511
|
if (!h) return false;
|
|
117512
117512
|
return collectRsaHeadlines(ad).includes(h);
|
|
117513
117513
|
}
|
|
117514
|
+
function overlapHeadlineCount(planned, live) {
|
|
117515
|
+
const liveSet = new Set(collectRsaHeadlines(live));
|
|
117516
|
+
let n = 0;
|
|
117517
|
+
for (const h of planned) {
|
|
117518
|
+
const key = h.trim().toLowerCase();
|
|
117519
|
+
if (key && liveSet.has(key)) n += 1;
|
|
117520
|
+
}
|
|
117521
|
+
return n;
|
|
117522
|
+
}
|
|
117523
|
+
function pickLiveAdForPlannedRsa(plannedHeadlines, primary, unmatchedLiveAds) {
|
|
117524
|
+
if (unmatchedLiveAds.length === 0) return null;
|
|
117525
|
+
const primaryKey = primary.trim().toLowerCase();
|
|
117526
|
+
let bestIdx = -1;
|
|
117527
|
+
let bestScore = 0;
|
|
117528
|
+
for (let i = 0; i < unmatchedLiveAds.length; i++) {
|
|
117529
|
+
const live = unmatchedLiveAds[i];
|
|
117530
|
+
const overlap = overlapHeadlineCount(plannedHeadlines, live);
|
|
117531
|
+
if (overlap <= 0) continue;
|
|
117532
|
+
const hasPrimary = primaryKey.length > 0 && liveAdHasHeadline(live, primary);
|
|
117533
|
+
const score = (hasPrimary ? 1e3 : 0) + overlap;
|
|
117534
|
+
if (score > bestScore) {
|
|
117535
|
+
bestScore = score;
|
|
117536
|
+
bestIdx = i;
|
|
117537
|
+
}
|
|
117538
|
+
}
|
|
117539
|
+
if (bestIdx < 0) return null;
|
|
117540
|
+
const [picked] = unmatchedLiveAds.splice(bestIdx, 1);
|
|
117541
|
+
return picked ?? null;
|
|
117542
|
+
}
|
|
117514
117543
|
function livePoolHasText(pool, text2) {
|
|
117515
117544
|
const t = text2.trim().toLowerCase();
|
|
117516
117545
|
return t.length > 0 && pool.has(t);
|
|
@@ -117768,12 +117797,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
117768
117797
|
const liveAdsInGroup = live.ads.filter(
|
|
117769
117798
|
(a) => belongsToCampaign(a, campaignId, campaignName) && belongsToAdGroup(a, groupName, liveGroupId)
|
|
117770
117799
|
);
|
|
117771
|
-
const
|
|
117772
|
-
const liveDescPool = /* @__PURE__ */ new Set();
|
|
117773
|
-
for (const la of liveAdsInGroup) {
|
|
117774
|
-
for (const h of collectRsaHeadlines(la)) liveHeadlinePool.add(h);
|
|
117775
|
-
for (const d of collectRsaDescriptions(la)) liveDescPool.add(d);
|
|
117776
|
-
}
|
|
117800
|
+
const unmatchedLiveAds = [...liveAdsInGroup];
|
|
117777
117801
|
const ads = g["AdsForBatchJob"];
|
|
117778
117802
|
if (Array.isArray(ads)) {
|
|
117779
117803
|
for (let ai = 0; ai < ads.length; ai++) {
|
|
@@ -117783,11 +117807,14 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
117783
117807
|
const primary = pickString2(ad["headlinePart1"], ad["AdTitle"]);
|
|
117784
117808
|
const finalUrl = pickString2(ad["Finalurl"], ad["DestinationUrl"], ad["finalUrl"]);
|
|
117785
117809
|
const rsaAssets = listPlannedRsaAssets(ad, groupPath, ai, groupName);
|
|
117810
|
+
const plannedHeadlines = rsaAssets.filter((a) => a.kind === "headline").map((a) => a.text);
|
|
117811
|
+
const adLabel = primary || `AdsForBatchJob[${ai}]`;
|
|
117786
117812
|
if (groupAbsent) {
|
|
117787
117813
|
pushItem(items3, {
|
|
117788
117814
|
layer: "ad",
|
|
117789
117815
|
path: path50,
|
|
117790
117816
|
adGroupName: groupName,
|
|
117817
|
+
adLabel,
|
|
117791
117818
|
plannedContent: primary ? `RSA \u9996\u6807\u9898: ${primary}${finalUrl ? ` | \u843D\u5730\u9875: ${finalUrl}` : ""}` : `AdsForBatchJob[${ai}]\uFF08\u7F3A\u5C11 headlinePart1\uFF09`,
|
|
117792
117819
|
status: "skipped",
|
|
117793
117820
|
liveNote: "\u5E7F\u544A\u7EC4\u672A\u521B\u5EFA\uFF0C\u8DF3\u8FC7\u521B\u610F\u6BD4\u5BF9",
|
|
@@ -117799,6 +117826,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
117799
117826
|
layer: "ad",
|
|
117800
117827
|
path: asset.path,
|
|
117801
117828
|
adGroupName: groupName,
|
|
117829
|
+
adLabel,
|
|
117802
117830
|
plannedContent: `${label}: ${asset.text}`,
|
|
117803
117831
|
status: "skipped",
|
|
117804
117832
|
liveNote: "\u5E7F\u544A\u7EC4\u672A\u521B\u5EFA\uFF0C\u8DF3\u8FC7\u6587\u6848\u6BD4\u5BF9",
|
|
@@ -117807,11 +117835,15 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
117807
117835
|
}
|
|
117808
117836
|
continue;
|
|
117809
117837
|
}
|
|
117838
|
+
const matchedLive = pickLiveAdForPlannedRsa(plannedHeadlines, primary, unmatchedLiveAds);
|
|
117839
|
+
const liveHeadlineSet = new Set(matchedLive ? collectRsaHeadlines(matchedLive) : []);
|
|
117840
|
+
const liveDescSet = new Set(matchedLive ? collectRsaDescriptions(matchedLive) : []);
|
|
117810
117841
|
if (!primary) {
|
|
117811
117842
|
pushItem(items3, {
|
|
117812
117843
|
layer: "ad",
|
|
117813
117844
|
path: path50,
|
|
117814
117845
|
adGroupName: groupName,
|
|
117846
|
+
adLabel,
|
|
117815
117847
|
plannedContent: `AdsForBatchJob[${ai}]\uFF08\u7F3A\u5C11 headlinePart1\uFF0C\u65E0\u6CD5\u6BD4\u5BF9\u6574\u6761 RSA\uFF09`,
|
|
117816
117848
|
status: "missing",
|
|
117817
117849
|
liveNote: `\u540C\u7EC4\u5DF2\u6709 ${liveAdsInGroup.length} \u6761\u521B\u610F`,
|
|
@@ -117819,41 +117851,44 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
117819
117851
|
});
|
|
117820
117852
|
for (const asset of rsaAssets) {
|
|
117821
117853
|
const label = asset.kind === "headline" ? "\u6807\u9898" : "\u63CF\u8FF0";
|
|
117822
|
-
const pool = asset.kind === "headline" ?
|
|
117854
|
+
const pool = asset.kind === "headline" ? liveHeadlineSet : liveDescSet;
|
|
117823
117855
|
const found = livePoolHasText(pool, asset.text);
|
|
117824
117856
|
pushItem(items3, {
|
|
117825
117857
|
layer: "ad",
|
|
117826
117858
|
path: asset.path,
|
|
117827
117859
|
adGroupName: groupName,
|
|
117860
|
+
adLabel,
|
|
117828
117861
|
plannedContent: `${label}: ${asset.text}`,
|
|
117829
117862
|
status: found ? "ok" : "missing",
|
|
117830
|
-
liveNote: found ? `\
|
|
117863
|
+
liveNote: found ? `\u5DF2\u5728\u5BF9\u5E94 RSA \u4E0A\u627E\u5230\u6B64${label}` : `\u5BF9\u5E94 RSA \u672A\u89C1\u6B64${label}`,
|
|
117831
117864
|
summary: found ? `RSA ${label}\u5DF2\u751F\u6548\uFF1A${asset.text}` : `RSA ${label}\u672A\u751F\u6548\uFF1A${asset.text}`
|
|
117832
117865
|
});
|
|
117833
117866
|
}
|
|
117834
117867
|
continue;
|
|
117835
117868
|
}
|
|
117836
|
-
const rsaFound =
|
|
117869
|
+
const rsaFound = matchedLive != null && liveAdHasHeadline(matchedLive, primary);
|
|
117837
117870
|
pushItem(items3, {
|
|
117838
117871
|
layer: "ad",
|
|
117839
117872
|
path: path50,
|
|
117840
117873
|
adGroupName: groupName,
|
|
117874
|
+
adLabel,
|
|
117841
117875
|
plannedContent: `RSA \u9996\u6807\u9898: ${primary}${finalUrl ? ` | \u843D\u5730\u9875: ${finalUrl}` : ""}`,
|
|
117842
117876
|
status: rsaFound ? "ok" : "missing",
|
|
117843
|
-
liveNote: rsaFound ? `\
|
|
117844
|
-
summary: rsaFound ? `RSA \u5DF2\u521B\u5EFA\uFF08\u9996\u6761\u6807\u9898\u300C${primary}\u300D\uFF09` : `RSA \u672A\u521B\u5EFA\uFF08\u9996\u6761\u6807\u9898\u300C${primary}\u300D\u5728\
|
|
117877
|
+
liveNote: rsaFound ? `\u5DF2\u4E00\u5BF9\u4E00\u5339\u914D\u5230\u540C\u7EC4 RSA\uFF08\u6807\u9898\u91CD\u53E0 ${overlapHeadlineCount(plannedHeadlines, matchedLive)}\uFF09` : `\u540C\u7EC4 ${liveAdsInGroup.length} \u6761 RSA \u4E2D\u65E0\u6B64\u9996\u6807\u9898\uFF0C\u6216\u5DF2\u88AB\u5176\u5B83\u8BA1\u5212\u5E7F\u544A\u8BA4\u9886`,
|
|
117878
|
+
summary: rsaFound ? `RSA \u5DF2\u521B\u5EFA\uFF08\u9996\u6761\u6807\u9898\u300C${primary}\u300D\uFF09` : `RSA \u672A\u521B\u5EFA\uFF08\u9996\u6761\u6807\u9898\u300C${primary}\u300D\u5728\u5BF9\u5E94\u5E7F\u544A\u4E0A\u672A\u627E\u5230\uFF09`
|
|
117845
117879
|
});
|
|
117846
117880
|
for (const asset of rsaAssets) {
|
|
117847
117881
|
const label = asset.kind === "headline" ? "\u6807\u9898" : "\u63CF\u8FF0";
|
|
117848
|
-
const pool = asset.kind === "headline" ?
|
|
117882
|
+
const pool = asset.kind === "headline" ? liveHeadlineSet : liveDescSet;
|
|
117849
117883
|
const found = livePoolHasText(pool, asset.text);
|
|
117850
117884
|
pushItem(items3, {
|
|
117851
117885
|
layer: "ad",
|
|
117852
117886
|
path: asset.path,
|
|
117853
117887
|
adGroupName: groupName,
|
|
117888
|
+
adLabel,
|
|
117854
117889
|
plannedContent: `${label}: ${asset.text}`,
|
|
117855
117890
|
status: found ? "ok" : "missing",
|
|
117856
|
-
liveNote: found ? `\
|
|
117891
|
+
liveNote: found ? `\u5DF2\u5728\u5BF9\u5E94 RSA \u4E0A\u627E\u5230\u6B64${label}` : `\u5BF9\u5E94 RSA \u672A\u89C1\u6B64${label}\uFF08\u8BE5\u5E7F\u544A\u6587\u6848 ${pool.size} \u6761\uFF09`,
|
|
117857
117892
|
summary: found ? `RSA ${label}\u5DF2\u751F\u6548\uFF1A${asset.text}` : `RSA ${label}\u672A\u751F\u6548\uFF1A${asset.text}`
|
|
117858
117893
|
});
|
|
117859
117894
|
}
|
|
@@ -118035,6 +118070,7 @@ async function fetchLiveSnapshotForCampaign(config, accountId, campaignId, campa
|
|
|
118035
118070
|
const params = new URLSearchParams();
|
|
118036
118071
|
params.set("startDate", toGoogleDate(void 0, -30));
|
|
118037
118072
|
params.set("endDate", toGoogleDate(void 0, 0));
|
|
118073
|
+
params.set("newest", "true");
|
|
118038
118074
|
const qs = params.toString();
|
|
118039
118075
|
const [campaigns, adGroups, keywords, negativeKeywords, ads, extensions, targetedLocations] = await Promise.all([
|
|
118040
118076
|
fetchList(
|
|
@@ -118353,13 +118389,31 @@ function buildCampaignCreateStatusMarkdown(result) {
|
|
|
118353
118389
|
byGroup.set(key, list);
|
|
118354
118390
|
}
|
|
118355
118391
|
for (const [groupName, groupItems] of byGroup) {
|
|
118356
|
-
|
|
118357
|
-
|
|
118358
|
-
|
|
118392
|
+
if (layer === "keyword") {
|
|
118393
|
+
lines.push(`## \u5E7F\u544A\u7EC4\u300C${groupName}\u300D\xB7 \u5173\u952E\u8BCD`, ``);
|
|
118394
|
+
lines.push(`| \u72B6\u6001 | \u8BA1\u5212\u5185\u5BB9 |`, `| --- | --- |`);
|
|
118395
|
+
for (const item of groupItems) {
|
|
118396
|
+
lines.push(`| ${statusLabel(item.status)} | ${mdCell(item.plannedContent)} |`);
|
|
118397
|
+
}
|
|
118398
|
+
lines.push(``);
|
|
118399
|
+
continue;
|
|
118400
|
+
}
|
|
118401
|
+
lines.push(`## \u5E7F\u544A\u7EC4\u300C${groupName}\u300D\xB7 RSA \u6587\u6848`, ``);
|
|
118402
|
+
const byAd = /* @__PURE__ */ new Map();
|
|
118359
118403
|
for (const item of groupItems) {
|
|
118360
|
-
|
|
118404
|
+
const key = item.adLabel ?? "\u2014";
|
|
118405
|
+
const list = byAd.get(key) ?? [];
|
|
118406
|
+
list.push(item);
|
|
118407
|
+
byAd.set(key, list);
|
|
118408
|
+
}
|
|
118409
|
+
for (const [adLabel, adItems] of byAd) {
|
|
118410
|
+
lines.push(`### \u5E7F\u544A\u300C${adLabel}\u300D`, ``);
|
|
118411
|
+
lines.push(`| \u72B6\u6001 | \u8BA1\u5212\u5185\u5BB9 |`, `| --- | --- |`);
|
|
118412
|
+
for (const item of adItems) {
|
|
118413
|
+
lines.push(`| ${statusLabel(item.status)} | ${mdCell(item.plannedContent)} |`);
|
|
118414
|
+
}
|
|
118415
|
+
lines.push(``);
|
|
118361
118416
|
}
|
|
118362
|
-
lines.push(``);
|
|
118363
118417
|
}
|
|
118364
118418
|
continue;
|
|
118365
118419
|
}
|
|
@@ -136974,19 +137028,30 @@ function mapAdGroupRows(payload) {
|
|
|
136974
137028
|
])
|
|
136975
137029
|
);
|
|
136976
137030
|
}
|
|
137031
|
+
function displayAdTitle(row) {
|
|
137032
|
+
const raw = String(row.adTitle ?? "").trim();
|
|
137033
|
+
if (raw) return { adTitle: raw, adTitleFallback: false };
|
|
137034
|
+
const camp = String(row.campaignName ?? "").trim();
|
|
137035
|
+
const group = String(row.adGroupName ?? "").trim();
|
|
137036
|
+
const fallback = [camp, group].filter(Boolean).join(" / ");
|
|
137037
|
+
return { adTitle: fallback, adTitleFallback: fallback.length > 0 };
|
|
137038
|
+
}
|
|
136977
137039
|
function mapAdRows(payload) {
|
|
136978
|
-
return asRows2(payload).map(
|
|
136979
|
-
|
|
137040
|
+
return asRows2(payload).map((r) => {
|
|
137041
|
+
const mapped = pickMetrics(r, [
|
|
136980
137042
|
"campaignId",
|
|
136981
137043
|
"campaignName",
|
|
136982
137044
|
"adGroupId",
|
|
136983
137045
|
"adGroupName",
|
|
136984
137046
|
"adId",
|
|
136985
|
-
"adTitle",
|
|
136986
137047
|
"adType",
|
|
136987
137048
|
"adStatus"
|
|
136988
|
-
])
|
|
136989
|
-
|
|
137049
|
+
]);
|
|
137050
|
+
const title = displayAdTitle(r);
|
|
137051
|
+
mapped.adTitle = title.adTitle;
|
|
137052
|
+
mapped.adTitleFallback = title.adTitleFallback;
|
|
137053
|
+
return mapped;
|
|
137054
|
+
});
|
|
136990
137055
|
}
|
|
136991
137056
|
function mapKeywordRows(payload) {
|
|
136992
137057
|
return asRows2(payload).map((r) => {
|
package/dist/skill/_meta.json
CHANGED
|
@@ -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}`(RSA
|
|
58
|
+
- `ads[]`:`{adTitle, adGroupName, adType, spend, clicks, ctr, conversions}`(RSA 的报表 `adTitle` 常为空,`render` 用「系列 / 广告组」兜底展示并在表上方注明)
|
|
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 撰写的叙事内容**):
|
|
@@ -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[]`。RSA
|
|
228
|
+
**数据呈现**:各表按消耗降序;系列含 `campaignStatus`;广告组可含质量分相关字段(以 outline 为准)。写入 JSON `tables.campaigns[]` / `tables.adGroups[]` / `tables.ads[]`。RSA 无创意标题时 HTML 仍展示「广告标题」列,内容为「系列 / 广告组」。
|
|
229
229
|
|
|
230
230
|
**分析(必写,三个子块各写一段,不可合并为一句带过)**:
|
|
231
231
|
|
|
@@ -1025,14 +1025,14 @@
|
|
|
1025
1025
|
const currency = currencyOf(data);
|
|
1026
1026
|
const section = data.narrative?.sections?.ads;
|
|
1027
1027
|
const sorted = topBySpend(rows, 10);
|
|
1028
|
-
const
|
|
1029
|
-
const titleNotice =
|
|
1030
|
-
? `<div class="empty-note">Bing
|
|
1028
|
+
const usedFallback = sorted.some((r) => r.adTitleFallback);
|
|
1029
|
+
const titleNotice = usedFallback
|
|
1030
|
+
? `<div class="empty-note">Bing 响应式搜索广告的报表不返回创意标题,本列用「系列 / 广告组」名称代替。</div>`
|
|
1031
1031
|
: "";
|
|
1032
1032
|
const body = sorted
|
|
1033
1033
|
.map(
|
|
1034
1034
|
(r) => `<tr>
|
|
1035
|
-
|
|
1035
|
+
<td>${escapeHtml(r.adTitle || "—")}</td>
|
|
1036
1036
|
<td>${escapeHtml(r.adGroupName || "—")}</td>
|
|
1037
1037
|
<td>${escapeHtml(r.adType || "—")}</td>
|
|
1038
1038
|
<td class="num">${money(r.spend, currency)}</td>
|
|
@@ -1047,7 +1047,7 @@
|
|
|
1047
1047
|
? `<div class="card-title-row"><h3>广告 Top 10</h3><span class="hint">共 ${rows.length} 条广告,按消耗排序</span></div>
|
|
1048
1048
|
${titleNotice}
|
|
1049
1049
|
<div class="table-wrap"><table>
|
|
1050
|
-
<thead><tr
|
|
1050
|
+
<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>
|
|
1051
1051
|
<tbody>${body}</tbody>
|
|
1052
1052
|
</table></div>`
|
|
1053
1053
|
: emptyNoteHtml();
|
|
@@ -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}`(RSA
|
|
58
|
+
- `ads[]`:`{adTitle, adGroupName, adType, spend, clicks, ctr, conversions}`(RSA 的报表 `adTitle` 常为空,`render` 用「系列 / 广告组」兜底展示并在表上方注明)
|
|
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 撰写的叙事内容**):
|
|
@@ -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[]`。RSA
|
|
228
|
+
**数据呈现**:各表按消耗降序;系列含 `campaignStatus`;广告组可含质量分相关字段(以 outline 为准)。写入 JSON `tables.campaigns[]` / `tables.adGroups[]` / `tables.ads[]`。RSA 无创意标题时 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.
|
|
12
|
+
$PKG_VERSION = '1.1.49-beta.4'
|
|
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.
|
|
12
|
+
readonly PKG_VERSION="1.1.49-beta.4"
|
|
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"
|