siluzan-tso-cli 1.1.59-beta.2 → 1.1.59-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +244 -48
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/assets/google-period-report.schema.json +23 -7
- package/dist/skill/assets/pmax-create-template.json +2 -1
- package/dist/skill/assets/pmax-create-template.md +15 -1
- package/dist/skill/references/analytics/account-analytics.md +5 -5
- package/dist/skill/references/core/agent-conventions.md +2 -2
- package/dist/skill/references/core/intent-routing.md +1 -1
- package/dist/skill/references/core/playbooks.md +7 -7
- package/dist/skill/references/core/subagent-orchestration.md +1 -1
- package/dist/skill/references/google-ads/google-ads-write.md +1 -1
- package/dist/skill/references/google-ads/pmax-api.md +3 -1
- package/dist/skill/references/report-templates/google-period-report.md +24 -10
- package/dist/skill/references/report-templates/okki-weekly-google-client.md +30 -13
- package/dist/skill/report-templates/google-period-report.html +56 -1
- package/dist/skill/report-templates/google-period-report.md +24 -10
- package/dist/skill/report-templates/okki-weekly-google-client.md +30 -13
- package/dist/skill/scripts/install.ps1 +1 -1
- package/dist/skill/scripts/install.sh +1 -1
- package/dist/skill/snippets/handoff-p6-okki.md +2 -2
- 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.59-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.59-beta.3),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -2570,7 +2570,9 @@ function buildAuthHeaders(config) {
|
|
|
2570
2570
|
}
|
|
2571
2571
|
async function parseApiResponse(status, text3, verbose) {
|
|
2572
2572
|
if (status < 200 || status >= 300) {
|
|
2573
|
-
|
|
2573
|
+
const err = new Error(formatHttpError(status, text3, { verbose }));
|
|
2574
|
+
Object.assign(err, { status, rawBody: text3 });
|
|
2575
|
+
throw err;
|
|
2574
2576
|
}
|
|
2575
2577
|
if (!text3.trim()) return null;
|
|
2576
2578
|
try {
|
|
@@ -6434,7 +6436,8 @@ var init_wrap_snapshot_envelope = __esm({
|
|
|
6434
6436
|
]);
|
|
6435
6437
|
GATEWAY_LIST_SECTIONS = /* @__PURE__ */ new Set([
|
|
6436
6438
|
"search-terms",
|
|
6437
|
-
"ads"
|
|
6439
|
+
"ads",
|
|
6440
|
+
"negative-keywords"
|
|
6438
6441
|
]);
|
|
6439
6442
|
}
|
|
6440
6443
|
});
|
|
@@ -7670,6 +7673,12 @@ var init_sections = __esm({
|
|
|
7670
7673
|
defaultQuery: { limit: 0, orderByCost: true, costGreater: 0 },
|
|
7671
7674
|
keywordOptions: true
|
|
7672
7675
|
},
|
|
7676
|
+
{
|
|
7677
|
+
name: "negative-keywords",
|
|
7678
|
+
description: "\u5F53\u524D\u8D26\u6237\u5426\u8BCD negativekeywordmanagement/v2/list\uFF08\u7CFB\u5217/\u5E7F\u544A\u7EC4/\u8D26\u6237\u4E09\u7EA7\uFF1B\u540D\u5355\u4E0D\u6309\u6D88\u8017\u8FC7\u6EE4\uFF0C0 \u6761\u4E5F\u843D\u76D8\uFF09",
|
|
7679
|
+
dateMode: "range",
|
|
7680
|
+
path: (id) => `/negativekeywordmanagement/v2/list/${id}`
|
|
7681
|
+
},
|
|
7673
7682
|
{
|
|
7674
7683
|
name: "search-terms",
|
|
7675
7684
|
description: "\u641C\u7D22\u8BCD searchtermmanagement/v2/list\uFF08\u9ED8\u8BA4 limit=0 \u4E0D\u5C01\u9876\uFF0CCLI \u843D\u76D8\u524D\u8FC7\u6EE4 spend>0\uFF09",
|
|
@@ -103462,6 +103471,8 @@ async function fetchGoogleAnalysisSectionJson(config, fullPath, verbose, name2,
|
|
|
103462
103471
|
return fetchSection();
|
|
103463
103472
|
case "keywords":
|
|
103464
103473
|
return fetchSection();
|
|
103474
|
+
case "negative-keywords":
|
|
103475
|
+
return fetchSection();
|
|
103465
103476
|
case "search-terms":
|
|
103466
103477
|
return fetchSection();
|
|
103467
103478
|
case "campaigns":
|
|
@@ -103867,6 +103878,37 @@ async function loadSectionFile(snapshotDir, file) {
|
|
|
103867
103878
|
return null;
|
|
103868
103879
|
}
|
|
103869
103880
|
}
|
|
103881
|
+
function negativeKeywordText(row) {
|
|
103882
|
+
const text3 = row.keywordText;
|
|
103883
|
+
if (Array.isArray(text3)) {
|
|
103884
|
+
const parts = text3.map((part) => String(part).trim()).filter(Boolean);
|
|
103885
|
+
if (parts.length > 0) return parts.join(", ");
|
|
103886
|
+
}
|
|
103887
|
+
if (typeof text3 === "string" && text3.trim()) return text3.trim();
|
|
103888
|
+
if (typeof row.keyword === "string" && row.keyword.trim()) return row.keyword.trim();
|
|
103889
|
+
return "\u2014";
|
|
103890
|
+
}
|
|
103891
|
+
function mapNegativeKeywordRows(rows) {
|
|
103892
|
+
const seen = /* @__PURE__ */ new Set();
|
|
103893
|
+
const mapped = [];
|
|
103894
|
+
for (const row of rows) {
|
|
103895
|
+
const id = row.id == null ? "" : String(row.id);
|
|
103896
|
+
if (id && seen.has(id)) continue;
|
|
103897
|
+
if (id) seen.add(id);
|
|
103898
|
+
const level = String(row.negativeLevel ?? "").trim();
|
|
103899
|
+
const campaignName = String(row.campaignName ?? "").trim();
|
|
103900
|
+
const adGroupName = String(row.adGroupName ?? "").trim();
|
|
103901
|
+
mapped.push({
|
|
103902
|
+
keyword: negativeKeywordText(row),
|
|
103903
|
+
matchType: String(row.matchTypeV2 ?? row.matchType ?? "\u2014"),
|
|
103904
|
+
level: level || "\u2014",
|
|
103905
|
+
levelZh: NEGATIVE_LEVEL_ZH[level] ?? (level || "\u2014"),
|
|
103906
|
+
campaignName: campaignName || "\u2014",
|
|
103907
|
+
adGroupName: adGroupName || "\u2014"
|
|
103908
|
+
});
|
|
103909
|
+
}
|
|
103910
|
+
return mapped;
|
|
103911
|
+
}
|
|
103870
103912
|
function dateTagFromRange(dateRange) {
|
|
103871
103913
|
if (!dateRange || dateRange.mode !== "range") return void 0;
|
|
103872
103914
|
return `${dateRange.start.replace(/-/g, "")}-${dateRange.end.replace(/-/g, "")}`;
|
|
@@ -104095,6 +104137,9 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
104095
104137
|
);
|
|
104096
104138
|
}
|
|
104097
104139
|
}
|
|
104140
|
+
if (sectionMap.has("negative-keywords")) {
|
|
104141
|
+
tables.negativeKeywords = mapNegativeKeywordRows(items(sectionMap.get("negative-keywords")));
|
|
104142
|
+
}
|
|
104098
104143
|
return {
|
|
104099
104144
|
...payload,
|
|
104100
104145
|
meta,
|
|
@@ -104103,7 +104148,7 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
104103
104148
|
tables
|
|
104104
104149
|
};
|
|
104105
104150
|
}
|
|
104106
|
-
var REPORT_MERGE_SECTIONS;
|
|
104151
|
+
var REPORT_MERGE_SECTIONS, NEGATIVE_LEVEL_ZH;
|
|
104107
104152
|
var init_merge_snapshot = __esm({
|
|
104108
104153
|
"src/commands/google-analysis/merge-snapshot.ts"() {
|
|
104109
104154
|
"use strict";
|
|
@@ -104116,8 +104161,15 @@ var init_merge_snapshot = __esm({
|
|
|
104116
104161
|
"devices",
|
|
104117
104162
|
"geographic",
|
|
104118
104163
|
"audience",
|
|
104119
|
-
"keywords"
|
|
104164
|
+
"keywords",
|
|
104165
|
+
"negative-keywords"
|
|
104120
104166
|
];
|
|
104167
|
+
NEGATIVE_LEVEL_ZH = {
|
|
104168
|
+
Account: "\u8D26\u6237",
|
|
104169
|
+
Campaign: "\u7CFB\u5217",
|
|
104170
|
+
AdGroup: "\u5E7F\u544A\u7EC4",
|
|
104171
|
+
"Ad group": "\u5E7F\u544A\u7EC4"
|
|
104172
|
+
};
|
|
104121
104173
|
}
|
|
104122
104174
|
});
|
|
104123
104175
|
|
|
@@ -104992,6 +105044,16 @@ function validateGooglePeriodReportContent(data) {
|
|
|
104992
105044
|
"\u81F3\u5C11\u4E00\u9879\u987B\u4E3A\u6709\u6548\u6570\u5B57\uFF1B\u8BF7\u52A0 --snapshot-dir \u4ECE google-analysis \u5FEB\u7167\u5408\u5E76\uFF08\u52FF\u624B\u586B HTML\uFF09"
|
|
104993
105045
|
);
|
|
104994
105046
|
}
|
|
105047
|
+
const negativeRows = asRecord22(data.tables)?.negativeKeywords;
|
|
105048
|
+
if (!Array.isArray(negativeRows)) {
|
|
105049
|
+
pushMissing2(
|
|
105050
|
+
missing,
|
|
105051
|
+
"tables-negative-keywords",
|
|
105052
|
+
"\u2468\u5426\u8BCD",
|
|
105053
|
+
"tables.negativeKeywords",
|
|
105054
|
+
"\u987B\u4E3A\u6570\u7EC4\uFF080 \u6761\u4E5F\u8981\u662F []\uFF09\u3002\u9ED8\u8BA4\u62C9\u6570\u52A0\u4E0A --sections negative-keywords\uFF0Crender \u5E26 --snapshot-dir"
|
|
105055
|
+
);
|
|
105056
|
+
}
|
|
104995
105057
|
const sectionAnalysisCounts = {};
|
|
104996
105058
|
const sectionSuggestionCounts = {};
|
|
104997
105059
|
for (const key of SECTION_KEYS) {
|
|
@@ -105028,7 +105090,7 @@ function validateGooglePeriodReportContent(data) {
|
|
|
105028
105090
|
pushMissing2(
|
|
105029
105091
|
missing,
|
|
105030
105092
|
"narrative-recommendations",
|
|
105031
|
-
"\
|
|
105093
|
+
"\u2469\u4F18\u5316\u5EFA\u8BAE",
|
|
105032
105094
|
"narrative.recommendations",
|
|
105033
105095
|
`\u22653 \u6761\u8DE8\u7EF4\u5EA6\u4F18\u5316\u5EFA\u8BAE\uFF08\u5F53\u524D ${recommendations.length} \u6761\uFF09`
|
|
105034
105096
|
);
|
|
@@ -105067,7 +105129,8 @@ var init_report_content = __esm({
|
|
|
105067
105129
|
"devices",
|
|
105068
105130
|
"geographic",
|
|
105069
105131
|
"audience",
|
|
105070
|
-
"keywords"
|
|
105132
|
+
"keywords",
|
|
105133
|
+
"negativeKeywords"
|
|
105071
105134
|
];
|
|
105072
105135
|
SECTION_LABELS = {
|
|
105073
105136
|
overview: "\u2460\u6267\u884C\u6458\u8981",
|
|
@@ -105077,7 +105140,8 @@ var init_report_content = __esm({
|
|
|
105077
105140
|
devices: "\u2464\u8BBE\u5907\u5206\u5E03",
|
|
105078
105141
|
geographic: "\u2465\u5730\u57DF\u5206\u5E03",
|
|
105079
105142
|
audience: "\u2466\u53D7\u4F17\u5206\u5E03",
|
|
105080
|
-
keywords: "\u2467\u5173\u952E\u8BCD\u8868\u73B0"
|
|
105143
|
+
keywords: "\u2467\u5173\u952E\u8BCD\u8868\u73B0",
|
|
105144
|
+
negativeKeywords: "\u2468\u5426\u8BCD"
|
|
105081
105145
|
};
|
|
105082
105146
|
}
|
|
105083
105147
|
});
|
|
@@ -105165,7 +105229,7 @@ async function runGoogleAnalysisRender(opts) {
|
|
|
105165
105229
|
\u2705 Google Ads ${cover}\u5206\u6790 HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
|
|
105166
105230
|
`);
|
|
105167
105231
|
console.log(
|
|
105168
|
-
` \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\
|
|
105232
|
+
` \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\u6570\u636E\u7EF4\u5206\u6790+\u5EFA\u8BAE\u5747\u5DF2\u9F50\u5168
|
|
105169
105233
|
`
|
|
105170
105234
|
);
|
|
105171
105235
|
console.log(
|
|
@@ -122330,6 +122394,119 @@ function assertPmaxImageSlotsResolved(slots, requiredKinds = ["marketing", "squa
|
|
|
122330
122394
|
import { readFileSync as readFileSync11 } from "fs";
|
|
122331
122395
|
init_shared();
|
|
122332
122396
|
|
|
122397
|
+
// src/commands/ad/pmax-create-http-error.ts
|
|
122398
|
+
function normalizePmaxResumeId(value) {
|
|
122399
|
+
if (value == null) return null;
|
|
122400
|
+
const text3 = String(value).trim();
|
|
122401
|
+
if (!text3 || text3.toLowerCase() === "null") return null;
|
|
122402
|
+
return text3;
|
|
122403
|
+
}
|
|
122404
|
+
function quoteLongResumeIds(rawBody) {
|
|
122405
|
+
return rawBody.replace(
|
|
122406
|
+
/"(budgetId|campaignId|assetGroupId)"\s*:\s*(-?\d+)/g,
|
|
122407
|
+
'"$1":"$2"'
|
|
122408
|
+
);
|
|
122409
|
+
}
|
|
122410
|
+
function parsePmaxCreateFailureBody(rawBody) {
|
|
122411
|
+
const text3 = rawBody.trim();
|
|
122412
|
+
if (!text3.startsWith("{")) return null;
|
|
122413
|
+
let parsed;
|
|
122414
|
+
try {
|
|
122415
|
+
parsed = JSON.parse(quoteLongResumeIds(text3));
|
|
122416
|
+
} catch {
|
|
122417
|
+
return null;
|
|
122418
|
+
}
|
|
122419
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
122420
|
+
const rec = parsed;
|
|
122421
|
+
if (typeof rec["message"] !== "string" || !rec["message"].trim()) return null;
|
|
122422
|
+
if (!("budgetId" in rec) && !("campaignId" in rec) && !("assetGroupId" in rec)) return null;
|
|
122423
|
+
return {
|
|
122424
|
+
message: rec["message"].trim(),
|
|
122425
|
+
budgetId: normalizePmaxResumeId(rec["budgetId"]),
|
|
122426
|
+
campaignId: normalizePmaxResumeId(rec["campaignId"]),
|
|
122427
|
+
assetGroupId: normalizePmaxResumeId(rec["assetGroupId"])
|
|
122428
|
+
};
|
|
122429
|
+
}
|
|
122430
|
+
function readHttpRawBody(err) {
|
|
122431
|
+
if (!err || typeof err !== "object" || !("rawBody" in err)) return "";
|
|
122432
|
+
const raw = err.rawBody;
|
|
122433
|
+
return typeof raw === "string" ? raw : "";
|
|
122434
|
+
}
|
|
122435
|
+
function sameResumeId(left, right) {
|
|
122436
|
+
const a = left?.trim() || null;
|
|
122437
|
+
const b = right?.trim() || null;
|
|
122438
|
+
return a === b;
|
|
122439
|
+
}
|
|
122440
|
+
function pmaxCreateRetryAdvice(requested, failure) {
|
|
122441
|
+
const got = [failure.budgetId, failure.campaignId, failure.assetGroupId];
|
|
122442
|
+
if (!got.some(Boolean)) return "fix-without-ids";
|
|
122443
|
+
const sent = [
|
|
122444
|
+
normalizePmaxResumeId(requested.budgetId),
|
|
122445
|
+
normalizePmaxResumeId(requested.campaignId),
|
|
122446
|
+
normalizePmaxResumeId(requested.assetGroupId)
|
|
122447
|
+
];
|
|
122448
|
+
if (sent.every((id, index) => sameResumeId(id, got[index]))) return "stop-same-step";
|
|
122449
|
+
for (let index = 0; index < sent.length; index++) {
|
|
122450
|
+
const previous = sent[index]?.trim() || null;
|
|
122451
|
+
if (previous && previous !== got[index]) return "stop-same-step";
|
|
122452
|
+
}
|
|
122453
|
+
return "resume";
|
|
122454
|
+
}
|
|
122455
|
+
function pmaxCreateResumeError(cfg) {
|
|
122456
|
+
const budgetId = normalizePmaxResumeId(cfg.budgetId) ?? "";
|
|
122457
|
+
const campaignId = normalizePmaxResumeId(cfg.campaignId) ?? "";
|
|
122458
|
+
const assetGroupId = normalizePmaxResumeId(cfg.assetGroupId) ?? "";
|
|
122459
|
+
if (campaignId && !budgetId) return "BudgetId is required when CampaignId is provided.";
|
|
122460
|
+
if (assetGroupId && (!budgetId || !campaignId)) {
|
|
122461
|
+
return "CampaignId is required when AssetGroupId is provided.";
|
|
122462
|
+
}
|
|
122463
|
+
const brandGuidelinesOn = cfg.brandGuidelinesEnabled !== false;
|
|
122464
|
+
if (brandGuidelinesOn && campaignId && !assetGroupId) {
|
|
122465
|
+
return "AssetGroupId is required when CampaignId is provided and brand guidelines are enabled.";
|
|
122466
|
+
}
|
|
122467
|
+
return null;
|
|
122468
|
+
}
|
|
122469
|
+
function classifyPmaxCreateHttpError(raw) {
|
|
122470
|
+
const msg = raw.trim() || "\u672A\u77E5\u9519\u8BEF";
|
|
122471
|
+
const lower = msg.toLowerCase();
|
|
122472
|
+
const hints = [];
|
|
122473
|
+
if (/^HTTP 400\b/i.test(msg) || lower.includes("bad request")) {
|
|
122474
|
+
if (/budget|日预算|budgetamount|micros/i.test(msg)) {
|
|
122475
|
+
hints.push("\u68C0\u67E5 campaign.budget / \u65E5\u9884\u7B97\u91D1\u989D\u4E0E\u5E01\u79CD\u662F\u5426\u5408\u7406");
|
|
122476
|
+
}
|
|
122477
|
+
if (/bidding|target.?cpa|target.?roas|maximize|出价/i.test(msg)) {
|
|
122478
|
+
hints.push("\u68C0\u67E5 BiddingStrategyTypeV2 \u4E0E\u914D\u5957\u91D1\u989D\u5B57\u6BB5\uFF08tCPA/tROAS\uFF09\u662F\u5426\u9F50\u5168\u4E14\u517C\u5BB9");
|
|
122479
|
+
}
|
|
122480
|
+
if (/image|asset|marketing|logo|图片/i.test(msg)) {
|
|
122481
|
+
hints.push("\u68C0\u67E5\u6A2A\u56FE/\u65B9\u56FE/Logo \u662F\u5426\u5DF2\u4E0A\u4F20\u6210\u529F\u3001\u89C4\u683C\u662F\u5426\u8FBE\u6807\uFF08\u5148\u8DD1 ad pmax-validate\uFF09");
|
|
122482
|
+
}
|
|
122483
|
+
if (/final.?url|landing|落地页|url/i.test(msg)) {
|
|
122484
|
+
hints.push("\u68C0\u67E5 finalUrls \u662F\u5426\u4E3A\u53EF\u8BBF\u95EE\u7684 http(s) \u843D\u5730\u9875");
|
|
122485
|
+
}
|
|
122486
|
+
if (/name|duplicate|已存在|同名/i.test(msg)) {
|
|
122487
|
+
hints.push("\u7CFB\u5217\u540D\u53EF\u80FD\u91CD\u590D\uFF1B\u6362\u4E00\u4E2A name \u518D\u8BD5");
|
|
122488
|
+
}
|
|
122489
|
+
if (/geo|location|language|地域|语言/i.test(msg)) {
|
|
122490
|
+
hints.push("\u68C0\u67E5 geo / language id \u662F\u5426\u6765\u81EA ad geo resolve / \u6587\u6863\u679A\u4E3E");
|
|
122491
|
+
}
|
|
122492
|
+
if (hints.length === 0) {
|
|
122493
|
+
hints.push(
|
|
122494
|
+
"\u5E38\u89C1\uFF1A\u9884\u7B97/\u51FA\u4EF7\u4E0D\u517C\u5BB9\u3001\u56FE\u7247\u8D44\u4EA7\u65E0\u6548\u3001finalUrl \u65E0\u6548\u3001\u7CFB\u5217\u91CD\u540D\uFF1B\u5148 ad pmax-validate\uFF0C\u4ECD\u5931\u8D25\u52A0 --verbose \u770B\u7F51\u5173\u539F\u6587"
|
|
122495
|
+
);
|
|
122496
|
+
}
|
|
122497
|
+
} else if (/^HTTP 403\b/i.test(msg)) {
|
|
122498
|
+
hints.push("\u8D26\u6237\u53EF\u80FD\u65E0\u6743\u521B\u5EFA\uFF1B\u5148 account check-access -a <id>\uFF0C\u786E\u8BA4\u5728\u5F53\u524D\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u4E0B");
|
|
122499
|
+
} else if (/^HTTP 401\b/i.test(msg)) {
|
|
122500
|
+
hints.push("\u9274\u6743\u5931\u6548\uFF1B\u6267\u884C siluzan-tso login \u540E\u91CD\u8BD5");
|
|
122501
|
+
}
|
|
122502
|
+
if (hints.length === 0) return msg;
|
|
122503
|
+
return `${msg}
|
|
122504
|
+
\u63D0\u793A\uFF1A${hints.join("\uFF1B")}`;
|
|
122505
|
+
}
|
|
122506
|
+
function pmaxCreateFollowUpFailed(input) {
|
|
122507
|
+
return Boolean(input.uploadError || input.linkError || input.extensionsAllOk === false);
|
|
122508
|
+
}
|
|
122509
|
+
|
|
122333
122510
|
// src/commands/ad/pmax-campaign-extensions.ts
|
|
122334
122511
|
init_auth();
|
|
122335
122512
|
var PMAX_POLICY_CALLOUT_MIN = 20;
|
|
@@ -122912,7 +123089,12 @@ function parseLocationLanguageIds(items3) {
|
|
|
122912
123089
|
id: Number(item.id)
|
|
122913
123090
|
}));
|
|
122914
123091
|
}
|
|
123092
|
+
function trimmedId(value) {
|
|
123093
|
+
return normalizePmaxResumeId(value) ?? void 0;
|
|
123094
|
+
}
|
|
122915
123095
|
function buildPmaxCreateApiBody(cfg, imageSlots) {
|
|
123096
|
+
const resumeError = pmaxCreateResumeError(cfg);
|
|
123097
|
+
if (resumeError) throw new Error(resumeError);
|
|
122916
123098
|
assertPmaxImageSlotsResolved(imageSlots);
|
|
122917
123099
|
const body = {
|
|
122918
123100
|
name: cfg.name.trim(),
|
|
@@ -122943,6 +123125,12 @@ function buildPmaxCreateApiBody(cfg, imageSlots) {
|
|
|
122943
123125
|
if (cfg.brandGuidelinesEnabled !== void 0) {
|
|
122944
123126
|
body.brandGuidelinesEnabled = cfg.brandGuidelinesEnabled;
|
|
122945
123127
|
}
|
|
123128
|
+
const budgetId = trimmedId(cfg.budgetId);
|
|
123129
|
+
const campaignId = trimmedId(cfg.campaignId);
|
|
123130
|
+
const assetGroupId = trimmedId(cfg.assetGroupId);
|
|
123131
|
+
if (budgetId) body.budgetId = budgetId;
|
|
123132
|
+
if (campaignId) body.campaignId = campaignId;
|
|
123133
|
+
if (assetGroupId) body.assetGroupId = assetGroupId;
|
|
122946
123134
|
return body;
|
|
122947
123135
|
}
|
|
122948
123136
|
function buildPmaxCreateUrl(googleApiUrl, accountId) {
|
|
@@ -123344,51 +123532,13 @@ async function linkPmaxVideoAfterCreate(opts) {
|
|
|
123344
123532
|
}
|
|
123345
123533
|
}
|
|
123346
123534
|
|
|
123347
|
-
// src/commands/ad/pmax-create-http-error.ts
|
|
123348
|
-
function classifyPmaxCreateHttpError(raw) {
|
|
123349
|
-
const msg = raw.trim() || "\u672A\u77E5\u9519\u8BEF";
|
|
123350
|
-
const lower = msg.toLowerCase();
|
|
123351
|
-
const hints = [];
|
|
123352
|
-
if (/^HTTP 400\b/i.test(msg) || lower.includes("bad request")) {
|
|
123353
|
-
if (/budget|日预算|budgetamount|micros/i.test(msg)) {
|
|
123354
|
-
hints.push("\u68C0\u67E5 campaign.budget / \u65E5\u9884\u7B97\u91D1\u989D\u4E0E\u5E01\u79CD\u662F\u5426\u5408\u7406");
|
|
123355
|
-
}
|
|
123356
|
-
if (/bidding|target.?cpa|target.?roas|maximize|出价/i.test(msg)) {
|
|
123357
|
-
hints.push("\u68C0\u67E5 BiddingStrategyTypeV2 \u4E0E\u914D\u5957\u91D1\u989D\u5B57\u6BB5\uFF08tCPA/tROAS\uFF09\u662F\u5426\u9F50\u5168\u4E14\u517C\u5BB9");
|
|
123358
|
-
}
|
|
123359
|
-
if (/image|asset|marketing|logo|图片/i.test(msg)) {
|
|
123360
|
-
hints.push("\u68C0\u67E5\u6A2A\u56FE/\u65B9\u56FE/Logo \u662F\u5426\u5DF2\u4E0A\u4F20\u6210\u529F\u3001\u89C4\u683C\u662F\u5426\u8FBE\u6807\uFF08\u5148\u8DD1 ad pmax-validate\uFF09");
|
|
123361
|
-
}
|
|
123362
|
-
if (/final.?url|landing|落地页|url/i.test(msg)) {
|
|
123363
|
-
hints.push("\u68C0\u67E5 finalUrls \u662F\u5426\u4E3A\u53EF\u8BBF\u95EE\u7684 http(s) \u843D\u5730\u9875");
|
|
123364
|
-
}
|
|
123365
|
-
if (/name|duplicate|已存在|同名/i.test(msg)) {
|
|
123366
|
-
hints.push("\u7CFB\u5217\u540D\u53EF\u80FD\u91CD\u590D\uFF1B\u6362\u4E00\u4E2A name \u518D\u8BD5");
|
|
123367
|
-
}
|
|
123368
|
-
if (/geo|location|language|地域|语言/i.test(msg)) {
|
|
123369
|
-
hints.push("\u68C0\u67E5 geo / language id \u662F\u5426\u6765\u81EA ad geo resolve / \u6587\u6863\u679A\u4E3E");
|
|
123370
|
-
}
|
|
123371
|
-
if (hints.length === 0) {
|
|
123372
|
-
hints.push(
|
|
123373
|
-
"\u5E38\u89C1\uFF1A\u9884\u7B97/\u51FA\u4EF7\u4E0D\u517C\u5BB9\u3001\u56FE\u7247\u8D44\u4EA7\u65E0\u6548\u3001finalUrl \u65E0\u6548\u3001\u7CFB\u5217\u91CD\u540D\uFF1B\u5148 ad pmax-validate\uFF0C\u4ECD\u5931\u8D25\u52A0 --verbose \u770B\u7F51\u5173\u539F\u6587"
|
|
123374
|
-
);
|
|
123375
|
-
}
|
|
123376
|
-
} else if (/^HTTP 403\b/i.test(msg)) {
|
|
123377
|
-
hints.push("\u8D26\u6237\u53EF\u80FD\u65E0\u6743\u521B\u5EFA\uFF1B\u5148 account check-access -a <id>\uFF0C\u786E\u8BA4\u5728\u5F53\u524D\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u4E0B");
|
|
123378
|
-
} else if (/^HTTP 401\b/i.test(msg)) {
|
|
123379
|
-
hints.push("\u9274\u6743\u5931\u6548\uFF1B\u6267\u884C siluzan-tso login \u540E\u91CD\u8BD5");
|
|
123380
|
-
}
|
|
123381
|
-
if (hints.length === 0) return msg;
|
|
123382
|
-
return `${msg}
|
|
123383
|
-
\u63D0\u793A\uFF1A${hints.join("\uFF1B")}`;
|
|
123384
|
-
}
|
|
123385
|
-
|
|
123386
123535
|
// src/commands/ad/pmax-create.ts
|
|
123387
123536
|
function printVideoLinkRecoveryHint(accountId, assetGroupId, campaignId, youtubeTarget) {
|
|
123388
123537
|
const cid = campaignId != null ? ` --campaign-id ${campaignId}` : "";
|
|
123389
123538
|
const yt = youtubeTarget ? ` --youtube "${youtubeTarget}"` : ` --video-path "<path>"`;
|
|
123390
123539
|
console.error(
|
|
123391
123540
|
` \u53EF\u624B\u52A8\uFF1Asiluzan-tso ad pmax-youtube-link -a ${accountId} --asset-group-id ${assetGroupId}${cid}${yt}
|
|
123541
|
+
\u6D3B\u52A8\u5DF2\u7ECF\u521B\u5EFA\u6210\u529F\uFF0C\u4E0D\u8981\u518D\u6267\u884C pmax-create\u3002
|
|
123392
123542
|
`
|
|
123393
123543
|
);
|
|
123394
123544
|
}
|
|
@@ -123471,6 +123621,39 @@ async function runAdPmaxCreate(opts) {
|
|
|
123471
123621
|
opts.verbose
|
|
123472
123622
|
);
|
|
123473
123623
|
} catch (err) {
|
|
123624
|
+
const failure = parsePmaxCreateFailureBody(readHttpRawBody(err));
|
|
123625
|
+
if (failure) {
|
|
123626
|
+
const advice = pmaxCreateRetryAdvice(cfg, failure);
|
|
123627
|
+
console.error(`
|
|
123628
|
+
\u274C \u521B\u5EFA PMax \u5E7F\u544A\u7CFB\u5217\u5931\u8D25\uFF1A${failure.message}`);
|
|
123629
|
+
console.error(` budgetId\uFF1A${failure.budgetId ?? "null"}`);
|
|
123630
|
+
console.error(` campaignId\uFF1A${failure.campaignId ?? "null"}`);
|
|
123631
|
+
console.error(` assetGroupId\uFF1A${failure.assetGroupId ?? "null"}`);
|
|
123632
|
+
if (advice === "resume") {
|
|
123633
|
+
console.error(
|
|
123634
|
+
" \u7EED\u8DD1\uFF1A\u628A\u975E\u7A7A\u7F16\u53F7\u6309\u540C\u540D\u5199\u56DE\u8FD9\u4EFD JSON\uFF0C\u5176\u4F59\u5185\u5BB9\u4E0D\u8981\u6539\uFF0C\u518D\u6267\u884C\u4E00\u6B21 pmax-create\u3002\u7F16\u53F7\u6309\u9884\u7B97\u3001\u7CFB\u5217\u3001\u7D20\u6750\u7EC4\u5E26\u3002\u540C\u4E00\u6B65\u518D\u5931\u8D25\u5C31\u505C\u3002\n"
|
|
123635
|
+
);
|
|
123636
|
+
} else if (advice === "stop-same-step") {
|
|
123637
|
+
console.error(" \u540C\u4E00\u6B65\u518D\u5931\u8D25\uFF0C\u505C\u6B62\u3002\u4E0D\u8981\u518D\u6267\u884C pmax-create\u3002\n");
|
|
123638
|
+
} else {
|
|
123639
|
+
console.error(" \u8FD9\u6B21\u8FD8\u6CA1\u6709\u5EFA\u6210\u9884\u7B97\u3001\u7CFB\u5217\u6216\u7D20\u6750\u7EC4\u3002\u4FEE\u6B63\u539F\u56E0\u540E\u7528\u539F JSON \u518D\u63D0\u4EA4\uFF0C\u4E0D\u8981\u586B\u8FD9\u4E09\u4E2A\u7F16\u53F7\u3002\n");
|
|
123640
|
+
}
|
|
123641
|
+
await emitCliJsonOrSnapshot(opts, {
|
|
123642
|
+
section: `ad-pmax-create-${accountId}`,
|
|
123643
|
+
commandLabel: "ad pmax-create",
|
|
123644
|
+
commandHint: accountId,
|
|
123645
|
+
payload: { ok: false, retryCreate: advice === "resume", ...failure },
|
|
123646
|
+
idSuffix: accountId,
|
|
123647
|
+
summaryExtras: {
|
|
123648
|
+
ok: false,
|
|
123649
|
+
retryCreate: advice === "resume",
|
|
123650
|
+
budgetId: failure.budgetId,
|
|
123651
|
+
campaignId: failure.campaignId,
|
|
123652
|
+
assetGroupId: failure.assetGroupId
|
|
123653
|
+
}
|
|
123654
|
+
});
|
|
123655
|
+
process.exit(1);
|
|
123656
|
+
}
|
|
123474
123657
|
const raw = err instanceof Error ? err.message : String(err);
|
|
123475
123658
|
console.error(`
|
|
123476
123659
|
\u274C \u521B\u5EFA PMax \u5E7F\u544A\u7CFB\u5217\u5931\u8D25\uFF1A${classifyPmaxCreateHttpError(raw)}
|
|
@@ -123534,6 +123717,7 @@ async function runAdPmaxCreate(opts) {
|
|
|
123534
123717
|
void 0
|
|
123535
123718
|
);
|
|
123536
123719
|
}
|
|
123720
|
+
console.error(" \u6D3B\u52A8\u5DF2\u7ECF\u521B\u5EFA\u6210\u529F\uFF0C\u4E0D\u8981\u518D\u6267\u884C pmax-create\u3002\n");
|
|
123537
123721
|
} else if (videoResult.linkError) {
|
|
123538
123722
|
console.error(`
|
|
123539
123723
|
\u26A0\uFE0F \u6D3B\u52A8\u5DF2\u521B\u5EFA\uFF0C\u4F46 YouTube \u94FE\u63A5\u5931\u8D25\uFF1A${videoResult.linkError}`);
|
|
@@ -123545,6 +123729,7 @@ async function runAdPmaxCreate(opts) {
|
|
|
123545
123729
|
videoResult.youtubeTarget
|
|
123546
123730
|
);
|
|
123547
123731
|
}
|
|
123732
|
+
console.error(" \u6D3B\u52A8\u5DF2\u7ECF\u521B\u5EFA\u6210\u529F\uFF0C\u4E0D\u8981\u518D\u6267\u884C pmax-create\u3002\n");
|
|
123548
123733
|
} else if (videoResult.skippedReason) {
|
|
123549
123734
|
console.error(`
|
|
123550
123735
|
\u26A0\uFE0F \u6D3B\u52A8\u5DF2\u521B\u5EFA\uFF0C\u4F46\u89C6\u9891\u672A\u94FE\u63A5\uFF1A${videoResult.skippedReason}
|
|
@@ -123557,6 +123742,7 @@ async function runAdPmaxCreate(opts) {
|
|
|
123557
123742
|
}
|
|
123558
123743
|
console.error(
|
|
123559
123744
|
` \u53EF\u624B\u52A8\u8865\u6302\uFF1Asiluzan-tso ad extension callout|snippet|lead-form -a ${accountId} --campaign-id ${campaignId} \u2026
|
|
123745
|
+
\u6D3B\u52A8\u5DF2\u7ECF\u521B\u5EFA\u6210\u529F\uFF0C\u4E0D\u8981\u518D\u6267\u884C pmax-create\u3002
|
|
123560
123746
|
`
|
|
123561
123747
|
);
|
|
123562
123748
|
} else if (extensionsResult?.allOk && extensionsResult.callouts.length + extensionsResult.structuredSnippets.length + extensionsResult.sitelinks.length + (extensionsResult.leadForm ? 1 : 0) + (extensionsResult.whatsapp ? 1 : 0) > 0) {
|
|
@@ -123564,12 +123750,22 @@ async function runAdPmaxCreate(opts) {
|
|
|
123564
123750
|
console.log("\n\u2705 Campaign \u9644\u52A0\u8D44\u4EA7\u5DF2\u6302\u8F7D");
|
|
123565
123751
|
}
|
|
123566
123752
|
}
|
|
123753
|
+
const followUpFailed = pmaxCreateFollowUpFailed({
|
|
123754
|
+
uploadError: videoResult.uploadError,
|
|
123755
|
+
linkError: videoResult.linkError,
|
|
123756
|
+
extensionsAllOk: extensionsResult ? extensionsResult.allOk : void 0
|
|
123757
|
+
});
|
|
123758
|
+
if (followUpFailed) {
|
|
123759
|
+
payload["created"] = true;
|
|
123760
|
+
payload["retryCreate"] = false;
|
|
123761
|
+
}
|
|
123567
123762
|
if (await emitCliJsonOrSnapshot(opts, {
|
|
123568
123763
|
section: `ad-pmax-create-${accountId}`,
|
|
123569
123764
|
commandLabel: "ad pmax-create",
|
|
123570
123765
|
commandHint: accountId,
|
|
123571
123766
|
payload,
|
|
123572
|
-
idSuffix: accountId
|
|
123767
|
+
idSuffix: accountId,
|
|
123768
|
+
summaryExtras: followUpFailed ? { created: true, retryCreate: false } : void 0
|
|
123573
123769
|
})) {
|
|
123574
123770
|
return;
|
|
123575
123771
|
}
|
package/dist/skill/_meta.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "siluzan-tso/google-period-report.schema.json",
|
|
4
4
|
"title": "GooglePeriodReport",
|
|
5
|
-
"description": "Google Ads 周期分析报告 JSON(对齐 google-period-report.md
|
|
5
|
+
"description": "Google Ads 周期分析报告 JSON(对齐 google-period-report.md 10 章:9 个数据维 + 优化建议;供 google-analysis render 注入 HTML)。Agent 只需撰写 narrative;kpis/charts/tables 由 --snapshot-dir 从 google-analysis 快照合并。render 校验要求合并后 kpis.cost/impressions/clicks 至少一项为有效数字,且 tables.negativeKeywords 为数组(0 条也要是 []),否则拒绝出 HTML。",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["meta", "narrative"],
|
|
8
8
|
"properties": {
|
|
@@ -93,7 +93,8 @@
|
|
|
93
93
|
"devices": { "$ref": "#/$defs/deviceRows" },
|
|
94
94
|
"geographic": { "$ref": "#/$defs/geographicRows" },
|
|
95
95
|
"audience": { "$ref": "#/$defs/audienceRows" },
|
|
96
|
-
"keywords": { "$ref": "#/$defs/keywordRows" }
|
|
96
|
+
"keywords": { "$ref": "#/$defs/keywordRows" },
|
|
97
|
+
"negativeKeywords": { "$ref": "#/$defs/negativeKeywordRows" }
|
|
97
98
|
}
|
|
98
99
|
},
|
|
99
100
|
"narrative": {
|
|
@@ -109,7 +110,7 @@
|
|
|
109
110
|
},
|
|
110
111
|
"sections": {
|
|
111
112
|
"type": "object",
|
|
112
|
-
"description": "
|
|
113
|
+
"description": "9 个数据维度各自的分析+建议(对应报告 §1~§9);每维度 analysis ≥2 条、suggestions ≥1 条",
|
|
113
114
|
"required": [
|
|
114
115
|
"overview",
|
|
115
116
|
"dailyTrend",
|
|
@@ -118,7 +119,8 @@
|
|
|
118
119
|
"devices",
|
|
119
120
|
"geographic",
|
|
120
121
|
"audience",
|
|
121
|
-
"keywords"
|
|
122
|
+
"keywords",
|
|
123
|
+
"negativeKeywords"
|
|
122
124
|
],
|
|
123
125
|
"properties": {
|
|
124
126
|
"overview": { "$ref": "#/$defs/sectionNarrative" },
|
|
@@ -128,12 +130,13 @@
|
|
|
128
130
|
"devices": { "$ref": "#/$defs/sectionNarrative" },
|
|
129
131
|
"geographic": { "$ref": "#/$defs/sectionNarrative" },
|
|
130
132
|
"audience": { "$ref": "#/$defs/sectionNarrative" },
|
|
131
|
-
"keywords": { "$ref": "#/$defs/sectionNarrative" }
|
|
133
|
+
"keywords": { "$ref": "#/$defs/sectionNarrative" },
|
|
134
|
+
"negativeKeywords": { "$ref": "#/$defs/sectionNarrative" }
|
|
132
135
|
}
|
|
133
136
|
},
|
|
134
137
|
"recommendations": {
|
|
135
138
|
"type": "array",
|
|
136
|
-
"description": "报告 §
|
|
139
|
+
"description": "报告 §10 优化建议;≥3 条,可跨维度综合",
|
|
137
140
|
"minItems": 3,
|
|
138
141
|
"items": {
|
|
139
142
|
"oneOf": [
|
|
@@ -249,6 +252,19 @@
|
|
|
249
252
|
"costPerConversion": { "type": "number" }
|
|
250
253
|
}
|
|
251
254
|
},
|
|
252
|
-
"keywordRows": { "type": "array", "items": { "$ref": "#/$defs/keywordRow" } }
|
|
255
|
+
"keywordRows": { "type": "array", "items": { "$ref": "#/$defs/keywordRow" } },
|
|
256
|
+
"negativeKeywordRow": {
|
|
257
|
+
"type": "object",
|
|
258
|
+
"description": "当前账户否词一行。0 条时数组为空,不要省略字段。",
|
|
259
|
+
"properties": {
|
|
260
|
+
"keyword": { "type": "string" },
|
|
261
|
+
"matchType": { "type": "string" },
|
|
262
|
+
"level": { "type": "string" },
|
|
263
|
+
"levelZh": { "type": "string" },
|
|
264
|
+
"campaignName": { "type": "string" },
|
|
265
|
+
"adGroupName": { "type": "string" }
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
"negativeKeywordRows": { "type": "array", "items": { "$ref": "#/$defs/negativeKeywordRow" } }
|
|
253
269
|
}
|
|
254
270
|
}
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"勿用 Search 的 ExtensionsForBatchJob;PMax 用 campaignExtensions(CLI 会尝试自动转换 CALLOUT/SNIPPET)",
|
|
17
17
|
"Lead Gen/B2B 询盘方案:默认保留 campaignExtensions.leadForm(勿只写 callouts/snippets);用户明确不要表单才删 leadForm",
|
|
18
18
|
"审查稿须单列「潜在客户表单」节(标题/描述/字段/privacyPolicyUrl);结构见 google-ads-pmax-launch-plan-template.md",
|
|
19
|
-
"不需要任何附加资产时设 skipCampaignExtensions: true(勿仅删除 campaignExtensions 块)"
|
|
19
|
+
"不需要任何附加资产时设 skipCampaignExtensions: true(勿仅删除 campaignExtensions 块)",
|
|
20
|
+
"创建失败:只有 400 且编号变多时,才把非空 budgetId / campaignId / assetGroupId 写回本 JSON 再跑一次,内容不改。首次创建不填。已经 200 只是扩展失败就不要再 pmax-create"
|
|
20
21
|
]
|
|
21
22
|
},
|
|
22
23
|
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
| Lead Gen 方案 | **默认**在 `campaignExtensions` 含 **`leadForm`**(B2B/询盘/留资);仅 callouts/snippets 不算完整方案;用户明确不要才省略 |
|
|
23
23
|
| 存量补表单 | 活动已创建时用 `ad extension lead-form`(见 `pmax-lead-form-template.md` 方式 B) |
|
|
24
24
|
| 改已上线 PMax | 先 `ad pmax-get` 看 `_brandGuidelinesActive`;改品牌见 `pmax-api.md` § Brand Guidelines |
|
|
25
|
+
| 创建失败 | 只有创建接口 400,且编号比这次请求多,才把非空的 `budgetId` / `campaignId` / `assetGroupId` 写回同一份 JSON,其余内容不要改,再 `pmax-create` 一次。首次创建不填这三个字段。同一步再失败就停。已经 200、只是扩展或视频失败:不要再 `pmax-create` |
|
|
25
26
|
| 列表复核 | `ad campaigns -a <id> --json-out ./snap`,`channelTypeV2` 应为 `PERFORMANCE_MAX` |
|
|
26
27
|
|
|
27
28
|
---
|
|
@@ -81,6 +82,9 @@ siluzan-tso ad campaigns -a <accountId> --json-out ./snap
|
|
|
81
82
|
| `targetCpa_BidingAmount` | number | | `TARGET_CPA` 或带目标 CPA 的 `MAXIMIZE_CONVERSIONS` 时必填(**元**) |
|
|
82
83
|
| `targetRoas` | number | | `TARGET_ROAS` 时必填(如 `2.5` = 250%) |
|
|
83
84
|
| `brandGuidelinesEnabled` | boolean | | 省略 = 网关默认 `true`(Campaign 级 BG);`false` = 品牌在 AssetGroup(旧路径) |
|
|
85
|
+
| `budgetId` | string | | 上次创建失败后已经建成的预算编号。空着就新建。续跑时和下面两个编号一起写回,其余字段不要改 |
|
|
86
|
+
| `campaignId` | string | | 上次创建失败后已经建成的系列编号。必须和 `budgetId` 一起传 |
|
|
87
|
+
| `assetGroupId` | string | | 上次创建失败后已经建成的素材组编号。必须和 `budgetId`、`campaignId` 一起传。默认 BG 开启时不能只写到系列 |
|
|
84
88
|
| `videoPath` | string | | YOUTUBE_VIDEO(可选):本地视频 ≥10s;与 `youtubeUrlOrId` 二选一,创建时最多 1 条;更多视频创建后用 `pmax-youtube-link` / `pmax-assets-update` 追加 |
|
|
85
89
|
| `videoTitle` | string | | PyAPI 上传标题(默认文件名) |
|
|
86
90
|
| `videoDescription` | string | | PyAPI 上传描述(可选) |
|
|
@@ -130,7 +134,17 @@ siluzan-tso ad campaigns -a <accountId> --json-out ./snap
|
|
|
130
134
|
| -------------- | -------------------------------------------------- |
|
|
131
135
|
| `campaignId` | 活动 ID |
|
|
132
136
|
| `assetGroupId` | 首个资产组 ID |
|
|
133
|
-
| `budgetId` | 日预算 ID(后续 PATCH 改预算时需与 `budget`
|
|
137
|
+
| `budgetId` | 日预算 ID(后续 PATCH 改预算时需与 `budget` 成对;创建失败后续跑也用它) |
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 创建失败后续跑
|
|
142
|
+
|
|
143
|
+
`POST .../campaign/pmax` 失败时返回 **JSON**,不再是纯文本。`message` 是原因。`budgetId`、`campaignId`、`assetGroupId` 里非空的是已经建成的编号,没建成的是 `null`。
|
|
144
|
+
|
|
145
|
+
把非空编号按同名写回**同一份** JSON,其余内容不要改,再执行一次 `pmax-create`。CLI 会原样转发这三个字段,不会改已有预算金额或系列名称。首次创建不要填它们。不带编号再提交会新建一套。编号按预算、系列、素材组带。默认品牌手册开启时不能只写 `campaignId`。同一步再失败就停(stdout 摘要 `retryCreate=false`)。
|
|
146
|
+
|
|
147
|
+
创建接口已经 **200**,只是附加资产或视频没挂上:活动已经在,**不要再 `pmax-create`**。扩展用 `ad extension`,视频用 `ad pmax-youtube-link`。这时 stdout 摘要是 `created=true`、`retryCreate=false`。
|
|
134
148
|
|
|
135
149
|
---
|
|
136
150
|
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
## 工作流入口(线性步骤见 playbooks)
|
|
45
45
|
|
|
46
46
|
> 线性步骤见 `playbooks.md`:单户画像 **P1**、Google 周期 **P4**、Meta 周期 **P4-FB**、多账户批处理 **P5**、OKKI **P6**、询盘 **P7**。
|
|
47
|
-
> 「使用 okki 周报模板」→ **P6**(勿按默认
|
|
47
|
+
> 「使用 okki 周报模板」→ **P6**(勿按默认 10 章追问;终稿 `google-analysis okki-render`,禁止 Agent 手写 xlsx)。Google 周期 Excel(非 OKKI/询盘)→ **P4** + `report-templates/google-period-report-excel.md`。
|
|
48
48
|
|
|
49
49
|
拉数落盘:`google-analysis … --json-out <dir>`(Google)或 `yandex-analysis` / `bing-analysis` / `facebook-analysis` / `tiktok-analysis`;目录内生成 `<section>-<accountId>.json` + `manifest-<accountId>.json`(Meta/TikTok/Bing/Yandex 为 `report-manifest-<accountId>.json`)。读盘协议、交付自检与报告首行标注统一见 `references/core/agent-conventions.md` §三、§七。
|
|
50
50
|
|
|
@@ -78,17 +78,17 @@ CLI 出口的所有 JSON / 表格金额已统一为**元**,关键字段:
|
|
|
78
78
|
|
|
79
79
|
## Google 账户分析:`google-analysis` 命令
|
|
80
80
|
|
|
81
|
-
> **重要**:`google-analysis`
|
|
81
|
+
> **重要**:`google-analysis` 是统一入口,所有维度都通过 `--sections` 选取。关键词维度用 `--sections keywords`,**不要**用 `ad keywords` 代替。否词名单用 `--sections negative-keywords`,**不要**另跑 `ad keywords --negative`。涉及「今天/当天」消耗的口径见本文顶部「数据时效性」表。
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
84
|
# 单维度
|
|
85
85
|
siluzan-tso google-analysis -a <id> --sections overview --json-out <dir>
|
|
86
86
|
|
|
87
|
-
# 多维度(周期报告默认
|
|
87
|
+
# 多维度(周期报告默认 9 个拉数维,含 audience 与 negative-keywords)
|
|
88
88
|
siluzan-tso google-analysis -a <id> --start YYYY-MM-DD --end YYYY-MM-DD \
|
|
89
|
-
--sections overview,daily-metrics,dimension-summary,campaigns,devices,geographic,audience,keywords --json-out <dir>
|
|
89
|
+
--sections overview,daily-metrics,dimension-summary,campaigns,devices,geographic,audience,keywords,negative-keywords --json-out <dir>
|
|
90
90
|
|
|
91
|
-
#
|
|
91
|
+
# 全部维度(省略 --sections)
|
|
92
92
|
siluzan-tso google-analysis -a <id> --json-out <dir>
|
|
93
93
|
|
|
94
94
|
# 排除慢/不需要的维度
|
|
@@ -228,7 +228,7 @@
|
|
|
228
228
|
|
|
229
229
|
**A · 币种**:首行含 `统计区间:…(货币:CNY|USD)`;全文符号与 `currencyCode` 一致(CNY=¥、USD=$,未混用);与当次 `list-accounts -k` 结果相同;多账户分币种分表、无跨币种「总计」行。
|
|
230
230
|
|
|
231
|
-
**B · 结构完整**(对照当次 `report-templates/*.md`):模板要求的每一章/Sheet 都存在;无整章空白(缺数据章节写 `[ 数据不可用:… ]`,禁止编造数字填坑);优化建议独立成节、引用当次数字(Meta 周期:四问 + 3 张建议卡 + 各章 analysis/advice;Google 诊断:每模块除表格外有「分析」+「建议」);Excel 的表头列须能在当次 `*.outline.txt` 找到对应字段、产物内账户 ID = 用户当轮给出的 `mediaCustomerId`;**Excel 内全部 ID 列为文本**(见上文「写 Excel 时所有 ID 必须是文本」)。**P6**:磁盘上必须已有 `google-analysis okki-render` 写出的 `.xlsx`(禁止 Agent
|
|
231
|
+
**B · 结构完整**(对照当次 `report-templates/*.md`):模板要求的每一章/Sheet 都存在;无整章空白(缺数据章节写 `[ 数据不可用:… ]`,禁止编造数字填坑);优化建议独立成节、引用当次数字(Meta 周期:四问 + 3 张建议卡 + 各章 analysis/advice;Google 诊断:每模块除表格外有「分析」+「建议」);Excel 的表头列须能在当次 `*.outline.txt` 找到对应字段、产物内账户 ID = 用户当轮给出的 `mediaCustomerId`;**Excel 内全部 ID 列为文本**(见上文「写 Excel 时所有 ID 必须是文本」)。**P6**:磁盘上必须已有 `google-analysis okki-render` 写出的 `.xlsx`(禁止 Agent 手写表)。发给用户的话术是一个代码块,行首为「您好,同步一下谷歌」、`1、消耗:` 至 `7、账户余额:`,①②③ 各一行;禁止改成「一、二、三、四」、合成一段,或在代码块外再写结论。只有用户本轮粘贴骨架或点名增删某一条时才改被点名的行。**P7**:磁盘上必须已有 `.xlsx`。对话 Markdown 表、手写 HTML/PDF **不能**当作终稿通过自检(用户明确只要话术除外)。
|
|
232
232
|
|
|
233
233
|
**C · 数字可信**(抽样,不读大 JSON):总消耗/CPA 数量级与生成过程中脚本 stdout 打印的汇总一致(若无,补跑一次极小 `node -e` 只打印 totals);账户 ID、区间与用户需求一致;无「示例账户」「占位 123456」等模板残留;表格行数符合预期(如 P3 每个 `-a` ID 占一行)。
|
|
234
234
|
|
|
@@ -333,7 +333,7 @@
|
|
|
333
333
|
4. **行动建议**:1~3 条,点名对象并引用数据;若建议含「打开网页 / 去充值 / 去授权」,链接须遵守 §四「禁止编造平台网页地址」,**禁止**随口给一个未经验证的 URL。
|
|
334
334
|
5. **下一步**:一句邀请。客户端 suggestion chips 另展示;**不要**在 skill 各任务文档维护 chips 全文清单。
|
|
335
335
|
|
|
336
|
-
**OKKI
|
|
336
|
+
**OKKI 周报不走上面 5 步。** 最终回复只有两样:一行 xlsx 路径,加一个代码块。代码块从「您好,同步一下谷歌」开始,行首依次是 `1、消耗:`、`2、展示点击:`、`3、点击率:`、`4、平均点击单价:`、`5、转化:`、`5.1`、`5.2`、`6、优化建议:`(下面 ①②③ 各占一行)、`7、账户余额:`。只替换数字和对象,不改这些行首,不合成一段。代码块外面不要写结论、概览、发现、下周动作。用户**本轮**换了格式或贴了新全文,就用用户那一版,仍然只放这一个代码块。细则见 `okki-weekly-google-client.md`「对外客户话术」。
|
|
337
337
|
|
|
338
338
|
长任务(>30s):中间用进度短句,勿长时间沉默后突然扔大表。
|
|
339
339
|
|
|
@@ -269,7 +269,7 @@
|
|
|
269
269
|
| 用户话术(含子串即可) | 工作流 | 备注 |
|
|
270
270
|
| ------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | --------------------------------------------------------- |
|
|
271
271
|
| **网址 / URL / 域名 / 官网 / 落地页** + **诊断 / 检测 / 监测 / 评估 / 体检 / 报告 / 符合投放要求 / 能不能投**(含「网络诊断」「网络检测」) | **P8** | 与 **§零** 同义;**优先 §零**;必 `website-diagnosis` CLI |
|
|
272
|
-
| `OKKI 周报` / `okki 周报` / `使用 okki 周报模板` | **P6** | **不按 P4 默认
|
|
272
|
+
| `OKKI 周报` / `okki 周报` / `使用 okki 周报模板` | **P6** | **不按 P4 默认 10 章**;写 JSON 后 `okki-render` 出 xlsx;「表格形式」= Excel,禁止对话 Markdown 表 / Agent 手写 xlsx |
|
|
273
273
|
| `Google 账户询盘分析` / `分析 … Google 账号的询盘效果`(**且有询盘附件/字段表**) | **P7** | 严格 3 个月窗口;无 CRM → 改 **§零·D / P3** |
|
|
274
274
|
| `转化成本监控` / `CPA 监控` / `谷歌ADS转化成本` / `低余额账户转化成本` | **P3(§零·D)** | `accounts-digest --max-cpa --skip-balance`;贴 `agentBrief.userDelivery` |
|
|
275
275
|
| `昨日零询盘` / `零询盘账户` / `0 转化` / `有消耗无转化`(**无 CRM 附件**) | **P3(§零·D)** | `--zero-conversions`;**不是 P7** |
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
| P1 | 单账户投放画像 | 单户拉数 + 诊断画像 |
|
|
10
10
|
| P2 | 多账户余额扫描 | 续航不足/充值预警巡检 |
|
|
11
11
|
| P3 | 多账户投放画像汇总 | accounts-digest 多户对比表 |
|
|
12
|
-
| P4 | Google 账户周期报告 | 默认
|
|
12
|
+
| P4 | Google 账户周期报告 | 默认 10 章周期汇总(含受众、否词) |
|
|
13
13
|
| P4-FB | Meta/Facebook 周期报告 | facebook-analysis 默认 5 维,默认 HTML |
|
|
14
14
|
| P5 | 多账户多维度批处理 | google-analysis-batch,禁外层 for-loop |
|
|
15
15
|
| P6 | OKKI 周报 | 写 JSON → okki-render 出 5 Sheet xlsx |
|
|
@@ -74,19 +74,19 @@
|
|
|
74
74
|
|
|
75
75
|
## P4 · Google 账户周期报告
|
|
76
76
|
|
|
77
|
-
- **触发**:Google 账户某区间的周期**数据汇总**(只要花费/转化/
|
|
77
|
+
- **触发**:Google 账户某区间的周期**数据汇总**(只要花费/转化/10 章,或用户列出 Sheet 要 **Excel**)。非 OKKI / 询盘 / **非**客户版经营报告。
|
|
78
78
|
- **不是 P10**:出现「月度报告 / 月总结与规划 / 季度报告 / Q3 汇总 / Q4 规划 / 季度汇报 / 投放季报 / 客户版 / 大族模板 / 下月规划 / 效率+区域询盘+国家明细」→ **P10**(即使带「行业参考」也不改 P9)。
|
|
79
79
|
- **必读**:`report-templates/google-period-report.md` + `references/analytics/account-analytics.md`;**要 Excel** 加 `report-templates/google-period-report-excel.md`(全文)。
|
|
80
80
|
- **步骤**:
|
|
81
81
|
1. **账户核验**:`list-accounts -m Google -k <mediaCustomerId> --json-out <dir>`;无记录则停止并告知用户。
|
|
82
82
|
2. 确认时间范围;区间 > 3 个月时分段(季度/月)。
|
|
83
|
-
3. **拉数**:用户已指定 Sheet/维度 → 只拉对应 `--sections`(见 `google-period-report-excel.md` 映射表);未指定 → 按 `google-period-report.md` 默认
|
|
83
|
+
3. **拉数**:用户已指定 Sheet/维度 → 只拉对应 `--sections`(见 `google-period-report-excel.md` 映射表);未指定 → 按 `google-period-report.md` 默认 9 个拉数维(含 `audience`、`negative-keywords`),并可并行追问可选追加。
|
|
84
84
|
4. **门禁**:对每个 section Read `<section>-<accountId>_*.outline.txt` 后再写脚本。
|
|
85
85
|
5. 脚本读盘写产物:默认 **HTML**(`report-template*.html` 版式参考);用户要 **Excel** → Agent 脚本写 xlsx(**禁止**宿主第三方 xlsx Skill;**无** CLI excel 子命令)。
|
|
86
|
-
6. 默认 HTML 报告须含:账户概览、投放趋势、Top
|
|
86
|
+
6. 默认 HTML 报告须含:账户概览、投放趋势、Top 关键词/系列/地区/受众分布、当前否词名单、优化建议;用户定制 Excel 以用户 Sheet 清单为准。
|
|
87
87
|
- **产物**:按 conventions §七 自检;`accountId` 须与用户给的 `mediaCustomerId` 一致。HTML 封面按区间写成「季度」或「月度」,对不上则仍是「周期」。不要打开 HTML 改称呼。
|
|
88
88
|
|
|
89
|
-
> 用户用「OKKI 周报」固定话术 → 改走 **P6**,不按默认
|
|
89
|
+
> 用户用「OKKI 周报」固定话术 → 改走 **P6**,不按默认 10 章追问。
|
|
90
90
|
> 只要账户级按日 Excel(无多维章节)→ **P4-DAILY**。
|
|
91
91
|
|
|
92
92
|
---
|
|
@@ -146,8 +146,8 @@
|
|
|
146
146
|
3. 同一 `--json-out` 目录执行模板命令组合:`stats`、`balance`、`google-analysis --sections overview,campaigns,keywords,search-terms,campaign-device,geo-targeted`。
|
|
147
147
|
4. 脚本读盘 → 只写 `okki-weekly-report.json` 的 `meta` + `narrative`(5 Sheet 表末分析 + 账户 Sheet 固定 5 维数据复盘;表为空也须写)。`deviceType` 是 `Desktop`/`Mobile`,禁止 `.get("DESKTOP")`,按大小写不敏感分类累加后再写设备金额。
|
|
148
148
|
5. `google-analysis okki-render --data ./okki-weekly-report.json --snapshot-dir ./snap-okki --out <xlsx>`。缺分析 / 缺 5 维复盘 / 缺 `campaign-device` 或 `geo-targeted` 会拒绝出文件;按 stderr 补 JSON 后重跑。**禁止** Agent 手写 xlsx。
|
|
149
|
-
6.
|
|
150
|
-
- **产物**:`okki-render` 写出的 `.xlsx` 路径(缺文件 = 未完成)+
|
|
149
|
+
6. **话术收口**(用户本轮没换格式时,行首一个字都不能改):先写一行 xlsx 路径,再贴一个代码块。块内从「您好,同步一下谷歌」起,依次是 `1、消耗:`、`2、展示点击:`、`3、点击率:`、`4、平均点击单价:`、`5、转化:`、`5.1`、`5.2`、`6、优化建议:` 与 ①②③ 各一行、`7、账户余额:`。只填当次数字。禁止合成一段,禁止改成「一、二、三、四」或「结论先行」。代码块外不要再写概览。用户**本轮**粘贴了新全文或点名改某一条,就按用户的来,只动被点名的行,仍然只发这一个代码块。填数规则见 `okki-weekly-google-client.md`。
|
|
150
|
+
- **产物**:`okki-render` 写出的 `.xlsx` 路径(缺文件 = 未完成)+ 上面这个代码块;**不**按 P4 默认 10 章追加。仅用户明确「只要话术 / 不要文件」时才省略 xlsx。
|
|
151
151
|
|
|
152
152
|
---
|
|
153
153
|
|
|
@@ -107,7 +107,7 @@ summary: <1-3 句中文,无业务数字除非来自 manifest>
|
|
|
107
107
|
1. 主 Agent:确认账户与日期区间。
|
|
108
108
|
2. **决策**:拉数阶段若日志长 → Bash/Task + `handoff-p6-okki.md` §拉数;否则主会话执行模板 §拉数命令。
|
|
109
109
|
3. **决策**:写叙事 JSON + `okki-render` → Task handoff(只读 `snapDir`,先 outline 后 JSON,见 `references/core/tips.md`);或主会话若上下文充足。**禁止** Agent 手写 xlsx。
|
|
110
|
-
4. 主 Agent
|
|
110
|
+
4. 主 Agent:最终回复只有 **xlsx 路径** + 一个话术代码块。行首用模板默认(「您好,同步一下谷歌」到 `7、账户余额:`),禁止另写「一、二、三、四」或结论摘要。用户本轮换了格式就用用户的。**没有 `.xlsx` 不得收口**(用户明确只要话术除外)。
|
|
111
111
|
|
|
112
112
|
### P5 · 多账户多维度
|
|
113
113
|
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
| 创建 | `siluzan-tso ad pmax-create --config-file ./pmax.json [--json-out ./snap-pmax]` |
|
|
70
70
|
| 复核 | `siluzan-tso ad campaigns -a <accountId> --json-out ./snap` → `channelTypeV2` 为 `PERFORMANCE_MAX` |
|
|
71
71
|
|
|
72
|
-
**PMax**:`ad pmax-create` **同步**返回 `campaignId`、`assetGroupId`、`budgetId`;无 `ad batch get/diff`。详见 `pmax-api.md`。
|
|
72
|
+
**PMax**:`ad pmax-create` **同步**返回 `campaignId`、`assetGroupId`、`budgetId`;无 `ad batch get/diff`。创建接口 400 才续跑:把比这次请求多出来的非空编号写回同一份 JSON,内容不改,再执行一次。首次创建不填这三个字段。已经 200、只是扩展或视频失败时不要再 `pmax-create`。详见 `pmax-api.md`。
|
|
73
73
|
|
|
74
74
|
**金额**:JSON 中 `budget`、`targetCpa_BidingAmount` 填主币种**元**;CLI 提交前 ×100(与 `ad campaigns` 列表 `budget` 口径一致)。
|
|
75
75
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
| 命令 | 说明 |
|
|
17
17
|
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
18
18
|
| `ad pmax-validate` | 本地校验 |
|
|
19
|
-
| `ad pmax-create` | 创建系列(同步返回 `campaignId`
|
|
19
|
+
| `ad pmax-create` | 创建系列(同步返回 `campaignId` 等;失败时把已建成编号写回 JSON 再跑) |
|
|
20
20
|
| `ad pmax-channel-types` | 渠道类型列表 |
|
|
21
21
|
| `ad pmax-get` | 系列详情(含 `brandAssets[]`;`--json-out` 附加 `_brandGuidelinesActive`、`_agentBrandHint`) |
|
|
22
22
|
| `ad pmax-edit` | 编辑系列(预算/出价/状态;**不可**改 `brandGuidelinesEnabled`) |
|
|
@@ -211,3 +211,5 @@ Google v21+ 新建 PMax **默认开启** BG:`businessName` / Logo 挂在 **Cam
|
|
|
211
211
|
|
|
212
212
|
- 勿对 PMax 使用 `ad campaign-edit`(会 **400**)
|
|
213
213
|
- 新建勿用 `ad campaign-create`;用 `ad pmax-create`
|
|
214
|
+
- 创建接口 400 的正文是 JSON:`message` 加 `budgetId` / `campaignId` / `assetGroupId`,没建成的是 `null`。CLI 会转发 JSON 里的这三个字段;首次创建不填。只有编号比这次请求多时,才写回同一份 JSON、内容不改、再执行一次。编号按预算、系列、素材组带。默认品牌手册开启时不能只写 `campaignId`。同一步再失败时 stdout 是 `retryCreate=false`,停。不带编号再提交会新建一套
|
|
215
|
+
- 创建接口已经 200,只是附加资产或视频失败:不要再 `pmax-create`。扩展用 `ad extension`,视频用 `ad pmax-youtube-link`。stdout 是 `created=true`、`retryCreate=false`
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
| ---------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
16
16
|
| **1. 拉数** | Agent 调 CLI | `google-analysis -a <id> --start <s> --end <e> --json-out ./snap-google --sections <见下方默认维度对应 CLI>` |
|
|
17
17
|
| **2. 分析** | Agent | 用 **node/python 脚本**读落盘 JSON(勿用 Read 打开业务 `*.json`),完成筛选、聚合、排序与洞察 |
|
|
18
|
-
| **3. 写 JSON** | Agent | 按 `assets/google-period-report.schema.json` 撰写 `google-period-report.json`:只写 `meta` + `narrative`(
|
|
19
|
-
| **4. 渲染 HTML** | CLI | `google-analysis render` — **校验 narrative
|
|
18
|
+
| **3. 写 JSON** | Agent | 按 `assets/google-period-report.schema.json` 撰写 `google-period-report.json`:只写 `meta` + `narrative`(9 个数据维 + 优化建议);`kpis`/`charts`/`tables` 留给步骤 4 的 `--snapshot-dir` 合并(合并后缺 KPI 或缺否词表会拒绝出 HTML) |
|
|
19
|
+
| **4. 渲染 HTML** | CLI | `google-analysis render` — **校验 narrative 10 章必含字段**(含受众、否词),缺项报错不生成 HTML;**禁止** Agent 手写/拼接 HTML |
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
# 步骤 1
|
|
23
23
|
siluzan-tso google-analysis -a <id> --start <s> --end <e> --json-out ./snap-google \
|
|
24
|
-
--sections overview,daily-metrics,dimension-summary,campaigns,devices,geographic,audience,keywords
|
|
24
|
+
--sections overview,daily-metrics,dimension-summary,campaigns,devices,geographic,audience,keywords,negative-keywords
|
|
25
25
|
|
|
26
26
|
# 步骤 4(步骤 2~3 完成后)
|
|
27
27
|
siluzan-tso google-analysis render \
|
|
@@ -35,10 +35,10 @@ siluzan-tso google-analysis render \
|
|
|
35
35
|
- `meta`:`accountId`(必填)/ `accountName` / `currency` / `startDate` / `endDate`;可省略由 `--snapshot-dir` 从 `overview` + manifest 自动合并。
|
|
36
36
|
- `kpis`:账户级 KPI(消耗/展示/点击/转化/CTR/CPC/CPA,含可选 `previousPeriod` 环比;另含 `activeDays` / `averageDailyCost`);Agent 可省略,但 **render 合并后** `cost`/`impressions`/`clicks` 至少一项须为有效数字,否则拒绝出 HTML(避免执行摘要卡片全「—」)。本期 KPI 由 CLI 优先对 `campaigns` 全系列累加并重算比率;无 campaigns 时回退 `overview.currentPeriod`,再回退 `dimension-summary`。`activeDays` / `averageDailyCost` 来自 `overview.record` 根(**不是** `currentPeriod`);环比仍来自 `overview.previousPeriod`(上期无活跃天数,不编造)。
|
|
37
37
|
- `charts.dailyTrend`:每日趋势(`dates`/`cost`/`conversions`/`cpa`);可省略由 `daily-metrics` 自动合并。
|
|
38
|
-
- `tables.{dimensionSummary,campaigns,devices,geographic,audience,keywords}`:对应 §3~§
|
|
38
|
+
- `tables.{dimensionSummary,campaigns,devices,geographic,audience,keywords,negativeKeywords}`:对应 §3~§9 表格数据;可省略由同名 `google-analysis` section 自动合并。`negativeKeywords` 合并后必须是数组,**0 条也是 `[]`**。
|
|
39
39
|
- `narrative`(**Agent 必填,唯一由 Agent 撰写的内容**):
|
|
40
40
|
- `executiveSummary[]`:≥1 段执行摘要
|
|
41
|
-
- `sections.{overview,dailyTrend,dimensionSummary,campaigns,devices,geographic,audience,keywords}`:每个维度各 `{analysis[](≥2 条), suggestions[](≥1 条)}`,对应报告 §1~§
|
|
41
|
+
- `sections.{overview,dailyTrend,dimensionSummary,campaigns,devices,geographic,audience,keywords,negativeKeywords}`:每个维度各 `{analysis[](≥2 条), suggestions[](≥1 条)}`,对应报告 §1~§9
|
|
42
42
|
- `recommendations[]`:≥3 条跨维度优化建议,对应报告 §9
|
|
43
43
|
|
|
44
44
|
完整字段定义见 `assets/google-period-report.schema.json`。
|
|
@@ -59,7 +59,7 @@ siluzan-tso google-analysis render \
|
|
|
59
59
|
|
|
60
60
|
## 默认报告维度
|
|
61
61
|
|
|
62
|
-
生成报告时,**默认包含**以下
|
|
62
|
+
生成报告时,**默认包含**以下 10 章(9 个拉数维 + 优化建议):
|
|
63
63
|
|
|
64
64
|
| # | 维度 | CLI |
|
|
65
65
|
| --- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
@@ -71,7 +71,8 @@ siluzan-tso google-analysis render \
|
|
|
71
71
|
| 6 | 地域分布(国家/地区 消耗占比) | `google-analysis --sections geographic` |
|
|
72
72
|
| 7 | 受众分布(意向/兴趣/再营销/客户匹配等列表表现) | `google-analysis --sections audience`(默认不传 `--audience-type`,系统+自定义一起拉) |
|
|
73
73
|
| 8 | 关键词表现(词/消耗/CTR/CPC 排行) | `google-analysis --sections keywords`(这个命令获取的关键词数据会来自多个系列,不能合并去重) |
|
|
74
|
-
| 9 |
|
|
74
|
+
| 9 | 否词(当前账户已有否定关键词:词面 / 匹配 / 级别 / 系列 / 广告组) | `google-analysis --sections negative-keywords`(0 条也要落盘;名单不按本周期消耗过滤) |
|
|
75
|
+
| 10 | 优化建议(根据以上数据给出可执行改进建议) | 不额外拉数,基于已有数据撰写 |
|
|
75
76
|
|
|
76
77
|
**在执行任何数据拉取之前**,先向用户展示以下可选维度,询问是否需要追加:
|
|
77
78
|
|
|
@@ -95,17 +96,17 @@ siluzan-tso google-analysis render \
|
|
|
95
96
|
|
|
96
97
|
---
|
|
97
98
|
|
|
98
|
-
## 拉数顺序(默认
|
|
99
|
+
## 拉数顺序(默认 9 个维度)
|
|
99
100
|
|
|
100
101
|
**首选:用 `google-analysis --sections` 一次拉齐**(单进程复用 keep-alive,比逐个 spawn 快 3-7×;详见 `references/analytics/account-analytics.md` 的 `all` 子命令说明):
|
|
101
102
|
|
|
102
103
|
```bash
|
|
103
104
|
mkdir -p ./snap-google
|
|
104
105
|
siluzan-tso google-analysis -a <id> --start <s> --end <e> --json-out ./snap-google \
|
|
105
|
-
--sections overview,daily-metrics,dimension-summary,campaigns,devices,geographic,audience,keywords
|
|
106
|
+
--sections overview,daily-metrics,dimension-summary,campaigns,devices,geographic,audience,keywords,negative-keywords
|
|
106
107
|
```
|
|
107
108
|
|
|
108
|
-
如果用户**追加**了维度(按上文「可选追加」表),把它们加入 `--sections`
|
|
109
|
+
如果用户**追加**了维度(按上文「可选追加」表),把它们加入 `--sections` 即可;要全部维度则省略 `--sections`。
|
|
109
110
|
|
|
110
111
|
> 仅在调试单个维度或需要传 `all` 暂未透传的特殊参数时,才回落到逐条 `siluzan-tso google-analysis <子命令> -a <id> …` 调用。
|
|
111
112
|
|
|
@@ -134,3 +135,16 @@ siluzan-tso google-analysis -a <id> --start <s> --end <e> --json-out ./snap-goog
|
|
|
134
135
|
- 区分系统意向/兴趣 vs 再营销/客户匹配;空耗列表建议缩小或排除,高效列表建议观察后再加码。
|
|
135
136
|
- 无行时写「本期间无受众行 / 未挂受众列表」,**禁止**编造年龄段或兴趣饼。
|
|
136
137
|
- 未确认前不要写自动改出价/排除命令。
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 否词(必写)
|
|
142
|
+
|
|
143
|
+
`negative-keywords-*.json` → `items[]`,合并后是 `tables.negativeKeywords[]`。这是**当前账户已经存在的否定关键词**(系列 / 广告组 / 账户三级),不是本周期的消耗或搜索词。
|
|
144
|
+
|
|
145
|
+
- **呈现**:词面、匹配方式、级别(`levelZh`:账户 / 系列 / 广告组)、广告系列、广告组。0 条时表格写「当前账户否词 0 条」。
|
|
146
|
+
- **分析** → `narrative.sections.negativeKeywords.{analysis,suggestions}`(≥2 / ≥1):
|
|
147
|
+
- 先写总条数,再按级别拆开。有词就点名几条;0 条就写「当前否词 0 条」。
|
|
148
|
+
- 其他章节如果建议加否词、出否词清单,必须引用这张表的条数。禁止在没拉到名单时写「账户没有否词」或只在建议里提否词。
|
|
149
|
+
- 这张表不说明该否哪些搜索词。要谈浪费搜索词,须另拉 `search-terms`,并对照本表里已有的否词。
|
|
150
|
+
- 未确认前不要执行 `ad keyword-negative-create`。
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
-
识别到上述意图时:**不要**再走 `google-period-report.md` 的「默认
|
|
26
|
+
识别到上述意图时:**不要**再走 `google-period-report.md` 的「默认 10 章 + 追问追加」流程;按本文固定维度拉数,`okki-render` 出 5 Sheet `.xlsx`。**先 `okki-render` 再发话术**;没有 `.xlsx` 路径不得收口。发给用户的话术默认就是下文那个代码块:只换数字,不换行首。用户本轮另给了全文或点名改某一条,才改被点名的部分。
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -241,13 +241,14 @@ function deviceKind(deviceType) {
|
|
|
241
241
|
|
|
242
242
|
## 对外客户话术(默认结构,填数来自 JSON)
|
|
243
243
|
|
|
244
|
-
>
|
|
244
|
+
> 话术由 Agent 填数,**不要写脚本生成**。用户没换格式时,下面代码块就是终稿:复制行首,只替换尖括号里的内容。
|
|
245
245
|
|
|
246
|
-
|
|
246
|
+
**发给用户时只有两样东西:**
|
|
247
247
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
248
|
+
1. 一行文件路径(xlsx)
|
|
249
|
+
2. 下面这一个代码块
|
|
250
|
+
|
|
251
|
+
代码块外面不要写「本周结论 / 概览 / 发现 / 下周动作」。`agent-conventions.md` 的「结论先行」五步**不用于**这次回复。
|
|
251
252
|
|
|
252
253
|
```
|
|
253
254
|
您好,同步一下谷歌<中文日期>投放的情况:
|
|
@@ -266,15 +267,31 @@ function deviceKind(deviceType) {
|
|
|
266
267
|
7、账户余额:<金额>(截至查询时点)。
|
|
267
268
|
```
|
|
268
269
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
**未获本轮用户改骨架时,禁止:**
|
|
270
|
+
换行是骨架的一部分。`1`~`5`、`5.1`、`5.2`、`7` 各占一行,行末用中文分号 `;`。`6、优化建议:` 单独一行,下面 **3~5 条**用 ①②③④⑤,每条一行。日期写成 `2026年4月1日-4月7日`(同年可省略结束年份),须与 `--start` / `--end` 对应。
|
|
272
271
|
|
|
273
|
-
|
|
274
|
-
- 把上一轮、另一个账户、对话里出现过的话术当成这次的骨架
|
|
275
|
-
- 用「结论先行」重写可复制块。运营摘要最多 3~5 行,放在代码块**外面**
|
|
272
|
+
**发出去之前对一下行首**(用户本轮没换格式时,缺一条或改了一个行首都重写这块,不要解释):
|
|
276
273
|
|
|
277
|
-
|
|
274
|
+
| 行首 | 必须长这样 |
|
|
275
|
+
| --- | --- |
|
|
276
|
+
| 开头 | `您好,同步一下谷歌` |
|
|
277
|
+
| 1 | `1、消耗:` |
|
|
278
|
+
| 2 | `2、展示点击:` |
|
|
279
|
+
| 3 | `3、点击率:` |
|
|
280
|
+
| 4 | `4、平均点击单价:` |
|
|
281
|
+
| 5 | `5、转化:` |
|
|
282
|
+
| 5.1 | `5.1` 且含「有转化的关键词」 |
|
|
283
|
+
| 5.2 | `5.2` 且含「有转化的国家」 |
|
|
284
|
+
| 6 | 单独一行 `6、优化建议:`,①②③ 不写在这一行 |
|
|
285
|
+
| 7 | `7、账户余额:` |
|
|
286
|
+
|
|
287
|
+
**这些都算没按格式,改回上面的行首再发:**
|
|
288
|
+
|
|
289
|
+
- 合成一段,或把 ①②③ 写进第 6 行
|
|
290
|
+
- 「一、本周概览」「二、本周发现」「三、下周优化」「四、需要您配合」
|
|
291
|
+
- 代码块前先写一段结论、要点、核心发现
|
|
292
|
+
- 沿用上一轮、另一个账户、或对话里见过的另一套话术
|
|
293
|
+
|
|
294
|
+
**用户要换格式时很好改:** 本轮粘贴了全文,或点名「不要 5.1」「第 3 条改成…」,就按用户的写,只动被点名的行。没点名的行仍用默认行首。数字仍来自当次 JSON,缺数写 `[数据不可用:原因]`。不要因为上一轮换过格式,这一轮就继续用旧骨架。
|
|
278
295
|
|
|
279
296
|
### 填数规则
|
|
280
297
|
|
|
@@ -807,6 +807,7 @@
|
|
|
807
807
|
<a href="#sec-geographic">地域分布</a>
|
|
808
808
|
<a href="#sec-audience">受众分布</a>
|
|
809
809
|
<a href="#sec-keywords">关键词</a>
|
|
810
|
+
<a href="#sec-negative-keywords">否词</a>
|
|
810
811
|
<a href="#sec-recommendations">优化建议</a>
|
|
811
812
|
</div>
|
|
812
813
|
</div>`;
|
|
@@ -1096,6 +1097,59 @@
|
|
|
1096
1097
|
</section>`;
|
|
1097
1098
|
}
|
|
1098
1099
|
|
|
1100
|
+
function renderNegativeKeywords(data) {
|
|
1101
|
+
const rows = Array.isArray(data.tables?.negativeKeywords)
|
|
1102
|
+
? data.tables.negativeKeywords
|
|
1103
|
+
: null;
|
|
1104
|
+
const section = data.narrative?.sections?.negativeKeywords;
|
|
1105
|
+
const head = sectionHead(
|
|
1106
|
+
"09",
|
|
1107
|
+
"否词",
|
|
1108
|
+
"当前账户已有否定关键词(系列 / 广告组 / 账户),不是本周期消耗",
|
|
1109
|
+
);
|
|
1110
|
+
const levelOrder = { 账户: 0, 系列: 1, 广告组: 2 };
|
|
1111
|
+
const sorted =
|
|
1112
|
+
rows == null
|
|
1113
|
+
? []
|
|
1114
|
+
: [...rows].sort((a, b) => {
|
|
1115
|
+
const la = levelOrder[a.levelZh] ?? 9;
|
|
1116
|
+
const lb = levelOrder[b.levelZh] ?? 9;
|
|
1117
|
+
if (la !== lb) return la - lb;
|
|
1118
|
+
return String(a.keyword || "").localeCompare(String(b.keyword || ""));
|
|
1119
|
+
});
|
|
1120
|
+
const shown = sorted.slice(0, 100);
|
|
1121
|
+
const body = shown
|
|
1122
|
+
.map(
|
|
1123
|
+
(r) => `
|
|
1124
|
+
<tr>
|
|
1125
|
+
<td>${escapeHtml(r.keyword || "—")}</td>
|
|
1126
|
+
<td>${escapeHtml(r.matchType || "—")}</td>
|
|
1127
|
+
<td>${escapeHtml(r.levelZh || r.level || "—")}</td>
|
|
1128
|
+
<td>${escapeHtml(r.campaignName || "—")}</td>
|
|
1129
|
+
<td>${escapeHtml(r.adGroupName || "—")}</td>
|
|
1130
|
+
</tr>`,
|
|
1131
|
+
)
|
|
1132
|
+
.join("");
|
|
1133
|
+
const table =
|
|
1134
|
+
rows == null
|
|
1135
|
+
? '<div class="empty-note">数据不可用:negative-keywords 未拉取</div>'
|
|
1136
|
+
: shown.length > 0
|
|
1137
|
+
? `<div class="table-wrap"><table>
|
|
1138
|
+
<thead><tr><th>否词</th><th>匹配方式</th><th>级别</th><th>广告系列</th><th>广告组</th></tr></thead>
|
|
1139
|
+
<tbody>${body}</tbody>
|
|
1140
|
+
</table></div>
|
|
1141
|
+
<div class="card-title-row" style="margin-top:10px"><span class="hint">当前否词 ${rows.length} 条,表格最多展示 100 条</span></div>`
|
|
1142
|
+
: '<div class="empty-note">当前账户否词 0 条</div>';
|
|
1143
|
+
return `
|
|
1144
|
+
<section id="sec-negative-keywords">
|
|
1145
|
+
${head}
|
|
1146
|
+
<div class="card">
|
|
1147
|
+
${table}
|
|
1148
|
+
${analysisBlock(section)}
|
|
1149
|
+
</div>
|
|
1150
|
+
</section>`;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1099
1153
|
function renderRecommendations(data) {
|
|
1100
1154
|
const recs = Array.isArray(data.narrative?.recommendations)
|
|
1101
1155
|
? data.narrative.recommendations
|
|
@@ -1116,7 +1170,7 @@
|
|
|
1116
1170
|
: '<div class="empty-note">(待 Agent 撰写优化建议)</div>';
|
|
1117
1171
|
return `
|
|
1118
1172
|
<section id="sec-recommendations">
|
|
1119
|
-
${sectionHead("
|
|
1173
|
+
${sectionHead("10", "优化建议", "综合以上数据的可执行改进建议")}
|
|
1120
1174
|
<div class="card">${body}</div>
|
|
1121
1175
|
</section>`;
|
|
1122
1176
|
}
|
|
@@ -1397,6 +1451,7 @@
|
|
|
1397
1451
|
renderGeographic(data),
|
|
1398
1452
|
renderAudience(data),
|
|
1399
1453
|
renderKeywords(data),
|
|
1454
|
+
renderNegativeKeywords(data),
|
|
1400
1455
|
renderRecommendations(data),
|
|
1401
1456
|
renderFooter(data),
|
|
1402
1457
|
].join("\n");
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
| ---------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
16
16
|
| **1. 拉数** | Agent 调 CLI | `google-analysis -a <id> --start <s> --end <e> --json-out ./snap-google --sections <见下方默认维度对应 CLI>` |
|
|
17
17
|
| **2. 分析** | Agent | 用 **node/python 脚本**读落盘 JSON(勿用 Read 打开业务 `*.json`),完成筛选、聚合、排序与洞察 |
|
|
18
|
-
| **3. 写 JSON** | Agent | 按 `assets/google-period-report.schema.json` 撰写 `google-period-report.json`:只写 `meta` + `narrative`(
|
|
19
|
-
| **4. 渲染 HTML** | CLI | `google-analysis render` — **校验 narrative
|
|
18
|
+
| **3. 写 JSON** | Agent | 按 `assets/google-period-report.schema.json` 撰写 `google-period-report.json`:只写 `meta` + `narrative`(9 个数据维 + 优化建议);`kpis`/`charts`/`tables` 留给步骤 4 的 `--snapshot-dir` 合并(合并后缺 KPI 或缺否词表会拒绝出 HTML) |
|
|
19
|
+
| **4. 渲染 HTML** | CLI | `google-analysis render` — **校验 narrative 10 章必含字段**(含受众、否词),缺项报错不生成 HTML;**禁止** Agent 手写/拼接 HTML |
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
# 步骤 1
|
|
23
23
|
siluzan-tso google-analysis -a <id> --start <s> --end <e> --json-out ./snap-google \
|
|
24
|
-
--sections overview,daily-metrics,dimension-summary,campaigns,devices,geographic,audience,keywords
|
|
24
|
+
--sections overview,daily-metrics,dimension-summary,campaigns,devices,geographic,audience,keywords,negative-keywords
|
|
25
25
|
|
|
26
26
|
# 步骤 4(步骤 2~3 完成后)
|
|
27
27
|
siluzan-tso google-analysis render \
|
|
@@ -35,10 +35,10 @@ siluzan-tso google-analysis render \
|
|
|
35
35
|
- `meta`:`accountId`(必填)/ `accountName` / `currency` / `startDate` / `endDate`;可省略由 `--snapshot-dir` 从 `overview` + manifest 自动合并。
|
|
36
36
|
- `kpis`:账户级 KPI(消耗/展示/点击/转化/CTR/CPC/CPA,含可选 `previousPeriod` 环比;另含 `activeDays` / `averageDailyCost`);Agent 可省略,但 **render 合并后** `cost`/`impressions`/`clicks` 至少一项须为有效数字,否则拒绝出 HTML(避免执行摘要卡片全「—」)。本期 KPI 由 CLI 优先对 `campaigns` 全系列累加并重算比率;无 campaigns 时回退 `overview.currentPeriod`,再回退 `dimension-summary`。`activeDays` / `averageDailyCost` 来自 `overview.record` 根(**不是** `currentPeriod`);环比仍来自 `overview.previousPeriod`(上期无活跃天数,不编造)。
|
|
37
37
|
- `charts.dailyTrend`:每日趋势(`dates`/`cost`/`conversions`/`cpa`);可省略由 `daily-metrics` 自动合并。
|
|
38
|
-
- `tables.{dimensionSummary,campaigns,devices,geographic,audience,keywords}`:对应 §3~§
|
|
38
|
+
- `tables.{dimensionSummary,campaigns,devices,geographic,audience,keywords,negativeKeywords}`:对应 §3~§9 表格数据;可省略由同名 `google-analysis` section 自动合并。`negativeKeywords` 合并后必须是数组,**0 条也是 `[]`**。
|
|
39
39
|
- `narrative`(**Agent 必填,唯一由 Agent 撰写的内容**):
|
|
40
40
|
- `executiveSummary[]`:≥1 段执行摘要
|
|
41
|
-
- `sections.{overview,dailyTrend,dimensionSummary,campaigns,devices,geographic,audience,keywords}`:每个维度各 `{analysis[](≥2 条), suggestions[](≥1 条)}`,对应报告 §1~§
|
|
41
|
+
- `sections.{overview,dailyTrend,dimensionSummary,campaigns,devices,geographic,audience,keywords,negativeKeywords}`:每个维度各 `{analysis[](≥2 条), suggestions[](≥1 条)}`,对应报告 §1~§9
|
|
42
42
|
- `recommendations[]`:≥3 条跨维度优化建议,对应报告 §9
|
|
43
43
|
|
|
44
44
|
完整字段定义见 `assets/google-period-report.schema.json`。
|
|
@@ -59,7 +59,7 @@ siluzan-tso google-analysis render \
|
|
|
59
59
|
|
|
60
60
|
## 默认报告维度
|
|
61
61
|
|
|
62
|
-
生成报告时,**默认包含**以下
|
|
62
|
+
生成报告时,**默认包含**以下 10 章(9 个拉数维 + 优化建议):
|
|
63
63
|
|
|
64
64
|
| # | 维度 | CLI |
|
|
65
65
|
| --- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
@@ -71,7 +71,8 @@ siluzan-tso google-analysis render \
|
|
|
71
71
|
| 6 | 地域分布(国家/地区 消耗占比) | `google-analysis --sections geographic` |
|
|
72
72
|
| 7 | 受众分布(意向/兴趣/再营销/客户匹配等列表表现) | `google-analysis --sections audience`(默认不传 `--audience-type`,系统+自定义一起拉) |
|
|
73
73
|
| 8 | 关键词表现(词/消耗/CTR/CPC 排行) | `google-analysis --sections keywords`(这个命令获取的关键词数据会来自多个系列,不能合并去重) |
|
|
74
|
-
| 9 |
|
|
74
|
+
| 9 | 否词(当前账户已有否定关键词:词面 / 匹配 / 级别 / 系列 / 广告组) | `google-analysis --sections negative-keywords`(0 条也要落盘;名单不按本周期消耗过滤) |
|
|
75
|
+
| 10 | 优化建议(根据以上数据给出可执行改进建议) | 不额外拉数,基于已有数据撰写 |
|
|
75
76
|
|
|
76
77
|
**在执行任何数据拉取之前**,先向用户展示以下可选维度,询问是否需要追加:
|
|
77
78
|
|
|
@@ -95,17 +96,17 @@ siluzan-tso google-analysis render \
|
|
|
95
96
|
|
|
96
97
|
---
|
|
97
98
|
|
|
98
|
-
## 拉数顺序(默认
|
|
99
|
+
## 拉数顺序(默认 9 个维度)
|
|
99
100
|
|
|
100
101
|
**首选:用 `google-analysis --sections` 一次拉齐**(单进程复用 keep-alive,比逐个 spawn 快 3-7×;详见 `references/analytics/account-analytics.md` 的 `all` 子命令说明):
|
|
101
102
|
|
|
102
103
|
```bash
|
|
103
104
|
mkdir -p ./snap-google
|
|
104
105
|
siluzan-tso google-analysis -a <id> --start <s> --end <e> --json-out ./snap-google \
|
|
105
|
-
--sections overview,daily-metrics,dimension-summary,campaigns,devices,geographic,audience,keywords
|
|
106
|
+
--sections overview,daily-metrics,dimension-summary,campaigns,devices,geographic,audience,keywords,negative-keywords
|
|
106
107
|
```
|
|
107
108
|
|
|
108
|
-
如果用户**追加**了维度(按上文「可选追加」表),把它们加入 `--sections`
|
|
109
|
+
如果用户**追加**了维度(按上文「可选追加」表),把它们加入 `--sections` 即可;要全部维度则省略 `--sections`。
|
|
109
110
|
|
|
110
111
|
> 仅在调试单个维度或需要传 `all` 暂未透传的特殊参数时,才回落到逐条 `siluzan-tso google-analysis <子命令> -a <id> …` 调用。
|
|
111
112
|
|
|
@@ -134,3 +135,16 @@ siluzan-tso google-analysis -a <id> --start <s> --end <e> --json-out ./snap-goog
|
|
|
134
135
|
- 区分系统意向/兴趣 vs 再营销/客户匹配;空耗列表建议缩小或排除,高效列表建议观察后再加码。
|
|
135
136
|
- 无行时写「本期间无受众行 / 未挂受众列表」,**禁止**编造年龄段或兴趣饼。
|
|
136
137
|
- 未确认前不要写自动改出价/排除命令。
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 否词(必写)
|
|
142
|
+
|
|
143
|
+
`negative-keywords-*.json` → `items[]`,合并后是 `tables.negativeKeywords[]`。这是**当前账户已经存在的否定关键词**(系列 / 广告组 / 账户三级),不是本周期的消耗或搜索词。
|
|
144
|
+
|
|
145
|
+
- **呈现**:词面、匹配方式、级别(`levelZh`:账户 / 系列 / 广告组)、广告系列、广告组。0 条时表格写「当前账户否词 0 条」。
|
|
146
|
+
- **分析** → `narrative.sections.negativeKeywords.{analysis,suggestions}`(≥2 / ≥1):
|
|
147
|
+
- 先写总条数,再按级别拆开。有词就点名几条;0 条就写「当前否词 0 条」。
|
|
148
|
+
- 其他章节如果建议加否词、出否词清单,必须引用这张表的条数。禁止在没拉到名单时写「账户没有否词」或只在建议里提否词。
|
|
149
|
+
- 这张表不说明该否哪些搜索词。要谈浪费搜索词,须另拉 `search-terms`,并对照本表里已有的否词。
|
|
150
|
+
- 未确认前不要执行 `ad keyword-negative-create`。
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
-
识别到上述意图时:**不要**再走 `google-period-report.md` 的「默认
|
|
26
|
+
识别到上述意图时:**不要**再走 `google-period-report.md` 的「默认 10 章 + 追问追加」流程;按本文固定维度拉数,`okki-render` 出 5 Sheet `.xlsx`。**先 `okki-render` 再发话术**;没有 `.xlsx` 路径不得收口。发给用户的话术默认就是下文那个代码块:只换数字,不换行首。用户本轮另给了全文或点名改某一条,才改被点名的部分。
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -241,13 +241,14 @@ function deviceKind(deviceType) {
|
|
|
241
241
|
|
|
242
242
|
## 对外客户话术(默认结构,填数来自 JSON)
|
|
243
243
|
|
|
244
|
-
>
|
|
244
|
+
> 话术由 Agent 填数,**不要写脚本生成**。用户没换格式时,下面代码块就是终稿:复制行首,只替换尖括号里的内容。
|
|
245
245
|
|
|
246
|
-
|
|
246
|
+
**发给用户时只有两样东西:**
|
|
247
247
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
248
|
+
1. 一行文件路径(xlsx)
|
|
249
|
+
2. 下面这一个代码块
|
|
250
|
+
|
|
251
|
+
代码块外面不要写「本周结论 / 概览 / 发现 / 下周动作」。`agent-conventions.md` 的「结论先行」五步**不用于**这次回复。
|
|
251
252
|
|
|
252
253
|
```
|
|
253
254
|
您好,同步一下谷歌<中文日期>投放的情况:
|
|
@@ -266,15 +267,31 @@ function deviceKind(deviceType) {
|
|
|
266
267
|
7、账户余额:<金额>(截至查询时点)。
|
|
267
268
|
```
|
|
268
269
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
**未获本轮用户改骨架时,禁止:**
|
|
270
|
+
换行是骨架的一部分。`1`~`5`、`5.1`、`5.2`、`7` 各占一行,行末用中文分号 `;`。`6、优化建议:` 单独一行,下面 **3~5 条**用 ①②③④⑤,每条一行。日期写成 `2026年4月1日-4月7日`(同年可省略结束年份),须与 `--start` / `--end` 对应。
|
|
272
271
|
|
|
273
|
-
|
|
274
|
-
- 把上一轮、另一个账户、对话里出现过的话术当成这次的骨架
|
|
275
|
-
- 用「结论先行」重写可复制块。运营摘要最多 3~5 行,放在代码块**外面**
|
|
272
|
+
**发出去之前对一下行首**(用户本轮没换格式时,缺一条或改了一个行首都重写这块,不要解释):
|
|
276
273
|
|
|
277
|
-
|
|
274
|
+
| 行首 | 必须长这样 |
|
|
275
|
+
| --- | --- |
|
|
276
|
+
| 开头 | `您好,同步一下谷歌` |
|
|
277
|
+
| 1 | `1、消耗:` |
|
|
278
|
+
| 2 | `2、展示点击:` |
|
|
279
|
+
| 3 | `3、点击率:` |
|
|
280
|
+
| 4 | `4、平均点击单价:` |
|
|
281
|
+
| 5 | `5、转化:` |
|
|
282
|
+
| 5.1 | `5.1` 且含「有转化的关键词」 |
|
|
283
|
+
| 5.2 | `5.2` 且含「有转化的国家」 |
|
|
284
|
+
| 6 | 单独一行 `6、优化建议:`,①②③ 不写在这一行 |
|
|
285
|
+
| 7 | `7、账户余额:` |
|
|
286
|
+
|
|
287
|
+
**这些都算没按格式,改回上面的行首再发:**
|
|
288
|
+
|
|
289
|
+
- 合成一段,或把 ①②③ 写进第 6 行
|
|
290
|
+
- 「一、本周概览」「二、本周发现」「三、下周优化」「四、需要您配合」
|
|
291
|
+
- 代码块前先写一段结论、要点、核心发现
|
|
292
|
+
- 沿用上一轮、另一个账户、或对话里见过的另一套话术
|
|
293
|
+
|
|
294
|
+
**用户要换格式时很好改:** 本轮粘贴了全文,或点名「不要 5.1」「第 3 条改成…」,就按用户的写,只动被点名的行。没点名的行仍用默认行首。数字仍来自当次 JSON,缺数写 `[数据不可用:原因]`。不要因为上一轮换过格式,这一轮就继续用旧骨架。
|
|
278
295
|
|
|
279
296
|
### 填数规则
|
|
280
297
|
|
|
@@ -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.59-beta.
|
|
12
|
+
$PKG_VERSION = '1.1.59-beta.3'
|
|
13
13
|
$CLI_BIN = 'siluzan-tso'
|
|
14
14
|
$SKILL_LABEL = 'Siluzan TSO'
|
|
15
15
|
$INSTALL_CMD = 'npm install -g siluzan-tso-cli@beta'
|
|
@@ -9,7 +9,7 @@ set -euo pipefail
|
|
|
9
9
|
# -- Package info (injected at build time) ------------------------------------
|
|
10
10
|
readonly PKG_NAME="siluzan-tso-cli"
|
|
11
11
|
# PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
|
|
12
|
-
readonly PKG_VERSION="1.1.59-beta.
|
|
12
|
+
readonly PKG_VERSION="1.1.59-beta.3"
|
|
13
13
|
readonly CLI_BIN="siluzan-tso"
|
|
14
14
|
readonly SKILL_LABEL="Siluzan TSO"
|
|
15
15
|
readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
|
|
@@ -45,7 +45,7 @@ siluzan-tso google-analysis -a <mediaCustomerId> --start <S> --end <E> --json-ou
|
|
|
45
45
|
|
|
46
46
|
**任务**:
|
|
47
47
|
|
|
48
|
-
1. Read `report-templates/okki-weekly-google-client.md`
|
|
48
|
+
1. Read `report-templates/okki-weekly-google-client.md` 的 **§对外客户话术**。交回的话术是一个代码块:行首为「您好,同步一下谷歌」、`1、消耗:`、`2、展示点击:`、`3、点击率:`、`4、平均点击单价:`、`5、转化:`、`5.1`、`5.2`、`6、优化建议:`、①②③ 各一行、`7、账户余额:`。主 Agent 若写明用户本轮换了骨架,照那个骨架交回,不要自己再改一版。
|
|
49
49
|
2. 按 `assets/okki-weekly-report.schema.json` 写 `okki-weekly-report.json`:只填 `meta` + `narrative.sheetAnalysis`(5 Sheet,各 summary≥1 / suggestions≥1)+ `narrative.review`(固定 5 维:账户/关键词/搜索字词/设备/国家,每维 overview+summary+suggestion)。表为空也须写分析。`campaign-device` 的 `deviceType` 是 `Desktop`/`Mobile`/`Tablet`(不是 `DESKTOP`/`MOBILE`);设备消耗按 `toLowerCase()` / `includes("desktop")` 分类累加,**禁止** `.get("DESKTOP")`。
|
|
50
50
|
3. 执行:
|
|
51
51
|
```bash
|
|
@@ -57,4 +57,4 @@ siluzan-tso google-analysis -a <mediaCustomerId> --start <S> --end <E> --json-ou
|
|
|
57
57
|
失败则按 stderr 补 JSON 后重跑,不得改用手写表。
|
|
58
58
|
4. 设备/国家必须来自 `campaign-device` / `geo-targeted`(由 render 校验)。国家不要用 `campaign-geo-matched`,那会把已移除的国家算进来。
|
|
59
59
|
|
|
60
|
-
**returnSchema**: xlsx 路径 + `okki-render` exitCode +
|
|
60
|
+
**returnSchema**: xlsx 路径 + `okki-render` exitCode + 客户话术代码块(行首见任务 1;用户本轮换过骨架则用用户的)。勿贴 xlsx 二进制,勿另附「结论先行」摘要。
|