siluzan-tso-cli 1.1.57-beta.1 → 1.1.57-beta.2
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 +126 -20
- package/dist/skill/SKILL.md +2 -1
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/core/agent-conventions.md +3 -1
- package/dist/skill/references/core/playbooks.md +2 -1
- package/dist/skill/references/report-templates/bing-period-report.md +1 -1
- package/dist/skill/references/report-templates/google-period-report.md +1 -1
- package/dist/skill/references/report-templates/google-quarterly-plan.md +2 -0
- package/dist/skill/references/report-templates/google-quarterly-report.md +2 -0
- package/dist/skill/references/report-templates/meta-period-report.md +1 -1
- package/dist/skill/references/report-templates/tiktok-period-report.md +1 -1
- package/dist/skill/references/report-templates/yandex-period-report.md +1 -1
- package/dist/skill/report-templates/bing-period-report.html +14 -2
- package/dist/skill/report-templates/bing-period-report.md +1 -1
- package/dist/skill/report-templates/google-period-report.html +14 -2
- package/dist/skill/report-templates/google-period-report.md +1 -1
- package/dist/skill/report-templates/google-quarterly-plan.md +2 -0
- package/dist/skill/report-templates/google-quarterly-report.html +20 -10
- package/dist/skill/report-templates/google-quarterly-report.md +2 -0
- package/dist/skill/report-templates/meta-period-report.html +6 -2
- package/dist/skill/report-templates/meta-period-report.md +1 -1
- package/dist/skill/report-templates/tiktok-period-report.html +14 -2
- package/dist/skill/report-templates/tiktok-period-report.md +1 -1
- package/dist/skill/report-templates/yandex-period-report.html +15 -1
- package/dist/skill/report-templates/yandex-period-report.md +1 -1
- 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.57-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.57-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -7241,6 +7241,80 @@ var init_geo_en2zh = __esm({
|
|
|
7241
7241
|
}
|
|
7242
7242
|
});
|
|
7243
7243
|
|
|
7244
|
+
// src/commands/report-period.ts
|
|
7245
|
+
function classifyReportPeriod(current, previous) {
|
|
7246
|
+
const start = tryParse(current?.start);
|
|
7247
|
+
const end = tryParse(current?.end);
|
|
7248
|
+
if (!start || !end) return customPeriod();
|
|
7249
|
+
if (isFullCalendarQuarter(start, end)) {
|
|
7250
|
+
const tag = quarterTagFromMonth(start.m);
|
|
7251
|
+
const prev = tryParse(previous?.start);
|
|
7252
|
+
const prevEnd = tryParse(previous?.end);
|
|
7253
|
+
return {
|
|
7254
|
+
current: tag,
|
|
7255
|
+
previous: prev && prevEnd && isFullCalendarQuarter(prev, prevEnd) ? quarterTagFromMonth(prev.m) : "\u4E0A\u671F",
|
|
7256
|
+
next: tag === "Q4" ? "Q1" : `Q${Number(tag.slice(1)) + 1}`,
|
|
7257
|
+
unit: "\u5B63\u5EA6"
|
|
7258
|
+
};
|
|
7259
|
+
}
|
|
7260
|
+
if (start.d === 1 && start.y === end.y && start.m === end.m) {
|
|
7261
|
+
const prev = tryParse(previous?.start);
|
|
7262
|
+
const prevEnd = tryParse(previous?.end);
|
|
7263
|
+
const prevSameMonth = prev && prevEnd && prev.y === prevEnd.y && prev.m === prevEnd.m;
|
|
7264
|
+
const nextMonth = start.m === 12 ? 1 : start.m + 1;
|
|
7265
|
+
return {
|
|
7266
|
+
current: `${start.m}\u6708`,
|
|
7267
|
+
previous: prevSameMonth ? `${prev.m}\u6708` : "\u4E0A\u671F",
|
|
7268
|
+
next: `${nextMonth}\u6708`,
|
|
7269
|
+
unit: "\u6708\u5EA6"
|
|
7270
|
+
};
|
|
7271
|
+
}
|
|
7272
|
+
return customPeriod();
|
|
7273
|
+
}
|
|
7274
|
+
function rangeFromMeta(meta) {
|
|
7275
|
+
if (!meta || typeof meta !== "object") return null;
|
|
7276
|
+
const record2 = meta;
|
|
7277
|
+
if (typeof record2.startDate !== "string" || typeof record2.endDate !== "string") return null;
|
|
7278
|
+
return { start: record2.startDate, end: record2.endDate };
|
|
7279
|
+
}
|
|
7280
|
+
function attachPeriodChrome(payload) {
|
|
7281
|
+
return { ...payload, periodChrome: classifyReportPeriod(rangeFromMeta(payload.meta)) };
|
|
7282
|
+
}
|
|
7283
|
+
function periodWord(unit, neutral) {
|
|
7284
|
+
return unit || neutral;
|
|
7285
|
+
}
|
|
7286
|
+
function customPeriod() {
|
|
7287
|
+
return { current: "\u672C\u671F", previous: "\u4E0A\u671F", next: "\u4E0B\u671F", unit: "" };
|
|
7288
|
+
}
|
|
7289
|
+
function tryParse(iso) {
|
|
7290
|
+
if (!iso) return null;
|
|
7291
|
+
const match = DATE_RE2.exec(iso.trim());
|
|
7292
|
+
if (!match) return null;
|
|
7293
|
+
const y = Number(match[1]);
|
|
7294
|
+
const m = Number(match[2]);
|
|
7295
|
+
const d = Number(match[3]);
|
|
7296
|
+
if (m < 1 || m > 12 || d < 1 || d > 31) return null;
|
|
7297
|
+
return { y, m, d };
|
|
7298
|
+
}
|
|
7299
|
+
function lastDayOfMonth(y, m) {
|
|
7300
|
+
return new Date(Date.UTC(y, m, 0)).getUTCDate();
|
|
7301
|
+
}
|
|
7302
|
+
function isFullCalendarQuarter(start, end) {
|
|
7303
|
+
if (start.y !== end.y || start.d !== 1) return false;
|
|
7304
|
+
if (![1, 4, 7, 10].includes(start.m)) return false;
|
|
7305
|
+
return end.m === start.m + 2 && end.d === lastDayOfMonth(end.y, end.m);
|
|
7306
|
+
}
|
|
7307
|
+
function quarterTagFromMonth(month) {
|
|
7308
|
+
return `Q${Math.ceil(month / 3)}`;
|
|
7309
|
+
}
|
|
7310
|
+
var DATE_RE2;
|
|
7311
|
+
var init_report_period = __esm({
|
|
7312
|
+
"src/commands/report-period.ts"() {
|
|
7313
|
+
"use strict";
|
|
7314
|
+
DATE_RE2 = /^(\d{4})-(\d{2})-(\d{2})/;
|
|
7315
|
+
}
|
|
7316
|
+
});
|
|
7317
|
+
|
|
7244
7318
|
// src/commands/google-analysis/sections.ts
|
|
7245
7319
|
var SECTIONS;
|
|
7246
7320
|
var init_sections = __esm({
|
|
@@ -104656,7 +104730,7 @@ function asRecord23(value) {
|
|
|
104656
104730
|
throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
|
|
104657
104731
|
}
|
|
104658
104732
|
function injectReportData2(html, payload) {
|
|
104659
|
-
const json = JSON.stringify(payload).replace(/</g, "\\u003c");
|
|
104733
|
+
const json = JSON.stringify(attachPeriodChrome(payload)).replace(/</g, "\\u003c");
|
|
104660
104734
|
if (!html.includes(INJECT_MARKER)) {
|
|
104661
104735
|
throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 google-period-report.html");
|
|
104662
104736
|
}
|
|
@@ -104699,8 +104773,9 @@ async function runGoogleAnalysisRender(opts) {
|
|
|
104699
104773
|
await fsPromises2.mkdir(outDir, { recursive: true });
|
|
104700
104774
|
await fsPromises2.writeFile(outPath, injectReportData2(html, data), "utf8");
|
|
104701
104775
|
const { stats } = contentCheck;
|
|
104776
|
+
const cover = periodWord(attachPeriodChrome(data).periodChrome.unit, "\u5468\u671F");
|
|
104702
104777
|
console.log(`
|
|
104703
|
-
\u2705 Google Ads \
|
|
104778
|
+
\u2705 Google Ads ${cover}\u5206\u6790 HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
|
|
104704
104779
|
`);
|
|
104705
104780
|
console.log(
|
|
104706
104781
|
` \u5FC5\u542B\u5B57\u6BB5\u5DF2\u5168\u90E8\u8986\u76D6\uFF5C\u6267\u884C\u6458\u8981 ${stats.executiveSummaryCount} \u6BB5\uFF5C\u4F18\u5316\u5EFA\u8BAE ${stats.recommendationCount} \u6761\uFF5C8 \u4E2A\u6570\u636E\u7EF4\u5206\u6790+\u5EFA\u8BAE\u5747\u5DF2\u9F50\u5168
|
|
@@ -104716,6 +104791,7 @@ var init_render_report = __esm({
|
|
|
104716
104791
|
"use strict";
|
|
104717
104792
|
init_merge_snapshot();
|
|
104718
104793
|
init_report_content();
|
|
104794
|
+
init_report_period();
|
|
104719
104795
|
TEMPLATE_BASENAME = "google-period-report.html";
|
|
104720
104796
|
INJECT_MARKER = "window.__GOOGLE_PERIOD_REPORT__ = window.__GOOGLE_PERIOD_REPORT__ || null;";
|
|
104721
104797
|
}
|
|
@@ -137077,9 +137153,10 @@ async function fetchYoutubeChannel(opts) {
|
|
|
137077
137153
|
}
|
|
137078
137154
|
|
|
137079
137155
|
// src/commands/google-quarterly/windows.ts
|
|
137080
|
-
|
|
137156
|
+
init_report_period();
|
|
137157
|
+
var DATE_RE3 = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
137081
137158
|
function parseIsoDate(iso) {
|
|
137082
|
-
const match =
|
|
137159
|
+
const match = DATE_RE3.exec(iso.trim());
|
|
137083
137160
|
if (!match) throw new Error(`\u65E5\u671F\u987B\u4E3A YYYY-MM-DD\uFF1A${iso}`);
|
|
137084
137161
|
const y = Number(match[1]);
|
|
137085
137162
|
const m = Number(match[2]);
|
|
@@ -137093,7 +137170,7 @@ function pad(n) {
|
|
|
137093
137170
|
function isoDate(y, m, d) {
|
|
137094
137171
|
return `${y}-${pad(m)}-${pad(d)}`;
|
|
137095
137172
|
}
|
|
137096
|
-
function
|
|
137173
|
+
function lastDayOfMonth2(y, m) {
|
|
137097
137174
|
return new Date(Date.UTC(y, m, 0)).getUTCDate();
|
|
137098
137175
|
}
|
|
137099
137176
|
function listMonths(start, end) {
|
|
@@ -137104,7 +137181,7 @@ function listMonths(start, end) {
|
|
|
137104
137181
|
let m = s.m;
|
|
137105
137182
|
while (y < e.y || y === e.y && m <= e.m) {
|
|
137106
137183
|
const monthStart = y === s.y && m === s.m ? start : isoDate(y, m, 1);
|
|
137107
|
-
const monthEnd = y === e.y && m === e.m ? end : isoDate(y, m,
|
|
137184
|
+
const monthEnd = y === e.y && m === e.m ? end : isoDate(y, m, lastDayOfMonth2(y, m));
|
|
137108
137185
|
months.push({
|
|
137109
137186
|
start: monthStart,
|
|
137110
137187
|
end: monthEnd,
|
|
@@ -137139,14 +137216,14 @@ function previousCalendarQuarter(start, end) {
|
|
|
137139
137216
|
const e = parseIsoDate(end);
|
|
137140
137217
|
const quarterStartMonths = [1, 4, 7, 10];
|
|
137141
137218
|
if (s.d !== 1 || !quarterStartMonths.includes(s.m)) return null;
|
|
137142
|
-
if (e.m !== s.m + 2 || e.d !==
|
|
137219
|
+
if (e.m !== s.m + 2 || e.d !== lastDayOfMonth2(e.y, e.m)) return null;
|
|
137143
137220
|
const prevEndMonth = s.m - 1;
|
|
137144
137221
|
const year = prevEndMonth < 1 ? s.y - 1 : s.y;
|
|
137145
137222
|
const endM = prevEndMonth < 1 ? 12 : prevEndMonth;
|
|
137146
137223
|
const startM = endM - 2;
|
|
137147
137224
|
return {
|
|
137148
137225
|
start: isoDate(year, startM, 1),
|
|
137149
|
-
end: isoDate(year, endM,
|
|
137226
|
+
end: isoDate(year, endM, lastDayOfMonth2(year, endM))
|
|
137150
137227
|
};
|
|
137151
137228
|
}
|
|
137152
137229
|
function yearOverYearRange(start, end) {
|
|
@@ -137159,13 +137236,23 @@ function yearOverYearRange(start, end) {
|
|
|
137159
137236
|
}
|
|
137160
137237
|
function shiftYear(p, delta) {
|
|
137161
137238
|
const y = p.y + delta;
|
|
137162
|
-
const last =
|
|
137239
|
+
const last = lastDayOfMonth2(y, p.m);
|
|
137163
137240
|
return isoDate(y, p.m, Math.min(p.d, last));
|
|
137164
137241
|
}
|
|
137165
137242
|
function utcToIso(ms) {
|
|
137166
137243
|
const d = new Date(ms);
|
|
137167
137244
|
return isoDate(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
|
|
137168
137245
|
}
|
|
137246
|
+
function reportPeriodChrome(current, previous) {
|
|
137247
|
+
const kind = classifyReportPeriod(current, previous);
|
|
137248
|
+
if (kind.unit === "\u5B63\u5EA6") {
|
|
137249
|
+
return { ...kind, reportTitle: "Google \u5B63\u5EA6\u7ECF\u8425\u62A5\u544A", coverEn: "QUARTERLY REPORT" };
|
|
137250
|
+
}
|
|
137251
|
+
if (kind.unit === "\u6708\u5EA6") {
|
|
137252
|
+
return { ...kind, reportTitle: "Google \u6708\u5EA6\u7ECF\u8425\u62A5\u544A", coverEn: "MONTHLY REPORT" };
|
|
137253
|
+
}
|
|
137254
|
+
return { ...kind, reportTitle: "Google \u7ECF\u8425\u62A5\u544A", coverEn: "PERFORMANCE REPORT" };
|
|
137255
|
+
}
|
|
137169
137256
|
function lastCompleteQuarter(now = /* @__PURE__ */ new Date()) {
|
|
137170
137257
|
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
137171
137258
|
timeZone: "Asia/Shanghai",
|
|
@@ -137180,7 +137267,7 @@ function lastCompleteQuarter(now = /* @__PURE__ */ new Date()) {
|
|
|
137180
137267
|
const year = m <= 3 ? y - 1 : y;
|
|
137181
137268
|
return {
|
|
137182
137269
|
start: isoDate(year, qStartMonth, 1),
|
|
137183
|
-
end: isoDate(year, qEndMonth,
|
|
137270
|
+
end: isoDate(year, qEndMonth, lastDayOfMonth2(year, qEndMonth))
|
|
137184
137271
|
};
|
|
137185
137272
|
}
|
|
137186
137273
|
|
|
@@ -138053,17 +138140,28 @@ async function runGoogleQuarterlyRender(opts) {
|
|
|
138053
138140
|
}
|
|
138054
138141
|
const outPath = path31.resolve(opts.out ?? path31.join(path31.dirname(dataPath), GOOGLE_QUARTERLY_HTML_BASENAME));
|
|
138055
138142
|
await fs26.mkdir(path31.dirname(outPath), { recursive: true });
|
|
138056
|
-
const
|
|
138143
|
+
const periodChrome = reportPeriodChrome(readMetaRange(merged, "current"), readMetaRange(merged, "previous"));
|
|
138144
|
+
const outHtml = injectQuarterlyReport(html, { ...merged, periodChrome });
|
|
138057
138145
|
if (!outHtml.includes("homeCDN/echarts.min.js") || /new Chart\s*\(/.test(outHtml)) {
|
|
138058
138146
|
console.error("\n\u274C \u6A21\u677F\u5FC5\u987B\u4F7F\u7528 ECharts\uFF0C\u7981\u6B62 Chart.js\u3002\n");
|
|
138059
138147
|
process.exit(1);
|
|
138060
138148
|
}
|
|
138061
138149
|
await fs26.writeFile(outPath, outHtml, "utf8");
|
|
138062
138150
|
console.log(`
|
|
138063
|
-
\u2705
|
|
138151
|
+
\u2705 ${periodChrome.reportTitle}\u5DF2\u751F\u6210\uFF1A${outPath}
|
|
138064
138152
|
`);
|
|
138065
138153
|
console.log("\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u5373\u53EF\u67E5\u770B\uFF08ECharts\uFF1Astaticpn.siluzan.com/homeCDN/echarts.min.js\uFF09\u3002\n");
|
|
138066
138154
|
}
|
|
138155
|
+
function readMetaRange(payload, key) {
|
|
138156
|
+
const meta = payload.meta;
|
|
138157
|
+
if (!meta || typeof meta !== "object") return null;
|
|
138158
|
+
const block = meta[key];
|
|
138159
|
+
if (!block || typeof block !== "object") return null;
|
|
138160
|
+
const start = block.start;
|
|
138161
|
+
const end = block.end;
|
|
138162
|
+
if (typeof start !== "string" || typeof end !== "string") return null;
|
|
138163
|
+
return { start, end };
|
|
138164
|
+
}
|
|
138067
138165
|
|
|
138068
138166
|
// src/commands/google-quarterly/register.ts
|
|
138069
138167
|
function registerGoogleQuarterlyCommands(program2) {
|
|
@@ -139011,6 +139109,7 @@ function formatMetaPeriodReportErrors(result) {
|
|
|
139011
139109
|
}
|
|
139012
139110
|
|
|
139013
139111
|
// src/commands/facebook-analysis/render-report.ts
|
|
139112
|
+
init_report_period();
|
|
139014
139113
|
var TEMPLATE_BASENAMES2 = {
|
|
139015
139114
|
html: "meta-period-report.html"
|
|
139016
139115
|
};
|
|
@@ -139060,7 +139159,7 @@ function asRecord27(value) {
|
|
|
139060
139159
|
throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
|
|
139061
139160
|
}
|
|
139062
139161
|
function injectReportData3(html, payload) {
|
|
139063
|
-
const json = JSON.stringify(payload).replace(/</g, "\\u003c");
|
|
139162
|
+
const json = JSON.stringify(attachPeriodChrome(payload)).replace(/</g, "\\u003c");
|
|
139064
139163
|
if (!html.includes(INJECT_MARKER2)) {
|
|
139065
139164
|
throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 meta-period-report.html");
|
|
139066
139165
|
}
|
|
@@ -139123,8 +139222,9 @@ async function runFacebookAnalysisRender(opts) {
|
|
|
139123
139222
|
written.push(outPath);
|
|
139124
139223
|
}
|
|
139125
139224
|
const { stats } = contentCheck;
|
|
139225
|
+
const cover = periodWord(attachPeriodChrome(data).periodChrome.unit, "\u5468\u671F");
|
|
139126
139226
|
console.log(`
|
|
139127
|
-
\u2705 Meta/Facebook \
|
|
139227
|
+
\u2705 Meta/Facebook ${cover}\u5206\u6790\u62A5\u544A\u5DF2\u751F\u6210\uFF1A${written.join(" \u3001 ")}
|
|
139128
139228
|
`);
|
|
139129
139229
|
console.log(
|
|
139130
139230
|
` \u5FC5\u542B\u5B57\u6BB5\u5DF2\u5168\u90E8\u8986\u76D6\uFF5C\u5EFA\u8BAE ${stats.recommendationCount}\uFF5C\u56DB\u95EE ${stats.fourQuestionsCount}\uFF5C\u65E5 ${stats.dailyRows}\uFF5C\u56FD\u5BB6 ${stats.countryRows}\uFF5C\u7CFB\u5217 ${stats.campaignRows}\uFF5C\u53D7\u4F17 ${stats.audienceRows}
|
|
@@ -141619,6 +141719,7 @@ function formatBingPeriodReportErrors(result) {
|
|
|
141619
141719
|
}
|
|
141620
141720
|
|
|
141621
141721
|
// src/commands/bing-analysis/render-report.ts
|
|
141722
|
+
init_report_period();
|
|
141622
141723
|
var TEMPLATE_BASENAME3 = "bing-period-report.html";
|
|
141623
141724
|
var INJECT_MARKER4 = "window.__BING_PERIOD_REPORT__ = window.__BING_PERIOD_REPORT__ || null;";
|
|
141624
141725
|
function resolveSkillTemplatePath6(basename13) {
|
|
@@ -141651,7 +141752,7 @@ function asRecord34(value) {
|
|
|
141651
141752
|
throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
|
|
141652
141753
|
}
|
|
141653
141754
|
function injectReportData5(html, payload) {
|
|
141654
|
-
const json = JSON.stringify(payload).replace(/</g, "\\u003c");
|
|
141755
|
+
const json = JSON.stringify(attachPeriodChrome(payload)).replace(/</g, "\\u003c");
|
|
141655
141756
|
if (!html.includes(INJECT_MARKER4)) {
|
|
141656
141757
|
throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 bing-period-report.html");
|
|
141657
141758
|
}
|
|
@@ -141694,8 +141795,9 @@ async function runBingAnalysisRender(opts) {
|
|
|
141694
141795
|
await fsPromises5.mkdir(outDir, { recursive: true });
|
|
141695
141796
|
await fsPromises5.writeFile(outPath, injectReportData5(html, data), "utf8");
|
|
141696
141797
|
const { stats } = contentCheck;
|
|
141798
|
+
const cover = periodWord(attachPeriodChrome(data).periodChrome.unit, "\u8D26\u6237");
|
|
141697
141799
|
console.log(`
|
|
141698
|
-
\u2705 Bing \u5E7F\u544A\
|
|
141800
|
+
\u2705 Bing \u5E7F\u544A${cover}\u5206\u6790 HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
|
|
141699
141801
|
`);
|
|
141700
141802
|
console.log(
|
|
141701
141803
|
` \u5FC5\u542B\u5B57\u6BB5\u5DF2\u5168\u90E8\u8986\u76D6\uFF5C\u6267\u884C\u6458\u8981 ${stats.executiveSummaryCount} \u6BB5\uFF5C\u4F18\u5316\u5EFA\u8BAE ${stats.recommendationCount} \u6761\uFF5C9 \u4E2A\u5206\u6790\u5C0F\u8282\u5747\u5DF2\u9F50\u5168
|
|
@@ -142516,6 +142618,7 @@ function formatTikTokPeriodReportErrors(result) {
|
|
|
142516
142618
|
}
|
|
142517
142619
|
|
|
142518
142620
|
// src/commands/tiktok-analysis/render-report.ts
|
|
142621
|
+
init_report_period();
|
|
142519
142622
|
var TEMPLATE_BASENAME4 = "tiktok-period-report.html";
|
|
142520
142623
|
var INJECT_MARKER5 = "window.__TIKTOK_PERIOD_REPORT__ = window.__TIKTOK_PERIOD_REPORT__ || null;";
|
|
142521
142624
|
function resolveSkillTemplatePath7(basename13) {
|
|
@@ -142548,7 +142651,7 @@ function asRecord37(value) {
|
|
|
142548
142651
|
throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
|
|
142549
142652
|
}
|
|
142550
142653
|
function injectReportData6(html, payload) {
|
|
142551
|
-
const json = JSON.stringify(payload).replace(/</g, "\\u003c");
|
|
142654
|
+
const json = JSON.stringify(attachPeriodChrome(payload)).replace(/</g, "\\u003c");
|
|
142552
142655
|
if (!html.includes(INJECT_MARKER5)) {
|
|
142553
142656
|
throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 tiktok-period-report.html");
|
|
142554
142657
|
}
|
|
@@ -142591,8 +142694,9 @@ async function runTikTokAnalysisRender(opts) {
|
|
|
142591
142694
|
await fsPromises6.mkdir(outDir, { recursive: true });
|
|
142592
142695
|
await fsPromises6.writeFile(outPath, injectReportData6(html, data), "utf8");
|
|
142593
142696
|
const { stats } = contentCheck;
|
|
142697
|
+
const cover = periodWord(attachPeriodChrome(data).periodChrome.unit, "\u8D26\u6237");
|
|
142594
142698
|
console.log(`
|
|
142595
|
-
\u2705 TikTok \u5E7F\u544A\u4E3B\
|
|
142699
|
+
\u2705 TikTok \u5E7F\u544A\u4E3B${cover}\u5206\u6790 HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
|
|
142596
142700
|
`);
|
|
142597
142701
|
console.log(
|
|
142598
142702
|
` \u5FC5\u542B\u5B57\u6BB5\u5DF2\u5168\u90E8\u8986\u76D6\uFF5C\u6267\u884C\u6458\u8981 ${stats.executiveSummaryCount} \u6BB5\uFF5C\u4F18\u5316\u5EFA\u8BAE ${stats.recommendationCount} \u6761\uFF5C\u5206\u6790\u5C0F\u8282\u5DF2\u6309\u6709\u6570\u636E\u7684\u7EF4\u5EA6\u6821\u9A8C
|
|
@@ -144290,6 +144394,7 @@ function formatYandexPeriodReportErrors(result) {
|
|
|
144290
144394
|
}
|
|
144291
144395
|
|
|
144292
144396
|
// src/commands/yandex-analysis/render-report.ts
|
|
144397
|
+
init_report_period();
|
|
144293
144398
|
var TEMPLATE_BASENAME5 = "yandex-period-report.html";
|
|
144294
144399
|
var INJECT_MARKER6 = "window.__YANDEX_PERIOD_REPORT__ = window.__YANDEX_PERIOD_REPORT__ || null;";
|
|
144295
144400
|
function resolveSkillTemplatePath8(basename13) {
|
|
@@ -144335,7 +144440,7 @@ function refreshWeeklyFromDaily(data) {
|
|
|
144335
144440
|
};
|
|
144336
144441
|
}
|
|
144337
144442
|
function injectReportData7(html, payload) {
|
|
144338
|
-
const json = JSON.stringify(payload).replace(/</g, "\\u003c");
|
|
144443
|
+
const json = JSON.stringify(attachPeriodChrome(payload)).replace(/</g, "\\u003c");
|
|
144339
144444
|
if (!html.includes(INJECT_MARKER6)) {
|
|
144340
144445
|
throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 yandex-period-report.html");
|
|
144341
144446
|
}
|
|
@@ -144377,8 +144482,9 @@ async function runYandexAnalysisRender(opts) {
|
|
|
144377
144482
|
await fsPromises7.mkdir(outDir, { recursive: true });
|
|
144378
144483
|
await fsPromises7.writeFile(outPath, injectReportData7(html, data), "utf8");
|
|
144379
144484
|
const { stats } = contentCheck;
|
|
144485
|
+
const cover = periodWord(attachPeriodChrome(data).periodChrome.unit, "\u8D26\u6237");
|
|
144380
144486
|
console.log(`
|
|
144381
|
-
\u2705 Yandex \u5E7F\u544A\
|
|
144487
|
+
\u2705 Yandex \u5E7F\u544A${cover}\u5206\u6790 HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
|
|
144382
144488
|
`);
|
|
144383
144489
|
console.log(
|
|
144384
144490
|
` \u5FC5\u542B\u5B57\u6BB5\u5DF2\u5168\u90E8\u8986\u76D6\uFF5C\u4F18\u5316\u5EFA\u8BAE ${stats.recommendationCount} \u6761\uFF5C8 \u4E2A\u5206\u6790\u7AE0\u8282\u5747\u5DF2\u9F50\u5168
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -46,6 +46,7 @@ siluzan-tso -h
|
|
|
46
46
|
- **Google 广告类型边界**:CLI/Skill **仅支持** Google **搜索广告(Search)** 与 **Performance Max(PMax)** 的方案、创建与精细管理;**不支持展示广告(Display)**(含自适应展示广告 RDA、独立 Display 系列)。用户要做展示广告时须明确告知不支持,可改推 Search / PMax;**禁止**用 `campaign-create` / `ad-create` 伪装创建 Display。
|
|
47
47
|
- **删系列 / 测试系列须先问(硬性)**:「尽快创建 / 按方案搭建」不含删除授权。创建失败(政策违规、名称占用、半成品)时**禁止**自行建测试系列逐地域排查、**禁止**自行 `ad campaign-delete`(含 Agent 自建系列);先报告原因与选项,用户同意后才带 `--i-confirm --commit`。也**禁止**为让创建通过而整类删减方案内容(否词/排除地域/投放国家):按 `batch get` 的 `failedOperations[]` 只改报错项。见 `google-ads-write.md`「创建失败时禁止的做法」。
|
|
48
48
|
- **创建完成后交付(硬性)**:`ad batch diff` 的 stdout 含 `BEGIN_USER_DELIVERY_MARKDOWN`…`END_USER_DELIVERY_MARKDOWN`——**立刻**把中间全文原样发给用户(可先于补建);禁止只摘要、禁止只说「创建成功/详情已交付/未发现缺失」、禁止等全部补建结束再交付。
|
|
49
|
+
- **报告 render 成功即交付(硬性)**:`google-quarterly render`、`google-analysis render`、`google-ads-diagnosis render`、`website-diagnosis render`、`market-analysis render`,以及 Bing / TikTok / Yandex / Meta 的 `render`,stdout 给出 HTML 路径后**立刻**把该文件交给用户。P10 与各媒体周期报告封面的「季度 / 月度」已按日期写好(其余仍是「周期」或「账户」)。**禁止**再打开、grep 或修改这份 HTML,**禁止**为此再 render。用户没要 PDF 就不要 `html_to_pdf`;要了也只转一次,**禁止**再用 pdftotext、PyMuPDF、截图或视觉模型读 PDF/页面。细则见 `agent-conventions.md` §七。
|
|
49
50
|
- `list-accounts` 无余额/消耗;列全部用 `--page-size 999`。查单户必须 `-k`;`itemCount < total` 时**禁止**判定账户未绑定。
|
|
50
51
|
- 多户余额用 `balance-scan`(P2),多户消耗 / **转化成本·CPA 巡检**用 `accounts-digest`(P3,`--max-cpa`;**没有** `cpa-risk-scan`)。禁止外层 for-loop 逐户拉数。
|
|
51
52
|
- `stats` 的 `spend` = **区间合计**,不是日消耗。
|
|
@@ -122,7 +123,7 @@ siluzan-tso -h
|
|
|
122
123
|
| ------------------ | ------ | -------- |
|
|
123
124
|
| Google 广告账户诊断 / 健康检查(**含账户 ID**) | **P1** | `references/analytics/account-analytics.md` + `report-templates/google-ads-diagnosis.md`(可选对照 `report-templates/google-account-diagnosis-report.md`) |
|
|
124
125
|
| Google 账户周期 **数据汇总**(只要花费/转化/9 章/Excel;**无**规划/客户版/大族) | **P4** | `report-templates/google-period-report.md` + `references/analytics/account-analytics.md`;Excel 加 `google-period-report-excel.md` |
|
|
125
|
-
| Google/谷歌 **月度报告** / **月总结与规划** / **季度报告** / **Q3 汇总** / **Q4 规划** / **季度汇报** / **投放季报** / 客户版 / 大族模板 / 下月规划(效率+区域询盘+国家明细) | **P10** | `report-templates/google-quarterly-report.md` + **`report-templates/google-quarterly-plan.md`**(**未带询盘表须先问并等待**,禁止不问就 collect;有表须先转 JSON 再 `--inquiries`,禁止把 xlsx 丢给 collect;`collect`+`render
|
|
126
|
+
| Google/谷歌 **月度报告** / **月总结与规划** / **季度报告** / **Q3 汇总** / **Q4 规划** / **季度汇报** / **投放季报** / 客户版 / 大族模板 / 下月规划(效率+区域询盘+国家明细) | **P10** | `report-templates/google-quarterly-report.md` + **`report-templates/google-quarterly-plan.md`**(**未带询盘表须先问并等待**,禁止不问就 collect;有表须先转 JSON 再 `--inquiries`,禁止把 xlsx 丢给 collect;`collect`+`render`;规划九章;花费三档基于本期、不是已批预算;**不做**行业数据;**render 成功立刻交付 HTML**,禁止改 HTML / 转 PDF / 截图验收) |
|
|
126
127
|
| Meta/Facebook 周期或诊断 | **P4-FB** | `report-templates/meta-period-report.md` + `assets/meta-period-report-rules.md` + `references/analytics/facebook-analysis-guide.md` |
|
|
127
128
|
| TikTok / Bing 周期报告 | **P4** | `references/analytics/account-analytics.md` + 对应 `report-templates/*-period-report.md` |
|
|
128
129
|
| Yandex 账户分析 / 周期 / 月报 | **P4** | `report-templates/yandex-period-report.md` + `references/analytics/account-analytics.md`;Excel 加 `yandex-period-report-excel.md` |
|
package/dist/skill/_meta.json
CHANGED
|
@@ -221,7 +221,9 @@
|
|
|
221
221
|
|
|
222
222
|
## 七、交付前自检(报告 / Excel / 含金额话术)
|
|
223
223
|
|
|
224
|
-
>
|
|
224
|
+
> CLI `render` 写出的 HTML(P1、P4、P4-FB、P8、P9、P10,以及 Bing / TikTok / Yandex / Meta 周期或诊断):命令成功并打印路径就是自检通过。**立刻交付该 HTML**。P10 与各媒体周期报告的「季度 / 月度」已按日期写进封面(对不上的周期报告仍写「周期」或「账户」),不要 Read、grep 或修改成品,不要为此再 render。用户没要 PDF 就不要转;要了也只转一次,不要用 pdftotext、PyMuPDF、截图或视觉模型读页面。下面 A/B/C 只用于 Agent 自己写的 Excel / Markdown。
|
|
225
|
+
|
|
226
|
+
> 在 Agent 自写产物已写入磁盘**之后、发给用户之前**执行;不靠外部校验脚本,由 Agent **亲自 Read 最终产物文件**(Markdown 等;二进制 xlsx 无法 Read 时在对话贴自检表逐条勾选,依据为生成脚本的 stdout 摘要)。审阅阶段只看最终产物 + 已掌握的账户元数据,**不**回头 Read 落盘业务 JSON。
|
|
225
227
|
|
|
226
228
|
**A · 币种**:首行含 `统计区间:…(货币:CNY|USD)`;全文符号与 `currencyCode` 一致(CNY=¥、USD=$,未混用);与当次 `list-accounts -k` 结果相同;多账户分币种分表、无跨币种「总计」行。
|
|
227
229
|
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
4. **门禁**:对每个 section Read `<section>-<accountId>_*.outline.txt` 后再写脚本。
|
|
83
83
|
5. 脚本读盘写产物:默认 **HTML**(`report-template*.html` 版式参考);用户要 **Excel** → Agent 脚本写 xlsx(**禁止**宿主第三方 xlsx Skill;**无** CLI excel 子命令)。
|
|
84
84
|
6. 默认 HTML 报告须含:账户概览、投放趋势、Top 关键词/系列/地区/受众分布、优化建议;用户定制 Excel 以用户 Sheet 清单为准。
|
|
85
|
-
- **产物**:按 conventions §七 自检;`accountId` 须与用户给的 `mediaCustomerId` 一致。
|
|
85
|
+
- **产物**:按 conventions §七 自检;`accountId` 须与用户给的 `mediaCustomerId` 一致。HTML 封面按区间写成「季度」或「月度」,对不上则仍是「周期」。不要打开 HTML 改称呼。
|
|
86
86
|
|
|
87
87
|
> 用户用「OKKI 周报」固定话术 → 改走 **P6**,不按默认 9 章追问。
|
|
88
88
|
> 只要账户级按日 Excel(无多维章节)→ **P4-DAILY**。
|
|
@@ -207,4 +207,5 @@
|
|
|
207
207
|
5. 读 `google-quarterly-collect.json`,只写 `narrative` 到 `google-quarterly.json`(禁止改 kpis / monthly / planDefaults 数字)。
|
|
208
208
|
6. 下期规划按 `google-quarterly-plan.md` 写九章。预算默认三档(维持本期 / +30% / +50%)+ 建议档月度 30/35/35,标「基于历史数据」,不是已批预算;CPA/询盘不外推;禁止自动调价/暂停指令。
|
|
209
209
|
7. `google-quarterly render --data ./snap-p10/google-quarterly.json --out ./snap-p10/google-quarterly-report.html`。
|
|
210
|
+
8. **render 成功即停并交付**该 HTML。封面称呼已按区间写好:完整自然季是「季度」,从 1 号起的同一个月(含未过完的本月)是「月度」,其余是「本期」。**禁止**打开或修改 HTML,**禁止**再把「季度 / 月度」改一遍,**禁止**再 render,**禁止**未要求就转 PDF、截图或用视觉模型验收。
|
|
210
211
|
- **产物**:HTML 路径;新探索(视频 / PMax / 发现 / 展示)全无量则模板自动隐藏该章。
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 统计区间:`{startDate}` ~ `{endDate}`(**不可包含今天或昨天**,见下文)
|
|
4
4
|
> 账户:`{mediaCustomerId}`(`{mediaCustomerName}`)
|
|
5
|
-
> **默认交付物**:一份可打开的、带 ECharts 图表的 **HTML 文件**(`bing-period-report.html
|
|
5
|
+
> **默认交付物**:一份可打开的、带 ECharts 图表的 **HTML 文件**(`bing-period-report.html`)。封面按日期写成「季度」或「月度」,对不上仍是「账户分析」。不要打开 HTML 改称呼。
|
|
6
6
|
> 用户**未指定格式**时一律走下方 **标准四步流程**;**禁止** Agent 手写/拼接 HTML(语法错误会导致报告无法显示)。
|
|
7
7
|
> **用户明确要求 Excel / xlsx** 时:拉数步骤不变,改为 Agent 脚本写 `.xlsx`;**不要**调用 `bing-analysis render`;禁止假设存在内置 excel 子命令。
|
|
8
8
|
|
|
@@ -52,7 +52,7 @@ siluzan-tso google-analysis render \
|
|
|
52
52
|
|
|
53
53
|
模板源码:`report-templates/google-period-report.html`(结构与样式,ECharts 渲染逻辑已内联)。
|
|
54
54
|
|
|
55
|
-
`render` 会向输出目录写入 HTML,并注入 `window.__GOOGLE_PERIOD_REPORT__
|
|
55
|
+
`render` 会向输出目录写入 HTML,并注入 `window.__GOOGLE_PERIOD_REPORT__`。封面按 `meta.startDate/endDate` 写成「季度分析」或「月度分析」;不是完整自然季、也不是从 1 号起的同一个月时,仍写「周期分析」。不要打开 HTML 改这几个字。
|
|
56
56
|
传 `--snapshot-dir` 时 CLI 自动从快照合并 KPI、每日趋势图表、周期汇总/系列/设备/地域/受众/关键词表格:**数值一律以 CLI 快照覆盖**(`meta` 仅补空)。本期 KPI 优先 `campaigns` 累加。若 manifest 缺条目但磁盘上仍有对应 `*.json`,CLI 会按文件名自愈补齐。Agent 只写 `narrative`,勿自填 `kpis`/`tables`。
|
|
57
57
|
|
|
58
58
|
---
|
|
@@ -63,6 +63,8 @@ siluzan-tso google-quarterly render \
|
|
|
63
63
|
--out ./snap-p10/google-quarterly-report.html
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
`render` 打印 HTML 路径后**立刻交付该文件并结束**。封面和章节称呼由日期决定,不用改 HTML:完整自然季 →「Q3 季度…」;从 1 号起、起止在同一个月(含未过完的本月,如 10-01~10-22)→「10月 月度…」;跨月但不是完整自然季 →「本期」。**禁止**打开、grep 或改写 HTML,**禁止**为改「季度 / 月度」再跑一遍 render。用户没要 PDF 不要转换;要了也只转一次,不要读 PDF 正文、不要截图、不要用视觉模型验收。
|
|
67
|
+
|
|
66
68
|
省略 `--start/--end` 时 collect 用上一完整自然季(上海日历)。
|
|
67
69
|
|
|
68
70
|
可选:`--keyword-seeds "a,b"` 走 Keyword Planner(标注数据源,禁止写成行业指数)。**不做**频道订阅总数、订阅来源饼、视频类型(要 YouTube Analytics / 人工分类)。YouTube 广告能拿到的曝光 / TrueView / 完播 / CPV / 频道 ID /(若已关联)广告带来订阅,collect 写入 `exploreAds.types[video]` + `videosSummary`;PMax / 发现 / 展示并行写入 `exploreAds`。render 用 tab 切换,不编造 ABCD / 素材组 / 受众切分。
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> 统计区间:`{startDate}` ~ `{endDate}`
|
|
4
4
|
> 账户:`{mediaCustomerId}`(`{mediaCustomerName}`)
|
|
5
5
|
|
|
6
|
-
> **默认交付物**:一份可打开的、带 ECharts 图表的 **HTML 文件**(`meta-period-report.html
|
|
6
|
+
> **默认交付物**:一份可打开的、带 ECharts 图表的 **HTML 文件**(`meta-period-report.html`)。封面按日期写成「季度复盘」或「月度复盘」,对不上仍是「周期复盘」。不要打开 HTML 改称呼。
|
|
7
7
|
> 用户**未指定格式**时一律走下方 **标准四步流程**;**禁止**仅 Markdown 摘要或纯 JSON 充当终稿。
|
|
8
8
|
> 用户要 **表格 / Excel / xlsx** 时:步骤 1~3 相同,步骤 4 改 `--format xlsx`(见文末)。
|
|
9
9
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
数据块:总览、系列/组/广告、视频素材、受众(含性别/年龄/兴趣合并)(**以 `--json-out` 实际字段为准**)。
|
|
7
7
|
|
|
8
|
-
**默认交付:HTML(`tiktok-analysis render` 注入 Agent JSON 生成;禁止手写/拼接 HTML)**。用户指定 Excel 时 Agent 脚本写 xlsx
|
|
8
|
+
**默认交付:HTML(`tiktok-analysis render` 注入 Agent JSON 生成;禁止手写/拼接 HTML)**。用户指定 Excel 时 Agent 脚本写 xlsx。封面按日期写成「季度」或「月度」,对不上仍是「账户分析」。不要打开 HTML 改称呼。
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 统计区间:`{startDate}` ~ `{endDate}`(**不能晚于今天**;省略默认近 7 天含今天)
|
|
4
4
|
> 账户:`{mediaCustomerId}`(如 `porg-xxx`,与 `list-accounts -m Yandex` 一致)
|
|
5
|
-
> **默认交付物**:一份可打开的 **HTML 文件**(`yandex-period-report.html
|
|
5
|
+
> **默认交付物**:一份可打开的 **HTML 文件**(`yandex-period-report.html`,样式对齐开发样例)。封面按日期写成「季度」或「月度」,对不上仍是「账户分析」。不要打开 HTML 改称呼。
|
|
6
6
|
> 用户**未指定格式**时一律走下方 **标准四步流程**;**禁止** Agent 手写/拼接 HTML。
|
|
7
7
|
> **用户明确要求 Excel / xlsx** 时:拉数步骤不变,改为 Agent 脚本写 `.xlsx`;**不要**调用 `yandex-analysis render`;禁止假设存在内置 excel 子命令。见 `yandex-period-report-excel.md`。
|
|
8
8
|
|
|
@@ -609,6 +609,12 @@
|
|
|
609
609
|
(function () {
|
|
610
610
|
"use strict";
|
|
611
611
|
|
|
612
|
+
/** 只读 render 注入的 periodChrome,规则与 report-period.ts 相同。 */
|
|
613
|
+
function periodWord(data, neutral) {
|
|
614
|
+
const unit = data && data.periodChrome && data.periodChrome.unit;
|
|
615
|
+
return unit === "季度" || unit === "月度" ? unit : neutral;
|
|
616
|
+
}
|
|
617
|
+
|
|
612
618
|
const PALETTE = [
|
|
613
619
|
"#0ea5e9",
|
|
614
620
|
"#6366f1",
|
|
@@ -772,7 +778,7 @@
|
|
|
772
778
|
.join("");
|
|
773
779
|
return `
|
|
774
780
|
<div class="hero">
|
|
775
|
-
<div class="eyebrow">BING ADS ·
|
|
781
|
+
<div class="eyebrow">BING ADS · ${periodWord(data, "账户")}分析报告</div>
|
|
776
782
|
<h1>${escapeHtml(meta.accountName || "Bing 广告账户")}</h1>
|
|
777
783
|
<div class="meta-chips">
|
|
778
784
|
<span class="meta-chip">📅 <b>${escapeHtml(period)}</b></span>
|
|
@@ -1159,7 +1165,7 @@ ${titleNotice}
|
|
|
1159
1165
|
|
|
1160
1166
|
function renderFooter(data) {
|
|
1161
1167
|
const generatedAt = data.meta?.generatedAt || new Date().toISOString();
|
|
1162
|
-
return `<div class="footer"><span class="brand">siluzan-tso</span> · Bing
|
|
1168
|
+
return `<div class="footer"><span class="brand">siluzan-tso</span> · Bing 广告${periodWord(data, "账户")}分析报告 · ${escapeHtml(generatedAt)} · 数据来源 bing-analysis</div>`;
|
|
1163
1169
|
}
|
|
1164
1170
|
|
|
1165
1171
|
function styleAxis(extra) {
|
|
@@ -1326,6 +1332,12 @@ ${titleNotice}
|
|
|
1326
1332
|
'<div class="card">未找到报告数据(window.__BING_PERIOD_REPORT__ 为空),请通过 `bing-analysis render` 生成本文件。</div>';
|
|
1327
1333
|
return;
|
|
1328
1334
|
}
|
|
1335
|
+
const cover = periodWord(data, "账户");
|
|
1336
|
+
document.title =
|
|
1337
|
+
(data.meta && data.meta.accountName ? data.meta.accountName + " · " : "") +
|
|
1338
|
+
"Bing 广告" +
|
|
1339
|
+
cover +
|
|
1340
|
+
"分析报告";
|
|
1329
1341
|
root.innerHTML = [
|
|
1330
1342
|
renderHero(data),
|
|
1331
1343
|
renderExecutiveSummary(data),
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 统计区间:`{startDate}` ~ `{endDate}`(**不可包含今天或昨天**,见下文)
|
|
4
4
|
> 账户:`{mediaCustomerId}`(`{mediaCustomerName}`)
|
|
5
|
-
> **默认交付物**:一份可打开的、带 ECharts 图表的 **HTML 文件**(`bing-period-report.html
|
|
5
|
+
> **默认交付物**:一份可打开的、带 ECharts 图表的 **HTML 文件**(`bing-period-report.html`)。封面按日期写成「季度」或「月度」,对不上仍是「账户分析」。不要打开 HTML 改称呼。
|
|
6
6
|
> 用户**未指定格式**时一律走下方 **标准四步流程**;**禁止** Agent 手写/拼接 HTML(语法错误会导致报告无法显示)。
|
|
7
7
|
> **用户明确要求 Excel / xlsx** 时:拉数步骤不变,改为 Agent 脚本写 `.xlsx`;**不要**调用 `bing-analysis render`;禁止假设存在内置 excel 子命令。
|
|
8
8
|
|
|
@@ -656,6 +656,12 @@
|
|
|
656
656
|
(function () {
|
|
657
657
|
"use strict";
|
|
658
658
|
|
|
659
|
+
/** 只读 render 注入的 periodChrome,规则与 report-period.ts 相同。 */
|
|
660
|
+
function periodWord(data, neutral) {
|
|
661
|
+
const unit = data && data.periodChrome && data.periodChrome.unit;
|
|
662
|
+
return unit === "季度" || unit === "月度" ? unit : neutral;
|
|
663
|
+
}
|
|
664
|
+
|
|
659
665
|
const PALETTE = [
|
|
660
666
|
"#3b82f6",
|
|
661
667
|
"#8b5cf6",
|
|
@@ -785,7 +791,7 @@
|
|
|
785
791
|
.join("");
|
|
786
792
|
return `
|
|
787
793
|
<div class="hero">
|
|
788
|
-
<div class="eyebrow">GOOGLE ADS ·
|
|
794
|
+
<div class="eyebrow">GOOGLE ADS · ${periodWord(data, "周期")}分析报告</div>
|
|
789
795
|
<h1>${escapeHtml(meta.accountName || "Google Ads 账户")}</h1>
|
|
790
796
|
<div class="meta-chips">
|
|
791
797
|
<span class="meta-chip">📅 <b>${escapeHtml(period)}</b></span>
|
|
@@ -1117,7 +1123,7 @@
|
|
|
1117
1123
|
|
|
1118
1124
|
function renderFooter(data) {
|
|
1119
1125
|
const generatedAt = data.meta?.generatedAt || new Date().toISOString();
|
|
1120
|
-
return `<div class="footer"><span class="brand">siluzan-tso</span> · Google Ads
|
|
1126
|
+
return `<div class="footer"><span class="brand">siluzan-tso</span> · Google Ads ${periodWord(data, "周期")}分析报告 · ${escapeHtml(generatedAt)} · 数据来源 google-analysis</div>`;
|
|
1121
1127
|
}
|
|
1122
1128
|
|
|
1123
1129
|
function styleAxis(extra) {
|
|
@@ -1375,6 +1381,12 @@
|
|
|
1375
1381
|
'<div class="card">未找到报告数据(window.__GOOGLE_PERIOD_REPORT__ 为空),请通过 `google-analysis render` 生成本文件。</div>';
|
|
1376
1382
|
return;
|
|
1377
1383
|
}
|
|
1384
|
+
const cover = periodWord(data, "周期");
|
|
1385
|
+
document.title =
|
|
1386
|
+
(data.meta && data.meta.accountName ? data.meta.accountName + " · " : "") +
|
|
1387
|
+
"Google Ads " +
|
|
1388
|
+
cover +
|
|
1389
|
+
"分析报告";
|
|
1378
1390
|
root.innerHTML = [
|
|
1379
1391
|
renderHero(data),
|
|
1380
1392
|
renderExecutiveSummary(data),
|
|
@@ -52,7 +52,7 @@ siluzan-tso google-analysis render \
|
|
|
52
52
|
|
|
53
53
|
模板源码:`report-templates/google-period-report.html`(结构与样式,ECharts 渲染逻辑已内联)。
|
|
54
54
|
|
|
55
|
-
`render` 会向输出目录写入 HTML,并注入 `window.__GOOGLE_PERIOD_REPORT__
|
|
55
|
+
`render` 会向输出目录写入 HTML,并注入 `window.__GOOGLE_PERIOD_REPORT__`。封面按 `meta.startDate/endDate` 写成「季度分析」或「月度分析」;不是完整自然季、也不是从 1 号起的同一个月时,仍写「周期分析」。不要打开 HTML 改这几个字。
|
|
56
56
|
传 `--snapshot-dir` 时 CLI 自动从快照合并 KPI、每日趋势图表、周期汇总/系列/设备/地域/受众/关键词表格:**数值一律以 CLI 快照覆盖**(`meta` 仅补空)。本期 KPI 优先 `campaigns` 累加。若 manifest 缺条目但磁盘上仍有对应 `*.json`,CLI 会按文件名自愈补齐。Agent 只写 `narrative`,勿自填 `kpis`/`tables`。
|
|
57
57
|
|
|
58
58
|
---
|
|
@@ -593,14 +593,24 @@
|
|
|
593
593
|
const adminLabel = meta.drillAdminLabel || "一级行政区";
|
|
594
594
|
const drillTitle = drillCountry ? drillCountry + "下钻" : "消耗最高国家下钻";
|
|
595
595
|
const showDrill = meta.usGeoSource === "subdivisions" && (data.usStates || []).length > 0;
|
|
596
|
-
|
|
596
|
+
const chrome = data.periodChrome || {
|
|
597
|
+
current: quarterTag(meta.current) || "本期",
|
|
598
|
+
previous: quarterTag(meta.previous) || "上期",
|
|
599
|
+
next: nextQuarterTag(meta.current),
|
|
600
|
+
unit: "季度",
|
|
601
|
+
reportTitle: "Google 季度经营报告",
|
|
602
|
+
coverEn: "QUARTERLY REPORT",
|
|
603
|
+
};
|
|
604
|
+
const curQ = chrome.current || "本期";
|
|
605
|
+
const prevQ = chrome.previous || "上期";
|
|
606
|
+
const nextQ = chrome.next || "下期";
|
|
607
|
+
const unit = chrome.unit || "";
|
|
608
|
+
const periodHead = unit ? curQ + " " + unit : curQ;
|
|
609
|
+
document.title = name + " · " + (chrome.reportTitle || "Google 经营报告");
|
|
597
610
|
document.getElementById("nav-brand").innerHTML = esc(name) + ' · <span>Google Ads</span>';
|
|
598
611
|
document.getElementById("nav-explore").classList.toggle("hidden", hideSpecial);
|
|
599
612
|
|
|
600
613
|
const exec = lines(narrative.executiveSummary);
|
|
601
|
-
const curQ = quarterTag(meta.current) || "本期";
|
|
602
|
-
const prevQ = quarterTag(meta.previous) || "上期";
|
|
603
|
-
const nextQ = nextQuarterTag(meta.current);
|
|
604
614
|
const prev = kpis.previous || {};
|
|
605
615
|
const vsYoy = kpis.vsYoy || {};
|
|
606
616
|
const months = data.monthly || [];
|
|
@@ -631,9 +641,9 @@
|
|
|
631
641
|
const root = document.getElementById("report-root");
|
|
632
642
|
root.innerHTML = `
|
|
633
643
|
<header class="cover" id="cover">
|
|
634
|
-
<div class="cover-tag">GOOGLE ADS · QUARTERLY REPORT</div>
|
|
644
|
+
<div class="cover-tag">GOOGLE ADS · ${esc(chrome.coverEn || "QUARTERLY REPORT")}</div>
|
|
635
645
|
<h1>${esc(name)}</h1>
|
|
636
|
-
<p class="subtitle">${esc(
|
|
646
|
+
<p class="subtitle">${esc(periodHead)}数据汇总 & ${esc(nextQ)} 规划</p>
|
|
637
647
|
<div class="cover-meta">
|
|
638
648
|
<span>账户:<strong>${esc(meta.accountId || "—")}</strong></span>
|
|
639
649
|
<span>渠道:<strong>${esc(channelsLabel)}</strong></span>
|
|
@@ -677,7 +687,7 @@
|
|
|
677
687
|
</section>
|
|
678
688
|
<section class="chapter" id="s1">
|
|
679
689
|
<p class="num">PART 01</p>
|
|
680
|
-
<h2>${esc(
|
|
690
|
+
<h2>${esc(periodHead)}数据表现</h2>
|
|
681
691
|
<p>先看效率是否变好,再看预算花在哪,最后看询盘从哪些国家来。</p>
|
|
682
692
|
</section>
|
|
683
693
|
<section class="page" id="efficiency">
|
|
@@ -1594,14 +1604,14 @@
|
|
|
1594
1604
|
function planChapters(plan) {
|
|
1595
1605
|
if (!plan || typeof plan !== "object") return analysisBlock(plan, "规划");
|
|
1596
1606
|
const chapters = [
|
|
1597
|
-
["analysis", "
|
|
1598
|
-
["goals", "
|
|
1607
|
+
["analysis", "投放结论"],
|
|
1608
|
+
["goals", "目标与 KPI"],
|
|
1599
1609
|
["budget", "预算与市场规划"],
|
|
1600
1610
|
["structure", "广告账户结构"],
|
|
1601
1611
|
["keywords", "关键词与搜索主题"],
|
|
1602
1612
|
["audience", "受众规划"],
|
|
1603
1613
|
["creative", "素材与落地页"],
|
|
1604
|
-
["roadmap", "
|
|
1614
|
+
["roadmap", "执行路线图"],
|
|
1605
1615
|
["risks", "风险与待确认"],
|
|
1606
1616
|
];
|
|
1607
1617
|
const parts = chapters.map(([key, title]) => {
|
|
@@ -63,6 +63,8 @@ siluzan-tso google-quarterly render \
|
|
|
63
63
|
--out ./snap-p10/google-quarterly-report.html
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
`render` 打印 HTML 路径后**立刻交付该文件并结束**。封面和章节称呼由日期决定,不用改 HTML:完整自然季 →「Q3 季度…」;从 1 号起、起止在同一个月(含未过完的本月,如 10-01~10-22)→「10月 月度…」;跨月但不是完整自然季 →「本期」。**禁止**打开、grep 或改写 HTML,**禁止**为改「季度 / 月度」再跑一遍 render。用户没要 PDF 不要转换;要了也只转一次,不要读 PDF 正文、不要截图、不要用视觉模型验收。
|
|
67
|
+
|
|
66
68
|
省略 `--start/--end` 时 collect 用上一完整自然季(上海日历)。
|
|
67
69
|
|
|
68
70
|
可选:`--keyword-seeds "a,b"` 走 Keyword Planner(标注数据源,禁止写成行业指数)。**不做**频道订阅总数、订阅来源饼、视频类型(要 YouTube Analytics / 人工分类)。YouTube 广告能拿到的曝光 / TrueView / 完播 / CPV / 频道 ID /(若已关联)广告带来订阅,collect 写入 `exploreAds.types[video]` + `videosSummary`;PMax / 发现 / 展示并行写入 `exploreAds`。render 用 tab 切换,不编造 ABCD / 素材组 / 受众切分。
|
|
@@ -126,7 +126,7 @@ table.data th,table.data td{white-space:nowrap;font-size:.8rem}
|
|
|
126
126
|
</nav>
|
|
127
127
|
<div class="wrap">
|
|
128
128
|
<header class="hero">
|
|
129
|
-
<div class="hero-badge">Facebook Ads · 周期复盘</div>
|
|
129
|
+
<div class="hero-badge" id="heroBadge">Facebook Ads · 周期复盘</div>
|
|
130
130
|
<h1 id="heroTitle">Meta / Facebook 广告投放分析报告</h1>
|
|
131
131
|
<p class="sub" id="heroSub"></p>
|
|
132
132
|
</header>
|
|
@@ -269,7 +269,11 @@ window.__META_PERIOD_REPORT__ = window.__META_PERIOD_REPORT__ || null;
|
|
|
269
269
|
var brand = meta.accountName || meta.brandName || "Facebook 广告账户";
|
|
270
270
|
var period = meta.periodLabel || ((meta.startDate || "") + " ~ " + (meta.endDate || ""));
|
|
271
271
|
var campaign = meta.campaignName || "";
|
|
272
|
-
|
|
272
|
+
var coverUnit = data.periodChrome && data.periodChrome.unit;
|
|
273
|
+
var coverWord = coverUnit === "季度" || coverUnit === "月度" ? coverUnit : "周期";
|
|
274
|
+
document.title = brand + " · Facebook " + coverWord + "分析报告" + (campaign ? " | " + campaign : "");
|
|
275
|
+
var heroBadge = document.getElementById("heroBadge");
|
|
276
|
+
if (heroBadge) heroBadge.textContent = "Facebook Ads · " + coverWord + "复盘";
|
|
273
277
|
document.getElementById("heroTitle").innerHTML = esc(brand) + " · Facebook 广告投放<br/>" + esc(period) + "分析报告";
|
|
274
278
|
document.getElementById("heroSub").textContent =
|
|
275
279
|
(campaign ? "广告系列:" + campaign + " | " : "") +
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> 统计区间:`{startDate}` ~ `{endDate}`
|
|
4
4
|
> 账户:`{mediaCustomerId}`(`{mediaCustomerName}`)
|
|
5
5
|
|
|
6
|
-
> **默认交付物**:一份可打开的、带 ECharts 图表的 **HTML 文件**(`meta-period-report.html
|
|
6
|
+
> **默认交付物**:一份可打开的、带 ECharts 图表的 **HTML 文件**(`meta-period-report.html`)。封面按日期写成「季度复盘」或「月度复盘」,对不上仍是「周期复盘」。不要打开 HTML 改称呼。
|
|
7
7
|
> 用户**未指定格式**时一律走下方 **标准四步流程**;**禁止**仅 Markdown 摘要或纯 JSON 充当终稿。
|
|
8
8
|
> 用户要 **表格 / Excel / xlsx** 时:步骤 1~3 相同,步骤 4 改 `--format xlsx`(见文末)。
|
|
9
9
|
|
|
@@ -639,6 +639,12 @@
|
|
|
639
639
|
(function () {
|
|
640
640
|
"use strict";
|
|
641
641
|
|
|
642
|
+
/** 只读 render 注入的 periodChrome,规则与 report-period.ts 相同。 */
|
|
643
|
+
function periodWord(data, neutral) {
|
|
644
|
+
const unit = data && data.periodChrome && data.periodChrome.unit;
|
|
645
|
+
return unit === "季度" || unit === "月度" ? unit : neutral;
|
|
646
|
+
}
|
|
647
|
+
|
|
642
648
|
const PALETTE = [
|
|
643
649
|
"#fe2c55",
|
|
644
650
|
"#25f4ee",
|
|
@@ -765,7 +771,7 @@
|
|
|
765
771
|
.join("");
|
|
766
772
|
return `
|
|
767
773
|
<div class="hero">
|
|
768
|
-
<div class="eyebrow">TIKTOK ADS ·
|
|
774
|
+
<div class="eyebrow">TIKTOK ADS · ${periodWord(data, "账户")}分析报告</div>
|
|
769
775
|
<h1>${escapeHtml(meta.accountName || "TikTok 广告主账户")}</h1>
|
|
770
776
|
<div class="meta-chips">
|
|
771
777
|
<span class="meta-chip">📅 <b>${escapeHtml(period)}</b></span>
|
|
@@ -1031,7 +1037,7 @@
|
|
|
1031
1037
|
|
|
1032
1038
|
function renderFooter(data) {
|
|
1033
1039
|
const generatedAt = data.meta?.generatedAt || new Date().toISOString();
|
|
1034
|
-
return `<div class="footer"><span class="brand">siluzan-tso</span> · TikTok
|
|
1040
|
+
return `<div class="footer"><span class="brand">siluzan-tso</span> · TikTok 广告主${periodWord(data, "账户")}分析报告 · ${escapeHtml(generatedAt)} · 数据来源 tiktok-analysis</div>`;
|
|
1035
1041
|
}
|
|
1036
1042
|
|
|
1037
1043
|
function styleAxis(extra) {
|
|
@@ -1122,6 +1128,12 @@
|
|
|
1122
1128
|
'<div class="card">未找到报告数据(window.__TIKTOK_PERIOD_REPORT__ 为空),请通过 `tiktok-analysis render` 生成本文件。</div>';
|
|
1123
1129
|
return;
|
|
1124
1130
|
}
|
|
1131
|
+
const cover = periodWord(data, "账户");
|
|
1132
|
+
document.title =
|
|
1133
|
+
(data.meta && data.meta.accountName ? data.meta.accountName + " · " : "") +
|
|
1134
|
+
"TikTok 广告主" +
|
|
1135
|
+
cover +
|
|
1136
|
+
"分析报告";
|
|
1125
1137
|
root.innerHTML = [
|
|
1126
1138
|
renderHero(data),
|
|
1127
1139
|
renderExecutiveSummary(data),
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
数据块:总览、系列/组/广告、视频素材、受众(含性别/年龄/兴趣合并)(**以 `--json-out` 实际字段为准**)。
|
|
7
7
|
|
|
8
|
-
**默认交付:HTML(`tiktok-analysis render` 注入 Agent JSON 生成;禁止手写/拼接 HTML)**。用户指定 Excel 时 Agent 脚本写 xlsx
|
|
8
|
+
**默认交付:HTML(`tiktok-analysis render` 注入 Agent JSON 生成;禁止手写/拼接 HTML)**。用户指定 Excel 时 Agent 脚本写 xlsx。封面按日期写成「季度」或「月度」,对不上仍是「账户分析」。不要打开 HTML 改称呼。
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -445,6 +445,12 @@
|
|
|
445
445
|
(function () {
|
|
446
446
|
"use strict";
|
|
447
447
|
|
|
448
|
+
/** 只读 render 注入的 periodChrome,规则与 report-period.ts 相同。 */
|
|
449
|
+
function periodWord(data, neutral) {
|
|
450
|
+
const unit = data && data.periodChrome && data.periodChrome.unit;
|
|
451
|
+
return unit === "季度" || unit === "月度" ? unit : neutral;
|
|
452
|
+
}
|
|
453
|
+
|
|
448
454
|
const EMPTY_NOTE = "本节暂无数据";
|
|
449
455
|
|
|
450
456
|
function escapeHtml(s) {
|
|
@@ -703,7 +709,9 @@
|
|
|
703
709
|
" 数据报告";
|
|
704
710
|
return (
|
|
705
711
|
'<div class="wrap hero">' +
|
|
706
|
-
'<div class="eyebrow">YANDEX DIRECT ·
|
|
712
|
+
'<div class="eyebrow">YANDEX DIRECT · ' +
|
|
713
|
+
periodWord(data, "账户") +
|
|
714
|
+
"分析报告</div>" +
|
|
707
715
|
"<h1>" +
|
|
708
716
|
escapeHtml(title) +
|
|
709
717
|
"</h1>" +
|
|
@@ -1470,6 +1478,12 @@
|
|
|
1470
1478
|
'<div class="wrap"><div class="panel empty-note">未找到报告数据(window.__YANDEX_PERIOD_REPORT__ 为空),请通过 `yandex-analysis render` 生成本文件。</div></div>';
|
|
1471
1479
|
return;
|
|
1472
1480
|
}
|
|
1481
|
+
const cover = periodWord(data, "账户");
|
|
1482
|
+
document.title =
|
|
1483
|
+
(data.meta && data.meta.accountName ? data.meta.accountName + " · " : "") +
|
|
1484
|
+
"Yandex 广告" +
|
|
1485
|
+
cover +
|
|
1486
|
+
"分析报告";
|
|
1473
1487
|
root.innerHTML = renderAll(data);
|
|
1474
1488
|
bindNav();
|
|
1475
1489
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 统计区间:`{startDate}` ~ `{endDate}`(**不能晚于今天**;省略默认近 7 天含今天)
|
|
4
4
|
> 账户:`{mediaCustomerId}`(如 `porg-xxx`,与 `list-accounts -m Yandex` 一致)
|
|
5
|
-
> **默认交付物**:一份可打开的 **HTML 文件**(`yandex-period-report.html
|
|
5
|
+
> **默认交付物**:一份可打开的 **HTML 文件**(`yandex-period-report.html`,样式对齐开发样例)。封面按日期写成「季度」或「月度」,对不上仍是「账户分析」。不要打开 HTML 改称呼。
|
|
6
6
|
> 用户**未指定格式**时一律走下方 **标准四步流程**;**禁止** Agent 手写/拼接 HTML。
|
|
7
7
|
> **用户明确要求 Excel / xlsx** 时:拉数步骤不变,改为 Agent 脚本写 `.xlsx`;**不要**调用 `yandex-analysis render`;禁止假设存在内置 excel 子命令。见 `yandex-period-report-excel.md`。
|
|
8
8
|
|
|
@@ -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.57-beta.
|
|
12
|
+
$PKG_VERSION = '1.1.57-beta.2'
|
|
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.57-beta.
|
|
12
|
+
readonly PKG_VERSION="1.1.57-beta.2"
|
|
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"
|