siluzan-tso-cli 1.1.28-beta.4 → 1.1.28-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,7 +31,7 @@
31
31
 
32
32
  ### 报告模板外部资源
33
33
 
34
- HTML 报告模板引用以下 CDN:`cdn.tailwindcss.com`、`cdnjs.cloudflare.com`、`cdn.jsdelivr.net`、`fonts.googleapis.com`。渲染报告时浏览器会发出对应网络请求。
34
+ HTML 报告模板外部资源:**ECharts** 统一使用 `https://staticpn.siluzan.com/assets/slz/homeCDN/echarts.js`;其余样式/脚本可能引用 `cdn.tailwindcss.com`、`cdnjs.cloudflare.com`、`fonts.googleapis.com` 等。
35
35
 
36
36
  ## 1. 环境
37
37
 
@@ -51,7 +51,7 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
51
51
  siluzan-tso init --force # 强制覆盖已存在文件
52
52
  ```
53
53
 
54
- > **注意**:当前为测试版(1.1.28-beta.4),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.28-beta.6),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -121873,7 +121873,9 @@ async function runWebsiteDiagnosisRender(opts) {
121873
121873
  `);
121874
121874
  console.log(` \u8FD0\u884C\u65F6\u811A\u672C\uFF1A${runtimeOut}
121875
121875
  `);
121876
- console.log("\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 HTML \u6587\u4EF6\u5373\u53EF\u67E5\u770B\u56FE\u8868\u4E0E\u5B8C\u6574\u7AE0\u8282\uFF08\u9700\u8054\u7F51\u52A0\u8F7D ECharts CDN\uFF09\u3002\n");
121876
+ console.log(
121877
+ "\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 HTML \u6587\u4EF6\u5373\u53EF\u67E5\u770B\u56FE\u8868\u4E0E\u5B8C\u6574\u7AE0\u8282\uFF08ECharts\uFF1Astaticpn.siluzan.com/homeCDN/echarts.js\uFF09\u3002\n"
121878
+ );
121877
121879
  }
121878
121880
 
121879
121881
  // src/commands/website-diagnosis/register.ts
@@ -122755,6 +122757,411 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
122755
122757
  };
122756
122758
  }
122757
122759
 
122760
+ // src/commands/facebook-analysis/report-content.ts
122761
+ var RECOMMENDATION_TITLES = ["\u7B80\u5316\u8868\u5355\u95EE\u9898", "\u533A\u57DF\u8C03\u6574", "\u9884\u7B97\u91CD\u6784", "\u7D20\u6750\u5EFA\u8BAE"];
122762
+ var LIFECYCLE_PHASES = ["test-market", "find-winner", "scale"];
122763
+ function textLen(value) {
122764
+ return typeof value === "string" ? value.trim().length : 0;
122765
+ }
122766
+ function isFiniteNumber(value) {
122767
+ return typeof value === "number" && Number.isFinite(value);
122768
+ }
122769
+ function asArray(value) {
122770
+ return Array.isArray(value) ? value : [];
122771
+ }
122772
+ function asRecord6(value) {
122773
+ if (value && typeof value === "object" && !Array.isArray(value)) {
122774
+ return value;
122775
+ }
122776
+ return null;
122777
+ }
122778
+ function pushMissing(missing, id, chapter, dimension, hint) {
122779
+ missing.push({ id, chapter, dimension, hint });
122780
+ }
122781
+ function countAdSetsWithSpend(tables) {
122782
+ const rows = asArray(tables?.adSets);
122783
+ return rows.filter((row) => {
122784
+ const r = asRecord6(row);
122785
+ const spend = r?.spend;
122786
+ return isFiniteNumber(spend) && spend > 0;
122787
+ }).length;
122788
+ }
122789
+ function chartLabels(chart) {
122790
+ const c = asRecord6(chart);
122791
+ return asArray(c?.labels).length;
122792
+ }
122793
+ function validateMetaPeriodReportContent(data) {
122794
+ const missing = [];
122795
+ const meta = asRecord6(data.meta) ?? {};
122796
+ const kpis = asRecord6(data.kpis) ?? {};
122797
+ const narrative = asRecord6(data.narrative) ?? {};
122798
+ const tables = asRecord6(data.tables);
122799
+ const charts = asRecord6(data.charts);
122800
+ const sections = asRecord6(data.sections);
122801
+ const health = asRecord6(data.healthDiagnosis);
122802
+ const priorityPlan = asRecord6(data.priorityPlan);
122803
+ const actionChecklist = asRecord6(data.actionChecklist);
122804
+ const adSetsWithSpend = countAdSetsWithSpend(tables ?? void 0);
122805
+ const regionalNarratives = asArray(narrative.regional).length;
122806
+ const recommendations = asArray(narrative.recommendations);
122807
+ const supplementary = asArray(data.supplementaryRecommendations);
122808
+ const executiveSummary = asArray(data.executiveSummary);
122809
+ const fourQuestions = asArray(health?.fourQuestions);
122810
+ const scorecard = asArray(health?.scorecard);
122811
+ const abTests = asArray(data.abTests);
122812
+ if (!textLen(meta.accountName) && !textLen(meta.accountId)) {
122813
+ pushMissing(
122814
+ missing,
122815
+ "meta-account",
122816
+ "\u5143\u4FE1\u606F",
122817
+ "\u8D26\u6237\u540D\u79F0\u6216 ID",
122818
+ "meta.accountName \u6216 meta.accountId \u81F3\u5C11\u4E00\u9879\uFF1B\u53EF\u7531 --snapshot-dir \u5408\u5E76 overview"
122819
+ );
122820
+ }
122821
+ if (!textLen(meta.periodLabel) && !(textLen(meta.startDate) && textLen(meta.endDate))) {
122822
+ pushMissing(
122823
+ missing,
122824
+ "meta-period",
122825
+ "\u5143\u4FE1\u606F",
122826
+ "\u62A5\u544A\u5468\u671F",
122827
+ "meta.periodLabel \u6216 meta.startDate + meta.endDate"
122828
+ );
122829
+ }
122830
+ for (const key of ["spend", "results", "costPerResult", "reach", "impressions", "frequency"]) {
122831
+ if (!isFiniteNumber(kpis[key])) {
122832
+ pushMissing(
122833
+ missing,
122834
+ `kpi-${key}`,
122835
+ "KPI",
122836
+ `\u8D26\u6237\u7EA7 ${key}`,
122837
+ "kpis \u987B\u542B\u6709\u6548\u6570\u503C\uFF1B\u53EF\u7531 --snapshot-dir \u5408\u5E76 overview"
122838
+ );
122839
+ }
122840
+ }
122841
+ if (chartLabels(charts?.platform) < 1) {
122842
+ pushMissing(missing, "chart-platform", "\u6570\u636E\u56FE\u8868", "\u5E73\u53F0 CPL \u56FE", "charts.platform.labels \u81F3\u5C11 1 \u9879");
122843
+ }
122844
+ if (chartLabels(charts?.country) < 1) {
122845
+ pushMissing(missing, "chart-country", "\u6570\u636E\u56FE\u8868", "\u56FD\u5BB6 CPL \u56FE", "charts.country.labels \u81F3\u5C11 1 \u9879");
122846
+ }
122847
+ if (chartLabels(charts?.audience) < 1) {
122848
+ pushMissing(missing, "chart-audience", "\u6570\u636E\u56FE\u8868", "\u53D7\u4F17 CPL \u56FE", "charts.audience.labels \u81F3\u5C11 1 \u9879");
122849
+ }
122850
+ const funnel = asRecord6(charts?.funnel);
122851
+ if (!isFiniteNumber(funnel?.reach)) {
122852
+ pushMissing(missing, "chart-funnel", "\u6570\u636E\u56FE\u8868", "\u6F0F\u6597\u56FE reach", "charts.funnel.reach \u987B\u4E3A\u6709\u6548\u6570\u503C");
122853
+ }
122854
+ if (adSetsWithSpend < 1) {
122855
+ pushMissing(
122856
+ missing,
122857
+ "table-adsets",
122858
+ "\u6570\u636E\u8868\u683C",
122859
+ "\u5E7F\u544A\u7EC4\u8868",
122860
+ "tables.adSets \u81F3\u5C11 1 \u884C\u82B1\u8D39>0\uFF1B\u53EF\u7531 --snapshot-dir \u5408\u5E76 ad-sets"
122861
+ );
122862
+ }
122863
+ if (asArray(tables?.audienceTop).length < 1) {
122864
+ pushMissing(missing, "table-audience-top", "\u6570\u636E\u8868\u683C", "\u53D7\u4F17 Top", "tables.audienceTop \u81F3\u5C11 1 \u884C");
122865
+ }
122866
+ if (asArray(tables?.audienceBottom).length < 1) {
122867
+ pushMissing(
122868
+ missing,
122869
+ "table-audience-bottom",
122870
+ "\u6570\u636E\u8868\u683C",
122871
+ "\u53D7\u4F17 Bottom",
122872
+ "tables.audienceBottom \u81F3\u5C11 1 \u884C"
122873
+ );
122874
+ }
122875
+ if (textLen(narrative.overall) < 120) {
122876
+ pushMissing(
122877
+ missing,
122878
+ "narrative-overall",
122879
+ "\u603B\u6570\u636E\u53D9\u4E8B",
122880
+ "narrative.overall",
122881
+ "\u2265120 \u5B57\uFF0C\u542B\u82B1\u8D39/\u7EBF\u7D22/CPL/\u8986\u76D6/\u5C55\u793A/\u9891\u6B21\u7B49\u81F3\u5C11 5 \u9879\u6570\u5B57"
122882
+ );
122883
+ }
122884
+ if (adSetsWithSpend > 0 && regionalNarratives < adSetsWithSpend) {
122885
+ pushMissing(
122886
+ missing,
122887
+ "narrative-regional",
122888
+ "\u603B\u6570\u636E\u53D9\u4E8B",
122889
+ "narrative.regional",
122890
+ `\u6BCF\u4E2A\u82B1\u8D39>0 \u7684\u5E7F\u544A\u7EC4\u5404 1 \u6BB5\uFF08\u5F53\u524D ${regionalNarratives}/${adSetsWithSpend}\uFF09`
122891
+ );
122892
+ } else if (regionalNarratives < 1) {
122893
+ pushMissing(
122894
+ missing,
122895
+ "narrative-regional",
122896
+ "\u603B\u6570\u636E\u53D9\u4E8B",
122897
+ "narrative.regional",
122898
+ "\u81F3\u5C11 1 \u6BB5\u533A\u57DF/\u5E7F\u544A\u7EC4\u5206\u6790\uFF0C\u6BCF\u6BB5 \u226580 \u5B57"
122899
+ );
122900
+ } else {
122901
+ for (let i = 0; i < regionalNarratives; i++) {
122902
+ const row = asRecord6(asArray(narrative.regional)[i]);
122903
+ if (textLen(row?.text) < 80) {
122904
+ pushMissing(
122905
+ missing,
122906
+ `narrative-regional-${i}`,
122907
+ "\u603B\u6570\u636E\u53D9\u4E8B",
122908
+ `narrative.regional[${i}]`,
122909
+ "\u6BCF\u6BB5 \u226580 \u5B57\uFF0C\u542B\u7EC4\u540D\u3001\u82B1\u8D39\u3001CPL\u3001\u9891\u6B21"
122910
+ );
122911
+ }
122912
+ }
122913
+ }
122914
+ if (textLen(narrative.country) < 80) {
122915
+ pushMissing(
122916
+ missing,
122917
+ "narrative-country",
122918
+ "\u603B\u6570\u636E\u53D9\u4E8B",
122919
+ "narrative.country",
122920
+ "\u226580 \u5B57\uFF0C\u542B CPL \u6700\u4F4E/\u6700\u9AD8\u56FD\u5BB6\u53CA\u5177\u4F53\u6570\u5B57"
122921
+ );
122922
+ }
122923
+ if (recommendations.length !== 4) {
122924
+ pushMissing(
122925
+ missing,
122926
+ "narrative-recommendations-count",
122927
+ "\u4F18\u5316\u5EFA\u8BAE",
122928
+ "narrative.recommendations",
122929
+ "\u5FC5\u987B\u6070\u597D 4 \u6761\uFF08\u7B80\u5316\u8868\u5355/\u533A\u57DF\u8C03\u6574/\u9884\u7B97\u91CD\u6784/\u7D20\u6750\u5EFA\u8BAE\uFF09"
122930
+ );
122931
+ } else {
122932
+ const titles = recommendations.map((r) => asRecord6(r)?.title);
122933
+ for (const title of RECOMMENDATION_TITLES) {
122934
+ if (!titles.includes(title)) {
122935
+ pushMissing(
122936
+ missing,
122937
+ `narrative-rec-title-${title}`,
122938
+ "\u4F18\u5316\u5EFA\u8BAE",
122939
+ `\u56FA\u5B9A\u6807\u9898\u300C${title}\u300D`,
122940
+ "4 \u6761 title \u987B\u4E0E\u89C4\u5219\u679A\u4E3E\u5B8C\u5168\u4E00\u81F4"
122941
+ );
122942
+ }
122943
+ }
122944
+ for (let i = 0; i < recommendations.length; i++) {
122945
+ const rec = asRecord6(recommendations[i]);
122946
+ if (textLen(rec?.content) < 150) {
122947
+ pushMissing(
122948
+ missing,
122949
+ `narrative-rec-content-${i}`,
122950
+ "\u4F18\u5316\u5EFA\u8BAE",
122951
+ `recommendations[${i}].content`,
122952
+ "\u6BCF\u6761 \u2265150 \u5B57\uFF0C\u5F15\u7528\u5F53\u6B21 CPL/\u56FD\u5BB6/\u7EC4\u540D\u7B49\u6570\u5B57"
122953
+ );
122954
+ }
122955
+ }
122956
+ }
122957
+ if (supplementary.length < 7) {
122958
+ pushMissing(
122959
+ missing,
122960
+ "supplementary-count",
122961
+ "\u8865\u5145\u5EFA\u8BAE",
122962
+ "supplementaryRecommendations",
122963
+ "\u5FC5\u987B 7 \u7EF4\u6E05\u5355\u9F50\u5168\uFF08\u9884\u7B97/\u5E73\u53F0/\u5730\u57DF/\u53D7\u4F17/\u521B\u610F/\u9891\u6B21/\u63A5\u53E3\u9650\u5236\uFF09"
122964
+ );
122965
+ } else {
122966
+ for (let i = 0; i < supplementary.length; i++) {
122967
+ const row = asRecord6(supplementary[i]);
122968
+ if (!textLen(row?.dimension) || !textLen(row?.issue)) {
122969
+ pushMissing(
122970
+ missing,
122971
+ `supplementary-fields-${i}`,
122972
+ "\u8865\u5145\u5EFA\u8BAE",
122973
+ `supplementaryRecommendations[${i}]`,
122974
+ "\u987B\u542B dimension\u3001issue\u3001suggestion"
122975
+ );
122976
+ }
122977
+ if (textLen(row?.suggestion) < 60) {
122978
+ pushMissing(
122979
+ missing,
122980
+ `supplementary-suggestion-${i}`,
122981
+ "\u8865\u5145\u5EFA\u8BAE",
122982
+ `supplementaryRecommendations[${i}].suggestion`,
122983
+ "\u6BCF\u6761 suggestion \u226560 \u5B57\u4E14\u542B\u81F3\u5C11 1 \u4E2A\u5F53\u6B21\u6570\u5B57\u6216\u540D\u79F0"
122984
+ );
122985
+ }
122986
+ }
122987
+ }
122988
+ for (const level of ["high", "medium", "low"]) {
122989
+ const items = asArray(priorityPlan?.[level]).filter((s) => textLen(s) >= 40);
122990
+ if (items.length < 2) {
122991
+ pushMissing(
122992
+ missing,
122993
+ `priority-${level}`,
122994
+ "\u4F18\u5148\u7EA7\u8BA1\u5212",
122995
+ `priorityPlan.${level}`,
122996
+ "\u9AD8/\u4E2D/\u4F4E\u5404 \u22652 \u6761\uFF0C\u6BCF\u6761 \u226540 \u5B57"
122997
+ );
122998
+ }
122999
+ }
123000
+ const summaryOk = executiveSummary.filter((p) => textLen(p) >= 80);
123001
+ if (summaryOk.length < 3) {
123002
+ pushMissing(
123003
+ missing,
123004
+ "executive-summary",
123005
+ "\u6267\u884C\u6458\u8981",
123006
+ "executiveSummary",
123007
+ "3\uFF5E5 \u6BB5\uFF0C\u6BCF\u6BB5 \u226580 \u5B57\uFF0C\u89E3\u91CA\u300C\u4E3A\u4EC0\u4E48\u300D"
123008
+ );
123009
+ }
123010
+ if (!LIFECYCLE_PHASES.includes(health?.lifecyclePhase)) {
123011
+ pushMissing(
123012
+ missing,
123013
+ "health-phase",
123014
+ "\u5065\u5EB7\u8BCA\u65AD",
123015
+ "healthDiagnosis.lifecyclePhase",
123016
+ "\u987B\u4E3A test-market / find-winner / scale"
123017
+ );
123018
+ }
123019
+ if (textLen(health?.lifecycleVerdict) < 60) {
123020
+ pushMissing(
123021
+ missing,
123022
+ "health-verdict",
123023
+ "\u5065\u5EB7\u8BCA\u65AD",
123024
+ "healthDiagnosis.lifecycleVerdict",
123025
+ "\u226560 \u5B57\uFF0C\u7ED3\u5408\u603B\u82B1\u8D39\u4E0E\u7EF4\u5EA6\u5206\u6563\u5EA6"
123026
+ );
123027
+ }
123028
+ if (fourQuestions.length !== 4) {
123029
+ pushMissing(
123030
+ missing,
123031
+ "health-four-questions",
123032
+ "\u5065\u5EB7\u8BCA\u65AD",
123033
+ "healthDiagnosis.fourQuestions",
123034
+ "\u6070\u597D 4 \u5F20\u56DB\u95EE\u5361\u7247"
123035
+ );
123036
+ } else {
123037
+ for (let i = 0; i < fourQuestions.length; i++) {
123038
+ const q = asRecord6(fourQuestions[i]);
123039
+ const evidence = asArray(q?.evidence).filter((e) => textLen(e) > 0);
123040
+ if (evidence.length < 2) {
123041
+ pushMissing(
123042
+ missing,
123043
+ `health-evidence-${i}`,
123044
+ "\u5065\u5EB7\u8BCA\u65AD",
123045
+ `fourQuestions[${i}].evidence`,
123046
+ "\u6BCF\u6761 evidence \u22652 \u6761\u4E14\u542B\u6570\u5B57"
123047
+ );
123048
+ }
123049
+ if (textLen(q?.action) < 40) {
123050
+ pushMissing(
123051
+ missing,
123052
+ `health-action-${i}`,
123053
+ "\u5065\u5EB7\u8BCA\u65AD",
123054
+ `fourQuestions[${i}].action`,
123055
+ "action \u226540 \u5B57"
123056
+ );
123057
+ }
123058
+ }
123059
+ }
123060
+ if (scorecard.length < 6) {
123061
+ pushMissing(
123062
+ missing,
123063
+ "health-scorecard",
123064
+ "\u5065\u5EB7\u8BCA\u65AD",
123065
+ "healthDiagnosis.scorecard",
123066
+ "\u7EA2\u7EFF\u706F\u8868 \u22656 \u884C\uFF08\u5E73\u53F0/\u56FD\u5BB6/\u5E7F\u544A\u7EC4/\u53D7\u4F17\u7B49\uFF09"
123067
+ );
123068
+ }
123069
+ for (const key of ["platform", "country", "adSets"]) {
123070
+ const insight = textLen(asRecord6(sections?.[key])?.insight);
123071
+ if (insight < 200) {
123072
+ pushMissing(
123073
+ missing,
123074
+ `sections-${key}`,
123075
+ "\u6DF1\u5EA6\u89E3\u8BFB",
123076
+ `sections.${key}.insight`,
123077
+ "\u2265200 \u5B57\uFF0C\u5F15\u7528\u5F53\u6B21\u5E73\u53F0/\u56FD\u5BB6/\u5E7F\u544A\u7EC4\u6570\u636E"
123078
+ );
123079
+ }
123080
+ }
123081
+ const audience = asRecord6(sections?.audience);
123082
+ if (asArray(audience?.goldenProfile).filter((s) => textLen(s) > 0).length < 3) {
123083
+ pushMissing(
123084
+ missing,
123085
+ "sections-audience-golden",
123086
+ "\u6DF1\u5EA6\u89E3\u8BFB",
123087
+ "sections.audience.goldenProfile",
123088
+ "\u22653 \u6761\u9EC4\u91D1\u53D7\u4F17\u753B\u50CF"
123089
+ );
123090
+ }
123091
+ if (asArray(audience?.antiProfile).filter((s) => textLen(s) > 0).length < 2) {
123092
+ pushMissing(
123093
+ missing,
123094
+ "sections-audience-anti",
123095
+ "\u6DF1\u5EA6\u89E3\u8BFB",
123096
+ "sections.audience.antiProfile",
123097
+ "\u22652 \u6761\u53CD\u753B\u50CF"
123098
+ );
123099
+ }
123100
+ if (textLen(audience?.insight) < 150) {
123101
+ pushMissing(
123102
+ missing,
123103
+ "sections-audience-insight",
123104
+ "\u6DF1\u5EA6\u89E3\u8BFB",
123105
+ "sections.audience.insight",
123106
+ "\u2265150 \u5B57"
123107
+ );
123108
+ }
123109
+ const landingRows = asArray(asRecord6(sections?.landingPage)?.rows);
123110
+ if (landingRows.length < 3) {
123111
+ pushMissing(
123112
+ missing,
123113
+ "sections-landing",
123114
+ "\u843D\u5730\u9875\u5206\u6790",
123115
+ "sections.landingPage.rows",
123116
+ "\u22653 \u884C\uFF08\u5FC3\u7406\u963B\u788D/\u6570\u636E\u4FE1\u53F7/\u63A8\u6F14/\u4F18\u5148\u7EA7\uFF09"
123117
+ );
123118
+ }
123119
+ if (abTests.length < 3) {
123120
+ pushMissing(
123121
+ missing,
123122
+ "ab-tests",
123123
+ "A/B \u5B9E\u9A8C",
123124
+ "abTests",
123125
+ "\u22653 \u4E2A\u5B9E\u9A8C\uFF08\u53D8\u91CF\u3001\u5047\u8BBE\u3001\u6210\u529F\u6807\u51C6\uFF09"
123126
+ );
123127
+ }
123128
+ const today = asArray(actionChecklist?.today).filter((s) => textLen(s) > 0);
123129
+ const thisWeek = asArray(actionChecklist?.thisWeek).filter((s) => textLen(s) > 0);
123130
+ const thisMonth = asArray(actionChecklist?.thisMonth).filter((s) => textLen(s) > 0);
123131
+ if (today.length < 2 || thisWeek.length < 3 || thisMonth.length < 3) {
123132
+ pushMissing(
123133
+ missing,
123134
+ "action-checklist",
123135
+ "\u53EF\u6267\u884C\u6E05\u5355",
123136
+ "actionChecklist",
123137
+ "today \u22652\u3001thisWeek \u22653\u3001thisMonth \u22653 \u6761\u53EF\u6267\u884C\u9879"
123138
+ );
123139
+ }
123140
+ return {
123141
+ ok: missing.length === 0,
123142
+ missing,
123143
+ stats: {
123144
+ recommendationCount: recommendations.length,
123145
+ supplementaryCount: supplementary.length,
123146
+ fourQuestionsCount: fourQuestions.length,
123147
+ scorecardRows: scorecard.length,
123148
+ regionalNarratives,
123149
+ adSetsWithSpend
123150
+ }
123151
+ };
123152
+ }
123153
+ function formatMetaPeriodReportErrors(result) {
123154
+ if (result.ok) return "";
123155
+ const lines = result.missing.map((m) => ` - [${m.chapter}] ${m.dimension}\uFF1A${m.hint}`);
123156
+ return [
123157
+ `\u62A5\u544A JSON \u7F3A\u5C11 ${result.missing.length} \u9879\u5FC5\u542B\u5185\u5BB9\uFF08\u5BF9\u9F50 meta-period-report-rules.md\uFF09\uFF1A`,
123158
+ ...lines,
123159
+ "",
123160
+ "\u8BF7 Read assets/meta-period-report-rules.md\uFF0C\u8865\u5168 meta-period-report.json \u540E\u91CD\u65B0\u6267\u884C render\u3002",
123161
+ "\u6570\u636E\u7C7B\u5B57\u6BB5\u53EF\u5148 facebook-analysis --json-out\uFF0Crender \u65F6\u52A0 --snapshot-dir \u81EA\u52A8\u5408\u5E76 KPI/\u56FE\u8868/\u8868\u683C\u3002"
123162
+ ].join("\n");
123163
+ }
123164
+
122758
123165
  // src/commands/facebook-analysis/render-report.ts
122759
123166
  var TEMPLATE_BASENAMES2 = {
122760
123167
  html: "meta-period-report.html",
@@ -122787,7 +123194,7 @@ async function readJsonFile2(filePath) {
122787
123194
  throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
122788
123195
  }
122789
123196
  }
122790
- function asRecord6(value) {
123197
+ function asRecord7(value) {
122791
123198
  if (value && typeof value === "object" && !Array.isArray(value)) {
122792
123199
  return value;
122793
123200
  }
@@ -122802,10 +123209,22 @@ function injectReportData2(html, payload) {
122802
123209
  }
122803
123210
  async function runFacebookAnalysisRender(opts) {
122804
123211
  const dataPath = path24.resolve(opts.dataFile);
122805
- let data = asRecord6(await readJsonFile2(dataPath));
123212
+ let data = asRecord7(await readJsonFile2(dataPath));
122806
123213
  if (opts.snapshotDir?.trim()) {
122807
123214
  data = await mergeFacebookSnapshotIntoReport(data, path24.resolve(opts.snapshotDir));
122808
123215
  }
123216
+ const contentCheck = validateMetaPeriodReportContent(data);
123217
+ if (!contentCheck.ok) {
123218
+ console.error(`
123219
+ \u274C ${formatMetaPeriodReportErrors(contentCheck)}
123220
+ `);
123221
+ const { stats: stats2 } = contentCheck;
123222
+ console.error(
123223
+ ` \u7EDF\u8BA1\uFF1A\u5EFA\u8BAE ${stats2.recommendationCount}/4\uFF5C\u8865\u5145 ${stats2.supplementaryCount}/7\uFF5C\u56DB\u95EE ${stats2.fourQuestionsCount}/4\uFF5C\u7EA2\u7EFF\u706F ${stats2.scorecardRows}/6\uFF5C\u533A\u57DF\u53D9\u4E8B ${stats2.regionalNarratives}/${stats2.adSetsWithSpend}
123224
+ `
123225
+ );
123226
+ process.exit(1);
123227
+ }
122809
123228
  const templatePath = metaPeriodReportTemplatePath();
122810
123229
  let html;
122811
123230
  try {
@@ -122831,9 +123250,14 @@ async function runFacebookAnalysisRender(opts) {
122831
123250
  runtimeCopied = true;
122832
123251
  } catch {
122833
123252
  }
123253
+ const { stats } = contentCheck;
122834
123254
  console.log(`
122835
123255
  \u2705 Meta/Facebook \u5468\u671F\u5206\u6790 HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
122836
123256
  `);
123257
+ console.log(
123258
+ ` \u5FC5\u542B\u5B57\u6BB5\u5DF2\u5168\u90E8\u8986\u76D6\uFF5C\u5EFA\u8BAE ${stats.recommendationCount}\uFF5C\u8865\u5145 ${stats.supplementaryCount}\uFF5C\u56DB\u95EE ${stats.fourQuestionsCount}\uFF5C\u7EA2\u7EFF\u706F ${stats.scorecardRows}
123259
+ `
123260
+ );
122837
123261
  if (runtimeCopied) {
122838
123262
  console.log(` \u8FD0\u884C\u65F6\u811A\u672C\uFF1A${runtimeOut}
122839
123263
  `);
@@ -122995,7 +123419,7 @@ function registerFacebookAnalysisCommands(program2) {
122995
123419
  );
122996
123420
  root.addCommand(registerBatchCommand(sectionHelp), { isDefault: true });
122997
123421
  root.command("render").description(
122998
- "\u6839\u636E Agent \u64B0\u5199\u7684\u62A5\u544A JSON \u751F\u6210 Meta/Facebook \u5468\u671F\u5206\u6790 HTML \u7EC8\u7A3F\uFF08meta-period-report.html\uFF09"
123422
+ "\u6839\u636E Agent \u64B0\u5199\u7684\u62A5\u544A JSON \u751F\u6210 Meta/Facebook \u5468\u671F\u5206\u6790 HTML \u7EC8\u7A3F\uFF08\u6821\u9A8C\u5FC5\u542B\u5B57\u6BB5\uFF0C\u7F3A\u9879\u62D2\u7EDD\u6E32\u67D3\uFF09"
122999
123423
  ).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 meta-period-report.json").option(
123000
123424
  "--snapshot-dir <dir>",
123001
123425
  "\u53EF\u9009\uFF1Afacebook-analysis --json-out \u76EE\u5F55\uFF0C\u81EA\u52A8\u5408\u5E76 KPI / \u56FE\u8868 / \u8868\u683C"
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.28-beta.4",
4
- "publishedAt": 1780970734894
3
+ "version": "1.1.28-beta.6",
4
+ "publishedAt": 1780991035189
5
5
  }
@@ -142,7 +142,7 @@ siluzan-tso facebook-analysis render \
142
142
  ```
143
143
 
144
144
  - `--snapshot-dir`:与拉数 `--json-out` 同目录;CLI 自动合并 KPI、平台/国家/受众图表、广告组与受众表格。
145
- - 模板:`report-templates/meta-period-report.html` + `meta-period-report.runtime.js`(Chart.js CDN)。
145
+ - 模板:`report-templates/meta-period-report.html`
146
146
  - JSON 字段说明:见 `report-templates/meta-period-report.md` § Agent JSON 结构。
147
147
 
148
148
  ---
@@ -4,7 +4,8 @@
4
4
  <meta charset="UTF-8"/>
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
6
  <title>Meta / Facebook 周期分析报告</title>
7
- <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
7
+
8
+ <script src="https://staticpn.siluzan.com/assets/slz/homeCDN/chart.umd.min.js"></script>
8
9
  <style>
9
10
  :root{--bg:#0f1419;--card:#1a2332;--text:#e7ecf3;--muted:#8b9cb3;--green:#22c55e;--yellow:#eab308;--red:#ef4444;--accent:#3b82f6;--border:#2d3a4f}
10
11
  *{box-sizing:border-box}
@@ -617,13 +618,27 @@ ${renderChecklist(data.actionChecklist)}
617
618
  }
618
619
 
619
620
  function boot() {
620
- if (window.__META_PERIOD_REPORT__) {
621
+ var data = window.__META_PERIOD_REPORT__;
622
+ if (!data) return;
623
+ function run() {
621
624
  if (document.readyState === "loading") {
622
- document.addEventListener("DOMContentLoaded", () => renderReport(window.__META_PERIOD_REPORT__));
625
+ document.addEventListener("DOMContentLoaded", function () {
626
+ renderReport(data);
627
+ });
623
628
  } else {
624
- renderReport(window.__META_PERIOD_REPORT__);
629
+ renderReport(data);
625
630
  }
626
631
  }
632
+ if (typeof window.__loadChartJs === "function") {
633
+ window.__loadChartJs(function (err) {
634
+ if (err) {
635
+ console.warn("[meta-period-report]", err.message);
636
+ }
637
+ run();
638
+ });
639
+ } else {
640
+ run();
641
+ }
627
642
  }
628
643
 
629
644
  boot();
@@ -20,7 +20,7 @@ Agent JSON Schema:`assets/meta-period-report.schema.json`。
20
20
  | **1. 拉数** | Agent 调 CLI | `facebook-analysis -a <id> --start <s> --end <e> --json-out ./snap-fb` |
21
21
  | **2. 分析** | Agent | 用 **node/python 脚本**读落盘 JSON(勿用 Read 打开业务 `*.json`),完成筛选、聚合、排序与洞察 |
22
22
  | **3. 写 JSON** | Agent | 按本纲要撰写 `meta-period-report.json`(`meta` / `narrative` / 可选 HTML 扩展字段) |
23
- | **4. 渲染 HTML** | CLI | `facebook-analysis render` — **禁止** Agent 手写/拼接 HTML |
23
+ | **4. 渲染 HTML** | CLI | `facebook-analysis render` — **校验 JSON 必含字段**,缺项报错不生成 HTML;**禁止** Agent 手写/拼接 HTML |
24
24
 
25
25
  ```bash
26
26
  # 步骤 1
@@ -1,4 +1,4 @@
1
- <!doctype html>
1
+ <!doctype html>
2
2
  <!--
3
3
  风格:专业学术(论文式层级、衬线标题、低饱和配色、弱装饰、表格偏「期刊」)
4
4
  与 report-template.html 区块结构、data-section-id 一致,可互换填充数据。
@@ -46,7 +46,7 @@
46
46
  crossorigin="anonymous"
47
47
  referrerpolicy="no-referrer"
48
48
  />
49
- <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
49
+ <script src="https://staticpn.siluzan.com/assets/slz/homeCDN/chart.umd.min.js"></script>
50
50
  <style>
51
51
  @media print {
52
52
  body {
@@ -1,4 +1,4 @@
1
- <!doctype html>
1
+ <!doctype html>
2
2
  <!--
3
3
  风格:演示 / 深色屏显(会议室投屏、深色控制台并列)
4
4
  与 report-template.html 的 data-section-id 一致。另见 README 中其它模板。
@@ -30,7 +30,7 @@
30
30
  crossorigin="anonymous"
31
31
  referrerpolicy="no-referrer"
32
32
  />
33
- <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
33
+ <script src="https://staticpn.siluzan.com/assets/slz/homeCDN/chart.umd.min.js"></script>
34
34
  <style>
35
35
  @media print {
36
36
  body {
@@ -1,4 +1,4 @@
1
- <!doctype html>
1
+ <!doctype html>
2
2
  <!--
3
3
  风格:正式文件 / 公文式(红头线、居中标题、黑框表格、中文序号「一、二、」、少彩色装饰)
4
4
  与 report-template.html 区块结构、data-section-id 一致,可互换填充数据。
@@ -39,7 +39,7 @@
39
39
  crossorigin="anonymous"
40
40
  referrerpolicy="no-referrer"
41
41
  />
42
- <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
42
+ <script src="https://staticpn.siluzan.com/assets/slz/homeCDN/chart.umd.min.js"></script>
43
43
  <style>
44
44
  @media print {
45
45
  body {
@@ -1,4 +1,4 @@
1
- <!doctype html>
1
+ <!doctype html>
2
2
  <!--
3
3
  风格:移动端优先(窄屏单列、大触控留白、表格横向滑动提示)
4
4
  与 report-template.html 的 data-section-id 一致;桌面端随 sm: 断点渐进为多列。
@@ -30,7 +30,7 @@
30
30
  crossorigin="anonymous"
31
31
  referrerpolicy="no-referrer"
32
32
  />
33
- <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
33
+ <script src="https://staticpn.siluzan.com/assets/slz/homeCDN/chart.umd.min.js"></script>
34
34
  <style>
35
35
  @media print {
36
36
  body {
@@ -1,4 +1,4 @@
1
- <!doctype html>
1
+ <!doctype html>
2
2
  <!--
3
3
  风格:对客营销单页(Executive one-pager)
4
4
  首屏叙事 + 主 KPI + 主图;第 2–6 节默认折叠(<details>),展开后结构与 report-template.html 一致、data-section-id 不变。
@@ -29,7 +29,7 @@
29
29
  crossorigin="anonymous"
30
30
  referrerpolicy="no-referrer"
31
31
  />
32
- <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
32
+ <script src="https://staticpn.siluzan.com/assets/slz/homeCDN/chart.umd.min.js"></script>
33
33
  <style>
34
34
  details > summary {
35
35
  list-style: none;
@@ -1,4 +1,4 @@
1
- <!doctype html>
1
+ <!doctype html>
2
2
  <!--
3
3
  风格:极简黑白 / 打印优先(灰阶、无阴影、无圆角装饰、状态不完全依赖色相)
4
4
  与 report-template.html 的 data-section-id 一致。打印时图表仍为灰度;正式印刷可导出 PDF 后转灰。
@@ -18,7 +18,7 @@
18
18
  crossorigin="anonymous"
19
19
  referrerpolicy="no-referrer"
20
20
  />
21
- <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
21
+ <script src="https://staticpn.siluzan.com/assets/slz/homeCDN/chart.umd.min.js"></script>
22
22
  <style>
23
23
  /* 屏幕上也保持「纸面」灰阶;打印增强对比 */
24
24
  body {
@@ -1,4 +1,4 @@
1
- <!doctype html>
1
+ <!doctype html>
2
2
  <!--
3
3
  siluzan-ads 报告 HTML 样式参考(供 AI / 脚本生成成品 HTML 时对齐版式)
4
4
 
@@ -25,7 +25,7 @@
25
25
  技术栈(均支持 CDN,单文件可离线打开需网络加载资源):
26
26
  - Tailwind CSS (cdn.tailwindcss.com):布局、间距、响应式、打印友好浅色底
27
27
  - Font Awesome 6 (cdnjs.cloudflare.com):列表与状态图标(类名稳定,便于 AI 输出)
28
- - ECharts 5 (cdn.jsdelivr.net):时序、构成、对比(与广告报表 JSON 数组结构天然契合)
28
+ - ECharts 5 (staticpn.siluzan.com/homeCDN/echarts.js):时序、构成、对比(与广告报表 JSON 数组结构天然契合)
29
29
 
30
30
  ⚠ 外部网络声明:在浏览器中打开本 HTML 文件时,会向上述 CDN 域名发出网络请求加载样式/脚本。
31
31
  如需离线使用,请将对应资源下载到本地并替换下方 <script>/<link> 的 src/href。
@@ -76,7 +76,7 @@
76
76
  referrerpolicy="no-referrer"
77
77
  />
78
78
  <!-- ECharts 5:图表 -->
79
- <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
79
+ <script src="https://staticpn.siluzan.com/assets/slz/homeCDN/chart.umd.min.js"></script>
80
80
  <style>
81
81
  /* 打印时尽量白底;与 Tailwind Play CDN 并存 */
82
82
  @media print {
@@ -1,4 +1,4 @@
1
- <!doctype html>
1
+ <!doctype html>
2
2
  <!--
3
3
  网站诊断报告 — 对齐 MarkAI WebsiteAnalysisReport/v3(ChatContent → WebsiteGuide)
4
4
 
@@ -19,7 +19,7 @@
19
19
  if (dark) document.documentElement.setAttribute("data-theme", "dark");
20
20
  })();
21
21
  </script>
22
- <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
22
+ <script src="https://staticpn.siluzan.com/assets/slz/homeCDN/chart.umd.min.js"></script>
23
23
  <style>
24
24
  * {
25
25
  box-sizing: border-box;
@@ -49,7 +49,7 @@ siluzan-tso website-diagnosis render --data ./diagnosis.json [--collect ./collec
49
49
  ## 样式与文件约定
50
50
 
51
51
  - 单文件 HTML,`lang` 与用户语言一致(默认 `zh-CN`)。
52
- - 可引用 CDN:Tailwind / ECharts(与 `report-template.html` 相同,见该文件头部说明)。
52
+ - ECharts 统一引用:`https://staticpn.siluzan.com/assets/slz/homeCDN/echarts.js`(与 `report-template.html` 相同)。
53
53
  - 落盘建议与 `--json-out` 同目录:`website-diagnosis-report.html`。
54
54
  - **推荐生成方式**:`siluzan-tso website-diagnosis render --data <diagnosis.json> [--collect <collect.json>]`
55
55
  - **禁止**只交付 Markdown 或纯 JSON 当作最终报告(JSON 可作为中间产物)。
@@ -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.28-beta.4'
12
+ $PKG_VERSION = '1.1.28-beta.6'
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.28-beta.4"
12
+ readonly PKG_VERSION="1.1.28-beta.6"
13
13
  readonly CLI_BIN="siluzan-tso"
14
14
  readonly SKILL_LABEL="Siluzan TSO"
15
15
  readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.28-beta.4",
3
+ "version": "1.1.28-beta.6",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",