siluzan-tso-cli 1.1.33-beta.2 → 1.1.33

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
@@ -43,7 +43,7 @@ HTML 报告模板外部资源:**ECharts** 统一使用 `https://staticpn.siluz
43
43
  在**用户的目标项目根目录**执行(根据用户使用的助手选择 `--ai`):
44
44
 
45
45
  ```bash
46
- npm install -g siluzan-tso-cli@beta
46
+ npm install -g siluzan-tso-cli
47
47
  siluzan-tso init --ai cursor # 写入 Cursor(默认)
48
48
  siluzan-tso init --ai cursor,claude # 同时写入多个平台
49
49
  siluzan-tso init --ai all # 写入所有支持的平台
@@ -51,7 +51,6 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
51
51
  siluzan-tso init --force # 强制覆盖已存在文件
52
52
  ```
53
53
 
54
- > **注意**:当前为测试版(1.1.33-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
54
 
56
55
  | 助手 | 建议 `--ai` |
57
56
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -3829,7 +3829,7 @@ var DEFAULT_API_BASE;
3829
3829
  var init_defaults = __esm({
3830
3830
  "src/config/defaults.ts"() {
3831
3831
  "use strict";
3832
- DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
3832
+ DEFAULT_API_BASE = "https://tso-api.siluzan.com";
3833
3833
  }
3834
3834
  });
3835
3835
 
@@ -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/render-report.ts
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
- const data = asRecord6(dataRaw);
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("--lenient", "\u7ED3\u6784\u8B66\u544A\uFF08\u5982\u7F3A analysis\uFF09\u65F6\u4E0D\u963B\u65AD\u6E32\u67D3", false).action(async (opts) => {
126268
- await runGoogleAdsDiagnosisRender({
126269
- dataFile: opts.data,
126270
- out: opts.out,
126271
- lenient: opts.lenient
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 asRecord7(value) {
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 asRecord7(JSON.parse(raw));
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 = asRecord7(overview?.currentPeriod);
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 asRecord8(value) {
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 = asRecord8(row);
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 = asRecord8(chart);
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 = asRecord8(data.meta) ?? {};
126873
- const kpis = asRecord8(data.kpis) ?? {};
126874
- const narrative = asRecord8(data.narrative) ?? {};
126875
- const tables = asRecord8(data.tables);
126876
- const charts = asRecord8(data.charts);
126877
- const sections = asRecord8(data.sections);
126878
- const health = asRecord8(data.healthDiagnosis);
126879
- const priorityPlan = asRecord8(data.priorityPlan);
126880
- const actionChecklist = asRecord8(data.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 = asRecord8(charts?.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 = asRecord8(asArray2(narrative.regional)[i]);
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) => asRecord8(r)?.title);
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 = asRecord8(recommendations[i]);
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 = asRecord8(supplementary[i]);
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 = asRecord8(fourQuestions[i]);
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 = asRecord8(scorecard[i]);
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(asRecord8(sections?.[key])?.insight);
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 = asRecord8(sections?.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(asRecord8(sections?.landingPage)?.rows);
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 asRecord9(value) {
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 = asRecord9(await readJsonFile2(dataPath));
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
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.33-beta.2",
4
- "publishedAt": 1782876066592
3
+ "version": "1.1.33",
4
+ "publishedAt": 1782887221443
5
5
  }
@@ -280,7 +280,7 @@ siluzan-tso account-history --start 2026-03-01 --end 2026-03-31 --json-out ./sna
280
280
  | 状态 | 含义 | 下一步操作 |
281
281
  | ---------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
282
282
  | `Pending` | 审核中 | 等待,可反复运行此命令轮询;审核周期因媒体而异 |
283
- | `Approved` | 审核通过 | 运行 `list-accounts -m <媒体>` 确认账户已出现;引导用户充值激活(`config show` 取 `webUrl`,按 `finance.md` 打开对应媒体充值页;例如 Google 为 `https://www-ci.siluzan.com/v3/foreign_trade/tso/recharge/pay?mediaType=Google`;Kwai、Yandex 当前没有对应充值界面) |
283
+ | `Approved` | 审核通过 | 运行 `list-accounts -m <媒体>` 确认账户已出现;引导用户充值激活(`config show` 取 `webUrl`,按 `finance.md` 打开对应媒体充值页;例如 Google 为 `https://www.siluzan.com/v3/foreign_trade/tso/recharge/pay?mediaType=Google`;Kwai、Yandex 当前没有对应充值界面) |
284
284
  | `Rejected` | 被拒 | 查看 `--json-out` 落盘中的 `reason` 字段了解拒绝原因;修改资料后重新提交;若原因不明,引导用户联系丝路赞客服 |
285
285
 
286
286
  ---
@@ -888,7 +888,7 @@ siluzan-tso account email-deauth -c <mediaCustomerId> --invitation-id <id> --res
888
888
 
889
889
  | 功能 | 媒体 | 网页路径 |
890
890
  | --------------------------------------- | ------ | ------------------------------------------------- |
891
- | **账户激活**(邀请他人激活 / 充值激活) | Google | `https://www-ci.siluzan.com/v3/foreign_trade/tso/manageAccounts` |
891
+ | **账户激活**(邀请他人激活 / 充值激活) | Google | `https://www.siluzan.com/v3/foreign_trade/tso/manageAccounts` |
892
892
 
893
893
  **Agent 建议话术**:
894
894
 
@@ -897,5 +897,5 @@ siluzan-tso account email-deauth -c <mediaCustomerId> --invitation-id <id> --res
897
897
  siluzan-tso config show # 查看 webUrl 字段
898
898
 
899
899
  # 账户激活(Google)→ 引导至账户管理页
900
- # https://www-ci.siluzan.com/v3/foreign_trade/tso/manageAccounts
900
+ # https://www.siluzan.com/v3/foreign_trade/tso/manageAccounts
901
901
  ```
@@ -117,7 +117,7 @@ siluzan-tso config show
117
117
 
118
118
  ### 充值页链接(按媒体 × 类型)
119
119
 
120
- 链接模式:`https://www-ci.siluzan.com/v3/foreign_trade/tso/recharge/<page>?mediaType=<mediaType>`;丝路赞钱包:`https://www-ci.siluzan.com/v3/foreign_trade/tso/recharge/siluzanWallet`(无媒体参数)。
120
+ 链接模式:`https://www.siluzan.com/v3/foreign_trade/tso/recharge/<page>?mediaType=<mediaType>`;丝路赞钱包:`https://www.siluzan.com/v3/foreign_trade/tso/recharge/siluzanWallet`(无媒体参数)。
121
121
 
122
122
  | 充值类型 | `<page>` | 支持媒体(`mediaType` 参数) |
123
123
  | ------------------------- | --------------------- | ------------------------------------------- |
@@ -1,6 +1,6 @@
1
1
  # 各媒体开户
2
2
 
3
- > 网页链接:`https://www-ci.siluzan.com/v3/foreign_trade/tso/accountOpeningHistory?tso=%2Fv3umijs%2Ftso%2FaccountOpeningHistory`
3
+ > 网页链接:`https://www.siluzan.com/v3/foreign_trade/tso/accountOpeningHistory?tso=%2Fv3umijs%2Ftso%2FaccountOpeningHistory`
4
4
  > 多命令串联见 `references/core/workflows.md` § 流程一。
5
5
 
6
6
  ## 首次响应硬规范(必读)
@@ -60,7 +60,7 @@ siluzan-tso open-account google-wizard
60
60
 
61
61
  ```bash
62
62
  siluzan-tso account-history -m Google
63
- # 审核通过后:config show → https://www-ci.siluzan.com/v3/foreign_trade/tso/recharge/pay?mediaType=Google
63
+ # 审核通过后:config show → https://www.siluzan.com/v3/foreign_trade/tso/recharge/pay?mediaType=Google
64
64
  ```
65
65
 
66
66
  ---
@@ -1,6 +1,6 @@
1
1
  # rag:知识库检索(TSO 广告投放辅助)
2
2
 
3
- 知识库管理页面在 https://www-ci.siluzan.com/knowledge-base/
3
+ 知识库管理页面在 https://www.siluzan.com/knowledge-base/
4
4
 
5
5
  为 **广告投放、账户分析、拓词、诊断报告** 等 TSO 业务提供**企业已入库**的产品、行业、客户背景事实依据。
6
6
 
@@ -192,9 +192,9 @@ siluzan-tso report push receive-emails -m Google [--json-out ./snap]
192
192
 
193
193
  | 媒体 | 报告类型 | URL 模板 |
194
194
  | ------ | ---------------- | ----------------------------------------------------------- |
195
- | Google | 日报(Daily) | `https://www-ci.siluzan.com/media-report/publish/{entityId}?culture=zh-CN` |
196
- | Google | 小时报(Hourly) | `https://www-ci.siluzan.com/media-report/hour/{entityId}?culture=zh-CN` |
197
- | TikTok | 日报 | `https://www-ci.siluzan.com/media-report/publish/{entityId}?culture=zh-CN` |
195
+ | Google | 日报(Daily) | `https://www.siluzan.com/media-report/publish/{entityId}?culture=zh-CN` |
196
+ | Google | 小时报(Hourly) | `https://www.siluzan.com/media-report/hour/{entityId}?culture=zh-CN` |
197
+ | TikTok | 日报 | `https://www.siluzan.com/media-report/publish/{entityId}?culture=zh-CN` |
198
198
 
199
199
  `entityId` 来自 `siluzan-tso report list --json-out ./snap` 中每条记录的 `entityId` 字段。
200
200
 
@@ -207,8 +207,8 @@ siluzan-tso report list -m Google --json-out ./snap
207
207
 
208
208
  # 第二步:查看 webUrl
209
209
  siluzan-tso config show
210
- # webUrl: https://www-ci.siluzan.com
210
+ # webUrl: https://www.siluzan.com
211
211
 
212
212
  # 第三步:拼接链接(Google 日报)
213
- # https://www-ci.siluzan.com/media-report/publish/rpt_abc123?culture=zh-CN
213
+ # https://www.siluzan.com/media-report/publish/rpt_abc123?culture=zh-CN
214
214
  ```
@@ -10,7 +10,7 @@
10
10
  ## 安装 CLI
11
11
 
12
12
  ```bash
13
- npm install -g siluzan-tso-cli@beta
13
+ npm install -g siluzan-tso-cli
14
14
  ```
15
15
 
16
16
  ---
@@ -64,7 +64,7 @@ siluzan-tso config set --api-key <Key> # 或 config 直接写入
64
64
  siluzan-tso config set --token <Token> # 备用:设置 JWT Token
65
65
  ```
66
66
 
67
- API Key 获取入口:`https://www-ci.siluzan.com/v3/foreign_trade/settings/apiKeyManagement`
67
+ API Key 获取入口:`https://www.siluzan.com/v3/foreign_trade/settings/apiKeyManagement`
68
68
 
69
69
  ```bash
70
70
  # 第 1 步:让用户报出手机号后,立刻发码(命令立即返回,不会等待输入)
@@ -129,9 +129,9 @@ siluzan-tso config show
129
129
 
130
130
  ```
131
131
  构建环境 : production
132
- apiBaseUrl : https://tso-api-ci.siluzan.com
133
- googleApiUrl : https://googleapi-ci.mysiluzan.com
134
- webUrl : https://www-ci.siluzan.com
132
+ apiBaseUrl : https://tso-api.siluzan.com
133
+ googleApiUrl : https://googleapi.mysiluzan.com
134
+ webUrl : https://www.siluzan.com
135
135
  apiKey : abcd****1234
136
136
  ```
137
137
 
@@ -6,7 +6,7 @@
6
6
  siluzan-tso config show # 取 webUrl
7
7
  ```
8
8
 
9
- 首页地址:`https://www-ci.siluzan.com/v3/foreign_trade/tso/home`
9
+ 首页地址:`https://www.siluzan.com/v3/foreign_trade/tso/home`
10
10
 
11
11
  ---
12
12
 
@@ -25,7 +25,7 @@ siluzan-tso config show # 取 webUrl
25
25
 
26
26
  ## 推荐话术
27
27
 
28
- 1. **「和首页一样的总览」** → 打开 `https://www-ci.siluzan.com/v3/foreign_trade/tso/home`。
28
+ 1. **「和首页一样的总览」** → 打开 `https://www.siluzan.com/v3/foreign_trade/tso/home`。
29
29
  2. **「某个 Google 账户昨天花了多少」** → `list-accounts -m Google` + `stats -m Google -a <id>`。
30
30
  3. **「有待充值账户」** → 说明聚合数据在首页;CLI 可 `list-accounts` + `balance` 逐户排查,或引导充值页。
31
31
 
@@ -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,11 +9,11 @@ $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.2'
12
+ $PKG_VERSION = '1.1.33'
13
13
  $CLI_BIN = 'siluzan-tso'
14
14
  $SKILL_LABEL = 'Siluzan TSO'
15
- $INSTALL_CMD = 'npm install -g siluzan-tso-cli@beta'
16
- $WEB_BASE = 'https://www-ci.siluzan.com'
15
+ $INSTALL_CMD = 'npm install -g siluzan-tso-cli'
16
+ $WEB_BASE = 'https://www.siluzan.com'
17
17
 
18
18
  # -- Constants ----------------------------------------------------------------
19
19
  $NODE_MAJOR_MIN = 18
@@ -9,11 +9,11 @@ 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.2"
12
+ readonly PKG_VERSION="1.1.33"
13
13
  readonly CLI_BIN="siluzan-tso"
14
14
  readonly SKILL_LABEL="Siluzan TSO"
15
- readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
16
- readonly WEB_BASE="https://www-ci.siluzan.com"
15
+ readonly INSTALL_CMD="npm install -g siluzan-tso-cli"
16
+ readonly WEB_BASE="https://www.siluzan.com"
17
17
 
18
18
  # -- Constants ----------------------------------------------------------------
19
19
  readonly NODE_MAJOR_MIN=18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.33-beta.2",
3
+ "version": "1.1.33",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",