siluzan-tso-cli 1.1.35-beta.5 → 1.1.36-beta.1
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 +224 -109
- package/dist/skill/SKILL.md +1 -1
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/assets/campaign-create-template.md +1 -1
- package/dist/skill/references/README.md +1 -0
- package/dist/skill/references/core/agent-conventions.md +3 -2
- package/dist/skill/references/core/user-communication-guide.md +112 -0
- package/dist/skill/references/core/workflows.md +3 -2
- package/dist/skill/references/google-ads/google-ads-campaign-plan.md +30 -2
- package/dist/skill/references/google-ads/google-ads.md +15 -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.
|
|
54
|
+
> **注意**:当前为测试版(1.1.36-beta.1),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -113676,17 +113676,120 @@ init_cli_table();
|
|
|
113676
113676
|
// src/commands/ad/campaign-batch-diff.ts
|
|
113677
113677
|
init_auth();
|
|
113678
113678
|
init_cli_json_snapshot();
|
|
113679
|
+
|
|
113680
|
+
// src/commands/ad/campaign-extension-remediate.ts
|
|
113681
|
+
function shellArg(value) {
|
|
113682
|
+
return /[\s"]/.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value;
|
|
113683
|
+
}
|
|
113684
|
+
function pickString(...vals) {
|
|
113685
|
+
for (const v of vals) {
|
|
113686
|
+
if (typeof v === "string" && v.trim()) return v.trim();
|
|
113687
|
+
}
|
|
113688
|
+
return "";
|
|
113689
|
+
}
|
|
113679
113690
|
function asRecord2(v) {
|
|
113680
113691
|
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
113681
113692
|
}
|
|
113682
|
-
function
|
|
113693
|
+
function extensionType2(ext) {
|
|
113694
|
+
return pickString(ext["typeV2"], ext["TypeV2"], ext["AssetFieldType"], ext["type"]).toUpperCase();
|
|
113695
|
+
}
|
|
113696
|
+
function extensionLevel(ext) {
|
|
113697
|
+
const level = pickString(ext["level"], ext["Level"]) || "Campaign";
|
|
113698
|
+
return level.charAt(0).toUpperCase() + level.slice(1).toLowerCase();
|
|
113699
|
+
}
|
|
113700
|
+
function buildExtensionRemediateCommandLine(opts) {
|
|
113701
|
+
const { accountId, campaignId, ext } = opts;
|
|
113702
|
+
const jsonOut = shellArg(opts.jsonOutDir ?? "./snap");
|
|
113703
|
+
const type = extensionType2(ext);
|
|
113704
|
+
const level = extensionLevel(ext);
|
|
113705
|
+
const props = asRecord2(ext["Properties"]) ?? {};
|
|
113706
|
+
const levelFlag = `--level ${level}`;
|
|
113707
|
+
const campaignFlag = level === "Campaign" ? `--campaign-id ${campaignId}` : "";
|
|
113708
|
+
const base = ["siluzan-tso", "ad", "extension"];
|
|
113709
|
+
if (type === "SITELINK") {
|
|
113710
|
+
const text = pickString(props["Text"], props["LinkText"]);
|
|
113711
|
+
const url = pickString(props["DestinationUrl"]);
|
|
113712
|
+
if (!text || !url) return null;
|
|
113713
|
+
const parts = [
|
|
113714
|
+
...base,
|
|
113715
|
+
"sitelink",
|
|
113716
|
+
`-a ${accountId}`,
|
|
113717
|
+
`--text ${shellArg(text)}`,
|
|
113718
|
+
`--url ${shellArg(url)}`
|
|
113719
|
+
];
|
|
113720
|
+
const line2 = pickString(props["Line2"], props["Description1"]);
|
|
113721
|
+
const line3 = pickString(props["Line3"], props["Description2"]);
|
|
113722
|
+
if (line2) parts.push(`--line2 ${shellArg(line2)}`);
|
|
113723
|
+
if (line3) parts.push(`--line3 ${shellArg(line3)}`);
|
|
113724
|
+
parts.push(levelFlag);
|
|
113725
|
+
if (campaignFlag) parts.push(campaignFlag);
|
|
113726
|
+
parts.push(`--json-out ${jsonOut}`);
|
|
113727
|
+
return parts.join(" ");
|
|
113728
|
+
}
|
|
113729
|
+
if (type === "CALLOUT") {
|
|
113730
|
+
const text = pickString(props["Text"], props["CalloutText"]);
|
|
113731
|
+
if (!text) return null;
|
|
113732
|
+
const parts = [
|
|
113733
|
+
...base,
|
|
113734
|
+
"callout",
|
|
113735
|
+
`-a ${accountId}`,
|
|
113736
|
+
`--text ${shellArg(text)}`,
|
|
113737
|
+
levelFlag
|
|
113738
|
+
];
|
|
113739
|
+
if (campaignFlag) parts.push(campaignFlag);
|
|
113740
|
+
parts.push(`--json-out ${jsonOut}`);
|
|
113741
|
+
return parts.join(" ");
|
|
113742
|
+
}
|
|
113743
|
+
if (type === "CALL") {
|
|
113744
|
+
const code = pickString(props["ContryCode"], props["CountryCode"]);
|
|
113745
|
+
const phone = pickString(props["PhoneNumber"], props["phoneNumber"]);
|
|
113746
|
+
if (!code || !phone) return null;
|
|
113747
|
+
const parts = [
|
|
113748
|
+
...base,
|
|
113749
|
+
"call",
|
|
113750
|
+
`-a ${accountId}`,
|
|
113751
|
+
`--country-code ${shellArg(code)}`,
|
|
113752
|
+
`--phone ${shellArg(phone)}`,
|
|
113753
|
+
levelFlag
|
|
113754
|
+
];
|
|
113755
|
+
if (campaignFlag) parts.push(campaignFlag);
|
|
113756
|
+
parts.push(`--json-out ${jsonOut}`);
|
|
113757
|
+
return parts.join(" ");
|
|
113758
|
+
}
|
|
113759
|
+
if (type === "STRUCTURED_SNIPPET") {
|
|
113760
|
+
const ssv = asRecord2(ext["StructuredSnippetHeaderValue"]);
|
|
113761
|
+
const header = pickString(ssv?.["Key"]);
|
|
113762
|
+
const values = ssv?.["Value"];
|
|
113763
|
+
if (!header || !Array.isArray(values) || values.length === 0) return null;
|
|
113764
|
+
const valueStr = values.filter((v) => typeof v === "string" && v.trim().length > 0).join(",");
|
|
113765
|
+
if (!valueStr) return null;
|
|
113766
|
+
const parts = [
|
|
113767
|
+
...base,
|
|
113768
|
+
"snippet",
|
|
113769
|
+
`-a ${accountId}`,
|
|
113770
|
+
`--header ${shellArg(header)}`,
|
|
113771
|
+
`--values ${shellArg(valueStr)}`,
|
|
113772
|
+
levelFlag
|
|
113773
|
+
];
|
|
113774
|
+
if (campaignFlag) parts.push(campaignFlag);
|
|
113775
|
+
parts.push(`--json-out ${jsonOut}`);
|
|
113776
|
+
return parts.join(" ");
|
|
113777
|
+
}
|
|
113778
|
+
return null;
|
|
113779
|
+
}
|
|
113780
|
+
|
|
113781
|
+
// src/commands/ad/campaign-batch-diff.ts
|
|
113782
|
+
function asRecord3(v) {
|
|
113783
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
113784
|
+
}
|
|
113785
|
+
function pickString2(...vals) {
|
|
113683
113786
|
for (const v of vals) {
|
|
113684
113787
|
if (typeof v === "string" && v.trim()) return v.trim();
|
|
113685
113788
|
}
|
|
113686
113789
|
return "";
|
|
113687
113790
|
}
|
|
113688
113791
|
function normMatchType(v) {
|
|
113689
|
-
return
|
|
113792
|
+
return pickString2(v).toUpperCase() || "BROAD";
|
|
113690
113793
|
}
|
|
113691
113794
|
function keywordKey(core, matchType) {
|
|
113692
113795
|
return `${normMatchType(matchType)}:${unwrapKeywordDisplayTextForEdit(core).trim().toLowerCase()}`;
|
|
@@ -113696,19 +113799,19 @@ function liveKeywordTexts(item) {
|
|
|
113696
113799
|
if (Array.isArray(kt)) {
|
|
113697
113800
|
return kt.filter((t2) => typeof t2 === "string" && t2.trim().length > 0);
|
|
113698
113801
|
}
|
|
113699
|
-
const t =
|
|
113802
|
+
const t = pickString2(item["text"], kt);
|
|
113700
113803
|
return t ? [t] : [];
|
|
113701
113804
|
}
|
|
113702
113805
|
function belongsToCampaign(item, campaignId, campaignName) {
|
|
113703
|
-
const cid =
|
|
113704
|
-
const cname =
|
|
113806
|
+
const cid = pickString2(item["campaignId"], item["campaignid"], item["CampaignId"]);
|
|
113807
|
+
const cname = pickString2(item["campaignName"], item["campaign"], item["CampaignName"]);
|
|
113705
113808
|
if (campaignId && cid) return cid === campaignId;
|
|
113706
113809
|
if (campaignName && cname) return cname === campaignName;
|
|
113707
113810
|
return false;
|
|
113708
113811
|
}
|
|
113709
113812
|
function belongsToAdGroup(item, adGroupName, adGroupId) {
|
|
113710
|
-
const gid =
|
|
113711
|
-
const gname =
|
|
113813
|
+
const gid = pickString2(item["adGroupId"], item["adgroupId"], item["AdGroupId"]);
|
|
113814
|
+
const gname = pickString2(item["adGroupName"], item["adGroup"], item["AdGroupName"]);
|
|
113712
113815
|
if (adGroupId && gid) return gid === adGroupId;
|
|
113713
113816
|
return gname === adGroupName;
|
|
113714
113817
|
}
|
|
@@ -113729,10 +113832,10 @@ function liveAdHasHeadline(ad, headline) {
|
|
|
113729
113832
|
}
|
|
113730
113833
|
function formatExtensionPlanned(ext) {
|
|
113731
113834
|
if (!ext) return "\u2014";
|
|
113732
|
-
const type =
|
|
113733
|
-
const props =
|
|
113734
|
-
const phone = props ?
|
|
113735
|
-
const code = props ?
|
|
113835
|
+
const type = pickString2(ext["typeV2"], ext["AssetFieldType"], ext["type"]);
|
|
113836
|
+
const props = asRecord3(ext["Properties"]);
|
|
113837
|
+
const phone = props ? pickString2(props["PhoneNumber"], props["phoneNumber"]) : "";
|
|
113838
|
+
const code = props ? pickString2(props["ContryCode"], props["CountryCode"]) : "";
|
|
113736
113839
|
const parts = [type ? `\u7C7B\u578B: ${type}` : "", code || phone ? `\u7535\u8BDD: ${code}${phone}` : ""].filter(
|
|
113737
113840
|
Boolean
|
|
113738
113841
|
);
|
|
@@ -113743,15 +113846,15 @@ function listPlannedKeywords(campaign) {
|
|
|
113743
113846
|
if (!Array.isArray(groups)) return [];
|
|
113744
113847
|
const out = [];
|
|
113745
113848
|
for (let gi = 0; gi < groups.length; gi++) {
|
|
113746
|
-
const g =
|
|
113849
|
+
const g = asRecord3(groups[gi]);
|
|
113747
113850
|
if (!g) continue;
|
|
113748
|
-
const groupName =
|
|
113851
|
+
const groupName = pickString2(g["Name"], g["name"]) || `AdGroupsForBatchJob[${gi}]`;
|
|
113749
113852
|
const blocks = g["KeywordsForBatchJob"];
|
|
113750
113853
|
if (!Array.isArray(blocks)) continue;
|
|
113751
113854
|
for (let bi = 0; bi < blocks.length; bi++) {
|
|
113752
|
-
const block =
|
|
113855
|
+
const block = asRecord3(blocks[bi]);
|
|
113753
113856
|
if (!block) continue;
|
|
113754
|
-
const matchTypeV2 =
|
|
113857
|
+
const matchTypeV2 = pickString2(block["MatchTypeV2"], block["matchTypeV2"]) || "BROAD";
|
|
113755
113858
|
const texts = block["KeywordText"];
|
|
113756
113859
|
if (!Array.isArray(texts)) continue;
|
|
113757
113860
|
for (let ki = 0; ki < texts.length; ki++) {
|
|
@@ -113773,9 +113876,9 @@ function listPlannedNegativeKeywords(campaign) {
|
|
|
113773
113876
|
if (!Array.isArray(blocks)) return [];
|
|
113774
113877
|
const out = [];
|
|
113775
113878
|
for (let bi = 0; bi < blocks.length; bi++) {
|
|
113776
|
-
const block =
|
|
113879
|
+
const block = asRecord3(blocks[bi]);
|
|
113777
113880
|
if (!block) continue;
|
|
113778
|
-
const matchTypeV2 =
|
|
113881
|
+
const matchTypeV2 = pickString2(block["MatchTypeV2"], block["matchTypeV2"]) || "BROAD";
|
|
113779
113882
|
const texts = block["KeywordText"];
|
|
113780
113883
|
if (!Array.isArray(texts)) continue;
|
|
113781
113884
|
for (let ki = 0; ki < texts.length; ki++) {
|
|
@@ -113793,7 +113896,7 @@ function listPlannedNegativeKeywords(campaign) {
|
|
|
113793
113896
|
}
|
|
113794
113897
|
function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
113795
113898
|
const campaign = cfg.campaign ?? {};
|
|
113796
|
-
const campaignName =
|
|
113899
|
+
const campaignName = pickString2(cfg.name, campaign["Name"], campaign["name"]);
|
|
113797
113900
|
const accountId = String(cfg.account ?? "");
|
|
113798
113901
|
const missing = [];
|
|
113799
113902
|
if (!live.campaignFound) {
|
|
@@ -113807,15 +113910,15 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
113807
113910
|
}
|
|
113808
113911
|
const liveGroups = live.adGroups;
|
|
113809
113912
|
const liveGroupNames = new Set(
|
|
113810
|
-
liveGroups.map((g) =>
|
|
113913
|
+
liveGroups.map((g) => pickString2(g["name"], g["Name"]).toLowerCase()).filter(Boolean)
|
|
113811
113914
|
);
|
|
113812
113915
|
const groups = campaign["AdGroupsForBatchJob"];
|
|
113813
113916
|
const plannedGroupCount = Array.isArray(groups) ? groups.length : 0;
|
|
113814
113917
|
if (Array.isArray(groups)) {
|
|
113815
113918
|
for (let gi = 0; gi < groups.length; gi++) {
|
|
113816
|
-
const g =
|
|
113919
|
+
const g = asRecord3(groups[gi]);
|
|
113817
113920
|
if (!g) continue;
|
|
113818
|
-
const groupName =
|
|
113921
|
+
const groupName = pickString2(g["Name"], g["name"]) || `AdGroupsForBatchJob[${gi}]`;
|
|
113819
113922
|
const groupPath = `campaign.AdGroupsForBatchJob[${gi}]`;
|
|
113820
113923
|
if (!liveGroupNames.has(groupName.toLowerCase())) {
|
|
113821
113924
|
missing.push({
|
|
@@ -113829,9 +113932,9 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
113829
113932
|
continue;
|
|
113830
113933
|
}
|
|
113831
113934
|
const liveGroup = liveGroups.find(
|
|
113832
|
-
(lg) =>
|
|
113935
|
+
(lg) => pickString2(lg["name"], lg["Name"]).toLowerCase() === groupName.toLowerCase()
|
|
113833
113936
|
);
|
|
113834
|
-
const liveGroupId = liveGroup ?
|
|
113937
|
+
const liveGroupId = liveGroup ? pickString2(liveGroup["id"], liveGroup["Id"]) : "";
|
|
113835
113938
|
const liveKwInGroup = live.keywords.filter(
|
|
113836
113939
|
(k) => belongsToCampaign(k, campaignId, campaignName) && belongsToAdGroup(k, groupName, liveGroupId)
|
|
113837
113940
|
);
|
|
@@ -113844,9 +113947,9 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
113844
113947
|
const blocks = g["KeywordsForBatchJob"];
|
|
113845
113948
|
if (Array.isArray(blocks)) {
|
|
113846
113949
|
for (let bi = 0; bi < blocks.length; bi++) {
|
|
113847
|
-
const block =
|
|
113950
|
+
const block = asRecord3(blocks[bi]);
|
|
113848
113951
|
if (!block) continue;
|
|
113849
|
-
const matchTypeV2 =
|
|
113952
|
+
const matchTypeV2 = pickString2(block["MatchTypeV2"], block["matchTypeV2"]) || "BROAD";
|
|
113850
113953
|
const texts = block["KeywordText"];
|
|
113851
113954
|
if (!Array.isArray(texts)) continue;
|
|
113852
113955
|
for (let ki = 0; ki < texts.length; ki++) {
|
|
@@ -113873,10 +113976,10 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
113873
113976
|
const ads = g["AdsForBatchJob"];
|
|
113874
113977
|
if (Array.isArray(ads)) {
|
|
113875
113978
|
for (let ai = 0; ai < ads.length; ai++) {
|
|
113876
|
-
const ad =
|
|
113979
|
+
const ad = asRecord3(ads[ai]);
|
|
113877
113980
|
if (!ad) continue;
|
|
113878
113981
|
const path34 = `${groupPath}.AdsForBatchJob[${ai}]`;
|
|
113879
|
-
const primary =
|
|
113982
|
+
const primary = pickString2(ad["headlinePart1"], ad["AdTitle"]);
|
|
113880
113983
|
if (!primary) {
|
|
113881
113984
|
missing.push({
|
|
113882
113985
|
layer: "ad",
|
|
@@ -113888,7 +113991,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
113888
113991
|
});
|
|
113889
113992
|
continue;
|
|
113890
113993
|
}
|
|
113891
|
-
const finalUrl =
|
|
113994
|
+
const finalUrl = pickString2(ad["Finalurl"], ad["DestinationUrl"], ad["finalUrl"]);
|
|
113892
113995
|
const found = liveAdsInGroup.some((la) => liveAdHasHeadline(la, primary));
|
|
113893
113996
|
if (!found) {
|
|
113894
113997
|
missing.push({
|
|
@@ -113929,13 +114032,15 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
113929
114032
|
).length;
|
|
113930
114033
|
if (plannedExt > liveExt && Array.isArray(extBlocks)) {
|
|
113931
114034
|
for (let ei = liveExt; ei < extBlocks.length; ei++) {
|
|
113932
|
-
const ext =
|
|
114035
|
+
const ext = asRecord3(extBlocks[ei]);
|
|
114036
|
+
const remediateCommand = ext && accountId && campaignId ? buildExtensionRemediateCommandLine({ accountId, campaignId, ext }) ?? void 0 : void 0;
|
|
113933
114037
|
missing.push({
|
|
113934
114038
|
layer: "extension",
|
|
113935
114039
|
path: `campaign.ExtensionsForBatchJob[${ei}]`,
|
|
113936
114040
|
plannedContent: formatExtensionPlanned(ext),
|
|
113937
114041
|
liveNote: `\u7CFB\u5217\u7EA7\u9644\u52A0\u4FE1\u606F\u8D26\u6237\u5185 ${liveExt}/${plannedExt} \u6761`,
|
|
113938
|
-
summary: `\u9644\u52A0\u4FE1\u606F\u672A\u521B\u5EFA\uFF1A${formatExtensionPlanned(ext)}
|
|
114042
|
+
summary: `\u9644\u52A0\u4FE1\u606F\u672A\u521B\u5EFA\uFF1A${formatExtensionPlanned(ext)}`,
|
|
114043
|
+
remediateCommand
|
|
113939
114044
|
});
|
|
113940
114045
|
}
|
|
113941
114046
|
}
|
|
@@ -113959,7 +114064,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
113959
114064
|
plannedKeywords: plannedKeywords.length,
|
|
113960
114065
|
liveKeywords: liveKwCount,
|
|
113961
114066
|
plannedAds: Array.isArray(groups) ? groups.reduce((n, g) => {
|
|
113962
|
-
const gr =
|
|
114067
|
+
const gr = asRecord3(g);
|
|
113963
114068
|
const ads = gr?.["AdsForBatchJob"];
|
|
113964
114069
|
return n + (Array.isArray(ads) ? ads.length : 0);
|
|
113965
114070
|
}, 0) : 0,
|
|
@@ -114020,7 +114125,7 @@ async function fetchLiveSnapshotForCampaign(config, accountId, campaignId, campa
|
|
|
114020
114125
|
)
|
|
114021
114126
|
]);
|
|
114022
114127
|
const campaignFound = campaigns.some(
|
|
114023
|
-
(c) =>
|
|
114128
|
+
(c) => pickString2(c["id"], c["Id"]) === campaignId || pickString2(c["name"], c["Name"]) === campaignName
|
|
114024
114129
|
);
|
|
114025
114130
|
return {
|
|
114026
114131
|
campaignFound,
|
|
@@ -114035,8 +114140,8 @@ async function fetchLiveSnapshotForCampaign(config, accountId, campaignId, campa
|
|
|
114035
114140
|
};
|
|
114036
114141
|
}
|
|
114037
114142
|
function resolveCampaignIdFromBatch(record) {
|
|
114038
|
-
const campaign =
|
|
114039
|
-
return
|
|
114143
|
+
const campaign = asRecord3(record["campaign"]);
|
|
114144
|
+
return pickString2(campaign?.["Id"], campaign?.["id"], record["campaignId"]);
|
|
114040
114145
|
}
|
|
114041
114146
|
function shellArgPath(p) {
|
|
114042
114147
|
return /[\s"]/.test(p) ? `"${p.replace(/"/g, '\\"')}"` : p;
|
|
@@ -114083,7 +114188,9 @@ function printBatchDiffFollowUpHint(opts) {
|
|
|
114083
114188
|
if (opts.status === "Successfully") {
|
|
114084
114189
|
console.log(" \u6838\u5BF9\u8BA1\u5212\u4E0E\u8D26\u6237\u662F\u5426\u4E00\u81F4\uFF08\u65E0\u7F3A\u5931\u5E94\u663E\u793A \u2705\uFF09\u3002\n");
|
|
114085
114190
|
} else {
|
|
114086
|
-
console.log(
|
|
114191
|
+
console.log(
|
|
114192
|
+
" \u5217\u51FA\u7F3A\u5931\u9879\u540E\u81EA\u52A8\u8865\u5EFA\uFF08\u9644\u52A0\u4FE1\u606F\u7528 ad extension *\uFF1B\u7EC4/\u8BCD/RSA \u7528\u5BF9\u5E94 create \u547D\u4EE4\uFF09\u3002\n"
|
|
114193
|
+
);
|
|
114087
114194
|
}
|
|
114088
114195
|
console.log(` ${cmd}
|
|
114089
114196
|
`);
|
|
@@ -114111,6 +114218,9 @@ function printMissingEntry(configFile, item) {
|
|
|
114111
114218
|
if (item.liveNote) {
|
|
114112
114219
|
console.log(` \u5B9E\u51B5: ${item.liveNote}`);
|
|
114113
114220
|
}
|
|
114221
|
+
if (item.remediateCommand) {
|
|
114222
|
+
console.log(` \u8865\u5EFA: ${item.remediateCommand}`);
|
|
114223
|
+
}
|
|
114114
114224
|
}
|
|
114115
114225
|
function printMissingGroupByAdGroup(configFile, layer, items) {
|
|
114116
114226
|
const byGroup = /* @__PURE__ */ new Map();
|
|
@@ -114178,6 +114288,11 @@ function formatBatchDiffGrouped(result) {
|
|
|
114178
114288
|
for (const m of layerItems) {
|
|
114179
114289
|
printMissingEntry(configFile, m);
|
|
114180
114290
|
}
|
|
114291
|
+
if (layer === "extension" && layerItems.some((m) => m.remediateCommand)) {
|
|
114292
|
+
console.log(
|
|
114293
|
+
"\n \u{1F4A1} \u9644\u52A0\u4FE1\u606F BatchJob \u5E38\u5931\u8D25\uFF1AAgent \u5E94**\u76F4\u63A5\u6267\u884C**\u4E0A\u65B9\u300C\u8865\u5EFA\u300D\u547D\u4EE4\uFF08\u7528\u6237\u5DF2\u786E\u8BA4\u8FC7 JSON\uFF09\uFF0C\u52FF\u4EC5\u8BE2\u95EE\u662F\u5426\u8865\u5EFA\u3002"
|
|
114294
|
+
);
|
|
114295
|
+
}
|
|
114181
114296
|
}
|
|
114182
114297
|
}
|
|
114183
114298
|
console.log();
|
|
@@ -114190,14 +114305,14 @@ async function runCampaignBatchDiff(opts) {
|
|
|
114190
114305
|
`);
|
|
114191
114306
|
process.exit(1);
|
|
114192
114307
|
}
|
|
114193
|
-
const accountId =
|
|
114308
|
+
const accountId = pickString2(opts.account, cfg.account);
|
|
114194
114309
|
if (!accountId) {
|
|
114195
114310
|
console.error("\n\u274C \u7F3A\u5C11\u8D26\u6237 ID\uFF1A\u8BF7\u5728 JSON \u4E2D\u586B\u5199 account \u6216\u4F7F\u7528 -a\n");
|
|
114196
114311
|
process.exit(1);
|
|
114197
114312
|
}
|
|
114198
114313
|
cfg.account = accountId;
|
|
114199
114314
|
const config = loadConfig(opts.token);
|
|
114200
|
-
let campaignId =
|
|
114315
|
+
let campaignId = pickString2(opts.campaignId);
|
|
114201
114316
|
let batchStatus;
|
|
114202
114317
|
if (opts.batchId) {
|
|
114203
114318
|
const batchUrl = `${config.apiBaseUrl}/query/campaign-creation-record/campaign-batch-asyncs/${opts.batchId}`;
|
|
@@ -114210,7 +114325,7 @@ async function runCampaignBatchDiff(opts) {
|
|
|
114210
114325
|
`);
|
|
114211
114326
|
process.exit(1);
|
|
114212
114327
|
}
|
|
114213
|
-
batchStatus =
|
|
114328
|
+
batchStatus = pickString2(batch["status"]);
|
|
114214
114329
|
if (batchStatus === "Creating") {
|
|
114215
114330
|
console.error("\n\u274C \u4EFB\u52A1\u4ECD\u5728\u521B\u5EFA\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u518D\u6267\u884C ad batch diff\n");
|
|
114216
114331
|
process.exit(1);
|
|
@@ -114228,7 +114343,7 @@ async function runCampaignBatchDiff(opts) {
|
|
|
114228
114343
|
}
|
|
114229
114344
|
}
|
|
114230
114345
|
const campaign = cfg.campaign ?? {};
|
|
114231
|
-
const campaignName =
|
|
114346
|
+
const campaignName = pickString2(cfg.name, campaign["Name"], campaign["name"]);
|
|
114232
114347
|
if (!campaignId && campaignName) {
|
|
114233
114348
|
const googleApiUrl = requireGoogleApi(config);
|
|
114234
114349
|
const params = new URLSearchParams();
|
|
@@ -114240,10 +114355,10 @@ async function runCampaignBatchDiff(opts) {
|
|
|
114240
114355
|
opts.verbose
|
|
114241
114356
|
);
|
|
114242
114357
|
const hit = campaigns.find(
|
|
114243
|
-
(c) =>
|
|
114358
|
+
(c) => pickString2(c["name"], c["Name"]).toLowerCase() === campaignName.toLowerCase()
|
|
114244
114359
|
);
|
|
114245
114360
|
if (hit) {
|
|
114246
|
-
campaignId =
|
|
114361
|
+
campaignId = pickString2(hit["id"], hit["Id"]);
|
|
114247
114362
|
}
|
|
114248
114363
|
}
|
|
114249
114364
|
if (!campaignId) {
|
|
@@ -123718,7 +123833,7 @@ function readJsonFile(filePath) {
|
|
|
123718
123833
|
}
|
|
123719
123834
|
});
|
|
123720
123835
|
}
|
|
123721
|
-
function
|
|
123836
|
+
function asRecord4(value) {
|
|
123722
123837
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
123723
123838
|
return value;
|
|
123724
123839
|
}
|
|
@@ -123744,10 +123859,10 @@ function injectReportData(html, payload) {
|
|
|
123744
123859
|
async function runWebsiteDiagnosisRender(opts) {
|
|
123745
123860
|
const dataPath = path17.resolve(opts.dataFile);
|
|
123746
123861
|
const dataRaw = await readJsonFile(dataPath);
|
|
123747
|
-
let data =
|
|
123862
|
+
let data = asRecord4(dataRaw);
|
|
123748
123863
|
if (opts.collectFile) {
|
|
123749
123864
|
const collectRaw = await readJsonFile(path17.resolve(opts.collectFile));
|
|
123750
|
-
data = mergeCollectLighthouse(data,
|
|
123865
|
+
data = mergeCollectLighthouse(data, asRecord4(collectRaw));
|
|
123751
123866
|
}
|
|
123752
123867
|
const templatePath = websiteDiagnosisReportTemplatePath();
|
|
123753
123868
|
let html;
|
|
@@ -124388,7 +124503,7 @@ function formatMarketReportContentErrors(result) {
|
|
|
124388
124503
|
}
|
|
124389
124504
|
|
|
124390
124505
|
// src/commands/market-analysis/render-report.ts
|
|
124391
|
-
function
|
|
124506
|
+
function asRecord5(value) {
|
|
124392
124507
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
124393
124508
|
return value;
|
|
124394
124509
|
}
|
|
@@ -124418,7 +124533,7 @@ async function runMarketAnalysisRender(opts) {
|
|
|
124418
124533
|
}
|
|
124419
124534
|
let html;
|
|
124420
124535
|
try {
|
|
124421
|
-
html = resolveMarketAnalysisHtml(
|
|
124536
|
+
html = resolveMarketAnalysisHtml(asRecord5(dataRaw));
|
|
124422
124537
|
} catch (err) {
|
|
124423
124538
|
const message = err instanceof Error ? err.message : String(err);
|
|
124424
124539
|
console.error(`
|
|
@@ -124430,7 +124545,7 @@ async function runMarketAnalysisRender(opts) {
|
|
|
124430
124545
|
console.error("\n\u274C \u62A5\u544A HTML \u5185\u5BB9\u4E3A\u7A7A\n");
|
|
124431
124546
|
process.exit(1);
|
|
124432
124547
|
}
|
|
124433
|
-
const reportData =
|
|
124548
|
+
const reportData = asRecord5(dataRaw);
|
|
124434
124549
|
const contentCheck = validateMarketReportContent(html, validationContextFromReport(reportData));
|
|
124435
124550
|
if (!contentCheck.ok) {
|
|
124436
124551
|
console.error(`
|
|
@@ -125453,21 +125568,21 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
|
|
|
125453
125568
|
}
|
|
125454
125569
|
|
|
125455
125570
|
// src/commands/google-ads-diagnosis/collect-integrity.ts
|
|
125456
|
-
function
|
|
125571
|
+
function asRecord6(value) {
|
|
125457
125572
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
125458
125573
|
return value;
|
|
125459
125574
|
}
|
|
125460
125575
|
return null;
|
|
125461
125576
|
}
|
|
125462
125577
|
function itemCount(block) {
|
|
125463
|
-
const items =
|
|
125578
|
+
const items = asRecord6(block)?.items;
|
|
125464
125579
|
return Array.isArray(items) ? items.length : 0;
|
|
125465
125580
|
}
|
|
125466
125581
|
function getCollectIntegrityWarnings(reportData) {
|
|
125467
125582
|
const warnings = [];
|
|
125468
125583
|
const kwItems = itemCount(reportData.keywords);
|
|
125469
125584
|
const fullKwItems = itemCount(reportData.fullKeywords);
|
|
125470
|
-
const keywordCount = Number(
|
|
125585
|
+
const keywordCount = Number(asRecord6(reportData.structure)?.keywordCount ?? 0);
|
|
125471
125586
|
if (kwItems === 0 && fullKwItems > 0) {
|
|
125472
125587
|
warnings.push(
|
|
125473
125588
|
`keywords.items \u5BF9\u6BD4\u8868\u4E3A\u7A7A\uFF0C\u4F46 fullKeywords.items \u6709 ${fullKwItems} \u884C\uFF1B\u8BF7\u786E\u8BA4 collect \u662F\u5426\u6210\u529F\u62C9\u53D6\u4E0A\u4E00\u5468\u671F keywords \u5FEB\u7167\uFF08\u73AF\u6BD4\u5217\u4F9D\u8D56 prev \u5468\u671F JSON\uFF09\u3002`
|
|
@@ -125478,7 +125593,7 @@ function getCollectIntegrityWarnings(reportData) {
|
|
|
125478
125593
|
);
|
|
125479
125594
|
}
|
|
125480
125595
|
const campaignItems = itemCount(reportData.campaigns);
|
|
125481
|
-
const campaignCount = Number(
|
|
125596
|
+
const campaignCount = Number(asRecord6(reportData.structure)?.campaignCount ?? 0);
|
|
125482
125597
|
if (campaignItems === 0 && campaignCount > 0) {
|
|
125483
125598
|
warnings.push(
|
|
125484
125599
|
`campaigns.items \u5BF9\u6BD4\u8868\u4E3A\u7A7A\uFF0C\u4F46 structure.campaignCount=${campaignCount}\uFF1B\u8BF7\u786E\u8BA4\u4E0A\u4E00\u5468\u671F campaigns \u5FEB\u7167\u662F\u5426\u62C9\u53D6\u6210\u529F\u3002`
|
|
@@ -125682,7 +125797,7 @@ import fs18 from "fs/promises";
|
|
|
125682
125797
|
import path23 from "path";
|
|
125683
125798
|
|
|
125684
125799
|
// src/commands/google-ads-diagnosis/report-content.ts
|
|
125685
|
-
function
|
|
125800
|
+
function asRecord7(value) {
|
|
125686
125801
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
125687
125802
|
return value;
|
|
125688
125803
|
}
|
|
@@ -125691,7 +125806,7 @@ function asRecord6(value) {
|
|
|
125691
125806
|
function getNestedValue(root, path34) {
|
|
125692
125807
|
let cur = root;
|
|
125693
125808
|
for (const key of path34) {
|
|
125694
|
-
const rec =
|
|
125809
|
+
const rec = asRecord7(cur);
|
|
125695
125810
|
if (!rec) return void 0;
|
|
125696
125811
|
cur = rec[key];
|
|
125697
125812
|
}
|
|
@@ -125708,7 +125823,7 @@ function validateBudgetCompetitivenessStrategies(data) {
|
|
|
125708
125823
|
}
|
|
125709
125824
|
const warnings = [];
|
|
125710
125825
|
for (let i = 0; i < items.length; i++) {
|
|
125711
|
-
const row =
|
|
125826
|
+
const row = asRecord7(items[i]);
|
|
125712
125827
|
const name2 = String(row?.name ?? row?.metric ?? i);
|
|
125713
125828
|
if (!hasNarrativeContent(row?.strategy)) {
|
|
125714
125829
|
warnings.push(`budgetCompetitiveness[${name2}].strategy \u4E3A\u7A7A\uFF08\u987B\u7531 Agent \u586B\u5199\uFF09`);
|
|
@@ -125717,13 +125832,13 @@ function validateBudgetCompetitivenessStrategies(data) {
|
|
|
125717
125832
|
return warnings;
|
|
125718
125833
|
}
|
|
125719
125834
|
function validateNewFeatureRecommendations(data) {
|
|
125720
|
-
const nf =
|
|
125835
|
+
const nf = asRecord7(data.newFeatures);
|
|
125721
125836
|
if (!nf) return [];
|
|
125722
125837
|
const items = nf.items;
|
|
125723
125838
|
if (!Array.isArray(items)) return [];
|
|
125724
125839
|
const warnings = [];
|
|
125725
125840
|
for (let i = 0; i < items.length; i++) {
|
|
125726
|
-
const row =
|
|
125841
|
+
const row = asRecord7(items[i]);
|
|
125727
125842
|
const name2 = String(row?.strategyName ?? row?.feature ?? row?.strategy ?? i);
|
|
125728
125843
|
if (!hasNarrativeContent(row?.optimizerRecommendation)) {
|
|
125729
125844
|
warnings.push(`newFeatures.items[${name2}].optimizerRecommendation \u4E3A\u7A7A\uFF08\u987B\u7531 Agent \u586B\u5199\uFF09`);
|
|
@@ -125732,13 +125847,13 @@ function validateNewFeatureRecommendations(data) {
|
|
|
125732
125847
|
return warnings;
|
|
125733
125848
|
}
|
|
125734
125849
|
function validateAdCreativeItemSuggestions(data) {
|
|
125735
|
-
const creative =
|
|
125850
|
+
const creative = asRecord7(data.adCreativeOptimization);
|
|
125736
125851
|
if (!creative) return [];
|
|
125737
125852
|
const items = creative.items;
|
|
125738
125853
|
if (!Array.isArray(items) || items.length === 0) return [];
|
|
125739
125854
|
const warnings = [];
|
|
125740
125855
|
for (let i = 0; i < items.length; i++) {
|
|
125741
|
-
const row =
|
|
125856
|
+
const row = asRecord7(items[i]);
|
|
125742
125857
|
const title = String(row?.adTitle ?? i);
|
|
125743
125858
|
if (!hasNarrativeContent(row?.suggestion ?? row?.suggestions)) {
|
|
125744
125859
|
warnings.push(
|
|
@@ -125753,7 +125868,7 @@ function validateGoogleAdsDiagnosisContent(data) {
|
|
|
125753
125868
|
(key) => data[key] === void 0
|
|
125754
125869
|
);
|
|
125755
125870
|
const warnings = [];
|
|
125756
|
-
const accountInfo =
|
|
125871
|
+
const accountInfo = asRecord7(data.accountInfo);
|
|
125757
125872
|
if (accountInfo && !accountInfo.companyName && !accountInfo.period) {
|
|
125758
125873
|
warnings.push("accountInfo \u7F3A\u5C11 companyName \u4E0E period\uFF0C\u9875\u7709\u53EF\u80FD\u4E3A\u7A7A");
|
|
125759
125874
|
}
|
|
@@ -125801,7 +125916,7 @@ var GOOGLE_ADS_DIAGNOSIS_RESTORE_ITEM_MODULES = [
|
|
|
125801
125916
|
GOOGLE_ADS_DIAGNOSIS_BIDDING_STRATEGY_MODULE,
|
|
125802
125917
|
GOOGLE_ADS_DIAGNOSIS_NEW_FEATURES_MODULE
|
|
125803
125918
|
];
|
|
125804
|
-
function
|
|
125919
|
+
function asRecord8(value) {
|
|
125805
125920
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
125806
125921
|
return value;
|
|
125807
125922
|
}
|
|
@@ -125812,7 +125927,7 @@ function isValidRateChange(value) {
|
|
|
125812
125927
|
return typeof value === "number" && Number.isFinite(value);
|
|
125813
125928
|
}
|
|
125814
125929
|
function isComparisonTableRowValid(row) {
|
|
125815
|
-
const rec =
|
|
125930
|
+
const rec = asRecord8(row);
|
|
125816
125931
|
if (!rec) return false;
|
|
125817
125932
|
if (!String(rec.title ?? "").trim()) return false;
|
|
125818
125933
|
if (!Number.isFinite(Number(rec.currentCost))) return false;
|
|
@@ -125824,12 +125939,12 @@ function isComparisonTableRowValid(row) {
|
|
|
125824
125939
|
return true;
|
|
125825
125940
|
}
|
|
125826
125941
|
function isNewFeatureRowValid(row) {
|
|
125827
|
-
const rec =
|
|
125942
|
+
const rec = asRecord8(row);
|
|
125828
125943
|
if (!rec) return false;
|
|
125829
125944
|
return Boolean(String(rec.strategyName ?? rec.feature ?? rec.strategy ?? "").trim());
|
|
125830
125945
|
}
|
|
125831
125946
|
function isBiddingStrategyRowValid(row) {
|
|
125832
|
-
const rec =
|
|
125947
|
+
const rec = asRecord8(row);
|
|
125833
125948
|
if (!rec) return false;
|
|
125834
125949
|
if (!String(rec.campaignName ?? "").trim()) return false;
|
|
125835
125950
|
if (!Number.isFinite(Number(rec.duration)) || Number(rec.duration) <= 0) return false;
|
|
@@ -125867,11 +125982,11 @@ function getFactItemsErrors(data) {
|
|
|
125867
125982
|
for (const mod of GOOGLE_ADS_DIAGNOSIS_RESTORE_ITEM_MODULES) {
|
|
125868
125983
|
const validator = MODULE_ITEM_VALIDATORS[mod];
|
|
125869
125984
|
const hint = MODULE_ITEM_ERROR_HINT[mod];
|
|
125870
|
-
const block =
|
|
125985
|
+
const block = asRecord8(data[mod]);
|
|
125871
125986
|
const items = block?.items;
|
|
125872
125987
|
if (!Array.isArray(items) || items.length === 0) {
|
|
125873
125988
|
if (mod === "keywords") {
|
|
125874
|
-
const fullKw =
|
|
125989
|
+
const fullKw = asRecord8(data.fullKeywords)?.items;
|
|
125875
125990
|
if (Array.isArray(fullKw) && fullKw.length > 0) {
|
|
125876
125991
|
errors.push(
|
|
125877
125992
|
"keywords.items \u4E3A\u7A7A\u4F46 fullKeywords.items \u6709\u6570\u636E\uFF1B\u8BF7\u4FDD\u7559\u540C\u76EE\u5F55 google-ads-diagnosis-collect.json \u540E\u91CD\u65B0 render\uFF08\u4F1A\u81EA\u52A8\u6062\u590D\u5BF9\u6BD4\u8868\uFF09"
|
|
@@ -125896,15 +126011,15 @@ var LANDING_PAGE_SPEED_KEYS = [
|
|
|
125896
126011
|
"speedIndex"
|
|
125897
126012
|
];
|
|
125898
126013
|
function isLandingPageSpeedValid(value) {
|
|
125899
|
-
const rec =
|
|
126014
|
+
const rec = asRecord8(value);
|
|
125900
126015
|
if (!rec) return false;
|
|
125901
126016
|
return LANDING_PAGE_SPEED_KEYS.every(
|
|
125902
126017
|
(key) => typeof rec[key] === "number" && Number.isFinite(rec[key])
|
|
125903
126018
|
);
|
|
125904
126019
|
}
|
|
125905
126020
|
function restoreLandingPageSpeedFromCollect(finalData, collectReportData) {
|
|
125906
|
-
const finalBlock =
|
|
125907
|
-
const collectBlock =
|
|
126021
|
+
const finalBlock = asRecord8(finalData[GOOGLE_ADS_DIAGNOSIS_LANDING_PAGE_MODULE]);
|
|
126022
|
+
const collectBlock = asRecord8(collectReportData[GOOGLE_ADS_DIAGNOSIS_LANDING_PAGE_MODULE]);
|
|
125908
126023
|
if (!collectBlock) return { data: finalData, restored: false };
|
|
125909
126024
|
const needsDesktop = !isLandingPageSpeedValid(finalBlock?.desktop) && isLandingPageSpeedValid(collectBlock.desktop);
|
|
125910
126025
|
const needsMobile = !isLandingPageSpeedValid(finalBlock?.mobile) && isLandingPageSpeedValid(collectBlock.mobile);
|
|
@@ -125922,8 +126037,8 @@ function mergeComparisonItemsFromCollect(finalData, collectReportData) {
|
|
|
125922
126037
|
const restoredModules = [];
|
|
125923
126038
|
for (const mod of GOOGLE_ADS_DIAGNOSIS_RESTORE_ITEM_MODULES) {
|
|
125924
126039
|
const validator = MODULE_ITEM_VALIDATORS[mod];
|
|
125925
|
-
const finalBlock =
|
|
125926
|
-
const collectBlock =
|
|
126040
|
+
const finalBlock = asRecord8(data[mod]);
|
|
126041
|
+
const collectBlock = asRecord8(collectReportData[mod]);
|
|
125927
126042
|
const finalItems = finalBlock?.items;
|
|
125928
126043
|
const collectItems = collectBlock?.items;
|
|
125929
126044
|
if (!Array.isArray(collectItems) || collectItems.length === 0) continue;
|
|
@@ -125939,7 +126054,7 @@ function mergeComparisonItemsFromCollect(finalData, collectReportData) {
|
|
|
125939
126054
|
}
|
|
125940
126055
|
|
|
125941
126056
|
// src/commands/google-ads-diagnosis/narrative-consistency-check.ts
|
|
125942
|
-
function
|
|
126057
|
+
function asRecord9(value) {
|
|
125943
126058
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
125944
126059
|
return value;
|
|
125945
126060
|
}
|
|
@@ -125947,14 +126062,14 @@ function asRecord8(value) {
|
|
|
125947
126062
|
}
|
|
125948
126063
|
function collectNarrativeTexts(data) {
|
|
125949
126064
|
const texts = [];
|
|
125950
|
-
const summary =
|
|
126065
|
+
const summary = asRecord9(data.summary);
|
|
125951
126066
|
if (Array.isArray(summary?.keyIssues)) {
|
|
125952
126067
|
for (const item of summary.keyIssues) texts.push(String(item ?? ""));
|
|
125953
126068
|
}
|
|
125954
|
-
const overview =
|
|
126069
|
+
const overview = asRecord9(data.diagnosisOverview);
|
|
125955
126070
|
if (Array.isArray(overview?.disadvantages)) {
|
|
125956
126071
|
for (const item of overview.disadvantages) {
|
|
125957
|
-
const rec =
|
|
126072
|
+
const rec = asRecord9(item);
|
|
125958
126073
|
if (rec) {
|
|
125959
126074
|
texts.push(String(rec.title ?? ""), String(rec.description ?? ""));
|
|
125960
126075
|
} else {
|
|
@@ -125965,16 +126080,16 @@ function collectNarrativeTexts(data) {
|
|
|
125965
126080
|
return texts.filter((t) => t.trim().length > 0);
|
|
125966
126081
|
}
|
|
125967
126082
|
function newFeatureAccountStatus(data, strategy) {
|
|
125968
|
-
const nf =
|
|
126083
|
+
const nf = asRecord9(data.newFeatures);
|
|
125969
126084
|
const items = nf?.items;
|
|
125970
126085
|
if (!Array.isArray(items)) return null;
|
|
125971
|
-
const row = items.find((it) =>
|
|
125972
|
-
const rec =
|
|
126086
|
+
const row = items.find((it) => asRecord9(it)?.strategy === strategy);
|
|
126087
|
+
const rec = asRecord9(row);
|
|
125973
126088
|
if (!rec || typeof rec.accountStatus !== "boolean") return null;
|
|
125974
126089
|
return rec.accountStatus;
|
|
125975
126090
|
}
|
|
125976
126091
|
function goldAccountFlag(data, key) {
|
|
125977
|
-
const gold =
|
|
126092
|
+
const gold = asRecord9(data.goldAccount);
|
|
125978
126093
|
const value = gold?.[key];
|
|
125979
126094
|
return typeof value === "boolean" ? value : null;
|
|
125980
126095
|
}
|
|
@@ -126005,7 +126120,7 @@ function getNarrativeConsistencyErrors(data) {
|
|
|
126005
126120
|
}
|
|
126006
126121
|
|
|
126007
126122
|
// src/commands/google-ads-diagnosis/render-report.ts
|
|
126008
|
-
function
|
|
126123
|
+
function asRecord10(value) {
|
|
126009
126124
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
126010
126125
|
return value;
|
|
126011
126126
|
}
|
|
@@ -126014,8 +126129,8 @@ function asRecord9(value) {
|
|
|
126014
126129
|
async function tryLoadCollectReportData(collectPath) {
|
|
126015
126130
|
try {
|
|
126016
126131
|
const raw = JSON.parse(await fs18.readFile(collectPath, "utf8"));
|
|
126017
|
-
const root =
|
|
126018
|
-
const reportData =
|
|
126132
|
+
const root = asRecord10(raw);
|
|
126133
|
+
const reportData = asRecord10(root?.reportData);
|
|
126019
126134
|
return reportData ?? root;
|
|
126020
126135
|
} catch {
|
|
126021
126136
|
return null;
|
|
@@ -126032,7 +126147,7 @@ async function runGoogleAdsDiagnosisRender(opts) {
|
|
|
126032
126147
|
`);
|
|
126033
126148
|
process.exit(1);
|
|
126034
126149
|
}
|
|
126035
|
-
let data =
|
|
126150
|
+
let data = asRecord10(dataRaw);
|
|
126036
126151
|
if (!opts.noMergeCollect) {
|
|
126037
126152
|
const collectPath = path23.resolve(
|
|
126038
126153
|
opts.collectFile ?? path23.join(path23.dirname(dataPath), GOOGLE_ADS_DIAGNOSIS_COLLECT_BASENAME)
|
|
@@ -126545,7 +126660,7 @@ function ensureScorecardFromSnapshot(healthDiagnosis, merged) {
|
|
|
126545
126660
|
}
|
|
126546
126661
|
|
|
126547
126662
|
// src/commands/facebook-analysis/merge-snapshot.ts
|
|
126548
|
-
function
|
|
126663
|
+
function asRecord11(value) {
|
|
126549
126664
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
126550
126665
|
return value;
|
|
126551
126666
|
}
|
|
@@ -126594,7 +126709,7 @@ async function readManifest(snapshotDir) {
|
|
|
126594
126709
|
async function loadSectionFile(snapshotDir, file) {
|
|
126595
126710
|
try {
|
|
126596
126711
|
const raw = await fs21.readFile(path28.join(snapshotDir, file), "utf8");
|
|
126597
|
-
return
|
|
126712
|
+
return asRecord11(JSON.parse(raw));
|
|
126598
126713
|
} catch {
|
|
126599
126714
|
return null;
|
|
126600
126715
|
}
|
|
@@ -126642,7 +126757,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
|
|
|
126642
126757
|
if (data) sectionMap.set(art.section, data);
|
|
126643
126758
|
}
|
|
126644
126759
|
const overview = sectionMap.get("overview");
|
|
126645
|
-
const current =
|
|
126760
|
+
const current = asRecord11(overview?.currentPeriod);
|
|
126646
126761
|
const accountName = typeof overview?.accountName === "string" ? overview.accountName : void 0;
|
|
126647
126762
|
const accountId = manifest.accountId ?? (typeof overview?.accountId === "string" ? overview.accountId : void 0);
|
|
126648
126763
|
const kpis = { ...payload.kpis ?? {} };
|
|
@@ -126764,7 +126879,7 @@ function isFiniteNumber(value) {
|
|
|
126764
126879
|
function asArray2(value) {
|
|
126765
126880
|
return Array.isArray(value) ? value : [];
|
|
126766
126881
|
}
|
|
126767
|
-
function
|
|
126882
|
+
function asRecord12(value) {
|
|
126768
126883
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
126769
126884
|
return value;
|
|
126770
126885
|
}
|
|
@@ -126776,26 +126891,26 @@ function pushMissing(missing, id, chapter, dimension, hint) {
|
|
|
126776
126891
|
function countAdSetsWithSpend(tables) {
|
|
126777
126892
|
const rows = asArray2(tables?.adSets);
|
|
126778
126893
|
return rows.filter((row) => {
|
|
126779
|
-
const r =
|
|
126894
|
+
const r = asRecord12(row);
|
|
126780
126895
|
const spend = r?.spend;
|
|
126781
126896
|
return isFiniteNumber(spend) && spend > 0;
|
|
126782
126897
|
}).length;
|
|
126783
126898
|
}
|
|
126784
126899
|
function chartLabels(chart) {
|
|
126785
|
-
const c =
|
|
126900
|
+
const c = asRecord12(chart);
|
|
126786
126901
|
return asArray2(c?.labels).length;
|
|
126787
126902
|
}
|
|
126788
126903
|
function validateMetaPeriodReportContent(data) {
|
|
126789
126904
|
const missing = [];
|
|
126790
|
-
const meta =
|
|
126791
|
-
const kpis =
|
|
126792
|
-
const narrative =
|
|
126793
|
-
const tables =
|
|
126794
|
-
const charts =
|
|
126795
|
-
const sections =
|
|
126796
|
-
const health =
|
|
126797
|
-
const priorityPlan =
|
|
126798
|
-
const actionChecklist =
|
|
126905
|
+
const meta = asRecord12(data.meta) ?? {};
|
|
126906
|
+
const kpis = asRecord12(data.kpis) ?? {};
|
|
126907
|
+
const narrative = asRecord12(data.narrative) ?? {};
|
|
126908
|
+
const tables = asRecord12(data.tables);
|
|
126909
|
+
const charts = asRecord12(data.charts);
|
|
126910
|
+
const sections = asRecord12(data.sections);
|
|
126911
|
+
const health = asRecord12(data.healthDiagnosis);
|
|
126912
|
+
const priorityPlan = asRecord12(data.priorityPlan);
|
|
126913
|
+
const actionChecklist = asRecord12(data.actionChecklist);
|
|
126799
126914
|
const adSetsWithSpend2 = countAdSetsWithSpend(tables ?? void 0);
|
|
126800
126915
|
const regionalNarratives = asArray2(narrative.regional).length;
|
|
126801
126916
|
const recommendations = asArray2(narrative.recommendations);
|
|
@@ -126867,7 +126982,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
126867
126982
|
"charts.audience.labels \u81F3\u5C11 1 \u9879"
|
|
126868
126983
|
);
|
|
126869
126984
|
}
|
|
126870
|
-
const funnel =
|
|
126985
|
+
const funnel = asRecord12(charts?.funnel);
|
|
126871
126986
|
if (!isFiniteNumber(funnel?.reach)) {
|
|
126872
126987
|
pushMissing(
|
|
126873
126988
|
missing,
|
|
@@ -126931,7 +127046,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
126931
127046
|
);
|
|
126932
127047
|
} else {
|
|
126933
127048
|
for (let i = 0; i < regionalNarratives; i++) {
|
|
126934
|
-
const row =
|
|
127049
|
+
const row = asRecord12(asArray2(narrative.regional)[i]);
|
|
126935
127050
|
if (textLen2(row?.text) < 80) {
|
|
126936
127051
|
pushMissing(
|
|
126937
127052
|
missing,
|
|
@@ -126961,7 +127076,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
126961
127076
|
"\u5FC5\u987B\u6070\u597D 4 \u6761\uFF08\u7B80\u5316\u8868\u5355/\u533A\u57DF\u8C03\u6574/\u9884\u7B97\u91CD\u6784/\u7D20\u6750\u5EFA\u8BAE\uFF09"
|
|
126962
127077
|
);
|
|
126963
127078
|
} else {
|
|
126964
|
-
const titles = recommendations.map((r) =>
|
|
127079
|
+
const titles = recommendations.map((r) => asRecord12(r)?.title);
|
|
126965
127080
|
for (const title of RECOMMENDATION_TITLES) {
|
|
126966
127081
|
if (!titles.includes(title)) {
|
|
126967
127082
|
pushMissing(
|
|
@@ -126974,7 +127089,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
126974
127089
|
}
|
|
126975
127090
|
}
|
|
126976
127091
|
for (let i = 0; i < recommendations.length; i++) {
|
|
126977
|
-
const rec =
|
|
127092
|
+
const rec = asRecord12(recommendations[i]);
|
|
126978
127093
|
if (textLen2(rec?.content) < 150) {
|
|
126979
127094
|
pushMissing(
|
|
126980
127095
|
missing,
|
|
@@ -126996,7 +127111,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
126996
127111
|
);
|
|
126997
127112
|
} else {
|
|
126998
127113
|
for (let i = 0; i < supplementary.length; i++) {
|
|
126999
|
-
const row =
|
|
127114
|
+
const row = asRecord12(supplementary[i]);
|
|
127000
127115
|
if (!textLen2(row?.dimension) || !textLen2(row?.issue)) {
|
|
127001
127116
|
pushMissing(
|
|
127002
127117
|
missing,
|
|
@@ -127078,7 +127193,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127078
127193
|
);
|
|
127079
127194
|
} else {
|
|
127080
127195
|
for (let i = 0; i < fourQuestions.length; i++) {
|
|
127081
|
-
const q =
|
|
127196
|
+
const q = asRecord12(fourQuestions[i]);
|
|
127082
127197
|
const evidence = asArray2(q?.evidence).filter((e) => textLen2(e) > 0);
|
|
127083
127198
|
if (evidence.length < 2) {
|
|
127084
127199
|
pushMissing(
|
|
@@ -127102,7 +127217,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127102
127217
|
}
|
|
127103
127218
|
let scorecardValidRows = 0;
|
|
127104
127219
|
for (let i = 0; i < scorecard.length; i++) {
|
|
127105
|
-
const row =
|
|
127220
|
+
const row = asRecord12(scorecard[i]);
|
|
127106
127221
|
const itemOk = textLen2(row?.item) > 0;
|
|
127107
127222
|
const dataOk = textLen2(row?.data) > 0;
|
|
127108
127223
|
const signalOk = SCORECARD_SIGNALS.includes(row?.signal);
|
|
@@ -127144,7 +127259,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127144
127259
|
);
|
|
127145
127260
|
}
|
|
127146
127261
|
for (const key of ["platform", "country", "adSets"]) {
|
|
127147
|
-
const insight = textLen2(
|
|
127262
|
+
const insight = textLen2(asRecord12(sections?.[key])?.insight);
|
|
127148
127263
|
if (insight < 200) {
|
|
127149
127264
|
pushMissing(
|
|
127150
127265
|
missing,
|
|
@@ -127155,7 +127270,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127155
127270
|
);
|
|
127156
127271
|
}
|
|
127157
127272
|
}
|
|
127158
|
-
const audience =
|
|
127273
|
+
const audience = asRecord12(sections?.audience);
|
|
127159
127274
|
if (asArray2(audience?.goldenProfile).filter((s) => textLen2(s) > 0).length < 3) {
|
|
127160
127275
|
pushMissing(
|
|
127161
127276
|
missing,
|
|
@@ -127183,7 +127298,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127183
127298
|
"\u2265150 \u5B57"
|
|
127184
127299
|
);
|
|
127185
127300
|
}
|
|
127186
|
-
const landingRows = asArray2(
|
|
127301
|
+
const landingRows = asArray2(asRecord12(sections?.landingPage)?.rows);
|
|
127187
127302
|
if (landingRows.length < 3) {
|
|
127188
127303
|
pushMissing(
|
|
127189
127304
|
missing,
|
|
@@ -127268,7 +127383,7 @@ async function readJsonFile2(filePath) {
|
|
|
127268
127383
|
throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
|
|
127269
127384
|
}
|
|
127270
127385
|
}
|
|
127271
|
-
function
|
|
127386
|
+
function asRecord13(value) {
|
|
127272
127387
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
127273
127388
|
return value;
|
|
127274
127389
|
}
|
|
@@ -127283,7 +127398,7 @@ function injectReportData2(html, payload) {
|
|
|
127283
127398
|
}
|
|
127284
127399
|
async function runFacebookAnalysisRender(opts) {
|
|
127285
127400
|
const dataPath = path29.resolve(opts.dataFile);
|
|
127286
|
-
let data =
|
|
127401
|
+
let data = asRecord13(await readJsonFile2(dataPath));
|
|
127287
127402
|
if (opts.snapshotDir?.trim()) {
|
|
127288
127403
|
data = await mergeFacebookSnapshotIntoReport(data, path29.resolve(opts.snapshotDir));
|
|
127289
127404
|
}
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -20,7 +20,7 @@ allowed-tools: Bash(siluzan-tso:*) Read Write
|
|
|
20
20
|
|
|
21
21
|
<!-- 注入到 SKILL.md.tmpl 的 {{AGENT_PREAMBLE}};构建时由 gen-skill-docs.mjs 合并 -->
|
|
22
22
|
|
|
23
|
-
> **Agent 纪律(每个新任务必读)**:先 Read `references/core/agent-conventions.md`(唯一规则真相源:加载纪律、数据处理协议、时间/币种、批量约束、交付前自检),再按下方路由表 Read
|
|
23
|
+
> **Agent 纪律(每个新任务必读)**:先 Read `references/core/agent-conventions.md`(唯一规则真相源:加载纪律、数据处理协议、时间/币种、批量约束、交付前自检),再按下方路由表 Read「必读文档」与对应工作流卡片。向用户回复时另 Read `references/core/user-communication-guide.md`(伙伴式沟通:结论先行、先执行后解释、禁止机械贴 CLI/编号)。禁止跨话题复用参数记忆;数据类任务一律 `--json-out` + **仅用代码**读落盘 JSON(脚本示例见 `references/core/tips.md`)。
|
|
24
24
|
>
|
|
25
25
|
> **报告 / 分析类统一消歧(必读)**:用户话术含「报告 / 诊断 / 分析 / 检测 / 监测 / 月报 / 周报」等,或对象不清(只有「帮我出份报告」)→ **必须先 Read `references/core/intent-routing.md`**。**§零 优先**:**网址/域名/官网/链接 + 诊断类词**(含用户说的「网络诊断/网络检测」)→ **直接 P8**,禁止走 P9/P1。**§零·B 次优先**:未命中 §零 的**行业/市场分析报告**(行业分析、市场分析、行业分析报告、「写一份 XX 行业报告」、战略/KA 市场报告、竞品/GTM 等,**无论是否提及丝路赞/广告**)→ **100% 走 P9 `market-analysis collect`+`render`**,**禁止**纯 WebSearch/WebFetch 在对话里写 Markdown/HTML 当终稿,**禁止**改走 P8/P1/P4/W5。**§零·C 关键词规划**:**拓词 / Keyword Planner / 长尾关键词 / 月搜索量 / 竞争度 / 核心词扩词**(含读 URL/文章后出 Google 词表,**无论是否提及丝路赞/账户**)→ **100% 走 W5 `keyword suggest --google-only --json-out`**,**禁止** WebSearch 编造搜索量。再按 §一 定唯一工作流。**禁止**见「报告」就默认 P4 周期或 P1 诊断。
|
|
26
26
|
>
|
package/dist/skill/_meta.json
CHANGED
|
@@ -250,4 +250,4 @@ siluzan-tso ad batch diff --batch-id <taskId> --config-file ./campaign.json --js
|
|
|
250
250
|
| 否词误填 | `KeywordsForBatchJob` 含常见否词词根或与 `NegativeKeywordsForBatchJob` 重复 → **warnings**(不阻断,须 Agent 修正后再 create) |
|
|
251
251
|
| 实务 | 日期格式与先后、出价策略与配套字段一致 |
|
|
252
252
|
|
|
253
|
-
`ad campaign-validate` 通过不保证 BatchJob 成功(例如仅写 `DestinationUrl` 未写 `Finalurl` 时 validate 仍可能 ✅)。异步结果用 `ad batch get` 轮询;`HasFailed` / 部分失败时用 `ad batch diff` 对照 JSON 补缺,系列级失败时改 JSON
|
|
253
|
+
`ad campaign-validate` 通过不保证 BatchJob 成功(例如仅写 `DestinationUrl` 未写 `Finalurl` 时 validate 仍可能 ✅)。异步结果用 `ad batch get` 轮询;`HasFailed` / 部分失败时用 `ad batch diff` 对照 JSON 补缺,系列级失败时改 JSON 重提,勿在半成品上反复整包创建。**附加信息(Sitelink 等)batch 漏建时**:`batch diff` 后 **自动** `ad extension *` 补挂,勿仅询问用户(见 `references/google-ads/google-ads-campaign-plan.md` § batch diff 后自动补建)。写操作须 `--commit`,见 `references/google-ads/google-ads.md` § ad campaign-create。
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
| ------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
9
9
|
| `references/core/setup.md` | 安装、登录(手机验证码优先)、配置、更新 |
|
|
10
10
|
| `references/core/agent-conventions.md` | **Agent 必读 · 唯一规则真相源**:加载纪律、数据处理协议、硬规范、时间/币种/批量、交付前自检 |
|
|
11
|
+
| `references/core/user-communication-guide.md` | **用户回复风格**:结论先行、伙伴语气、自然语言路由;禁止机械贴 CLI/编号 |
|
|
11
12
|
| `references/core/intent-routing.md` | **报告 / 分析类统一消歧**:P1–P9 与 W3/W7 同义词、信号优先级、一次追问模板 |
|
|
12
13
|
| `references/core/tips.md` | `--json-out` 脚本食谱(node -e 示例、文件命名、分页) |
|
|
13
14
|
| `references/core/playbooks.md` | **工作流目录 · 分析/报告类**(P1–P9,统一卡片:触发/必读/步骤/交付) |
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
| 触发 | 动作 |
|
|
14
14
|
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
15
|
| **新的用户任务 / 同对话内换话题**(新需求、新账户、新媒体、新报告类型;例:刚查余额 → 改问建系列) | 按 `SKILL.md` 路由表 **重新 Read** 该任务的「必读文档」与工作流卡片后再执行 CLI;**禁止**沿用上一任务的参数记忆——对话会被压缩,「读过」≠ 当前上下文仍含正确字段名与 flags |
|
|
16
|
+
| **向用户回复结论、建议、诊断说明**(非纯静默跑命令) | Read `references/core/user-communication-guide.md`:结论先行、伙伴语气、禁止机械贴 CLI/编号 |
|
|
16
17
|
| **工作流编号不同**(P1→P3、P4→P5) | 即使刚做过 P1,仍须 Read 新卡片及其必读文档 |
|
|
17
18
|
| **报告 / 分析类话术模糊**(只说「报告/诊断/分析/检测/监测」、对象不清) | **必须先 Read** `references/core/intent-routing.md`,定唯一 P*/W* 后再 Read 工作流卡片;**禁止**默认 P4 或 P1 |
|
|
18
19
|
| **专用 report-templates**(OKKI / 询盘分析 / 周期报告纲要) | Read `references/report-templates/<名>.md` **全文**(与 `report-templates/<名>.md` 同源);勿只凭 SKILL 摘要 |
|
|
@@ -40,11 +41,11 @@
|
|
|
40
41
|
|
|
41
42
|
**计划 → 确认 → 执行 → 验证 → 推测下一步**:
|
|
42
43
|
|
|
43
|
-
1. 按上表 Read 当次任务 references → 用 `-h` 确认命令 →
|
|
44
|
+
1. 按上表 Read 当次任务 references → 用 `-h` 确认命令 → 向用户输出操作计划(**计划用业务语言一句带过**,详见 `user-communication-guide.md`)。
|
|
44
45
|
2. 涉及写入/修改/删除的操作必须与用户确认;多数破坏性操作还需 `--commit`。
|
|
45
46
|
3. 按计划执行,说明每步意图。
|
|
46
47
|
4. 用成对的读命令复核写入结果;异步任务每 5s 轮询直到完成。
|
|
47
|
-
5. 报告/Excel/含金额话术交付前,按本文件 **§七 交付前自检**
|
|
48
|
+
5. 报告/Excel/含金额话术交付前,按本文件 **§七 交付前自检** 审阅最终产物;**同时**按 `references/core/user-communication-guide.md` 组织面向用户的摘要(结论先行,禁止机械贴命令输出)。
|
|
48
49
|
6. 全部完成后预测用户下一步操作。
|
|
49
50
|
|
|
50
51
|
### 执行模式速查
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# 用户沟通风格(Agent 回复规范)
|
|
2
|
+
|
|
3
|
+
> **何时必读**:每个面向用户的任务(查数、诊断、报告、开户、优化、异常排查等)在**组织最终回复文字**前 Read 本文件。CLI 纪律、字段口径仍以 `agent-conventions.md` 为准;本文件只管**怎么说**,不管**怎么跑命令**。
|
|
4
|
+
>
|
|
5
|
+
> 产品背景见《丝路赞 TSO Agent 用户全链路引导方案》;此处为 Skill 可执行摘要,**禁止**向用户暴露 P1/W12/playbook 编号。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 一、定位
|
|
10
|
+
|
|
11
|
+
用户不会按开发者逻辑提问(不会说「执行 P4」或「查 campaignStatus」)。Agent 是**广告运营伙伴**,不是「等指令的工具」。
|
|
12
|
+
|
|
13
|
+
| 避免(机械) | 改为(伙伴感) |
|
|
14
|
+
| ------------ | -------------- |
|
|
15
|
+
| 「请执行 `google-analysis --sections overview`」 | 「我先帮您拉一下账户整体数据,稍等。」 |
|
|
16
|
+
| 「您需要先关联 Google 账户」 | 「我看了一下,您还没绑定 Google 账户。绑好后我就能帮你看投放数据和优化建议,要现在开始吗?」 |
|
|
17
|
+
| 贴一整段 CLI stdout / JSON | **结论先行**,数字来自脚本,用业务语言概括 |
|
|
18
|
+
| 连续反问 5 个技术参数 | **能推断就先做**;缺关键项只问 1~2 个,其余用合理默认并在结论里说明 |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 二、回复结构(默认模板)
|
|
23
|
+
|
|
24
|
+
**共情/确认 → 结论先行 → 数据支撑 → 行动建议 → 下一步**
|
|
25
|
+
|
|
26
|
+
1. **共情/确认**:用一句话接住用户意图(「好的,我帮您看看…」「收到,我来排查一下…」)。
|
|
27
|
+
2. **结论先行**:第一句给出可操作的判断(正常 / 有问题 / 原因是什么),不要让用户从表格里自己猜。
|
|
28
|
+
3. **数据支撑**:引用**当次 CLI/脚本**的真实数字(消耗、CPA、余额、状态等);禁止编造、禁止示例 ID。
|
|
29
|
+
4. **行动建议**:1~3 条可执行项,点名对象(系列名、账户、关键词)并引用数据。
|
|
30
|
+
5. **下一步**:一句预测或邀请(「要我帮您调整预算吗?」「还想看关键词明细吗?」)。
|
|
31
|
+
|
|
32
|
+
长任务(预计 >30s):中间用**进度式**短句(「正在拉取系列数据…」「正在检查余额…」),不要长时间沉默后突然扔一大段表。
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 三、先执行后解释(与纪律的平衡)
|
|
37
|
+
|
|
38
|
+
| 场景 | 做法 |
|
|
39
|
+
| ---- | ---- |
|
|
40
|
+
| 查数、诊断、报告、列表 | **先拉数再汇报**;缺账户 ID 才问;缺时间范围按 `agent-conventions.md` §五 处理(能默认则默认并注明区间) |
|
|
41
|
+
| 模糊意图(「帮我看一下」「最近怎么样」) | 选**最可能**的工作流先执行(有 Google 户 → 画像/总览);结论后附「您是不是还想…」 |
|
|
42
|
+
| **campaign-create 后 Sitelink 等附加信息 batch 失败** | **自动** `ad batch diff` → 执行 `ad extension *` 补建 → 汇报已补挂条数;**勿**反问「需要我现在补上吗?」(用户已确认过 JSON) |
|
|
43
|
+
| 写入/删除/充值/开户提交 | **必须先确认**(`agent-conventions.md` §四);话术用业务语言说明「将要做什么」 |
|
|
44
|
+
| 对象不清且无法推断 | **只问一次**,给 2~3 个选项,不要问卷式连问 |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 四、自然语言 ↔ 内部路由(对用户隐藏编号)
|
|
49
|
+
|
|
50
|
+
对用户只说业务语言;内部仍按 `SKILL.md` + `intent-routing.md` 路由。
|
|
51
|
+
|
|
52
|
+
| 用户可能说 | 内部路由(勿写出) | 对用户怎么说 |
|
|
53
|
+
| ---------- | ------------------ | ------------ |
|
|
54
|
+
| 帮我看一下 Google 广告 / 账户怎么样 | P1 / P3 + 余额 | 「我先看看您 Google 账户的整体情况。」 |
|
|
55
|
+
| 广告怎么不跑了 / 没花费了 | 余额 + 系列状态 + 拒审 + 落地页 | 「我来帮您排查,通常是预算、状态或审核几类原因。」 |
|
|
56
|
+
| 最近效果怎么样 / 出个月报 | P4 / P1 | 「我拉一下这段时间的数据,给您一份完整报告。」 |
|
|
57
|
+
| 帮我优化一下 | W6 / 优化建议 | 「我先看看哪些广告表现偏弱,给您一份优化清单。」 |
|
|
58
|
+
| 哪些账户快没钱了 | P2 balance-scan | 「正在扫描全部账户余额…」 |
|
|
59
|
+
| 帮我开个户 / 建广告 | W2 / W3 | 「没问题,我先告诉您需要准备哪些资料。」 |
|
|
60
|
+
| 网站行不行 | P8 | 「我来给这个网站做个体检,看是否符合投放要求。」 |
|
|
61
|
+
|
|
62
|
+
完整话术矩阵见产品文档第十章;Skill 内记住**结构**即可,勿背诵全文。
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 五、场景话术要点(示例骨架)
|
|
67
|
+
|
|
68
|
+
### 账户概览(正常)
|
|
69
|
+
|
|
70
|
+
> 帮您看完了!「{账户名}」近 {N} 天花费 {金额},点击 {次数},转化 {次数},CPA {金额},余额 {金额}(预计还能跑 {天数} 天)。整体运行正常。要我深入看看有哪些可以优化的地方吗?
|
|
71
|
+
|
|
72
|
+
### 投放异常(找到原因)
|
|
73
|
+
|
|
74
|
+
> 帮您排查完了,找到原因了:
|
|
75
|
+
>
|
|
76
|
+
> **问题**:系列「{名称}」当前是「{campaignStatusDisplay}」,{一句话原因,如预算熔断/余额不足/被拒审}。
|
|
77
|
+
>
|
|
78
|
+
> **建议**:{1~2 条具体动作}
|
|
79
|
+
>
|
|
80
|
+
> 要我帮您{下一步动作}吗?
|
|
81
|
+
|
|
82
|
+
### 数据不足 / 拉数失败
|
|
83
|
+
|
|
84
|
+
> 这部分数据暂时拿不到({简短原因})。我已经把能查到的部分整理好了;{缺什么}需要您{补什么}后我再帮您查。
|
|
85
|
+
|
|
86
|
+
### 用户说「不是这个意思」
|
|
87
|
+
|
|
88
|
+
> 抱歉理解偏了!您方便再说一下具体想做什么吗?或者从下面选一个:{选项 A} / {选项 B}
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 六、禁止事项(机械感来源)
|
|
93
|
+
|
|
94
|
+
- **禁止**把 playbook 编号、CLI 命令名、JSON 字段名当对用户的主回复内容(技术细节放代码块或附录,且仅在用户要「技术细节」时)。
|
|
95
|
+
- **禁止**无结论的数据堆砌(大表前没有一句话总结)。
|
|
96
|
+
- **禁止**每条 bullet 都是「建议优化」「请关注」而无数字、无对象名。
|
|
97
|
+
- **禁止**用冷冰冰的系统提示语气(「操作失败」「参数错误」)而不给下一步。
|
|
98
|
+
- **禁止**在报告/分析类任务里,用户未要求时输出冗长「执行计划」清单;计划可在脑中或简短一句带过,**尽快进入执行**。
|
|
99
|
+
- 交付 HTML/Excel 时:附**3~5 行摘要**(区间、币种、核心 KPI、最大问题、建议动作),不要只丢文件路径。
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 七、与现有纪律的关系
|
|
104
|
+
|
|
105
|
+
| 文档 | 分工 |
|
|
106
|
+
| ---- | ---- |
|
|
107
|
+
| `agent-conventions.md` | 怎么拉数、怎么读 outline、怎么自检、硬规范 |
|
|
108
|
+
| `intent-routing.md` | 报告/分析消歧、选哪个 P/W |
|
|
109
|
+
| `playbooks.md` / `workflows.md` | 步骤清单 |
|
|
110
|
+
| **本文件** | **对用户怎么说** |
|
|
111
|
+
|
|
112
|
+
数值纪律不变:所有金额、状态、ID 仍只来自当次 CLI/`--json-out` 脚本;本文件只约束**表述方式**,不放宽数据真实性要求。
|
|
@@ -74,8 +74,9 @@
|
|
|
74
74
|
1. 地域 ID:`ad geo search -a <id> -q "United States"` 写入 `campaign.targetedLocations[].id`。
|
|
75
75
|
2. 门禁:`ad campaign-validate --config-file ./campaign.json`(必跑)。
|
|
76
76
|
3. 用户确认后创建:`ad campaign-create --config-file ./campaign.json`,记录返回 taskId。
|
|
77
|
-
4. 轮询:`ad batch get --id <taskId
|
|
78
|
-
5.
|
|
77
|
+
4. 轮询:`ad batch get --id <taskId>` 直至非 Creating。
|
|
78
|
+
5. **必做** `ad batch diff`;缺失的附加信息(Sitelink 等)**自动**用 `ad extension *` 补建,勿反问用户(见 `google-ads-campaign-plan.md` § batch diff 后自动补建)。
|
|
79
|
+
6. 复核:`ad campaigns` / `ad extension list` 确认系列与扩展齐全。
|
|
79
80
|
- **精细管理与日常运营**:`ad adgroup-create` / `ad keyword-create` / `ad keyword-negative-create` / `ad ad-create`(拓词辅助见 **W5**);调整用 `ad adgroup-status` / `ad campaign-status` / `ad ad-delete` / `ad keyword-negative-delete`。完整参数见 `references/google-ads/google-ads.md`。
|
|
80
81
|
- **交付/确认**:关键词匹配格式 `running shoes`=广泛 / `"..."`=词组 / `[...]`=精确;结构性写操作(新建/暂停/删除)须用户确认;写后用成对读命令复核。
|
|
81
82
|
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
| 6 | 输出:**JSON 代码块** → **Markdown**(`google-ads-launch-plan-template.md` 正文)→ 待确认 | — |
|
|
45
45
|
| 7 | 用户确认后 **`ad campaign-create`** | `references/google-ads/google-ads.md` |
|
|
46
46
|
| 8 | 每隔5s 获取创建结果 | `ad batch get --id <taskId> --config-file ./campaign.json` |
|
|
47
|
-
| 9 |
|
|
48
|
-
| 10 |
|
|
47
|
+
| 9 | 成功或部分成功后 **`ad batch diff`** 对照 JSON 与账户实况 | |
|
|
48
|
+
| 10 | **自动补建缺失项**(见下文「batch diff 后自动补建」):附加信息(Sitelink/Callout 等)**直接**执行 `ad extension *`;组/词/RSA 用对应 create 命令。**勿**仅汇报失败并反问用户是否补建 | `references/google-ads/google-ads.md` § batch diff 后自动补建 |
|
|
49
49
|
|
|
50
50
|
多系列:每系列一个 JSON;可选 `campaign-manifest.json`(`role: brand|competitor|generic`)仅作文件组织参考。
|
|
51
51
|
|
|
@@ -108,6 +108,34 @@ validate 与 create **共用** `runCampaignCreateValidation`:词面规范化 +
|
|
|
108
108
|
|
|
109
109
|
---
|
|
110
110
|
|
|
111
|
+
## batch diff 后自动补建(Agent 必遵)
|
|
112
|
+
|
|
113
|
+
`campaign-create` BatchJob **常漏附加信息**(尤其 Sitelink);系列/组/关键词/RSA 已创建成功时,**不要**停在「需要我现在补上吗?」——用户确认 JSON 时已包含这些内容,Agent **应自动补建**。
|
|
114
|
+
|
|
115
|
+
| 步骤 | 动作 |
|
|
116
|
+
| ---- | ---- |
|
|
117
|
+
| 1 | `ad batch get` 直至 `Successfully` 或 `HasFailed`(Creating 时每 5s 轮询) |
|
|
118
|
+
| 2 | `ad batch diff --batch-id <taskId> --config-file ./campaign.json [--campaign-id <id>]` |
|
|
119
|
+
| 3 | 读缺失明细(或 `--json-out` 落盘中的 `missing[]`);每条 `extension` 层若有 `remediateCommand`,**直接执行** |
|
|
120
|
+
| 4 | 无 `remediateCommand` 时,按 JSON 路径与 `google-ads.md` § 批量创建工作流 映射为 `ad extension sitelink` / `callout` / `call` / `snippet` |
|
|
121
|
+
| 5 | 缺组/词/RSA/否词:用 `ad adgroup-create` / `keyword-create` / `ad-create` / `keyword-negative-create` 补建(补建前 `ad extension list` 等核对,避免重复) |
|
|
122
|
+
| 6 | 全部补建后再 `ad batch diff` 或 `ad extension list --campaign-id <id>` 复核;汇总告诉用户「已自动补上 N 条 Sitelink」 |
|
|
123
|
+
| 7 | 仅当补建命令也失败时,才向用户说明原因并给改写方案 |
|
|
124
|
+
|
|
125
|
+
**Sitelink 单条示例**(字段来自 `ExtensionsForBatchJob[i].Properties`):
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
siluzan-tso ad extension sitelink -a <accountId> \
|
|
129
|
+
--level Campaign --campaign-id <campaignId> \
|
|
130
|
+
--text "<Properties.Text>" --url "<Properties.DestinationUrl>" \
|
|
131
|
+
[--line2 "<Line2>"] [--line3 "<Line3>"] \
|
|
132
|
+
--json-out ./snap
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**禁止**:系列主体已按计划建成,却因 Sitelink batch 失败就结束任务、等用户二次确认(除非用户明确说「先不要挂扩展」)。
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
111
139
|
## 已上线后的修改
|
|
112
140
|
|
|
113
141
|
- **勿**用 `campaign-create` 覆盖已有系列;用 `ad campaign-edit` / `adgroup-*` / `keyword-*` / `ad-edit` 等(见 `references/google-ads/google-ads.md`)。
|
|
@@ -371,8 +371,21 @@ siluzan-tso ad batch diff --batch-id <taskId> --config-file ./campaign.json
|
|
|
371
371
|
2. **仅少数组/资产失败**(系列与大部分组已有 `Id`):
|
|
372
372
|
- 缺组:`ad adgroup-create`(`--json-out` 取 `id`)
|
|
373
373
|
- 缺词:`ad keyword-create`(词面格式同 JSON `KeywordsForBatchJob`)
|
|
374
|
-
- 缺 RSA:`ad ad-create`(`--headlines` / `--descriptions` 从 JSON `AdsForBatchJob` 抄)
|
|
375
|
-
|
|
374
|
+
- 缺 RSA:`ad ad-create`(`--headlines` / `--descriptions` 从 JSON `AdsForBatchJob` 抄)
|
|
375
|
+
- **缺附加信息(常见)**:**自动**执行 `ad extension sitelink` / `callout` / `call` / `snippet`(见下节);`batch diff` 输出含 `补建:` 行或 `--json-out` 的 `missing[].remediateCommand`
|
|
376
|
+
补建前可用 `ad groups` / `ad keywords` / `ad extension list` 核对账户现状。
|
|
377
|
+
|
|
378
|
+
### batch diff 后自动补建附加信息(Agent 必遵)
|
|
379
|
+
|
|
380
|
+
BatchJob 中 `ExtensionsForBatchJob`(尤其 **Sitelink**)失败率较高,但系列/组/词/RSA 往往已成功。用户确认创建时已认可 JSON 内全部附加信息 → Agent **须直接补建**,**禁止**仅汇报「Sitelink 未创建,需要我现在补上吗?」。
|
|
381
|
+
|
|
382
|
+
1. 跑 `ad batch diff`;对 `layer=extension` 的缺失项执行输出中的 **`补建:`** 命令(或读落盘 `missing[].remediateCommand`)。
|
|
383
|
+
2. 字段映射:`Properties.Text` + `DestinationUrl` → `ad extension sitelink`;`CALLOUT` → `callout`;`CALL` → `call`;`STRUCTURED_SNIPPET` → `snippet`(`StructuredSnippetHeaderValue`)。
|
|
384
|
+
3. 系列级统一 `--level Campaign --campaign-id <id>`;每条 `--json-out ./snap`。
|
|
385
|
+
4. 补建完成后 `ad extension list -a <accountId> --campaign-id <id> --type SITELINK` 复核条数。
|
|
386
|
+
5. 向用户汇报:「系列已创建;Sitelink 等 X 条已通过单独命令补挂完成」(附系列 ID 与条数即可)。
|
|
387
|
+
|
|
388
|
+
完整流水线见 `references/google-ads/google-ads-campaign-plan.md` § batch diff 后自动补建。
|
|
376
389
|
|
|
377
390
|
**`ad batch diff` 比对维度:**系列是否存在 → 各 `AdGroupsForBatchJob[].Name` → 组内关键词(匹配类型+词面)→ RSA 首条标题 → 系列否定词 → 附加信息条数。默认按层级树状输出每条缺失的 **JSON 路径 + 计划内容 + 账户实况**;`--json-out` 可落盘完整结构。
|
|
378
391
|
|
|
@@ -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.
|
|
12
|
+
$PKG_VERSION = '1.1.36-beta.1'
|
|
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.
|
|
12
|
+
readonly PKG_VERSION="1.1.36-beta.1"
|
|
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"
|