siluzan-tso-cli 1.1.33-beta.2 → 1.1.33-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 +1 -1
- package/dist/index.js +154 -38
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/report-templates/google-ads-diagnosis.md +4 -0
- package/dist/skill/report-templates/GoogleAdsDiagnosisReport.html +133 -0
- package/dist/skill/report-templates/google-ads-diagnosis.md +4 -0
- 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.33-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.33-beta.3),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -125029,8 +125029,8 @@ function buildGoogleAdsDiagnosisCollectAgentHint(snapDir) {
|
|
|
125029
125029
|
"Google Ads \u8BCA\u65AD collect \u5DF2\u5B8C\u6210\uFF08\u4EC5\u4E8B\u5B9E\u6570\u636E\uFF0C\u4E0D\u542B\u5EFA\u8BAE\u6027\u6587\u6848\uFF09\u3002",
|
|
125030
125030
|
`1) Read ${dir}/${GOOGLE_ADS_DIAGNOSIS_COLLECT_BASENAME}\uFF08reportData + agentBrief\uFF09\uFF1B`,
|
|
125031
125031
|
"2) Read references/report-templates/google-ads-diagnosis.md\uFF0C\u7531 Agent \u64B0\u5199\u5168\u90E8 analysis/suggestions\u3001diagnosisOverview\u3001summary\u3001accountInfo.businessModel/industry\u3001budgetCompetitiveness[].strategy\u3001newFeatures[].optimizerRecommendation\u3001adCreativeOptimization.items[].suggestion \u7B49\uFF1B",
|
|
125032
|
-
`3) \u5C06 reportData \u4E0E\u53D9\u4E8B\u5B57\u6BB5\u5408\u5E76\u5199\u5165 ${dir}/${GOOGLE_ADS_DIAGNOSIS_REPORT_BASENAME}\uFF1B`,
|
|
125033
|
-
`4) siluzan-tso google-ads-diagnosis render --data ${dir}/${GOOGLE_ADS_DIAGNOSIS_REPORT_BASENAME} --out ${dir}/google-ads-diagnosis-report.html\u3002`,
|
|
125032
|
+
`3) \u5C06 reportData \u4E0E\u53D9\u4E8B\u5B57\u6BB5\u5408\u5E76\u5199\u5165 ${dir}/${GOOGLE_ADS_DIAGNOSIS_REPORT_BASENAME}\uFF08**\u7981\u6B62\u6539\u5199** campaigns/geographic/keywords \u7684 items \u884C\uFF0C\u53EA\u586B analysis/suggestions\uFF09\uFF1B`,
|
|
125033
|
+
`4) siluzan-tso google-ads-diagnosis render --data ${dir}/${GOOGLE_ADS_DIAGNOSIS_REPORT_BASENAME} --out ${dir}/google-ads-diagnosis-report.html\uFF08\u9ED8\u8BA4\u540C\u76EE\u5F55 collect \u81EA\u52A8\u6062\u590D\u88AB\u6539\u574F\u7684 items\uFF09\u3002`,
|
|
125034
125034
|
"\u7981\u6B62\u5728\u672A\u7ECF\u8FC7 Agent \u64B0\u5199\u7684\u60C5\u51B5\u4E0B\u76F4\u63A5 render collect \u4EA7\u7269\uFF1B\u7F3A\u4EFB\u4E00\u5EFA\u8BAE\u5B57\u6BB5 render \u4F1A\u5931\u8D25\u3002"
|
|
125035
125035
|
].join("\n");
|
|
125036
125036
|
}
|
|
@@ -126155,13 +126155,90 @@ function formatGoogleAdsDiagnosisContentErrors(check) {
|
|
|
126155
126155
|
return parts.join("\n");
|
|
126156
126156
|
}
|
|
126157
126157
|
|
|
126158
|
-
// src/commands/google-ads-diagnosis/
|
|
126158
|
+
// src/commands/google-ads-diagnosis/merge-collect-facts.ts
|
|
126159
|
+
var GOOGLE_ADS_DIAGNOSIS_COMPARISON_MODULES = [
|
|
126160
|
+
"campaigns",
|
|
126161
|
+
"geographic",
|
|
126162
|
+
"keywords"
|
|
126163
|
+
];
|
|
126159
126164
|
function asRecord6(value) {
|
|
126165
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
126166
|
+
return value;
|
|
126167
|
+
}
|
|
126168
|
+
return null;
|
|
126169
|
+
}
|
|
126170
|
+
function isValidRateChange(value) {
|
|
126171
|
+
if (value === null || value === void 0) return true;
|
|
126172
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
126173
|
+
}
|
|
126174
|
+
function isComparisonTableRowValid(row) {
|
|
126175
|
+
const rec = asRecord6(row);
|
|
126176
|
+
if (!rec) return false;
|
|
126177
|
+
if (!String(rec.title ?? "").trim()) return false;
|
|
126178
|
+
if (!Number.isFinite(Number(rec.currentCost))) return false;
|
|
126179
|
+
if (!Number.isFinite(Number(rec.currentClicks))) return false;
|
|
126180
|
+
if (!Number.isFinite(Number(rec.currentCvr))) return false;
|
|
126181
|
+
if (!isValidRateChange(rec.costRateChange)) return false;
|
|
126182
|
+
if (!isValidRateChange(rec.clicksRateChange)) return false;
|
|
126183
|
+
if (!isValidRateChange(rec.cvrRateChange)) return false;
|
|
126184
|
+
return true;
|
|
126185
|
+
}
|
|
126186
|
+
function moduleItemsNeedRestore(items) {
|
|
126187
|
+
if (!Array.isArray(items) || items.length === 0) return false;
|
|
126188
|
+
return items.some((row) => !isComparisonTableRowValid(row));
|
|
126189
|
+
}
|
|
126190
|
+
function getComparisonTableErrors(data) {
|
|
126191
|
+
const errors = [];
|
|
126192
|
+
for (const mod of GOOGLE_ADS_DIAGNOSIS_COMPARISON_MODULES) {
|
|
126193
|
+
const block = asRecord6(data[mod]);
|
|
126194
|
+
const items = block?.items;
|
|
126195
|
+
if (!Array.isArray(items) || items.length === 0) continue;
|
|
126196
|
+
for (let i = 0; i < items.length; i++) {
|
|
126197
|
+
if (!isComparisonTableRowValid(items[i])) {
|
|
126198
|
+
errors.push(
|
|
126199
|
+
`${mod}.items[${i}] \u7F3A\u5C11 title/currentCost/currentClicks \u6216\u73AF\u6BD4\u5B57\u6BB5\u88AB\u5199\u6210\u5B57\u7B26\u4E32\uFF08\u987B\u4FDD\u7559 collect \u4E8B\u5B9E\u884C\uFF0C\u4EC5\u7531 Agent \u586B\u5199 analysis/suggestions\uFF09`
|
|
126200
|
+
);
|
|
126201
|
+
}
|
|
126202
|
+
}
|
|
126203
|
+
}
|
|
126204
|
+
return errors;
|
|
126205
|
+
}
|
|
126206
|
+
function mergeComparisonItemsFromCollect(finalData, collectReportData) {
|
|
126207
|
+
const data = { ...finalData };
|
|
126208
|
+
const restoredModules = [];
|
|
126209
|
+
for (const mod of GOOGLE_ADS_DIAGNOSIS_COMPARISON_MODULES) {
|
|
126210
|
+
const finalBlock = asRecord6(data[mod]);
|
|
126211
|
+
const collectBlock = asRecord6(collectReportData[mod]);
|
|
126212
|
+
const finalItems = finalBlock?.items;
|
|
126213
|
+
const collectItems = collectBlock?.items;
|
|
126214
|
+
if (!moduleItemsNeedRestore(finalItems)) continue;
|
|
126215
|
+
if (!Array.isArray(collectItems) || collectItems.length === 0) continue;
|
|
126216
|
+
data[mod] = {
|
|
126217
|
+
...finalBlock ?? {},
|
|
126218
|
+
items: collectItems
|
|
126219
|
+
};
|
|
126220
|
+
restoredModules.push(mod);
|
|
126221
|
+
}
|
|
126222
|
+
return { data, restoredModules };
|
|
126223
|
+
}
|
|
126224
|
+
|
|
126225
|
+
// src/commands/google-ads-diagnosis/render-report.ts
|
|
126226
|
+
function asRecord7(value) {
|
|
126160
126227
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
126161
126228
|
return value;
|
|
126162
126229
|
}
|
|
126163
126230
|
throw new Error("\u8BCA\u65AD\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
|
|
126164
126231
|
}
|
|
126232
|
+
async function tryLoadCollectReportData(collectPath) {
|
|
126233
|
+
try {
|
|
126234
|
+
const raw = JSON.parse(await fs18.readFile(collectPath, "utf8"));
|
|
126235
|
+
const root = asRecord7(raw);
|
|
126236
|
+
const reportData = asRecord7(root?.reportData);
|
|
126237
|
+
return reportData ?? root;
|
|
126238
|
+
} catch {
|
|
126239
|
+
return null;
|
|
126240
|
+
}
|
|
126241
|
+
}
|
|
126165
126242
|
async function runGoogleAdsDiagnosisRender(opts) {
|
|
126166
126243
|
const dataPath = path23.resolve(opts.dataFile);
|
|
126167
126244
|
let dataRaw;
|
|
@@ -126173,7 +126250,39 @@ async function runGoogleAdsDiagnosisRender(opts) {
|
|
|
126173
126250
|
`);
|
|
126174
126251
|
process.exit(1);
|
|
126175
126252
|
}
|
|
126176
|
-
|
|
126253
|
+
let data = asRecord7(dataRaw);
|
|
126254
|
+
if (!opts.noMergeCollect) {
|
|
126255
|
+
const collectPath = path23.resolve(
|
|
126256
|
+
opts.collectFile ?? path23.join(path23.dirname(dataPath), GOOGLE_ADS_DIAGNOSIS_COLLECT_BASENAME)
|
|
126257
|
+
);
|
|
126258
|
+
const collectReportData = await tryLoadCollectReportData(collectPath);
|
|
126259
|
+
if (collectReportData) {
|
|
126260
|
+
const { data: merged, restoredModules } = mergeComparisonItemsFromCollect(
|
|
126261
|
+
data,
|
|
126262
|
+
collectReportData
|
|
126263
|
+
);
|
|
126264
|
+
data = merged;
|
|
126265
|
+
if (restoredModules.length > 0) {
|
|
126266
|
+
console.log(
|
|
126267
|
+
`
|
|
126268
|
+
\u2139\uFE0F \u5DF2\u4ECE collect \u6062\u590D \xA73.5 \u5BF9\u6BD4\u8868 items\uFF08${restoredModules.join("\u3001")}\uFF09\uFF1BAgent \u53EA\u5E94\u586B\u5199 analysis/suggestions\uFF0C\u52FF\u6539\u5199 items \u884C\u6570\u636E\u3002
|
|
126269
|
+
\u6765\u6E90\uFF1A${collectPath}
|
|
126270
|
+
`
|
|
126271
|
+
);
|
|
126272
|
+
}
|
|
126273
|
+
}
|
|
126274
|
+
}
|
|
126275
|
+
const comparisonErrors = getComparisonTableErrors(data);
|
|
126276
|
+
if (comparisonErrors.length > 0) {
|
|
126277
|
+
console.error("\n\u274C \u5BF9\u6BD4\u8868 items \u4E0D\u5B8C\u6574\uFF0CHTML \u4E2D\u7CFB\u5217/\u5730\u57DF/\u5173\u952E\u8BCD\u8868\u4F1A\u663E\u793A\u4E3A\u7A7A\uFF1A\n");
|
|
126278
|
+
for (const err of comparisonErrors) {
|
|
126279
|
+
console.error(` \xB7 ${err}`);
|
|
126280
|
+
}
|
|
126281
|
+
console.error(
|
|
126282
|
+
"\n \u4FEE\u590D\uFF1A\u540C\u76EE\u5F55\u4FDD\u7559 google-ads-diagnosis-collect.json \u540E\u91CD\u65B0 render\uFF08\u9ED8\u8BA4\u81EA\u52A8\u5408\u5E76\uFF09\uFF1B\u6216\u624B\u52A8\u4ECE collect.reportData \u590D\u5236 campaigns/geographic/keywords.items\u3002\n"
|
|
126283
|
+
);
|
|
126284
|
+
process.exit(1);
|
|
126285
|
+
}
|
|
126177
126286
|
const contentCheck = validateGoogleAdsDiagnosisContent(data);
|
|
126178
126287
|
if (!contentCheck.ok) {
|
|
126179
126288
|
console.error(`
|
|
@@ -126264,13 +126373,20 @@ function registerGoogleAdsDiagnosisCommands(program2) {
|
|
|
126264
126373
|
);
|
|
126265
126374
|
root.command("render").description(
|
|
126266
126375
|
"\u5C06 google-ads-diagnosis.json \u6CE8\u5165 GoogleAdsDiagnosisReport.html\uFF0C\u8F93\u51FA\u4E0E MarkAI \u4E00\u81F4\u7684 HTML \u7EC8\u7A3F"
|
|
126267
|
-
).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684\u8BCA\u65AD JSON\uFF08\u4E0E MarkAI sectionData \u540C\u7ED3\u6784\uFF09").option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").option(
|
|
126268
|
-
|
|
126269
|
-
|
|
126270
|
-
|
|
126271
|
-
|
|
126272
|
-
|
|
126273
|
-
|
|
126376
|
+
).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684\u8BCA\u65AD JSON\uFF08\u4E0E MarkAI sectionData \u540C\u7ED3\u6784\uFF09").option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").option(
|
|
126377
|
+
"--collect <file>",
|
|
126378
|
+
"collect JSON \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\u7684 google-ads-diagnosis-collect.json\uFF0C\u7528\u4E8E\u6062\u590D\u5BF9\u6BD4\u8868 items\uFF09"
|
|
126379
|
+
).option("--no-merge-collect", "\u4E0D\u4ECE collect \u81EA\u52A8\u6062\u590D campaigns/geographic/keywords.items", false).option("--lenient", "\u7ED3\u6784\u8B66\u544A\uFF08\u5982\u7F3A analysis\uFF09\u65F6\u4E0D\u963B\u65AD\u6E32\u67D3", false).action(
|
|
126380
|
+
async (opts) => {
|
|
126381
|
+
await runGoogleAdsDiagnosisRender({
|
|
126382
|
+
dataFile: opts.data,
|
|
126383
|
+
out: opts.out,
|
|
126384
|
+
collectFile: opts.collect,
|
|
126385
|
+
noMergeCollect: opts.noMergeCollect,
|
|
126386
|
+
lenient: opts.lenient
|
|
126387
|
+
});
|
|
126388
|
+
}
|
|
126389
|
+
);
|
|
126274
126390
|
}
|
|
126275
126391
|
|
|
126276
126392
|
// src/index.ts
|
|
@@ -126627,7 +126743,7 @@ function ensureScorecardFromSnapshot(healthDiagnosis, merged) {
|
|
|
126627
126743
|
}
|
|
126628
126744
|
|
|
126629
126745
|
// src/commands/facebook-analysis/merge-snapshot.ts
|
|
126630
|
-
function
|
|
126746
|
+
function asRecord8(value) {
|
|
126631
126747
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
126632
126748
|
return value;
|
|
126633
126749
|
}
|
|
@@ -126676,7 +126792,7 @@ async function readManifest(snapshotDir) {
|
|
|
126676
126792
|
async function loadSectionFile(snapshotDir, file) {
|
|
126677
126793
|
try {
|
|
126678
126794
|
const raw = await fs21.readFile(path28.join(snapshotDir, file), "utf8");
|
|
126679
|
-
return
|
|
126795
|
+
return asRecord8(JSON.parse(raw));
|
|
126680
126796
|
} catch {
|
|
126681
126797
|
return null;
|
|
126682
126798
|
}
|
|
@@ -126724,7 +126840,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
|
|
|
126724
126840
|
if (data) sectionMap.set(art.section, data);
|
|
126725
126841
|
}
|
|
126726
126842
|
const overview = sectionMap.get("overview");
|
|
126727
|
-
const current =
|
|
126843
|
+
const current = asRecord8(overview?.currentPeriod);
|
|
126728
126844
|
const accountName = typeof overview?.accountName === "string" ? overview.accountName : void 0;
|
|
126729
126845
|
const accountId = manifest.accountId ?? (typeof overview?.accountId === "string" ? overview.accountId : void 0);
|
|
126730
126846
|
const kpis = { ...payload.kpis ?? {} };
|
|
@@ -126846,7 +126962,7 @@ function isFiniteNumber(value) {
|
|
|
126846
126962
|
function asArray2(value) {
|
|
126847
126963
|
return Array.isArray(value) ? value : [];
|
|
126848
126964
|
}
|
|
126849
|
-
function
|
|
126965
|
+
function asRecord9(value) {
|
|
126850
126966
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
126851
126967
|
return value;
|
|
126852
126968
|
}
|
|
@@ -126858,26 +126974,26 @@ function pushMissing(missing, id, chapter, dimension, hint) {
|
|
|
126858
126974
|
function countAdSetsWithSpend(tables) {
|
|
126859
126975
|
const rows = asArray2(tables?.adSets);
|
|
126860
126976
|
return rows.filter((row) => {
|
|
126861
|
-
const r =
|
|
126977
|
+
const r = asRecord9(row);
|
|
126862
126978
|
const spend = r?.spend;
|
|
126863
126979
|
return isFiniteNumber(spend) && spend > 0;
|
|
126864
126980
|
}).length;
|
|
126865
126981
|
}
|
|
126866
126982
|
function chartLabels(chart) {
|
|
126867
|
-
const c =
|
|
126983
|
+
const c = asRecord9(chart);
|
|
126868
126984
|
return asArray2(c?.labels).length;
|
|
126869
126985
|
}
|
|
126870
126986
|
function validateMetaPeriodReportContent(data) {
|
|
126871
126987
|
const missing = [];
|
|
126872
|
-
const meta =
|
|
126873
|
-
const kpis =
|
|
126874
|
-
const narrative =
|
|
126875
|
-
const tables =
|
|
126876
|
-
const charts =
|
|
126877
|
-
const sections =
|
|
126878
|
-
const health =
|
|
126879
|
-
const priorityPlan =
|
|
126880
|
-
const actionChecklist =
|
|
126988
|
+
const meta = asRecord9(data.meta) ?? {};
|
|
126989
|
+
const kpis = asRecord9(data.kpis) ?? {};
|
|
126990
|
+
const narrative = asRecord9(data.narrative) ?? {};
|
|
126991
|
+
const tables = asRecord9(data.tables);
|
|
126992
|
+
const charts = asRecord9(data.charts);
|
|
126993
|
+
const sections = asRecord9(data.sections);
|
|
126994
|
+
const health = asRecord9(data.healthDiagnosis);
|
|
126995
|
+
const priorityPlan = asRecord9(data.priorityPlan);
|
|
126996
|
+
const actionChecklist = asRecord9(data.actionChecklist);
|
|
126881
126997
|
const adSetsWithSpend2 = countAdSetsWithSpend(tables ?? void 0);
|
|
126882
126998
|
const regionalNarratives = asArray2(narrative.regional).length;
|
|
126883
126999
|
const recommendations = asArray2(narrative.recommendations);
|
|
@@ -126949,7 +127065,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
126949
127065
|
"charts.audience.labels \u81F3\u5C11 1 \u9879"
|
|
126950
127066
|
);
|
|
126951
127067
|
}
|
|
126952
|
-
const funnel =
|
|
127068
|
+
const funnel = asRecord9(charts?.funnel);
|
|
126953
127069
|
if (!isFiniteNumber(funnel?.reach)) {
|
|
126954
127070
|
pushMissing(
|
|
126955
127071
|
missing,
|
|
@@ -127013,7 +127129,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127013
127129
|
);
|
|
127014
127130
|
} else {
|
|
127015
127131
|
for (let i = 0; i < regionalNarratives; i++) {
|
|
127016
|
-
const row =
|
|
127132
|
+
const row = asRecord9(asArray2(narrative.regional)[i]);
|
|
127017
127133
|
if (textLen2(row?.text) < 80) {
|
|
127018
127134
|
pushMissing(
|
|
127019
127135
|
missing,
|
|
@@ -127043,7 +127159,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127043
127159
|
"\u5FC5\u987B\u6070\u597D 4 \u6761\uFF08\u7B80\u5316\u8868\u5355/\u533A\u57DF\u8C03\u6574/\u9884\u7B97\u91CD\u6784/\u7D20\u6750\u5EFA\u8BAE\uFF09"
|
|
127044
127160
|
);
|
|
127045
127161
|
} else {
|
|
127046
|
-
const titles = recommendations.map((r) =>
|
|
127162
|
+
const titles = recommendations.map((r) => asRecord9(r)?.title);
|
|
127047
127163
|
for (const title of RECOMMENDATION_TITLES) {
|
|
127048
127164
|
if (!titles.includes(title)) {
|
|
127049
127165
|
pushMissing(
|
|
@@ -127056,7 +127172,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127056
127172
|
}
|
|
127057
127173
|
}
|
|
127058
127174
|
for (let i = 0; i < recommendations.length; i++) {
|
|
127059
|
-
const rec =
|
|
127175
|
+
const rec = asRecord9(recommendations[i]);
|
|
127060
127176
|
if (textLen2(rec?.content) < 150) {
|
|
127061
127177
|
pushMissing(
|
|
127062
127178
|
missing,
|
|
@@ -127078,7 +127194,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127078
127194
|
);
|
|
127079
127195
|
} else {
|
|
127080
127196
|
for (let i = 0; i < supplementary.length; i++) {
|
|
127081
|
-
const row =
|
|
127197
|
+
const row = asRecord9(supplementary[i]);
|
|
127082
127198
|
if (!textLen2(row?.dimension) || !textLen2(row?.issue)) {
|
|
127083
127199
|
pushMissing(
|
|
127084
127200
|
missing,
|
|
@@ -127160,7 +127276,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127160
127276
|
);
|
|
127161
127277
|
} else {
|
|
127162
127278
|
for (let i = 0; i < fourQuestions.length; i++) {
|
|
127163
|
-
const q =
|
|
127279
|
+
const q = asRecord9(fourQuestions[i]);
|
|
127164
127280
|
const evidence = asArray2(q?.evidence).filter((e) => textLen2(e) > 0);
|
|
127165
127281
|
if (evidence.length < 2) {
|
|
127166
127282
|
pushMissing(
|
|
@@ -127184,7 +127300,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127184
127300
|
}
|
|
127185
127301
|
let scorecardValidRows = 0;
|
|
127186
127302
|
for (let i = 0; i < scorecard.length; i++) {
|
|
127187
|
-
const row =
|
|
127303
|
+
const row = asRecord9(scorecard[i]);
|
|
127188
127304
|
const itemOk = textLen2(row?.item) > 0;
|
|
127189
127305
|
const dataOk = textLen2(row?.data) > 0;
|
|
127190
127306
|
const signalOk = SCORECARD_SIGNALS.includes(row?.signal);
|
|
@@ -127226,7 +127342,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127226
127342
|
);
|
|
127227
127343
|
}
|
|
127228
127344
|
for (const key of ["platform", "country", "adSets"]) {
|
|
127229
|
-
const insight = textLen2(
|
|
127345
|
+
const insight = textLen2(asRecord9(sections?.[key])?.insight);
|
|
127230
127346
|
if (insight < 200) {
|
|
127231
127347
|
pushMissing(
|
|
127232
127348
|
missing,
|
|
@@ -127237,7 +127353,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127237
127353
|
);
|
|
127238
127354
|
}
|
|
127239
127355
|
}
|
|
127240
|
-
const audience =
|
|
127356
|
+
const audience = asRecord9(sections?.audience);
|
|
127241
127357
|
if (asArray2(audience?.goldenProfile).filter((s) => textLen2(s) > 0).length < 3) {
|
|
127242
127358
|
pushMissing(
|
|
127243
127359
|
missing,
|
|
@@ -127265,7 +127381,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127265
127381
|
"\u2265150 \u5B57"
|
|
127266
127382
|
);
|
|
127267
127383
|
}
|
|
127268
|
-
const landingRows = asArray2(
|
|
127384
|
+
const landingRows = asArray2(asRecord9(sections?.landingPage)?.rows);
|
|
127269
127385
|
if (landingRows.length < 3) {
|
|
127270
127386
|
pushMissing(
|
|
127271
127387
|
missing,
|
|
@@ -127350,7 +127466,7 @@ async function readJsonFile2(filePath) {
|
|
|
127350
127466
|
throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
|
|
127351
127467
|
}
|
|
127352
127468
|
}
|
|
127353
|
-
function
|
|
127469
|
+
function asRecord10(value) {
|
|
127354
127470
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
127355
127471
|
return value;
|
|
127356
127472
|
}
|
|
@@ -127365,7 +127481,7 @@ function injectReportData2(html, payload) {
|
|
|
127365
127481
|
}
|
|
127366
127482
|
async function runFacebookAnalysisRender(opts) {
|
|
127367
127483
|
const dataPath = path29.resolve(opts.dataFile);
|
|
127368
|
-
let data =
|
|
127484
|
+
let data = asRecord10(await readJsonFile2(dataPath));
|
|
127369
127485
|
if (opts.snapshotDir?.trim()) {
|
|
127370
127486
|
data = await mergeFacebookSnapshotIntoReport(data, path29.resolve(opts.snapshotDir));
|
|
127371
127487
|
}
|
package/dist/skill/_meta.json
CHANGED
|
@@ -33,6 +33,9 @@ siluzan-tso google-ads-diagnosis render \
|
|
|
33
33
|
| `collect` | 拉 google-analysis + 可选 Lighthouse;输出 `google-ads-diagnosis-collect.json` |
|
|
34
34
|
| `render` | 读取 Agent 产出的 `google-ads-diagnosis.json`,注入 `GoogleAdsDiagnosisReport.html` |
|
|
35
35
|
|
|
36
|
+
- **render 自动合并(默认开启)**:若同目录存在 `google-ads-diagnosis-collect.json`,且 Agent 改坏了 `campaigns` / `geographic` / `keywords` 的 `items`(缺 `title`/`currentCost` 或环比写成字符串),CLI **自动从 collect.reportData 恢复 items**,保留 Agent 的 `analysis`/`suggestions`。可用 `--no-merge-collect` 关闭;`--collect <file>` 指定 collect 路径。
|
|
37
|
+
- **render 硬校验**:合并后对比表仍不完整则 **exit 1**(避免 HTML 表体全空)。
|
|
38
|
+
|
|
36
39
|
- **禁止**在 `collect` 阶段生成或写入 `analysis` / `suggestions` / `diagnosisOverview` / `summary` 等建议性文案。
|
|
37
40
|
- **禁止**跳过 Agent 直接 `render` collect 产物(`render` 会校验全部叙事 / 建议字段;调试可加 `--lenient`)。
|
|
38
41
|
- JSON 顶层结构须与 MarkAI `sectionData.js` / `fetchData` 输出一致(见下文各 `section-*` 数据对象)。
|
|
@@ -204,6 +207,7 @@ siluzan-tso google-ads-diagnosis render \
|
|
|
204
207
|
- 指标列:花费(上期/本期/环比)、点击(上期/本期/环比)、转化率(上期/本期/环比)
|
|
205
208
|
- **数据**:`campaigns.items`(`title`、`previousCost`、`currentCost`、`costRateChange`、`previousClicks`、`currentClicks`、`clicksRateChange`、`previousCvr`、`currentCvr`、`cvrRateChange`)
|
|
206
209
|
- **分析 / 建议**:`campaigns.analysis`、`campaigns.suggestions`
|
|
210
|
+
- **Agent 禁止改写 `items` 行**(须原样保留 collect `reportData`);`render` 会从同目录 collect 自动恢复被改坏的 items
|
|
207
211
|
|
|
208
212
|
#### 国家地区分析
|
|
209
213
|
|
|
@@ -4296,6 +4296,75 @@
|
|
|
4296
4296
|
const labels = items.map((item, index) =>
|
|
4297
4297
|
formatDateLabel(item?.date, index),
|
|
4298
4298
|
);
|
|
4299
|
+
const totalConversions = items.reduce(
|
|
4300
|
+
(sum, item) => sum + (Number(item?.conversions) || 0),
|
|
4301
|
+
0,
|
|
4302
|
+
);
|
|
4303
|
+
// 零转化账户:CPA/转化折线全为 0 视觉上像空图,改展示消耗 + 点击按日趋势
|
|
4304
|
+
const useCostClickTrend = totalConversions === 0;
|
|
4305
|
+
|
|
4306
|
+
if (useCostClickTrend) {
|
|
4307
|
+
const costData = items.map((item) => Number(item?.cost) || 0);
|
|
4308
|
+
const clicksData = items.map((item) => Number(item?.clicks) || 0);
|
|
4309
|
+
return {
|
|
4310
|
+
type: "line",
|
|
4311
|
+
data: {
|
|
4312
|
+
labels,
|
|
4313
|
+
datasets: [
|
|
4314
|
+
{
|
|
4315
|
+
label: "消耗",
|
|
4316
|
+
data: costData,
|
|
4317
|
+
borderColor: "#3b82f6",
|
|
4318
|
+
backgroundColor: "rgba(59, 130, 246, 0.12)",
|
|
4319
|
+
tension: 0.3,
|
|
4320
|
+
yAxisID: "y1",
|
|
4321
|
+
pointRadius: 3,
|
|
4322
|
+
},
|
|
4323
|
+
{
|
|
4324
|
+
label: "点击次数",
|
|
4325
|
+
data: clicksData,
|
|
4326
|
+
borderColor: "#10b981",
|
|
4327
|
+
backgroundColor: "rgba(16, 185, 129, 0.12)",
|
|
4328
|
+
tension: 0.3,
|
|
4329
|
+
yAxisID: "y2",
|
|
4330
|
+
pointRadius: 3,
|
|
4331
|
+
},
|
|
4332
|
+
],
|
|
4333
|
+
},
|
|
4334
|
+
options: {
|
|
4335
|
+
responsive: true,
|
|
4336
|
+
maintainAspectRatio: false,
|
|
4337
|
+
interaction: { mode: "index", intersect: false },
|
|
4338
|
+
stacked: false,
|
|
4339
|
+
plugins: {
|
|
4340
|
+
legend: { labels: { font: { size: 12 } } },
|
|
4341
|
+
},
|
|
4342
|
+
scales: {
|
|
4343
|
+
y1: {
|
|
4344
|
+
type: "linear",
|
|
4345
|
+
position: "left",
|
|
4346
|
+
beginAtZero: true,
|
|
4347
|
+
ticks: { font: { size: 11 } },
|
|
4348
|
+
title: { display: true, text: "消耗", font: { size: 12 } },
|
|
4349
|
+
},
|
|
4350
|
+
y2: {
|
|
4351
|
+
type: "linear",
|
|
4352
|
+
position: "right",
|
|
4353
|
+
beginAtZero: true,
|
|
4354
|
+
ticks: { font: { size: 11 } },
|
|
4355
|
+
grid: { drawOnChartArea: false },
|
|
4356
|
+
title: {
|
|
4357
|
+
display: true,
|
|
4358
|
+
text: "点击次数",
|
|
4359
|
+
font: { size: 12 },
|
|
4360
|
+
},
|
|
4361
|
+
},
|
|
4362
|
+
x: { ticks: { font: { size: 11 } } },
|
|
4363
|
+
},
|
|
4364
|
+
},
|
|
4365
|
+
};
|
|
4366
|
+
}
|
|
4367
|
+
|
|
4299
4368
|
const cpaData = items.map((item) => Number(item?.cpa) || 0);
|
|
4300
4369
|
const conversionsData = items.map((item) =>
|
|
4301
4370
|
Number(item?.conversions ?? 0),
|
|
@@ -4401,6 +4470,40 @@
|
|
|
4401
4470
|
};
|
|
4402
4471
|
};
|
|
4403
4472
|
|
|
4473
|
+
const buildConversionCostDailyTableHtml = (items) => {
|
|
4474
|
+
const rows = Array.isArray(items) ? items : [];
|
|
4475
|
+
if (!rows.length) {
|
|
4476
|
+
return '<tr><td colspan="5" class="empty-row">暂无按日趋势数据</td></tr>';
|
|
4477
|
+
}
|
|
4478
|
+
return rows
|
|
4479
|
+
.map((item) => {
|
|
4480
|
+
const rawDate = item?.date;
|
|
4481
|
+
let dateLabel = rawDate;
|
|
4482
|
+
const parsed = new Date(rawDate);
|
|
4483
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
4484
|
+
dateLabel = parsed.toISOString().slice(0, 10);
|
|
4485
|
+
}
|
|
4486
|
+
const cost = Number(item?.cost);
|
|
4487
|
+
const clicks = Number(item?.clicks);
|
|
4488
|
+
const conversions = Number(item?.conversions ?? 0);
|
|
4489
|
+
const cpa = Number(item?.cpa);
|
|
4490
|
+
const cpaDisplay =
|
|
4491
|
+
conversions > 0 && Number.isFinite(cpa)
|
|
4492
|
+
? formatNumber(cpa, 2)
|
|
4493
|
+
: "—";
|
|
4494
|
+
return `
|
|
4495
|
+
<tr>
|
|
4496
|
+
<td data-label="日期">${escapeHtml(String(dateLabel || "—"))}</td>
|
|
4497
|
+
<td data-label="消耗">${escapeHtml(formatNumber(cost, 2))}</td>
|
|
4498
|
+
<td data-label="点击">${escapeHtml(formatNumber(clicks, 0))}</td>
|
|
4499
|
+
<td data-label="转化">${escapeHtml(formatNumber(conversions, 0))}</td>
|
|
4500
|
+
<td data-label="CPA">${escapeHtml(cpaDisplay)}</td>
|
|
4501
|
+
</tr>
|
|
4502
|
+
`;
|
|
4503
|
+
})
|
|
4504
|
+
.join("");
|
|
4505
|
+
};
|
|
4506
|
+
|
|
4404
4507
|
const createAccountInfoSection = () => {
|
|
4405
4508
|
const reportMain = document.getElementById("report-main");
|
|
4406
4509
|
if (!reportMain) return null;
|
|
@@ -4883,17 +4986,47 @@
|
|
|
4883
4986
|
const conversionTrendSuggestionsList = toArray(
|
|
4884
4987
|
conversionCost.suggestions,
|
|
4885
4988
|
).map((item) => `<li class="insight-item">${escapeHtml(item)}</li>`);
|
|
4989
|
+
const conversionCostDailyItems = Array.isArray(conversionCost.items)
|
|
4990
|
+
? conversionCost.items
|
|
4991
|
+
: [];
|
|
4992
|
+
const conversionDailyTableHtml = buildConversionCostDailyTableHtml(
|
|
4993
|
+
conversionCostDailyItems,
|
|
4994
|
+
);
|
|
4886
4995
|
const conversionTrendContent =
|
|
4887
4996
|
(chartConfig ||
|
|
4997
|
+
conversionCostDailyItems.length ||
|
|
4888
4998
|
conversionTrendAnalysisList.length ||
|
|
4889
4999
|
conversionTrendSuggestionsList.length) &&
|
|
4890
5000
|
`
|
|
4891
5001
|
<div class="analysis-dimension conversion-trend-section">
|
|
4892
5002
|
<h4 class="dimension-title">转化成本与转化趋势优化</h4>
|
|
4893
5003
|
<div class="dimension-content">
|
|
5004
|
+
<div class="table-block" data-table-key="conversion-cost-daily">
|
|
5005
|
+
<table class="data-table">
|
|
5006
|
+
<thead>
|
|
5007
|
+
<tr>
|
|
5008
|
+
<th>日期</th>
|
|
5009
|
+
<th>消耗</th>
|
|
5010
|
+
<th>点击</th>
|
|
5011
|
+
<th>转化</th>
|
|
5012
|
+
<th>CPA</th>
|
|
5013
|
+
</tr>
|
|
5014
|
+
</thead>
|
|
5015
|
+
<tbody>
|
|
5016
|
+
${conversionDailyTableHtml}
|
|
5017
|
+
</tbody>
|
|
5018
|
+
</table>
|
|
5019
|
+
</div>
|
|
4894
5020
|
${
|
|
4895
5021
|
chartConfig
|
|
4896
5022
|
? `
|
|
5023
|
+
<p class="conversion-trend-chart-note" style="margin:12px 0 8px;font-size:13px;color:#64748b;">${
|
|
5024
|
+
conversionCostDailyItems.every(
|
|
5025
|
+
(item) => !(Number(item?.conversions) > 0),
|
|
5026
|
+
) && conversionCostDailyItems.length
|
|
5027
|
+
? "本期无转化记录,下图展示消耗与点击按日趋势。"
|
|
5028
|
+
: "下图展示 CPA 与转化次数按日趋势。"
|
|
5029
|
+
}</p>
|
|
4897
5030
|
<div class="chart-card conversion-trend-chart">
|
|
4898
5031
|
<div style="height: 280px;">
|
|
4899
5032
|
<canvas id="${chartId}" height="260"></canvas>
|
|
@@ -33,6 +33,9 @@ siluzan-tso google-ads-diagnosis render \
|
|
|
33
33
|
| `collect` | 拉 google-analysis + 可选 Lighthouse;输出 `google-ads-diagnosis-collect.json` |
|
|
34
34
|
| `render` | 读取 Agent 产出的 `google-ads-diagnosis.json`,注入 `GoogleAdsDiagnosisReport.html` |
|
|
35
35
|
|
|
36
|
+
- **render 自动合并(默认开启)**:若同目录存在 `google-ads-diagnosis-collect.json`,且 Agent 改坏了 `campaigns` / `geographic` / `keywords` 的 `items`(缺 `title`/`currentCost` 或环比写成字符串),CLI **自动从 collect.reportData 恢复 items**,保留 Agent 的 `analysis`/`suggestions`。可用 `--no-merge-collect` 关闭;`--collect <file>` 指定 collect 路径。
|
|
37
|
+
- **render 硬校验**:合并后对比表仍不完整则 **exit 1**(避免 HTML 表体全空)。
|
|
38
|
+
|
|
36
39
|
- **禁止**在 `collect` 阶段生成或写入 `analysis` / `suggestions` / `diagnosisOverview` / `summary` 等建议性文案。
|
|
37
40
|
- **禁止**跳过 Agent 直接 `render` collect 产物(`render` 会校验全部叙事 / 建议字段;调试可加 `--lenient`)。
|
|
38
41
|
- JSON 顶层结构须与 MarkAI `sectionData.js` / `fetchData` 输出一致(见下文各 `section-*` 数据对象)。
|
|
@@ -204,6 +207,7 @@ siluzan-tso google-ads-diagnosis render \
|
|
|
204
207
|
- 指标列:花费(上期/本期/环比)、点击(上期/本期/环比)、转化率(上期/本期/环比)
|
|
205
208
|
- **数据**:`campaigns.items`(`title`、`previousCost`、`currentCost`、`costRateChange`、`previousClicks`、`currentClicks`、`clicksRateChange`、`previousCvr`、`currentCvr`、`cvrRateChange`)
|
|
206
209
|
- **分析 / 建议**:`campaigns.analysis`、`campaigns.suggestions`
|
|
210
|
+
- **Agent 禁止改写 `items` 行**(须原样保留 collect `reportData`);`render` 会从同目录 collect 自动恢复被改坏的 items
|
|
207
211
|
|
|
208
212
|
#### 国家地区分析
|
|
209
213
|
|
|
@@ -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.33-beta.
|
|
12
|
+
$PKG_VERSION = '1.1.33-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.33-beta.
|
|
12
|
+
readonly PKG_VERSION="1.1.33-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"
|