siluzan-tso-cli 1.1.20-beta.15 → 1.1.20-beta.16
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 +49 -356
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/assets/campaign-create-template.md +1 -2
- package/dist/skill/references/google-ads-campaign-plan.md +5 -7
- package/dist/skill/references/google-ads-rules/google-ads-campaign-optimization.md +1 -15
- package/dist/skill/references/google-ads-rules/google-ads-keyword-taxonomy.md +13 -32
- package/dist/skill/references/google-ads-rules/google-ads-launch-plan-template.md +5 -5
- package/dist/skill/references/google-ads.md +2 -2
- package/dist/skill/references/keyword-planner-workflows.md +1 -1
- package/dist/skill/scripts/install.ps1 +1 -1
- package/dist/skill/scripts/install.sh +1 -1
- package/eval/cases/uj-ad-campaign-validate-before-create-stub.scenario.json +1 -1
- package/eval/cases/uj-ad-keywords-camping-tent-outdoor-plan.scenario.json +1 -1
- package/eval/cases/uj-ad-outdoor-campgear-search-plan.scenario.json +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.20-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.20-beta.16),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -110086,292 +110086,6 @@ function validateCampaignRsaCrossGroupHeadlines(campaign, errors) {
|
|
|
110086
110086
|
}
|
|
110087
110087
|
}
|
|
110088
110088
|
|
|
110089
|
-
// src/commands/ad/campaign-launch-strategy.ts
|
|
110090
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
110091
|
-
var KEYWORD_TIERS = ["core", "longtail"];
|
|
110092
|
-
var KEYWORD_CLASSES = [
|
|
110093
|
-
"product",
|
|
110094
|
-
"service",
|
|
110095
|
-
"pain",
|
|
110096
|
-
"competitor",
|
|
110097
|
-
"industry",
|
|
110098
|
-
"scenario",
|
|
110099
|
-
"geo",
|
|
110100
|
-
"tech",
|
|
110101
|
-
"question"
|
|
110102
|
-
];
|
|
110103
|
-
var CORE_MIN = 5;
|
|
110104
|
-
var CORE_MAX = 15;
|
|
110105
|
-
var LONGTAIL_MIN = 10;
|
|
110106
|
-
var LONGTAIL_MAX = 25;
|
|
110107
|
-
var EXACT_MIN = 2;
|
|
110108
|
-
var EXACT_MAX = 8;
|
|
110109
|
-
var PHRASE_MIN = 3;
|
|
110110
|
-
var PHRASE_MAX = 10;
|
|
110111
|
-
var BROAD_MIN = 1;
|
|
110112
|
-
var BROAD_MAX = 3;
|
|
110113
|
-
var NEGATIVE_MIN = 20;
|
|
110114
|
-
var AD_GROUP_MAX = 20;
|
|
110115
|
-
var EXACT_RATIO_MIN = 0.3;
|
|
110116
|
-
var EXACT_RATIO_MAX = 0.4;
|
|
110117
|
-
var PHRASE_RATIO_MIN = 0.5;
|
|
110118
|
-
var PHRASE_RATIO_MAX = 0.6;
|
|
110119
|
-
var BROAD_RATIO_MIN = 0.1;
|
|
110120
|
-
var BROAD_RATIO_MAX = 0.2;
|
|
110121
|
-
function normalizeGroupKey(name) {
|
|
110122
|
-
return name.trim().toLowerCase();
|
|
110123
|
-
}
|
|
110124
|
-
function pushErr(errors, msg) {
|
|
110125
|
-
errors.push(msg);
|
|
110126
|
-
}
|
|
110127
|
-
function isValidTier(t) {
|
|
110128
|
-
return typeof t === "string" && KEYWORD_TIERS.includes(t);
|
|
110129
|
-
}
|
|
110130
|
-
function isValidClass(c) {
|
|
110131
|
-
return typeof c === "string" && KEYWORD_CLASSES.includes(c);
|
|
110132
|
-
}
|
|
110133
|
-
function keywordStemFromDisplay(text) {
|
|
110134
|
-
return unwrapKeywordDisplayTextForEdit(text).trim().toLowerCase();
|
|
110135
|
-
}
|
|
110136
|
-
function countNegatives(campaign) {
|
|
110137
|
-
const neg = campaign["NegativeKeywordsForBatchJob"];
|
|
110138
|
-
if (!Array.isArray(neg)) return 0;
|
|
110139
|
-
const seen = /* @__PURE__ */ new Set();
|
|
110140
|
-
for (const block of neg) {
|
|
110141
|
-
const texts = block?.["KeywordText"];
|
|
110142
|
-
if (!Array.isArray(texts)) continue;
|
|
110143
|
-
for (const t of texts) {
|
|
110144
|
-
if (typeof t !== "string") continue;
|
|
110145
|
-
const stem = keywordStemFromDisplay(t);
|
|
110146
|
-
if (stem) seen.add(stem);
|
|
110147
|
-
}
|
|
110148
|
-
}
|
|
110149
|
-
return seen.size;
|
|
110150
|
-
}
|
|
110151
|
-
function countMatchTypeInGroup(kws) {
|
|
110152
|
-
let exact = 0;
|
|
110153
|
-
let phrase = 0;
|
|
110154
|
-
let broad = 0;
|
|
110155
|
-
const stems = /* @__PURE__ */ new Set();
|
|
110156
|
-
if (!Array.isArray(kws)) return { exact, phrase, broad, stems };
|
|
110157
|
-
for (const block of kws) {
|
|
110158
|
-
const b = block;
|
|
110159
|
-
const mt = String(b["MatchTypeV2"] ?? "").toUpperCase();
|
|
110160
|
-
const texts = b["KeywordText"];
|
|
110161
|
-
if (!Array.isArray(texts)) continue;
|
|
110162
|
-
for (const raw of texts) {
|
|
110163
|
-
if (typeof raw !== "string") continue;
|
|
110164
|
-
const stem = keywordStemFromDisplay(raw);
|
|
110165
|
-
if (!stem) continue;
|
|
110166
|
-
stems.add(stem);
|
|
110167
|
-
if (mt === "EXACT") exact++;
|
|
110168
|
-
else if (mt === "PHRASE") phrase++;
|
|
110169
|
-
else broad++;
|
|
110170
|
-
}
|
|
110171
|
-
}
|
|
110172
|
-
return { exact, phrase, broad, stems };
|
|
110173
|
-
}
|
|
110174
|
-
function parseV2ByGroup(v2) {
|
|
110175
|
-
const map = /* @__PURE__ */ new Map();
|
|
110176
|
-
if (!Array.isArray(v2)) return map;
|
|
110177
|
-
for (const entry of v2) {
|
|
110178
|
-
const key = typeof entry?.Key === "string" ? normalizeGroupKey(entry.Key) : "";
|
|
110179
|
-
if (!key) continue;
|
|
110180
|
-
let core = 0;
|
|
110181
|
-
let longtail = 0;
|
|
110182
|
-
const stems = /* @__PURE__ */ new Set();
|
|
110183
|
-
const values = entry.Value;
|
|
110184
|
-
if (!Array.isArray(values)) {
|
|
110185
|
-
map.set(key, { core: 0, longtail: 0, stems });
|
|
110186
|
-
continue;
|
|
110187
|
-
}
|
|
110188
|
-
for (const item of values) {
|
|
110189
|
-
const row = item;
|
|
110190
|
-
const kw = typeof row["keyword"] === "string" ? row["keyword"].trim() : "";
|
|
110191
|
-
if (!kw) continue;
|
|
110192
|
-
stems.add(kw.toLowerCase());
|
|
110193
|
-
const tier = row["tier"];
|
|
110194
|
-
if (tier === "core") core++;
|
|
110195
|
-
else if (tier === "longtail") longtail++;
|
|
110196
|
-
}
|
|
110197
|
-
map.set(key, { core, longtail, stems });
|
|
110198
|
-
}
|
|
110199
|
-
return map;
|
|
110200
|
-
}
|
|
110201
|
-
function validateCampaignLaunchStrategy(cfg, errors, options = {}) {
|
|
110202
|
-
const stats = {
|
|
110203
|
-
adGroups: 0,
|
|
110204
|
-
core: 0,
|
|
110205
|
-
longtail: 0,
|
|
110206
|
-
exact: 0,
|
|
110207
|
-
phrase: 0,
|
|
110208
|
-
broad: 0,
|
|
110209
|
-
negatives: 0
|
|
110210
|
-
};
|
|
110211
|
-
if (!cfg.campaign || typeof cfg.campaign !== "object" || Array.isArray(cfg.campaign)) {
|
|
110212
|
-
return stats;
|
|
110213
|
-
}
|
|
110214
|
-
const campaign = cfg.campaign;
|
|
110215
|
-
stats.negatives = countNegatives(campaign);
|
|
110216
|
-
if (stats.negatives < NEGATIVE_MIN) {
|
|
110217
|
-
pushErr(
|
|
110218
|
-
errors,
|
|
110219
|
-
`campaign.NegativeKeywordsForBatchJob \u53BB\u91CD\u540E\u987B \u2265 ${NEGATIVE_MIN} \u6761\uFF08\u5F53\u524D ${stats.negatives}\uFF09`
|
|
110220
|
-
);
|
|
110221
|
-
}
|
|
110222
|
-
const v2 = cfg.KeywordRecommendationsV2;
|
|
110223
|
-
if (!Array.isArray(v2) || v2.length === 0) {
|
|
110224
|
-
pushErr(
|
|
110225
|
-
errors,
|
|
110226
|
-
"\u7F3A\u5C11\u9876\u5C42 KeywordRecommendationsV2\uFF08\u987B\u6309\u5E7F\u544A\u7EC4\u586B\u5199 tier=core|longtail \u4E0E class\uFF09"
|
|
110227
|
-
);
|
|
110228
|
-
return stats;
|
|
110229
|
-
}
|
|
110230
|
-
const v2ByGroup = parseV2ByGroup(v2);
|
|
110231
|
-
const adGroups = campaign["AdGroupsForBatchJob"];
|
|
110232
|
-
if (!Array.isArray(adGroups)) return stats;
|
|
110233
|
-
stats.adGroups = adGroups.length;
|
|
110234
|
-
if (adGroups.length > AD_GROUP_MAX) {
|
|
110235
|
-
pushErr(
|
|
110236
|
-
errors,
|
|
110237
|
-
`campaign.AdGroupsForBatchJob \u6570\u91CF\u987B\u5728 1\u2013${AD_GROUP_MAX}\uFF08\u5F53\u524D ${adGroups.length}\uFF09`
|
|
110238
|
-
);
|
|
110239
|
-
}
|
|
110240
|
-
for (let i = 0; i < adGroups.length; i++) {
|
|
110241
|
-
const g = adGroups[i];
|
|
110242
|
-
const gPrefix = `campaign.AdGroupsForBatchJob[${i}]`;
|
|
110243
|
-
const gName = typeof g["Name"] === "string" ? g["Name"].trim() : "";
|
|
110244
|
-
if (!gName) continue;
|
|
110245
|
-
const gKey = normalizeGroupKey(gName);
|
|
110246
|
-
const v2Row = v2ByGroup.get(gKey);
|
|
110247
|
-
if (!v2Row) {
|
|
110248
|
-
pushErr(
|
|
110249
|
-
errors,
|
|
110250
|
-
`${gPrefix}\uFF08${gName}\uFF09\u5728 KeywordRecommendationsV2 \u4E2D\u7F3A\u5C11 Key \u6761\u76EE`
|
|
110251
|
-
);
|
|
110252
|
-
continue;
|
|
110253
|
-
}
|
|
110254
|
-
if (v2Row.stems.size === 0) {
|
|
110255
|
-
pushErr(errors, `KeywordRecommendationsV2 Key="${gName}" \u7684 Value \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
110256
|
-
continue;
|
|
110257
|
-
}
|
|
110258
|
-
stats.core += v2Row.core;
|
|
110259
|
-
stats.longtail += v2Row.longtail;
|
|
110260
|
-
if (v2Row.core < CORE_MIN || v2Row.core > CORE_MAX) {
|
|
110261
|
-
pushErr(
|
|
110262
|
-
errors,
|
|
110263
|
-
`${gPrefix} \u6838\u5FC3\u8BCD\uFF08tier=core\uFF09\u987B ${CORE_MIN}\u2013${CORE_MAX} \u6761\uFF08\u5F53\u524D ${v2Row.core}\uFF09`
|
|
110264
|
-
);
|
|
110265
|
-
}
|
|
110266
|
-
if (v2Row.longtail < LONGTAIL_MIN || v2Row.longtail > LONGTAIL_MAX) {
|
|
110267
|
-
pushErr(
|
|
110268
|
-
errors,
|
|
110269
|
-
`${gPrefix} \u957F\u5C3E\u8BCD\uFF08tier=longtail\uFF09\u987B ${LONGTAIL_MIN}\u2013${LONGTAIL_MAX} \u6761\uFF08\u5F53\u524D ${v2Row.longtail}\uFF09`
|
|
110270
|
-
);
|
|
110271
|
-
}
|
|
110272
|
-
for (const entry of v2) {
|
|
110273
|
-
if (normalizeGroupKey(String(entry.Key)) !== gKey) continue;
|
|
110274
|
-
const values = entry.Value;
|
|
110275
|
-
if (!Array.isArray(values)) break;
|
|
110276
|
-
for (let vi = 0; vi < values.length; vi++) {
|
|
110277
|
-
const row = values[vi];
|
|
110278
|
-
if (!isValidTier(row["tier"])) {
|
|
110279
|
-
pushErr(
|
|
110280
|
-
errors,
|
|
110281
|
-
`KeywordRecommendationsV2["${gName}"].Value[${vi}].tier \u987B\u4E3A core | longtail`
|
|
110282
|
-
);
|
|
110283
|
-
}
|
|
110284
|
-
if (row["class"] !== void 0 && !isValidClass(row["class"])) {
|
|
110285
|
-
pushErr(
|
|
110286
|
-
errors,
|
|
110287
|
-
`KeywordRecommendationsV2["${gName}"].Value[${vi}].class \u65E0\u6548\uFF08\u89C1 google-ads-keyword-taxonomy.md\uFF09`
|
|
110288
|
-
);
|
|
110289
|
-
}
|
|
110290
|
-
}
|
|
110291
|
-
break;
|
|
110292
|
-
}
|
|
110293
|
-
const { exact, phrase, broad, stems } = countMatchTypeInGroup(g["KeywordsForBatchJob"]);
|
|
110294
|
-
stats.exact += exact;
|
|
110295
|
-
stats.phrase += phrase;
|
|
110296
|
-
stats.broad += broad;
|
|
110297
|
-
if (exact < EXACT_MIN || exact > EXACT_MAX) {
|
|
110298
|
-
pushErr(
|
|
110299
|
-
errors,
|
|
110300
|
-
`${gPrefix} EXACT \u5173\u952E\u8BCD\u987B ${EXACT_MIN}\u2013${EXACT_MAX} \u6761\uFF08\u5F53\u524D ${exact}\uFF09`
|
|
110301
|
-
);
|
|
110302
|
-
}
|
|
110303
|
-
if (phrase < PHRASE_MIN || phrase > PHRASE_MAX) {
|
|
110304
|
-
pushErr(
|
|
110305
|
-
errors,
|
|
110306
|
-
`${gPrefix} PHRASE \u5173\u952E\u8BCD\u987B ${PHRASE_MIN}\u2013${PHRASE_MAX} \u6761\uFF08\u5F53\u524D ${phrase}\uFF09`
|
|
110307
|
-
);
|
|
110308
|
-
}
|
|
110309
|
-
if (broad < BROAD_MIN || broad > BROAD_MAX) {
|
|
110310
|
-
pushErr(
|
|
110311
|
-
errors,
|
|
110312
|
-
`${gPrefix} BROAD \u5173\u952E\u8BCD\u987B ${BROAD_MIN}\u2013${BROAD_MAX} \u6761\uFF08\u5F53\u524D ${broad}\uFF09`
|
|
110313
|
-
);
|
|
110314
|
-
}
|
|
110315
|
-
const total = exact + phrase + broad;
|
|
110316
|
-
if (total > 0) {
|
|
110317
|
-
const er = exact / total;
|
|
110318
|
-
const pr = phrase / total;
|
|
110319
|
-
const br = broad / total;
|
|
110320
|
-
if (er < EXACT_RATIO_MIN || er > EXACT_RATIO_MAX) {
|
|
110321
|
-
pushErr(
|
|
110322
|
-
errors,
|
|
110323
|
-
`${gPrefix} EXACT \u5360\u6BD4\u987B\u5728 ${EXACT_RATIO_MIN * 100}\u2013${EXACT_RATIO_MAX * 100}%\uFF08\u5F53\u524D ${(er * 100).toFixed(1)}%\uFF09`
|
|
110324
|
-
);
|
|
110325
|
-
}
|
|
110326
|
-
if (pr < PHRASE_RATIO_MIN || pr > PHRASE_RATIO_MAX) {
|
|
110327
|
-
pushErr(
|
|
110328
|
-
errors,
|
|
110329
|
-
`${gPrefix} PHRASE \u5360\u6BD4\u987B\u5728 ${PHRASE_RATIO_MIN * 100}\u2013${PHRASE_RATIO_MAX * 100}%\uFF08\u5F53\u524D ${(pr * 100).toFixed(1)}%\uFF09`
|
|
110330
|
-
);
|
|
110331
|
-
}
|
|
110332
|
-
if (br < BROAD_RATIO_MIN || br > BROAD_RATIO_MAX) {
|
|
110333
|
-
pushErr(
|
|
110334
|
-
errors,
|
|
110335
|
-
`${gPrefix} BROAD \u5360\u6BD4\u987B\u5728 ${BROAD_RATIO_MIN * 100}\u2013${BROAD_RATIO_MAX * 100}%\uFF08\u5F53\u524D ${(br * 100).toFixed(1)}%\uFF09`
|
|
110336
|
-
);
|
|
110337
|
-
}
|
|
110338
|
-
}
|
|
110339
|
-
for (const stem of v2Row.stems) {
|
|
110340
|
-
if (!stems.has(stem)) {
|
|
110341
|
-
pushErr(
|
|
110342
|
-
errors,
|
|
110343
|
-
`${gPrefix} KeywordsForBatchJob \u672A\u5305\u542B KeywordRecommendationsV2 \u8BCD\u5E72\uFF1A${stem}`
|
|
110344
|
-
);
|
|
110345
|
-
}
|
|
110346
|
-
}
|
|
110347
|
-
}
|
|
110348
|
-
if (!options.skipManifest && options.manifestPath) {
|
|
110349
|
-
validateManifestRoles(options.manifestPath, errors);
|
|
110350
|
-
}
|
|
110351
|
-
return stats;
|
|
110352
|
-
}
|
|
110353
|
-
function validateManifestRoles(manifestPath, errors) {
|
|
110354
|
-
let raw;
|
|
110355
|
-
try {
|
|
110356
|
-
raw = JSON.parse(readFileSync4(manifestPath, "utf8"));
|
|
110357
|
-
} catch {
|
|
110358
|
-
pushErr(errors, `\u65E0\u6CD5\u8BFB\u53D6 campaign-manifest\uFF1A${manifestPath}`);
|
|
110359
|
-
return;
|
|
110360
|
-
}
|
|
110361
|
-
const m = raw;
|
|
110362
|
-
if (!m?.campaigns || !Array.isArray(m.campaigns)) {
|
|
110363
|
-
pushErr(errors, "campaign-manifest \u987B\u5305\u542B campaigns \u6570\u7EC4");
|
|
110364
|
-
return;
|
|
110365
|
-
}
|
|
110366
|
-
const roles = new Set(m.campaigns.map((c) => c.role?.toLowerCase()).filter(Boolean));
|
|
110367
|
-
if (!roles.has("brand")) {
|
|
110368
|
-
pushErr(errors, "campaign-manifest \u987B\u81F3\u5C11\u5305\u542B role=brand \u7684\u7CFB\u5217");
|
|
110369
|
-
}
|
|
110370
|
-
if (!roles.has("competitor")) {
|
|
110371
|
-
pushErr(errors, "campaign-manifest \u987B\u81F3\u5C11\u5305\u542B role=competitor \u7684\u7CFB\u5217");
|
|
110372
|
-
}
|
|
110373
|
-
}
|
|
110374
|
-
|
|
110375
110089
|
// src/commands/ad/campaign-create-validate.ts
|
|
110376
110090
|
var VALID_BIDDING_STRATEGIES = [
|
|
110377
110091
|
"TARGET_SPEND",
|
|
@@ -110386,7 +110100,7 @@ var VALID_STATUS_V2 = ["Enabled", "Paused"];
|
|
|
110386
110100
|
var VALID_CHANNEL_V2 = ["SEARCH", "DISPLAY", "VIDEO", "SHOPPING", "MULTI_CHANNEL"];
|
|
110387
110101
|
var URL_REGEX = /^https?:\/\/.+/;
|
|
110388
110102
|
var DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
|
|
110389
|
-
function
|
|
110103
|
+
function pushErr(errors, msg) {
|
|
110390
110104
|
errors.push(msg);
|
|
110391
110105
|
}
|
|
110392
110106
|
function pushWarn(warnings, msg) {
|
|
@@ -110419,48 +110133,48 @@ function validateRsaAd(prefix, ad, errors, warnings) {
|
|
|
110419
110133
|
(x) => typeof x === "string" && x.length > 0
|
|
110420
110134
|
);
|
|
110421
110135
|
if (headlines.length < 3) {
|
|
110422
|
-
|
|
110136
|
+
pushErr(errors, `${prefix} \u6807\u9898\u81F3\u5C11 3 \u6761\uFF08headlinePart1/2/3 \u5FC5\u586B\uFF09\uFF0C\u5F53\u524D ${headlines.length} \u6761`);
|
|
110423
110137
|
} else if (headlines.length > 15) {
|
|
110424
|
-
|
|
110138
|
+
pushErr(errors, `${prefix} \u6807\u9898\u6700\u591A 15 \u6761\uFF08\u542B AddtionalHeadlines\uFF09\uFF0C\u5F53\u524D ${headlines.length} \u6761`);
|
|
110425
110139
|
} else if (headlines.length < 12) {
|
|
110426
110140
|
pushWarn(warnings, `${prefix} \u6807\u9898\u63A8\u8350 12\u201315 \u6761\uFF08\u5F53\u524D ${headlines.length} \u6761\uFF09\uFF0C\u66F4\u591A\u7EC4\u5408\u53EF\u63D0\u5347\u6295\u653E\u8868\u73B0`);
|
|
110427
110141
|
}
|
|
110428
110142
|
for (let i = 0; i < headlines.length; i++) {
|
|
110429
110143
|
const len = calcGoogleCharLength(headlines[i]);
|
|
110430
110144
|
if (len > 30) {
|
|
110431
|
-
|
|
110145
|
+
pushErr(
|
|
110432
110146
|
errors,
|
|
110433
110147
|
`${prefix} \u6807\u9898[${i}] \u8D85\u8FC7 30 \u5B57\u7B26\uFF08\u5F53\u524D ${len}\uFF0CCJK \u8BA1 2\uFF09\uFF1A"${headlines[i].slice(0, 30)}"`
|
|
110434
110148
|
);
|
|
110435
110149
|
}
|
|
110436
110150
|
}
|
|
110437
110151
|
if (descriptions.length < 2) {
|
|
110438
|
-
|
|
110152
|
+
pushErr(
|
|
110439
110153
|
errors,
|
|
110440
110154
|
`${prefix} \u63CF\u8FF0\u81F3\u5C11 2 \u6761\uFF08adDescription/adDescription2 \u5FC5\u586B\uFF09\uFF0C\u5F53\u524D ${descriptions.length} \u6761`
|
|
110441
110155
|
);
|
|
110442
110156
|
} else if (descriptions.length > 4) {
|
|
110443
|
-
|
|
110157
|
+
pushErr(errors, `${prefix} \u63CF\u8FF0\u6700\u591A 4 \u6761\uFF0C\u5F53\u524D ${descriptions.length} \u6761`);
|
|
110444
110158
|
} else if (descriptions.length < 4) {
|
|
110445
110159
|
pushWarn(warnings, `${prefix} \u63CF\u8FF0\u63A8\u8350 4 \u6761\uFF08\u5F53\u524D ${descriptions.length} \u6761\uFF09`);
|
|
110446
110160
|
}
|
|
110447
110161
|
for (let i = 0; i < descriptions.length; i++) {
|
|
110448
110162
|
const len = calcGoogleCharLength(descriptions[i]);
|
|
110449
110163
|
if (len > 90) {
|
|
110450
|
-
|
|
110164
|
+
pushErr(errors, `${prefix} \u63CF\u8FF0[${i}] \u8D85\u8FC7 90 \u5B57\u7B26\uFF08\u5F53\u524D ${len}\uFF09\uFF1A"${descriptions[i].slice(0, 40)}"`);
|
|
110451
110165
|
}
|
|
110452
110166
|
}
|
|
110453
110167
|
const p1 = ad["Path1"];
|
|
110454
110168
|
const p2 = ad["Path2"];
|
|
110455
110169
|
if (typeof p1 === "string" && calcGoogleCharLength(p1) > 15) {
|
|
110456
|
-
|
|
110170
|
+
pushErr(errors, `${prefix} Path1 \u8D85\u8FC7 15 \u5B57\u7B26\uFF08CJK \u8BA1 2\uFF09\uFF1A"${p1}"`);
|
|
110457
110171
|
}
|
|
110458
110172
|
if (typeof p2 === "string" && calcGoogleCharLength(p2) > 15) {
|
|
110459
|
-
|
|
110173
|
+
pushErr(errors, `${prefix} Path2 \u8D85\u8FC7 15 \u5B57\u7B26\uFF08CJK \u8BA1 2\uFF09\uFF1A"${p2}"`);
|
|
110460
110174
|
}
|
|
110461
110175
|
const finalUrl = ad["Finalurl"] ?? ad["DestinationUrl"];
|
|
110462
110176
|
if (typeof finalUrl === "string" && finalUrl.length > 0 && !URL_REGEX.test(finalUrl)) {
|
|
110463
|
-
|
|
110177
|
+
pushErr(errors, `${prefix} Finalurl \u683C\u5F0F\u4E0D\u6B63\u786E\uFF08\u5E94\u4EE5 http(s):// \u5F00\u5934\uFF09\uFF1A${finalUrl}`);
|
|
110464
110178
|
}
|
|
110465
110179
|
validateRsaHeadlinesWithinAd(prefix, ad, errors);
|
|
110466
110180
|
}
|
|
@@ -110471,18 +110185,18 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
110471
110185
|
normalizeCampaignKeywordTrees(cfg.campaign, errors, warnings);
|
|
110472
110186
|
}
|
|
110473
110187
|
if (!cfg.account?.toString().trim()) {
|
|
110474
|
-
|
|
110188
|
+
pushErr(errors, "account\uFF08\u9876\u5C42 customerId\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
|
|
110475
110189
|
}
|
|
110476
110190
|
if (!cfg.customerName?.trim()) {
|
|
110477
|
-
|
|
110191
|
+
pushErr(errors, "customerName \u4E0D\u80FD\u4E3A\u7A7A\uFF08\u540E\u7AEF\uFF1Acustomer name is null or empty\uFF09");
|
|
110478
110192
|
}
|
|
110479
110193
|
if (!cfg.campaign || typeof cfg.campaign !== "object" || Array.isArray(cfg.campaign)) {
|
|
110480
|
-
|
|
110194
|
+
pushErr(errors, "campaign \u5BF9\u8C61\u4E0D\u80FD\u4E3A\u7A7A\uFF08\u540E\u7AEF\uFF1Acampaign is null\uFF09");
|
|
110481
110195
|
return { errors, warnings };
|
|
110482
110196
|
}
|
|
110483
110197
|
const campaign = cfg.campaign;
|
|
110484
110198
|
if (campaign["TargetPartnerSearchNetwork"] === true) {
|
|
110485
|
-
|
|
110199
|
+
pushErr(
|
|
110486
110200
|
errors,
|
|
110487
110201
|
"campaign.TargetPartnerSearchNetwork \u4E0D\u80FD\u4E3A true\uFF08\u540E\u7AEF\u62D2\u7EDD\uFF1Acannot set TargetPartnerSearchNetwork\uFF09"
|
|
110488
110202
|
);
|
|
@@ -110492,48 +110206,48 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
110492
110206
|
const targetGoogleSearch = tgs === void 0 ? true : tgs === true;
|
|
110493
110207
|
const targetSearchNetwork = tsn === true;
|
|
110494
110208
|
if (!targetGoogleSearch && targetSearchNetwork) {
|
|
110495
|
-
|
|
110209
|
+
pushErr(
|
|
110496
110210
|
errors,
|
|
110497
110211
|
"campaign.TargetSearchNetwork=true \u65F6 campaign.TargetGoogleSearch \u5FC5\u987B\u4E3A true\uFF08\u540E\u7AEF\u62D2\u7EDD\uFF1ATargetGoogleSearch cannot be set to false when TargetSearchNetwork is set to true\uFF09"
|
|
110498
110212
|
);
|
|
110499
110213
|
}
|
|
110500
110214
|
if (targetSearchNetwork) {
|
|
110501
|
-
|
|
110215
|
+
pushErr(
|
|
110502
110216
|
errors,
|
|
110503
110217
|
"campaign.TargetSearchNetwork \u5FC5\u987B\u4E3A false\uFF08\u641C\u7D22\u4E13\u5C5E\u65B9\u6848\uFF1A\u7981\u6B62 Google \u641C\u7D22\u5408\u4F5C\u4F19\u4F34\u7F51\u7EDC\uFF09"
|
|
110504
110218
|
);
|
|
110505
110219
|
}
|
|
110506
110220
|
if (campaign["TargetContentNetwork"] === true) {
|
|
110507
|
-
|
|
110221
|
+
pushErr(
|
|
110508
110222
|
errors,
|
|
110509
110223
|
"campaign.TargetContentNetwork \u5FC5\u987B\u4E3A false\uFF08\u641C\u7D22\u4E13\u5C5E\u65B9\u6848\uFF1A\u7981\u6B62\u5C55\u793A\u5E7F\u544A\u7F51\u7EDC\uFF09"
|
|
110510
110224
|
);
|
|
110511
110225
|
}
|
|
110512
110226
|
const name = campaign["Name"];
|
|
110513
110227
|
if (typeof name !== "string" || !name.trim()) {
|
|
110514
|
-
|
|
110228
|
+
pushErr(errors, "campaign.Name \u4E0D\u80FD\u4E3A\u7A7A");
|
|
110515
110229
|
}
|
|
110516
110230
|
const budget = campaign["Budget"];
|
|
110517
110231
|
if (typeof budget !== "number" || !Number.isFinite(budget) || budget <= 0) {
|
|
110518
|
-
|
|
110232
|
+
pushErr(errors, "campaign.Budget\uFF08\u65E5\u9884\u7B97\uFF0C\u5355\u4F4D\u300C\u5143\u300D\uFF09\u5FC5\u987B\u4E3A\u6B63\u6570");
|
|
110519
110233
|
}
|
|
110520
110234
|
const channelType = campaign["ChannelTypeV2"];
|
|
110521
110235
|
if (channelType !== void 0 && !VALID_CHANNEL_V2.includes(String(channelType))) {
|
|
110522
|
-
|
|
110236
|
+
pushErr(
|
|
110523
110237
|
errors,
|
|
110524
110238
|
`campaign.ChannelTypeV2 \u65E0\u6548\uFF08${String(channelType)}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1A${VALID_CHANNEL_V2.join(" | ")}`
|
|
110525
110239
|
);
|
|
110526
110240
|
}
|
|
110527
110241
|
const statusV2 = campaign["StatusV2"];
|
|
110528
110242
|
if (statusV2 !== void 0 && !VALID_STATUS_V2.includes(String(statusV2))) {
|
|
110529
|
-
|
|
110243
|
+
pushErr(
|
|
110530
110244
|
errors,
|
|
110531
110245
|
`campaign.StatusV2 \u65E0\u6548\uFF08${String(statusV2)}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1A${VALID_STATUS_V2.join(" | ")}`
|
|
110532
110246
|
);
|
|
110533
110247
|
}
|
|
110534
110248
|
const deliveryMethod = campaign["BudgetBudgetDeliveryMethodV2"];
|
|
110535
110249
|
if (deliveryMethod !== void 0 && !VALID_BUDGET_DELIVERY.includes(String(deliveryMethod))) {
|
|
110536
|
-
|
|
110250
|
+
pushErr(
|
|
110537
110251
|
errors,
|
|
110538
110252
|
`campaign.BudgetBudgetDeliveryMethodV2 \u65E0\u6548\uFF08${String(deliveryMethod)}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1A${VALID_BUDGET_DELIVERY.join(" | ")}`
|
|
110539
110253
|
);
|
|
@@ -110541,7 +110255,7 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
110541
110255
|
const bidding = campaign["BiddingStrategyTypeV2"];
|
|
110542
110256
|
const biddingStr = typeof bidding === "string" ? bidding : "";
|
|
110543
110257
|
if (!VALID_BIDDING_STRATEGIES.includes(biddingStr)) {
|
|
110544
|
-
|
|
110258
|
+
pushErr(
|
|
110545
110259
|
errors,
|
|
110546
110260
|
`campaign.BiddingStrategyTypeV2 \u65E0\u6548\uFF08${String(bidding)}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1A${VALID_BIDDING_STRATEGIES.join(" | ")}`
|
|
110547
110261
|
);
|
|
@@ -110549,7 +110263,7 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
110549
110263
|
if (biddingStr === "TARGET_SPEND") {
|
|
110550
110264
|
const ceiling = campaign["TargetSpend_BidCeilingAmount"];
|
|
110551
110265
|
if (typeof ceiling !== "number" || !Number.isFinite(ceiling) || ceiling <= 0) {
|
|
110552
|
-
|
|
110266
|
+
pushErr(
|
|
110553
110267
|
errors,
|
|
110554
110268
|
"TARGET_SPEND \u51FA\u4EF7\u7B56\u7565\u4E0B campaign.TargetSpend_BidCeilingAmount\uFF08CPC \u4E0A\u9650\uFF0C\u5143\uFF09\u5FC5\u987B\u4E3A\u6B63\u6570"
|
|
110555
110269
|
);
|
|
@@ -110558,13 +110272,13 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
110558
110272
|
if (biddingStr === "TARGET_CPA") {
|
|
110559
110273
|
const cpa = campaign["TargetCpa_BidingAmount"];
|
|
110560
110274
|
if (typeof cpa !== "number" || !Number.isFinite(cpa) || cpa <= 0) {
|
|
110561
|
-
|
|
110275
|
+
pushErr(errors, "TARGET_CPA \u51FA\u4EF7\u7B56\u7565\u4E0B campaign.TargetCpa_BidingAmount\uFF08\u5143\uFF09\u5FC5\u987B\u4E3A\u6B63\u6570");
|
|
110562
110276
|
}
|
|
110563
110277
|
}
|
|
110564
110278
|
if (biddingStr === "TARGET_ROAS") {
|
|
110565
110279
|
const roas = campaign["TargetRoas"];
|
|
110566
110280
|
if (typeof roas !== "number" || !Number.isFinite(roas) || roas <= 0) {
|
|
110567
|
-
|
|
110281
|
+
pushErr(errors, "TARGET_ROAS \u51FA\u4EF7\u7B56\u7565\u4E0B campaign.TargetRoas \u5FC5\u987B\u4E3A\u6B63\u6570");
|
|
110568
110282
|
} else if (roas > 1e3) {
|
|
110569
110283
|
pushWarn(
|
|
110570
110284
|
warnings,
|
|
@@ -110575,7 +110289,7 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
110575
110289
|
}
|
|
110576
110290
|
const targetedLocations = campaign["targetedLocations"];
|
|
110577
110291
|
if (!Array.isArray(targetedLocations) || targetedLocations.length === 0) {
|
|
110578
|
-
|
|
110292
|
+
pushErr(
|
|
110579
110293
|
errors,
|
|
110580
110294
|
"campaign.targetedLocations \u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u8BF7\u5148\u6267\u884C\uFF1Asiluzan-tso ad geo search -a <accountId> -q <\u5730\u533A\u540D>"
|
|
110581
110295
|
);
|
|
@@ -110583,52 +110297,52 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
110583
110297
|
for (let i = 0; i < targetedLocations.length; i++) {
|
|
110584
110298
|
const loc = targetedLocations[i];
|
|
110585
110299
|
if (!loc || loc["id"] === void 0 && loc["Id"] === void 0) {
|
|
110586
|
-
|
|
110300
|
+
pushErr(errors, `campaign.targetedLocations[${i}] \u7F3A\u5C11 id \u5B57\u6BB5`);
|
|
110587
110301
|
}
|
|
110588
110302
|
}
|
|
110589
110303
|
}
|
|
110590
110304
|
const targetedLanguages = campaign["targetedLanguages"];
|
|
110591
110305
|
if (!Array.isArray(targetedLanguages) || targetedLanguages.length === 0) {
|
|
110592
|
-
|
|
110306
|
+
pushErr(errors, "campaign.targetedLanguages \u4E0D\u80FD\u4E3A\u7A7A\uFF08\u82F1\u8BED id=1000\uFF0C\u4E2D\u6587 id=1017\uFF09");
|
|
110593
110307
|
}
|
|
110594
110308
|
const start = campaign["StartTime"];
|
|
110595
110309
|
const end = campaign["EndTime"];
|
|
110596
110310
|
if (typeof start === "string" && start.length > 0 && !DATE_REGEX.test(start)) {
|
|
110597
|
-
|
|
110311
|
+
pushErr(errors, `campaign.StartTime \u683C\u5F0F\u4E0D\u6B63\u786E\uFF08${start}\uFF09\uFF0C\u5E94\u4E3A YYYY-MM-DD`);
|
|
110598
110312
|
}
|
|
110599
110313
|
if (typeof end === "string" && end.length > 0 && !DATE_REGEX.test(end)) {
|
|
110600
|
-
|
|
110314
|
+
pushErr(errors, `campaign.EndTime \u683C\u5F0F\u4E0D\u6B63\u786E\uFF08${end}\uFF09\uFF0C\u5E94\u4E3A YYYY-MM-DD`);
|
|
110601
110315
|
}
|
|
110602
110316
|
if (typeof start === "string" && typeof end === "string" && DATE_REGEX.test(start) && DATE_REGEX.test(end)) {
|
|
110603
110317
|
if (end <= start) {
|
|
110604
|
-
|
|
110318
|
+
pushErr(errors, `campaign.EndTime\uFF08${end}\uFF09\u5FC5\u987B\u665A\u4E8E StartTime\uFF08${start}\uFF09`);
|
|
110605
110319
|
}
|
|
110606
110320
|
}
|
|
110607
110321
|
if (cfg.url !== void 0 && cfg.url !== "" && !URL_REGEX.test(cfg.url)) {
|
|
110608
|
-
|
|
110322
|
+
pushErr(errors, `url\uFF08${cfg.url}\uFF09\u683C\u5F0F\u4E0D\u6B63\u786E\uFF0C\u5E94\u4EE5 http(s):// \u5F00\u5934`);
|
|
110609
110323
|
}
|
|
110610
110324
|
const adGroups = campaign["AdGroupsForBatchJob"];
|
|
110611
110325
|
if (!Array.isArray(adGroups) || adGroups.length === 0) {
|
|
110612
|
-
|
|
110326
|
+
pushErr(errors, "campaign.AdGroupsForBatchJob \u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u81F3\u5C11\u914D\u7F6E\u4E00\u4E2A\u5E7F\u544A\u7EC4");
|
|
110613
110327
|
} else {
|
|
110614
110328
|
for (let i = 0; i < adGroups.length; i++) {
|
|
110615
110329
|
const g = adGroups[i];
|
|
110616
110330
|
const gPrefix = `campaign.AdGroupsForBatchJob[${i}]`;
|
|
110617
110331
|
if (!g || typeof g !== "object") {
|
|
110618
|
-
|
|
110332
|
+
pushErr(errors, `${gPrefix} \u5FC5\u987B\u662F\u5BF9\u8C61`);
|
|
110619
110333
|
continue;
|
|
110620
110334
|
}
|
|
110621
110335
|
const gName = g["Name"];
|
|
110622
110336
|
if (typeof gName !== "string" || !gName.trim()) {
|
|
110623
|
-
|
|
110337
|
+
pushErr(errors, `${gPrefix}.Name \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
110624
110338
|
}
|
|
110625
110339
|
const mc = g["MaxCPCAmount"];
|
|
110626
110340
|
if (mc !== void 0 && (typeof mc !== "number" || !Number.isFinite(mc) || mc < 0)) {
|
|
110627
|
-
|
|
110341
|
+
pushErr(errors, `${gPrefix}.MaxCPCAmount \u5FC5\u987B\u4E3A\u975E\u8D1F\u6570\u5B57\uFF08\u5355\u4F4D\u300C\u5143\u300D\uFF09`);
|
|
110628
110342
|
}
|
|
110629
110343
|
if (biddingStr === "MANUAL_CPC") {
|
|
110630
110344
|
if (typeof mc !== "number" || mc <= 0) {
|
|
110631
|
-
|
|
110345
|
+
pushErr(errors, `MANUAL_CPC \u4E0B ${gPrefix}.MaxCPCAmount \u5FC5\u987B\u4E3A\u6B63\u6570`);
|
|
110632
110346
|
}
|
|
110633
110347
|
}
|
|
110634
110348
|
const ads = g["AdsForBatchJob"];
|
|
@@ -110646,7 +110360,7 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
110646
110360
|
const block = kws[j];
|
|
110647
110361
|
const texts = block?.["KeywordText"];
|
|
110648
110362
|
if (!Array.isArray(texts) || texts.length === 0) {
|
|
110649
|
-
|
|
110363
|
+
pushErr(errors, `${gPrefix}.KeywordsForBatchJob[${j}].KeywordText \u4E0D\u80FD\u4E3A\u7A7A\u6570\u7EC4`);
|
|
110650
110364
|
}
|
|
110651
110365
|
}
|
|
110652
110366
|
}
|
|
@@ -110655,17 +110369,12 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
110655
110369
|
}
|
|
110656
110370
|
return { errors, warnings };
|
|
110657
110371
|
}
|
|
110658
|
-
function runCampaignCreateValidation(cfg
|
|
110659
|
-
|
|
110660
|
-
const stats = validateCampaignLaunchStrategy(cfg, errors, {
|
|
110661
|
-
manifestPath: options.manifestPath,
|
|
110662
|
-
skipManifest: options.skipManifest
|
|
110663
|
-
});
|
|
110664
|
-
return { errors, warnings, stats };
|
|
110372
|
+
function runCampaignCreateValidation(cfg) {
|
|
110373
|
+
return validateCampaignCreateConfigCore(cfg);
|
|
110665
110374
|
}
|
|
110666
110375
|
|
|
110667
110376
|
// src/commands/ad/campaign-load.ts
|
|
110668
|
-
import { readFileSync as
|
|
110377
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
110669
110378
|
function stripMetaKeys(value) {
|
|
110670
110379
|
if (Array.isArray(value)) {
|
|
110671
110380
|
return value.map((item) => stripMetaKeys(item));
|
|
@@ -110696,7 +110405,7 @@ function loadCampaignCreateConfig(configFile) {
|
|
|
110696
110405
|
function tryLoadCampaignCreateConfig(configFile) {
|
|
110697
110406
|
let raw;
|
|
110698
110407
|
try {
|
|
110699
|
-
raw = JSON.parse(
|
|
110408
|
+
raw = JSON.parse(readFileSync4(configFile, "utf8"));
|
|
110700
110409
|
} catch {
|
|
110701
110410
|
return null;
|
|
110702
110411
|
}
|
|
@@ -110919,9 +110628,6 @@ async function runAdCampaignCreate(opts) {
|
|
|
110919
110628
|
DraftStatus: cfg.draft ? "Draft" : "Published",
|
|
110920
110629
|
campaign: campaignWithCents
|
|
110921
110630
|
};
|
|
110922
|
-
if (cfg.KeywordRecommendationsV2 !== void 0) {
|
|
110923
|
-
body["KeywordRecommendationsV2"] = cfg.KeywordRecommendationsV2;
|
|
110924
|
-
}
|
|
110925
110631
|
const url = `${config.apiBaseUrl}/command/campaign-creation-record/campaign-batch-asyncs`;
|
|
110926
110632
|
let data;
|
|
110927
110633
|
try {
|
|
@@ -113220,10 +112926,7 @@ async function runAiCreationUpdate(opts) {
|
|
|
113220
112926
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
113221
112927
|
async function runAdCampaignValidate(opts) {
|
|
113222
112928
|
const cfg = loadCampaignCreateConfig(opts.configFile);
|
|
113223
|
-
const { errors, warnings
|
|
113224
|
-
manifestPath: opts.manifestFile,
|
|
113225
|
-
skipManifest: opts.skipManifest
|
|
113226
|
-
});
|
|
112929
|
+
const { errors, warnings } = runCampaignCreateValidation(cfg);
|
|
113227
112930
|
if (opts.writeNormalized) {
|
|
113228
112931
|
const toWrite = stripMetaKeysForExport(cfg);
|
|
113229
112932
|
writeFileSync3(opts.writeNormalized, `${JSON.stringify(toWrite, null, 2)}
|
|
@@ -113235,8 +112938,7 @@ async function runAdCampaignValidate(opts) {
|
|
|
113235
112938
|
{
|
|
113236
112939
|
ok: errors.length === 0,
|
|
113237
112940
|
errors,
|
|
113238
|
-
warnings
|
|
113239
|
-
stats
|
|
112941
|
+
warnings
|
|
113240
112942
|
},
|
|
113241
112943
|
null,
|
|
113242
112944
|
2
|
|
@@ -113250,17 +112952,10 @@ async function runAdCampaignValidate(opts) {
|
|
|
113250
112952
|
if (errors.length > 0) {
|
|
113251
112953
|
console.error("\n\u274C \u6295\u653E\u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A");
|
|
113252
112954
|
for (const e of errors) console.error(` \u2022 ${e}`);
|
|
113253
|
-
console.error(
|
|
113254
|
-
`
|
|
113255
|
-
\u7EDF\u8BA1\uFF1A\u5E7F\u544A\u7EC4 ${stats.adGroups} | \u6838\u5FC3 ${stats.core} | \u957F\u5C3E ${stats.longtail} | Exact ${stats.exact} | Phrase ${stats.phrase} | Broad ${stats.broad} | \u5426\u8BCD ${stats.negatives}
|
|
113256
|
-
`
|
|
113257
|
-
);
|
|
112955
|
+
console.error();
|
|
113258
112956
|
process.exit(1);
|
|
113259
112957
|
}
|
|
113260
112958
|
console.log("\n\u2705 \u6295\u653E\u914D\u7F6E\u6821\u9A8C\u901A\u8FC7");
|
|
113261
|
-
console.log(
|
|
113262
|
-
` \u5E7F\u544A\u7EC4 ${stats.adGroups} | \u6838\u5FC3 ${stats.core} | \u957F\u5C3E ${stats.longtail} | Exact ${stats.exact} | Phrase ${stats.phrase} | Broad ${stats.broad} | \u5426\u8BCD ${stats.negatives}`
|
|
113263
|
-
);
|
|
113264
112959
|
if (opts.writeNormalized) {
|
|
113265
112960
|
console.log(` \u5DF2\u5199\u5165\u89C4\u8303\u5316 JSON\uFF1A${opts.writeNormalized}`);
|
|
113266
112961
|
}
|
|
@@ -113618,16 +113313,14 @@ function register20(program2) {
|
|
|
113618
113313
|
}
|
|
113619
113314
|
);
|
|
113620
113315
|
adCmd.command("campaign-validate").description(
|
|
113621
|
-
"\u6821\u9A8C campaign-create JSON\uFF08\u8BCD\u9762\u89C4\u8303\u5316 + \u540E\u7AEF\u786C\u7EA6\u675F
|
|
113622
|
-
).requiredOption("--config-file <path>", "campaign-create JSON \u8DEF\u5F84").option(
|
|
113316
|
+
"\u6821\u9A8C campaign-create JSON\uFF08\u8BCD\u9762\u89C4\u8303\u5316 + \u540E\u7AEF\u786C\u7EA6\u675F\uFF1B\u4E0D\u8C03\u7528 API\uFF09\n\n \u7528\u6CD5\uFF1A\n siluzan-tso ad campaign-validate --config-file ./campaign.json\n siluzan-tso ad campaign-validate --config-file ./campaign.json --write-normalized ./campaign.normalized.json"
|
|
113317
|
+
).requiredOption("--config-file <path>", "campaign-create JSON \u8DEF\u5F84").option(
|
|
113623
113318
|
"--write-normalized <path>",
|
|
113624
113319
|
"\u5C06\u89C4\u8303\u5316\u540E\u7684 JSON \u5199\u5165\u8BE5\u8DEF\u5F84\uFF08\u5173\u952E\u8BCD\u8BCD\u9762\u5DF2\u4FEE\u6B63\uFF09"
|
|
113625
|
-
).option("--json", "\u8F93\u51FA { ok, errors, warnings
|
|
113320
|
+
).option("--json", "\u8F93\u51FA { ok, errors, warnings }", false).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
113626
113321
|
async (opts) => {
|
|
113627
113322
|
await runAdCampaignValidate({
|
|
113628
113323
|
configFile: opts.configFile,
|
|
113629
|
-
manifestFile: opts.manifestFile,
|
|
113630
|
-
skipManifest: opts.skipManifest,
|
|
113631
113324
|
writeNormalized: opts.writeNormalized,
|
|
113632
113325
|
json: opts.json,
|
|
113633
113326
|
verbose: opts.verbose
|
package/dist/skill/_meta.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `ad campaign-create` JSON 配置说明
|
|
2
2
|
|
|
3
|
-
`siluzan-tso ad campaign-create` **仅**接受 `--config-file` 指向的 JSON
|
|
3
|
+
`siluzan-tso ad campaign-create` **仅**接受 `--config-file` 指向的 JSON 文件
|
|
4
4
|
|
|
5
5
|
**CLI 不做结构转换、字段重命名或默认值填充**,只做三件事:
|
|
6
6
|
|
|
@@ -32,7 +32,6 @@ JSON 模板:同目录 [`campaign-create-template.json`](campaign-create-templa
|
|
|
32
32
|
| `productWords` | string[] | | 智投/推荐用产品核心词 |
|
|
33
33
|
| `googleDataRecordId` | string \| null | | 智投记录 UUID;省略由 CLI 生成 |
|
|
34
34
|
| `draft` | boolean | | `false`(默认)立即发布到 Google;`true` 仅保存草稿,需后续 `ad batch publish` |
|
|
35
|
-
| `KeywordRecommendationsV2` | object[] | ✅(方案轨) | 每广告组一条:`{ Key: <AdGroup.Name>, Value: [{ keyword, tier: core\|longtail, class, montlySearch? }] }`;**validate 必填**,见 `google-ads-keyword-taxonomy.md` |
|
|
36
35
|
| `campaign` | object | ✅ | 内层 Campaign 对象,见下表 |
|
|
37
36
|
|
|
38
37
|
---
|
|
@@ -15,7 +15,6 @@ flowchart TB
|
|
|
15
15
|
subgraph build [构建]
|
|
16
16
|
RAG[rag 可选]
|
|
17
17
|
KW[keyword 拓词]
|
|
18
|
-
V2[KeywordRecommendationsV2]
|
|
19
18
|
KFJ[KeywordsForBatchJob]
|
|
20
19
|
JSON[campaign-create JSON]
|
|
21
20
|
end
|
|
@@ -29,7 +28,7 @@ flowchart TB
|
|
|
29
28
|
POLL[ad batch get / diff]
|
|
30
29
|
end
|
|
31
30
|
A --> JSON
|
|
32
|
-
B --> RAG --> KW -->
|
|
31
|
+
B --> RAG --> KW --> KFJ --> JSON
|
|
33
32
|
JSON --> VAL
|
|
34
33
|
VAL -->|通过| MD --> OK --> CC --> POLL
|
|
35
34
|
```
|
|
@@ -54,13 +53,13 @@ flowchart TB
|
|
|
54
53
|
|----|------|-----------|
|
|
55
54
|
| 1 | `list-accounts` 锁定 `account` / `customerName` / 币种 | `references/currency.md` |
|
|
56
55
|
| 2 | 可选 `rag query`;`keyword` / `keyword geo-list` 拓词 | `references/keyword-planner-workflows.md` |
|
|
57
|
-
| 3 |
|
|
56
|
+
| 3 | 按分层规则写入 `KeywordsForBatchJob`(Exact/Phrase/Broad) | `google-ads-rules/google-ads-keyword-taxonomy.md`(参考,非 CLI 强制) |
|
|
58
57
|
| 4 | 填 `campaign`(预算/出价/地域/否词≥20/RSA/附加信息) | `assets/campaign-create-template.md` |
|
|
59
58
|
| 5 | **`ad campaign-validate --config-file <json>`**(失败只改 JSON) | 下文「校验」 |
|
|
60
59
|
| 6 | 输出:**JSON 代码块** → **Markdown**(`google-ads-launch-plan-template.md` 正文)→ 待确认 | — |
|
|
61
60
|
| 7 | 用户确认后 **`ad campaign-create`** → `ad batch get` → 成功则 **`ad batch diff`** | `google-ads.md` § batch |
|
|
62
61
|
|
|
63
|
-
多系列:每系列一个 JSON;可选 `campaign-manifest.json`(`role: brand|competitor|generic
|
|
62
|
+
多系列:每系列一个 JSON;可选 `campaign-manifest.json`(`role: brand|competitor|generic`)仅作文件组织参考。
|
|
64
63
|
|
|
65
64
|
---
|
|
66
65
|
|
|
@@ -70,7 +69,7 @@ flowchart TB
|
|
|
70
69
|
|
|
71
70
|
| 文档 | 用途 |
|
|
72
71
|
|------|------|
|
|
73
|
-
| `google-ads-rules/google-ads-keyword-taxonomy.md` |
|
|
72
|
+
| `google-ads-rules/google-ads-keyword-taxonomy.md` | 核心/长尾与匹配块**建议**(Agent 参考,CLI 不强制) |
|
|
74
73
|
| `google-ads-rules/google-ads-compliance.md` | 词与文案合规 |
|
|
75
74
|
| `google-ads-rules/sensitive-industries.md` | 敏感行业(若相关) |
|
|
76
75
|
| `google-ads-rules/google-ads-launch-plan-template.md` | 用户可见 Markdown 结构与 RSA/否词表 |
|
|
@@ -98,13 +97,12 @@ flowchart TB
|
|
|
98
97
|
|
|
99
98
|
```bash
|
|
100
99
|
siluzan-tso ad campaign-validate --config-file ./campaign.json [--json] [--write-normalized <path>]
|
|
101
|
-
siluzan-tso ad campaign-validate --config-file ./campaign.json --manifest-file ./manifest.json
|
|
102
100
|
siluzan-tso ad campaign-create --config-file ./campaign.json
|
|
103
101
|
siluzan-tso ad batch get --id <taskId> --config-file ./campaign.json
|
|
104
102
|
siluzan-tso ad batch diff --batch-id <taskId> --config-file ./campaign.json
|
|
105
103
|
```
|
|
106
104
|
|
|
107
|
-
validate 与 create **共用** `runCampaignCreateValidation
|
|
105
|
+
validate 与 create **共用** `runCampaignCreateValidation`:词面规范化 + 后端/Google 硬约束(预算、RSA、匹配符号与 `MatchTypeV2` 对齐、搜索网络等)。**不含**关键词分层数量、匹配占比、否词条数下限。
|
|
108
106
|
|
|
109
107
|
---
|
|
110
108
|
|
|
@@ -484,21 +484,7 @@ DDA 最低要求:~300 转化 + ~3,000 广告互动/30 天。
|
|
|
484
484
|
siluzan-tso ad geo search -a <CID> -q "United States"
|
|
485
485
|
|
|
486
486
|
# 2. 一体化创建(系列 + 广告组 + 关键词 + 广告)
|
|
487
|
-
siluzan-tso ad campaign-create
|
|
488
|
-
-a <CID> \
|
|
489
|
-
--customer-name "账户名" \
|
|
490
|
-
--name "Search_LeadGen_CRM_US" \
|
|
491
|
-
--budget 100 \
|
|
492
|
-
--bidding TARGET_SPEND \
|
|
493
|
-
--location-ids 2840 \
|
|
494
|
-
--adgroup-name "核心词_CRM" \
|
|
495
|
-
--max-cpc 5 \
|
|
496
|
-
--url "https://www.example.com" \
|
|
497
|
-
--keywords "[CRM software],[project management tool],\"best CRM\",business software" \
|
|
498
|
-
--headlines "H1,H2,H3,..." \
|
|
499
|
-
--descriptions "D1,D2" \
|
|
500
|
-
--final-url "https://www.example.com/crm" \
|
|
501
|
-
--path1 "CRM" --path2 "Free-Trial"
|
|
487
|
+
siluzan-tso ad campaign-create
|
|
502
488
|
|
|
503
489
|
# 3. 查看创建进度
|
|
504
490
|
siluzan-tso ad batch get --id <taskId>
|
|
@@ -1,21 +1,7 @@
|
|
|
1
|
-
# Google
|
|
1
|
+
# Google 搜索广告关键词分层与数量规范(Agent 参考)
|
|
2
2
|
|
|
3
|
-
> 所属 skill:`siluzan-tso
|
|
4
|
-
>
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## KeywordRecommendationsV2 字段
|
|
9
|
-
|
|
10
|
-
| 字段 | 说明 |
|
|
11
|
-
|------|------|
|
|
12
|
-
| `Key` | 与 `AdGroupsForBatchJob[].Name` 一致(校验时忽略大小写/首尾空格) |
|
|
13
|
-
| `Value[].keyword` | 词干(英文小写书写,无匹配符号) |
|
|
14
|
-
| `Value[].tier` | `core` \| `longtail` |
|
|
15
|
-
| `Value[].class` | 见下表 `class` 列 |
|
|
16
|
-
| `Value[].montlySearch` 等 | 可选;来自 `siluzan-tso keyword` Planner |
|
|
17
|
-
|
|
18
|
-
`KeywordsForBatchJob` 中**每个** V2 词干须至少出现一次(规范化后比对);匹配符号由 `MatchTypeV2` 决定。
|
|
3
|
+
> 所属 skill:`siluzan-tso`。本文为**方案撰写参考**,**不由** `ad campaign-validate` / `ad campaign-create` 强制执行。
|
|
4
|
+
> 建户 JSON 契约与 CLI 硬约束见 `assets/campaign-create-template.md`、`references/google-ads-campaign-plan.md`。
|
|
19
5
|
|
|
20
6
|
---
|
|
21
7
|
|
|
@@ -24,13 +10,13 @@
|
|
|
24
10
|
| 模块 | 规则 | 建议数量 | 示例 |
|
|
25
11
|
|------|------|----------|------|
|
|
26
12
|
| Campaign | 按产品线拆分 | 3–10 个系列(多文件 + `campaign-manifest.json`) | Payment Gateway / CRM |
|
|
27
|
-
| Ad Group | 一个搜索意图一组 | 每组 5–20
|
|
28
|
-
|
|
|
29
|
-
|
|
|
13
|
+
| Ad Group | 一个搜索意图一组 | 每组 5–20 个词 | Payment API Integration |
|
|
14
|
+
| 核心词 | 高商业意图 | 每组 **5–15** | payment api pricing |
|
|
15
|
+
| 长尾词 | 场景明确的长 query | 每组 **10–25** | crm for manufacturing |
|
|
30
16
|
| Exact Match | 核心高转化 | 每组 **2–8** 条 | [stripe alternative] |
|
|
31
17
|
| Phrase Match | 主流量 | 每组 **3–10** 条 | "payment solution" |
|
|
32
18
|
| Broad Match | 少量测试 | 每组 **1–3** 条 | payment platform |
|
|
33
|
-
| 否定关键词 | 基础否词库 |
|
|
19
|
+
| 否定关键词 | 基础否词库 | 系列级建议 **≥20** | free / jobs / tutorial |
|
|
34
20
|
| 品牌系列 | 独立 Campaign | manifest `role: brand` | company crm |
|
|
35
21
|
| 竞品系列 | 独立 Campaign | manifest `role: competitor` | stripe alternative |
|
|
36
22
|
| Search Terms | 运营节奏 | 每周检查 | `ad search-terms` |
|
|
@@ -43,9 +29,11 @@
|
|
|
43
29
|
| Phrase | 50%–60% | 主流量 |
|
|
44
30
|
| Broad | 10%–20% | 扩量测试 |
|
|
45
31
|
|
|
32
|
+
关键词写入 JSON 的 **`campaign.AdGroupsForBatchJob[].KeywordsForBatchJob`**(`MatchTypeV2` + `KeywordText` 词面);无顶层 `KeywordRecommendationsV2` 字段。
|
|
33
|
+
|
|
46
34
|
---
|
|
47
35
|
|
|
48
|
-
##
|
|
36
|
+
## 核心词生成规则
|
|
49
37
|
|
|
50
38
|
| 类型 | 规则 | 示例 |
|
|
51
39
|
|------|------|------|
|
|
@@ -55,11 +43,9 @@
|
|
|
55
43
|
| 竞品词 | competitor + alternative | stripe alternative |
|
|
56
44
|
| 行业术语 | 专业词汇/缩写 | merchant acquiring |
|
|
57
45
|
|
|
58
|
-
`class` 取值:`product` | `service` | `pain` | `competitor` | `industry`。
|
|
59
|
-
|
|
60
46
|
---
|
|
61
47
|
|
|
62
|
-
##
|
|
48
|
+
## 长尾词生成规则
|
|
63
49
|
|
|
64
50
|
| 类型 | 规则 | 示例 |
|
|
65
51
|
|------|------|------|
|
|
@@ -68,13 +54,11 @@
|
|
|
68
54
|
| 技术词 | api/sdk/integration | payment sdk integration |
|
|
69
55
|
| 问题词 | how to + 问题 | how to reduce failed payments |
|
|
70
56
|
|
|
71
|
-
`class` 取值:`scenario` | `geo` | `tech` | `question`。
|
|
72
|
-
|
|
73
57
|
拓词编排见 `references/keyword-planner-workflows.md`;Planner 出价用 `*USD` 与 `*CNY`,根级 `bidAmountCurrency` / `usdToCnyExchangeRate`。
|
|
74
58
|
|
|
75
59
|
---
|
|
76
60
|
|
|
77
|
-
## 多系列 manifest
|
|
61
|
+
## 多系列 manifest(可选,仅组织多份 JSON)
|
|
78
62
|
|
|
79
63
|
```json
|
|
80
64
|
{
|
|
@@ -88,12 +72,9 @@
|
|
|
88
72
|
}
|
|
89
73
|
```
|
|
90
74
|
|
|
91
|
-
校验:`ad campaign-validate --config-file ./campaign-generic.json --manifest-file ./campaign-manifest.json`
|
|
92
|
-
单系列草稿:`--skip-manifest`。
|
|
93
|
-
|
|
94
75
|
---
|
|
95
76
|
|
|
96
|
-
##
|
|
77
|
+
## 搜索网络(`campaign-validate` / `campaign-create` 硬约束)
|
|
97
78
|
|
|
98
79
|
JSON 中必须为:
|
|
99
80
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
> 触发:用户要投放方案/确认稿;**先完成 JSON + validate,再填本模板正文**。
|
|
7
7
|
>
|
|
8
8
|
> 字段契约:`assets/campaign-create-template.json` + `campaign-create-template.md`
|
|
9
|
-
>
|
|
9
|
+
> 关键词数量建议:`google-ads-keyword-taxonomy.md`(Agent 参考,CLI 不强制)
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
| 语言 | `campaign.targetedLanguages: [{ id: 1000 }]`(英语 1000 / 中文 1017) |
|
|
45
45
|
| 起止日期 | `campaign.StartTime`、`campaign.EndTime`(YYYY-MM-DD) |
|
|
46
46
|
| 落地页 | 外层 `url`;广告组级 `KeywordsForBatchJob[].FinalURL`、创意级 `AdsForBatchJob[].Finalurl` |
|
|
47
|
-
|
|
|
47
|
+
| 关键词 | `campaign.AdGroupsForBatchJob[].KeywordsForBatchJob`(`MatchTypeV2` + `KeywordText`) |
|
|
48
48
|
| 否定词 | `campaign.NegativeKeywordsForBatchJob: [{ KeywordText: [...], MatchTypeV2: "BROAD", FinalURL: "" }]` |
|
|
49
49
|
| 附加信息 | `campaign.ExtensionsForBatchJob` |
|
|
50
50
|
| 广告组 | `campaign.AdGroupsForBatchJob[]`:`Name`、`MaxCPCAmount`、`KeywordsForBatchJob`、`AdsForBatchJob` |
|
|
@@ -140,7 +140,7 @@ AI 生成计划时,**先写好 JSON,再按以下格式输出说明**。`{{
|
|
|
140
140
|
|
|
141
141
|
### 3.3 广告组(Ad Group)与关键词矩阵
|
|
142
142
|
|
|
143
|
-
**原则**:每一个广告组内的关键词**意图必须高度一致**;匹配符号:`[完全]`、`"词组"`、广泛无括号。Markdown
|
|
143
|
+
**原则**:每一个广告组内的关键词**意图必须高度一致**;匹配符号:`[完全]`、`"词组"`、广泛无括号。Markdown 表列建议含 **词面 | 分层(tier) | 类型(class) | MatchTypeV2 | FinalURL**(分层列可来自方案笔记,写入 JSON 的仅为 `KeywordsForBatchJob`)。数量建议见 `google-ads-keyword-taxonomy.md`。
|
|
144
144
|
|
|
145
145
|
#### 系列 1:{{系列名称}}
|
|
146
146
|
|
|
@@ -326,7 +326,7 @@ Display URL:`{{domain}}/{{path1}}/{{path2}}`(Path1/Path2 各 ≤ 15 字符
|
|
|
326
326
|
|
|
327
327
|
在生成计划前,至少确认以下信息(缺失则向用户询问):
|
|
328
328
|
|
|
329
|
-
| 必须 | 信息 | 用途
|
|
329
|
+
| 必须 | 信息 | 用途 zz |
|
|
330
330
|
| ---- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
331
331
|
| ✅ | 广告账户 ID | 关联投放账户(执行时由助手使用,勿向用户解释命令行) |
|
|
332
332
|
| ✅ | 推广的产品/服务 | 决定关键词和文案方向 |
|
|
@@ -347,7 +347,7 @@ Display URL:`{{domain}}/{{path1}}/{{path2}}`(Path1/Path2 各 ≤ 15 字符
|
|
|
347
347
|
| 系列命名 | 遵循 `[类型]_[目标]_[定向]_[地域]_[匹配]` 规范(参考 `google-ads-campaign-optimization.md` 2.1 节);可与业务名并用(如「B2B 源头寻源」) |
|
|
348
348
|
| 出价策略(首月) | **产品默认**:核心系列用 **Manual CPC**,且**关闭 eCPC**,写明建议 CPC 上限区间;测流/爆款系列可用 **Maximize Clicks + 最高 CPC**;与 `TARGET_SPEND` 等等价映射以实际 CLI/API 可选值为准时在助手侧转换,**用户可见正文始终用 Google Ads 界面用语** |
|
|
349
349
|
| 出价策略(次月) | **产品默认**:近 **30 天满 30 个**约定转化(如表单)后切换 **tCPA**;无足够数据则延续人工或 Max Clicks 并写明条件 |
|
|
350
|
-
| 关键词(每广告组) |
|
|
350
|
+
| 关键词(每广告组) | 数量与匹配块建议见 **`google-ads-keyword-taxonomy.md`**;组内意图一致;符号 `[完全]` / `"词组"` / 广泛;策略争议见 `google-ads-keyword-strategy.md` |
|
|
351
351
|
| 否定词 | **账户级 5 类词包**填满模板表;系列级补充 5–10 条;上线后搜索词**每日**迭代 |
|
|
352
352
|
| RSA | **12–15** 标题、**4** 描述;**至少 H1、H2 与 D1** 在表中标注【固定📌】及目标位置与理由;字符合规见第十一章字数≤30 |
|
|
353
353
|
| 附加信息 | Sitelink **6–8**(可含 OEM/验厂/报价/目录/联系);Callout **6–8**;Snippet **≥1 组、每组 ≥4 值**;**Lead Form** 线索业务建议填标题与必填字段 |
|
|
@@ -273,7 +273,7 @@ siluzan-tso keyword geo-list [--country-code <US,CN,...>] [--name-contains <text
|
|
|
273
273
|
|
|
274
274
|
## ad campaign-validate — 投放 JSON 校验
|
|
275
275
|
|
|
276
|
-
不提交 API
|
|
276
|
+
不提交 API;创建系列前**建议**跑。命令、选项、与 create 共用校验逻辑见 **`references/google-ads-campaign-plan.md`** § 校验与创建(后端/Google 硬约束,不含关键词分层占比)。
|
|
277
277
|
|
|
278
278
|
---
|
|
279
279
|
|
|
@@ -337,7 +337,7 @@ siluzan-tso ad batch diff --batch-id <taskId> --config-file ./campaign.json
|
|
|
337
337
|
2. 缺失 `googleDataRecordId` 时生成 UUID;
|
|
338
338
|
3. 把 `campaign` 子树内金额字段(`Budget`、`MaxCPCAmount`、`TargetSpend_BidCeilingAmount`、`TargetCpa_BidingAmount`、`MaxCpmAmount`、`MaxCPVAmount`、`TargetCpaAmount`、`MaxCPC`)从「元」深遍历 ×100 转为「分」。
|
|
339
339
|
|
|
340
|
-
**字段校验:**提交前自动执行 `runCampaignCreateValidation`(与 `ad campaign-validate` 相同):后端镜像硬约束 + `google-ads-keyword-taxonomy.md
|
|
340
|
+
**字段校验:**提交前自动执行 `runCampaignCreateValidation`(与 `ad campaign-validate` 相同):后端镜像硬约束 + 词面/RSA/搜索网络等;关键词分层与匹配占比见 `google-ads-keyword-taxonomy.md`(仅 Agent 参考,CLI 不校验)。
|
|
341
341
|
|
|
342
342
|
**广告组:** 写在 `campaign.AdGroupsForBatchJob` 数组中(至少 1 项),字段名严格 PascalCase(`Name` / `MaxCPCAmount` / `KeywordsForBatchJob` / `AdsForBatchJob`)。详见 `campaign-create-template.md`。
|
|
343
343
|
|
|
@@ -162,7 +162,7 @@ siluzan-tso keyword -k "structural adhesive,SG-200,curtain wall bonding" --url "
|
|
|
162
162
|
|
|
163
163
|
### 6)词包 → campaign-create JSON
|
|
164
164
|
|
|
165
|
-
拓词落盘结果 + `google-ads-keyword-taxonomy.md`
|
|
165
|
+
拓词落盘结果 + `google-ads-keyword-taxonomy.md` 分层建议 → 填 JSON(`KeywordsForBatchJob`、`campaign-validate`、`campaign-create`)见 **`references/google-ads-campaign-plan.md`** § 标准流水线 **步 3–7**。
|
|
166
166
|
|
|
167
167
|
### 7)拓词结果标准化导出
|
|
168
168
|
|
|
@@ -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.20-beta.
|
|
12
|
+
$PKG_VERSION = '1.1.20-beta.16'
|
|
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.20-beta.
|
|
12
|
+
readonly PKG_VERSION="1.1.20-beta.16"
|
|
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"
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"turns": [
|
|
5
5
|
"账户 6326027735,客户名从 list-accounts 取。为 B2B payment API 做一条美国搜索系列方案 JSON,含 1 个广告组、核心词与长尾词分层,落盘后请先校验再让我确认是否创建。"
|
|
6
6
|
],
|
|
7
|
-
"judgeExpectation": "路径:生成 campaign-create JSON 后应调用 ad campaign-validate(可用 stub),通过后再提议 campaign-create;不得跳过 validate。\n输出:JSON 含
|
|
7
|
+
"judgeExpectation": "路径:生成 campaign-create JSON 后应调用 ad campaign-validate(可用 stub),通过后再提议 campaign-create;不得跳过 validate。\n输出:JSON 含 KeywordsForBatchJob;须提及 validate 门禁(硬约束,非 taxonomy 数量强制)。",
|
|
8
8
|
"skillMapping": "references/google-ads-campaign-plan.md",
|
|
9
9
|
"judgeReferencePaths": [
|
|
10
10
|
"references/google-ads-campaign-plan.md"
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"turns": [
|
|
5
5
|
"针对关键词「camping tent, outdoor gear, hiking equipment」生成一个完整的 Google 广告投放方案,包括系列与广告组结构。"
|
|
6
6
|
],
|
|
7
|
-
"judgeExpectation": "路径:应引用关键词策略/匹配方式/否定词等规则文档思路,给出分层与预算分配,用户确认前不执行写命令。\n输出:campaign-create JSON + Markdown 说明;含
|
|
7
|
+
"judgeExpectation": "路径:应引用关键词策略/匹配方式/否定词等规则文档思路,给出分层与预算分配,用户确认前不执行写命令。\n输出:campaign-create JSON + Markdown 说明;含 KeywordsForBatchJob;说明 validate 步骤;不要求真实 create。",
|
|
8
8
|
"skillMapping": "references/google-ads-rules/google-ads-keyword-strategy.md"
|
|
9
9
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"turns": [
|
|
5
5
|
"我卖户外露营装备,主要面向美国和欧洲市场,网站是 https://www.campgear.com ,每天预算 3000 美金,帮我规划一套 Google 搜索广告方案。先出方案别直接开户投钱。"
|
|
6
6
|
],
|
|
7
|
-
"judgeExpectation": "路径:应先阅读 google-ads.md 规则流程,再输出可确认的投放方案(地域/语言/预算/系列结构),不得跳过合规与确认直接执行 campaign-create。\n输出:须含可执行的 campaign-create JSON(唯一数据源)+ 从 JSON 推导的 Markdown
|
|
7
|
+
"judgeExpectation": "路径:应先阅读 google-ads.md 规则流程,再输出可确认的投放方案(地域/语言/预算/系列结构),不得跳过合规与确认直接执行 campaign-create。\n输出:须含可执行的 campaign-create JSON(唯一数据源)+ 从 JSON 推导的 Markdown 说明;关键词在 KeywordsForBatchJob;方案阶段应说明须 ad campaign-validate 通过后再 create。",
|
|
8
8
|
"skillMapping": "references/google-ads-campaign-plan.md;google-ads-rules",
|
|
9
9
|
"judgeReferencePaths": [
|
|
10
10
|
"references/google-ads-campaign-plan.md"
|