siluzan-tso-cli 1.1.64-beta.1 → 1.1.64-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +109 -31
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/analytics/account-analytics.md +1 -1
- package/dist/skill/references/google-ads/google-ads-read.md +14 -2
- package/dist/skill/references/report-templates/google-period-report.md +3 -3
- package/dist/skill/report-templates/google-period-report.md +3 -3
- package/dist/skill/scripts/install.ps1 +1 -1
- package/dist/skill/scripts/install.sh +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
|
|
|
51
51
|
siluzan-tso init --force # 强制覆盖已存在文件
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
> **注意**:当前为测试版(1.1.64-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.64-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -5560,6 +5560,17 @@ function resolveAdAccountId(account, label2 = "-a, --account") {
|
|
|
5560
5560
|
function withResolvedAdAccount(opts) {
|
|
5561
5561
|
return { ...opts, account: resolveAdAccountId(opts.account) };
|
|
5562
5562
|
}
|
|
5563
|
+
function negativeKeywordSlimListUrl(googleApiUrl, accountId, query) {
|
|
5564
|
+
const params = new URLSearchParams();
|
|
5565
|
+
if (query?.appliedLevel) params.set("appliedlevel", query.appliedLevel);
|
|
5566
|
+
if (query?.readDeleted) params.set("readDeleted", "true");
|
|
5567
|
+
if (query?.excludePaused) params.set("excludePaused", "true");
|
|
5568
|
+
const text3 = query?.keywordText?.trim();
|
|
5569
|
+
if (text3) params.set("keywordtext", text3);
|
|
5570
|
+
if (query?.matchType) params.set("matchtype", query.matchType);
|
|
5571
|
+
const qs = params.toString();
|
|
5572
|
+
return `${googleApiUrl}/negativekeywordmanagement/v2/list/${accountId}/slim${qs ? `?${qs}` : ""}`;
|
|
5573
|
+
}
|
|
5563
5574
|
async function findItemInList(listUrl, config, id, verbose) {
|
|
5564
5575
|
const res = await apiFetch2(listUrl, config, {}, verbose);
|
|
5565
5576
|
const item = (res.data ?? []).find((x) => x["id"] === id);
|
|
@@ -6398,6 +6409,20 @@ function coerceToRowArray(value) {
|
|
|
6398
6409
|
}
|
|
6399
6410
|
return [];
|
|
6400
6411
|
}
|
|
6412
|
+
function dedupeRowsById(items3) {
|
|
6413
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6414
|
+
const out = [];
|
|
6415
|
+
for (const item of items3) {
|
|
6416
|
+
const rec = asRecord(item);
|
|
6417
|
+
const id = rec?.id == null ? "" : String(rec.id);
|
|
6418
|
+
if (id) {
|
|
6419
|
+
if (seen.has(id)) continue;
|
|
6420
|
+
seen.add(id);
|
|
6421
|
+
}
|
|
6422
|
+
out.push(item);
|
|
6423
|
+
}
|
|
6424
|
+
return out;
|
|
6425
|
+
}
|
|
6401
6426
|
function asRecord(value) {
|
|
6402
6427
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
|
|
6403
6428
|
return value;
|
|
@@ -6432,7 +6457,7 @@ function wrapGoogleAnalysisSnapshotPayload(section, payload) {
|
|
|
6432
6457
|
const bag = asRecord(payload);
|
|
6433
6458
|
if (bag) {
|
|
6434
6459
|
const r = readGatewayList(bag);
|
|
6435
|
-
items3 = r.items;
|
|
6460
|
+
items3 = section === "negative-keywords" ? dedupeRowsById(r.items) : r.items;
|
|
6436
6461
|
meta = r.meta;
|
|
6437
6462
|
}
|
|
6438
6463
|
} else if (ROOT_ARRAY_SECTIONS.has(section)) {
|
|
@@ -8120,9 +8145,10 @@ var init_sections = __esm({
|
|
|
8120
8145
|
},
|
|
8121
8146
|
{
|
|
8122
8147
|
name: "negative-keywords",
|
|
8123
|
-
description: "\u5F53\u524D\u8D26\u6237\u5426\u8BCD negativekeywordmanagement/v2/list\
|
|
8148
|
+
description: "\u5F53\u524D\u8D26\u6237\u5426\u8BCD negativekeywordmanagement/v2/list/{id}/slim\uFF08id/\u8BCD\u9762/\u5339\u914D/\u7EA7\u522B/\u7CFB\u5217 id/\u5E7F\u544A\u7EC4 id\uFF1B\u4E0D\u6309\u6D88\u8017\u548C\u65E5\u671F\u8FC7\u6EE4\uFF0C0 \u6761\u4E5F\u843D\u76D8\uFF09",
|
|
8149
|
+
// 接口忽略日期。保留 range 是为了快照文件名仍带报告区间,避免和上一次落盘互相覆盖。
|
|
8124
8150
|
dateMode: "range",
|
|
8125
|
-
path: (id) => `/negativekeywordmanagement/v2/list/${id}`
|
|
8151
|
+
path: (id) => `/negativekeywordmanagement/v2/list/${id}/slim`
|
|
8126
8152
|
},
|
|
8127
8153
|
{
|
|
8128
8154
|
name: "search-terms",
|
|
@@ -104342,6 +104368,8 @@ function mapNegativeKeywordRows(rows) {
|
|
|
104342
104368
|
if (id && seen.has(id)) continue;
|
|
104343
104369
|
if (id) seen.add(id);
|
|
104344
104370
|
const level = String(row.negativeLevel ?? "").trim();
|
|
104371
|
+
const campaignId = String(row.campaignId ?? "").trim();
|
|
104372
|
+
const adGroupId = String(row.adGroupId ?? "").trim();
|
|
104345
104373
|
const campaignName = String(row.campaignName ?? "").trim();
|
|
104346
104374
|
const adGroupName = String(row.adGroupName ?? "").trim();
|
|
104347
104375
|
mapped.push({
|
|
@@ -104349,6 +104377,8 @@ function mapNegativeKeywordRows(rows) {
|
|
|
104349
104377
|
matchType: String(row.matchTypeV2 ?? row.matchType ?? "\u2014"),
|
|
104350
104378
|
level: level || "\u2014",
|
|
104351
104379
|
levelZh: NEGATIVE_LEVEL_ZH[level] ?? (level || "\u2014"),
|
|
104380
|
+
campaignId: campaignId || "\u2014",
|
|
104381
|
+
adGroupId: adGroupId || "\u2014",
|
|
104352
104382
|
campaignName: campaignName || "\u2014",
|
|
104353
104383
|
adGroupName: adGroupName || "\u2014"
|
|
104354
104384
|
});
|
|
@@ -115257,6 +115287,7 @@ function resolveKeywordTexts(opts) {
|
|
|
115257
115287
|
}
|
|
115258
115288
|
function firstKeywordTextFromRecord(k) {
|
|
115259
115289
|
const kt = k["keywordText"];
|
|
115290
|
+
if (typeof kt === "string") return kt;
|
|
115260
115291
|
if (Array.isArray(kt) && kt.length > 0 && typeof kt[0] === "string") {
|
|
115261
115292
|
return kt[0];
|
|
115262
115293
|
}
|
|
@@ -115266,9 +115297,27 @@ function firstKeywordTextFromRecord(k) {
|
|
|
115266
115297
|
}
|
|
115267
115298
|
return "";
|
|
115268
115299
|
}
|
|
115300
|
+
function negativeKeywordWriteBody(row) {
|
|
115301
|
+
const text3 = firstKeywordTextFromRecord(row);
|
|
115302
|
+
const body = {
|
|
115303
|
+
id: row["id"],
|
|
115304
|
+
keywordText: text3 ? [text3] : [],
|
|
115305
|
+
matchTypeV2: row["matchTypeV2"],
|
|
115306
|
+
negativeLevel: row["negativeLevel"]
|
|
115307
|
+
};
|
|
115308
|
+
const campaignId = row["campaignId"];
|
|
115309
|
+
const adGroupId = row["adGroupId"];
|
|
115310
|
+
if (typeof campaignId === "string" && campaignId) body["campaignId"] = campaignId;
|
|
115311
|
+
if (typeof adGroupId === "string" && adGroupId) body["adGroupId"] = adGroupId;
|
|
115312
|
+
return body;
|
|
115313
|
+
}
|
|
115269
115314
|
function toNegativeKeywordAppliedLevelQuery(level) {
|
|
115270
115315
|
return level === "AdGroup" ? "Ad group" : level;
|
|
115271
115316
|
}
|
|
115317
|
+
function filterNegativeKeywordsByMatchType(items3, matchType) {
|
|
115318
|
+
const want = matchType.toLowerCase();
|
|
115319
|
+
return items3.filter((item) => String(item["matchTypeV2"] ?? "").toLowerCase() === want);
|
|
115320
|
+
}
|
|
115272
115321
|
function keywordRowToYuan(item) {
|
|
115273
115322
|
const { maxCPC: rawMaxCpc, ...rest } = item;
|
|
115274
115323
|
if (rawMaxCpc == null) return item;
|
|
@@ -115278,14 +115327,20 @@ async function runAdKeywords(opts) {
|
|
|
115278
115327
|
opts = withResolvedAdAccount(opts);
|
|
115279
115328
|
const config = loadConfig(opts.token);
|
|
115280
115329
|
const googleApiUrl = requireGoogleApi(config);
|
|
115281
|
-
|
|
115282
|
-
|
|
115283
|
-
|
|
115284
|
-
|
|
115285
|
-
|
|
115330
|
+
let url;
|
|
115331
|
+
if (opts.negative) {
|
|
115332
|
+
url = negativeKeywordSlimListUrl(googleApiUrl, opts.account, {
|
|
115333
|
+
appliedLevel: opts.level ? toNegativeKeywordAppliedLevelQuery(opts.level) : void 0,
|
|
115334
|
+
keywordText: opts.keywordText,
|
|
115335
|
+
excludePaused: opts.excludePaused,
|
|
115336
|
+
readDeleted: opts.readDeleted
|
|
115337
|
+
});
|
|
115338
|
+
} else {
|
|
115339
|
+
const params = new URLSearchParams();
|
|
115340
|
+
params.set("startDate", toGoogleDate(opts.startDate, -30));
|
|
115341
|
+
params.set("endDate", toGoogleDate(opts.endDate, 0));
|
|
115342
|
+
url = `${googleApiUrl}/keywordmanagement/v2/list/${opts.account}?${params}`;
|
|
115286
115343
|
}
|
|
115287
|
-
const endpoint = opts.negative ? "negativekeywordmanagement" : "keywordmanagement";
|
|
115288
|
-
const url = `${googleApiUrl}/${endpoint}/v2/list/${opts.account}?${params}`;
|
|
115289
115344
|
let data;
|
|
115290
115345
|
try {
|
|
115291
115346
|
data = await apiFetch2(url, config, {}, opts.verbose);
|
|
@@ -115297,12 +115352,15 @@ async function runAdKeywords(opts) {
|
|
|
115297
115352
|
}
|
|
115298
115353
|
const rawItems = data.data ?? [];
|
|
115299
115354
|
const seen = /* @__PURE__ */ new Set();
|
|
115300
|
-
|
|
115355
|
+
let items3 = rawItems.filter((item) => {
|
|
115301
115356
|
const key = item["id"];
|
|
115302
115357
|
if (key !== void 0 && seen.has(key)) return false;
|
|
115303
115358
|
if (key !== void 0) seen.add(key);
|
|
115304
115359
|
return true;
|
|
115305
115360
|
});
|
|
115361
|
+
if (opts.negative && opts.matchType) {
|
|
115362
|
+
items3 = filterNegativeKeywordsByMatchType(items3, opts.matchType);
|
|
115363
|
+
}
|
|
115306
115364
|
const label2 = opts.negative ? "\u5426\u5B9A\u5173\u952E\u8BCD" : "\u5173\u952E\u8BCD";
|
|
115307
115365
|
const n = items3.length;
|
|
115308
115366
|
const displayItems = opts.negative ? items3 : items3.map((row) => keywordRowToYuan(row));
|
|
@@ -115310,7 +115368,7 @@ async function runAdKeywords(opts) {
|
|
|
115310
115368
|
wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items: displayItems })
|
|
115311
115369
|
);
|
|
115312
115370
|
if (await emitCliJsonOrSnapshot(opts, {
|
|
115313
|
-
section: `ad-keywords-${opts.
|
|
115371
|
+
section: `ad-keywords-${opts.negative ? "negative" : "positive"}`,
|
|
115314
115372
|
commandLabel: "ad keywords",
|
|
115315
115373
|
commandHint: opts.account,
|
|
115316
115374
|
payload: kwPayload,
|
|
@@ -115332,7 +115390,11 @@ ${label2}\uFF08\u8D26\u6237\uFF1A${opts.account}\uFF0C\u7B2C 1 \u9875\uFF0C\u672
|
|
|
115332
115390
|
const kwText = Array.isArray(item["keywordText"]) ? item["keywordText"].join(", ") : String(item["text"] ?? item["keywordText"] ?? item["id"] ?? "\u2014");
|
|
115333
115391
|
const matchType = item["matchTypeV2"] ?? "\u2014";
|
|
115334
115392
|
const level = item["negativeLevel"] ?? "\u2014";
|
|
115335
|
-
|
|
115393
|
+
const campaignId = item["campaignId"] ? ` \u7CFB\u5217: ${String(item["campaignId"])}` : "";
|
|
115394
|
+
const adGroupId = item["adGroupId"] ? ` \u5E7F\u544A\u7EC4: ${String(item["adGroupId"])}` : "";
|
|
115395
|
+
console.log(
|
|
115396
|
+
` [${matchType}] ${kwText} id: ${String(item["id"] ?? "")} \u7EA7\u522B: ${level}${campaignId}${adGroupId}`
|
|
115397
|
+
);
|
|
115336
115398
|
});
|
|
115337
115399
|
} else {
|
|
115338
115400
|
const kwW = Math.min(
|
|
@@ -115516,10 +115578,7 @@ async function runAdNegativeKeywordCreate(opts) {
|
|
|
115516
115578
|
`);
|
|
115517
115579
|
process.exit(1);
|
|
115518
115580
|
}
|
|
115519
|
-
const
|
|
115520
|
-
dateParams.set("startDate", toGoogleDate(void 0, -30));
|
|
115521
|
-
dateParams.set("endDate", toGoogleDate(void 0, 0));
|
|
115522
|
-
const listUrl = `${googleApiUrl}/negativekeywordmanagement/v2/list/${opts.account}?${dateParams}`;
|
|
115581
|
+
const listUrl = negativeKeywordSlimListUrl(googleApiUrl, opts.account);
|
|
115523
115582
|
const readList = async () => {
|
|
115524
115583
|
try {
|
|
115525
115584
|
const listed = await apiFetch2(
|
|
@@ -115793,10 +115852,7 @@ async function runAdNegativeKeywordEdit(opts) {
|
|
|
115793
115852
|
opts = withResolvedAdAccount(opts);
|
|
115794
115853
|
const config = loadConfig(opts.token);
|
|
115795
115854
|
const googleApiUrl = requireGoogleApi(config);
|
|
115796
|
-
const
|
|
115797
|
-
dateParams.set("startDate", toGoogleDate(opts.startDate, -30));
|
|
115798
|
-
dateParams.set("endDate", toGoogleDate(opts.endDate, 0));
|
|
115799
|
-
const listUrl = `${googleApiUrl}/negativekeywordmanagement/v2/list/${opts.account}?${dateParams}`;
|
|
115855
|
+
const listUrl = negativeKeywordSlimListUrl(googleApiUrl, opts.account);
|
|
115800
115856
|
let keyword;
|
|
115801
115857
|
try {
|
|
115802
115858
|
keyword = await findItemInList(listUrl, config, opts.id, opts.verbose);
|
|
@@ -115806,7 +115862,7 @@ async function runAdNegativeKeywordEdit(opts) {
|
|
|
115806
115862
|
`);
|
|
115807
115863
|
process.exit(1);
|
|
115808
115864
|
}
|
|
115809
|
-
const body =
|
|
115865
|
+
const body = negativeKeywordWriteBody(keyword);
|
|
115810
115866
|
if (opts.matchType !== void 0) {
|
|
115811
115867
|
const base2 = opts.text !== void 0 ? opts.text : firstKeywordTextFromRecord(keyword);
|
|
115812
115868
|
if (!String(base2).trim()) {
|
|
@@ -115849,10 +115905,7 @@ async function runAdNegativeKeywordDelete(opts) {
|
|
|
115849
115905
|
opts = withResolvedAdAccount(opts);
|
|
115850
115906
|
const config = loadConfig(opts.token);
|
|
115851
115907
|
const googleApiUrl = requireGoogleApi(config);
|
|
115852
|
-
const
|
|
115853
|
-
dateParams.set("startDate", toGoogleDate(opts.startDate, -30));
|
|
115854
|
-
dateParams.set("endDate", toGoogleDate(opts.endDate, 0));
|
|
115855
|
-
const listUrl = `${googleApiUrl}/negativekeywordmanagement/v2/list/${opts.account}?${dateParams}`;
|
|
115908
|
+
const listUrl = negativeKeywordSlimListUrl(googleApiUrl, opts.account);
|
|
115856
115909
|
let keyword;
|
|
115857
115910
|
try {
|
|
115858
115911
|
keyword = await findItemInList(listUrl, config, opts.id, opts.verbose);
|
|
@@ -115864,7 +115917,10 @@ async function runAdNegativeKeywordDelete(opts) {
|
|
|
115864
115917
|
}
|
|
115865
115918
|
const url = `${googleApiUrl}/negativekeywordmanagement/negativekeyword/${opts.account}/${opts.id}`;
|
|
115866
115919
|
try {
|
|
115867
|
-
await apiDeleteRaw(url, config, {
|
|
115920
|
+
await apiDeleteRaw(url, config, {
|
|
115921
|
+
body: JSON.stringify(negativeKeywordWriteBody(keyword)),
|
|
115922
|
+
verbose: opts.verbose
|
|
115923
|
+
});
|
|
115868
115924
|
} catch (err) {
|
|
115869
115925
|
console.error(`
|
|
115870
115926
|
\u274C \u5220\u9664\u5426\u5B9A\u5173\u952E\u8BCD\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
@@ -121142,7 +121198,7 @@ async function fetchLiveSnapshotForCampaign(config, accountId, campaignId, campa
|
|
|
121142
121198
|
),
|
|
121143
121199
|
fetchList(
|
|
121144
121200
|
config,
|
|
121145
|
-
|
|
121201
|
+
negativeKeywordSlimListUrl(googleApiUrl, accountId),
|
|
121146
121202
|
verbose
|
|
121147
121203
|
),
|
|
121148
121204
|
fetchList(
|
|
@@ -127001,11 +127057,18 @@ function register21(program2) {
|
|
|
127001
127057
|
}
|
|
127002
127058
|
);
|
|
127003
127059
|
adCmd.command("keywords").alias("keyword-list").description(
|
|
127004
|
-
"\u67E5\u8BE2\u5173\u952E\u8BCD\u5217\u8868\uFF08--negative \
|
|
127005
|
-
).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").option("--negative", "\u67E5\u8BE2\u5426\u5B9A\u5173\u952E\u8BCD", false).option(
|
|
127060
|
+
"\u67E5\u8BE2\u5173\u952E\u8BCD\u5217\u8868\uFF08--negative \u8D70\u5426\u8BCD\u7CBE\u7B80\u5217\u8868\uFF0C\u53EF\u7528 --level/--keyword/--match-type/--exclude-paused/--read-deleted\uFF1B\u6B63\u5411\u5173\u952E\u8BCD\u65E5\u671F\u9ED8\u8BA4\u8FD1 30 \u5929\uFF1B\u522B\u540D keyword-list\uFF09"
|
|
127061
|
+
).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").option("--negative", "\u67E5\u8BE2\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u7CBE\u7B80\u5217\u8868\uFF0C\u4E0D\u6309\u65E5\u671F\u8FC7\u6EE4\uFF09", false).option(
|
|
127006
127062
|
"--level <level>",
|
|
127007
127063
|
"\u5426\u5B9A\u5173\u952E\u8BCD\u5C42\u7EA7\u8FC7\u6EE4\uFF1AAccount | Campaign | AdGroup\uFF08\u4EC5\u914D\u5408 --negative\uFF1B\u4E0D\u4F20\u5219\u4E09\u7EA7\u90FD\u663E\u793A\uFF09"
|
|
127008
|
-
).option("--
|
|
127064
|
+
).option("--keyword <text>", "\u5426\u5B9A\u5173\u952E\u8BCD\u6587\u672C\uFF0C\u5305\u542B\u5373\u547D\u4E2D\uFF08\u4EC5\u914D\u5408 --negative\uFF0C\u4E0D\u533A\u5206\u5927\u5C0F\u5199\uFF09").option(
|
|
127065
|
+
"--match-type <type>",
|
|
127066
|
+
"\u5426\u5B9A\u5173\u952E\u8BCD\u5339\u914D\u7C7B\u578B\uFF1ABroad | Phrase | Exact\uFF08\u4EC5\u914D\u5408 --negative\uFF1B\u6309\u8FD4\u56DE\u7684 matchTypeV2 \u672C\u5730\u8FC7\u6EE4\uFF09"
|
|
127067
|
+
).option("--exclude-paused", "\u53BB\u6389\u6682\u505C\u7684\u5426\u8BCD\uFF0C\u4EE5\u53CA\u6682\u505C\u7CFB\u5217\u3001\u6682\u505C\u5E7F\u544A\u7EC4\u4E0A\u7684\u5426\u8BCD\uFF08\u4EC5\u914D\u5408 --negative\uFF09", false).option(
|
|
127068
|
+
"--read-deleted",
|
|
127069
|
+
"\u5305\u542B\u5DF2\u79FB\u9664\u7684\u7CFB\u5217\u3001\u5E7F\u544A\u7EC4\uFF08\u5426\u8BCD\u672C\u8EAB\u88AB\u79FB\u9664\u7684\u4ECD\u4E0D\u8FD4\u56DE\uFF1B\u4EC5\u914D\u5408 --negative\uFF09",
|
|
127070
|
+
false
|
|
127071
|
+
).option("--start <date>", "\u5F00\u59CB\u65E5\u671F\uFF08YYYY-MM-DD\uFF0C\u9ED8\u8BA4 30 \u5929\u524D\uFF1B\u5426\u8BCD\u7CBE\u7B80\u5217\u8868\u5FFD\u7565\u65E5\u671F\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F\uFF08YYYY-MM-DD\uFF0C\u9ED8\u8BA4\u4ECA\u5929\uFF1B\u5426\u8BCD\u7CBE\u7B80\u5217\u8868\u5FFD\u7565\u65E5\u671F\uFF09").option("-t, --token <token>", "Auth Token").option(
|
|
127009
127072
|
"--json-out <path>",
|
|
127010
127073
|
"\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
|
|
127011
127074
|
void 0
|
|
@@ -127015,11 +127078,26 @@ function register21(program2) {
|
|
|
127015
127078
|
console.error("\n\u274C --level \u53EA\u63A5\u53D7 Account | Campaign | AdGroup\n");
|
|
127016
127079
|
process.exit(1);
|
|
127017
127080
|
}
|
|
127081
|
+
const negativeOnly = opts.level || opts.keyword || opts.matchType || opts.excludePaused || opts.readDeleted;
|
|
127082
|
+
if (negativeOnly && !opts.negative) {
|
|
127083
|
+
console.error(
|
|
127084
|
+
"\n\u274C --level / --keyword / --match-type / --exclude-paused / --read-deleted \u53EA\u7528\u4E8E\u5426\u5B9A\u5173\u952E\u8BCD\uFF0C\u8BF7\u52A0\u4E0A --negative\n"
|
|
127085
|
+
);
|
|
127086
|
+
process.exit(1);
|
|
127087
|
+
}
|
|
127088
|
+
if (opts.matchType && !["Broad", "Phrase", "Exact"].includes(opts.matchType)) {
|
|
127089
|
+
console.error("\n\u274C --match-type \u53EA\u63A5\u53D7 Broad | Phrase | Exact\n");
|
|
127090
|
+
process.exit(1);
|
|
127091
|
+
}
|
|
127018
127092
|
await runAdKeywords({
|
|
127019
127093
|
token: opts.token,
|
|
127020
127094
|
account: opts.account,
|
|
127021
127095
|
negative: opts.negative,
|
|
127022
127096
|
level: opts.level,
|
|
127097
|
+
keywordText: opts.keyword,
|
|
127098
|
+
matchType: opts.matchType,
|
|
127099
|
+
excludePaused: opts.excludePaused,
|
|
127100
|
+
readDeleted: opts.readDeleted,
|
|
127023
127101
|
startDate: opts.start,
|
|
127024
127102
|
endDate: opts.end,
|
|
127025
127103
|
jsonOut: opts.jsonOut,
|
package/dist/skill/_meta.json
CHANGED
|
@@ -78,7 +78,7 @@ CLI 出口的所有 JSON / 表格金额已统一为**元**,关键字段:
|
|
|
78
78
|
|
|
79
79
|
## Google 账户分析:`google-analysis` 命令
|
|
80
80
|
|
|
81
|
-
> **重要**:`google-analysis` 是统一入口,所有维度都通过 `--sections` 选取。关键词维度用 `--sections keywords`,**不要**用 `ad keywords` 代替。否词名单用 `--sections negative-keywords`,**不要**另跑 `ad keywords --negative
|
|
81
|
+
> **重要**:`google-analysis` 是统一入口,所有维度都通过 `--sections` 选取。关键词维度用 `--sections keywords`,**不要**用 `ad keywords` 代替。否词名单用 `--sections negative-keywords`,**不要**另跑 `ad keywords --negative`。这份否词是精简列表:`keywordText`、`matchTypeV2`、`negativeLevel`、`campaignId`、`adGroupId`,没有系列名,也不按 `--start` / `--end` 过滤。涉及「今天/当天」消耗的口径见本文顶部「数据时效性」表。
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
84
|
# 单维度
|
|
@@ -186,10 +186,10 @@ siluzan-tso ad ad-delete -a <accountId> --id <adId>
|
|
|
186
186
|
### 查询
|
|
187
187
|
|
|
188
188
|
```bash
|
|
189
|
-
siluzan-tso ad keywords -a <accountId> [--
|
|
189
|
+
siluzan-tso ad keywords -a <accountId> [--start/--end <date>] [--json-out ./snap]
|
|
190
190
|
```
|
|
191
191
|
|
|
192
|
-
带日期时行上有 `conversionsValue`(账户币种的转化价值,不是 `conversions` 次数)。`--json-out`
|
|
192
|
+
带日期时行上有 `conversionsValue`(账户币种的转化价值,不是 `conversions` 次数)。`--json-out` 里直接读这个字段;终端表「转化价值」列在字段缺失时为「—」。出价读 `maxCPCYuan`(元)。本列表按账户和日期缓存约 60 秒,**没有** `--fresh`,也**没有** `--id`。`keyword-edit` 回读报「列表仍是」时,等约 60 秒再看 `maxCPCYuan`,不要立刻重跑编辑。否定关键词不要用这条带日期的列表,见下方。
|
|
193
193
|
|
|
194
194
|
### 添加关键词
|
|
195
195
|
|
|
@@ -207,6 +207,18 @@ siluzan-tso ad keyword-create \
|
|
|
207
207
|
|
|
208
208
|
### 否定关键词
|
|
209
209
|
|
|
210
|
+
查询走精简列表 `GET /negativekeywordmanagement/v2/list/{id}/slim`。一条对应一条否词,字段只有 `id`、`keywordText`(字符串)、`matchTypeV2`、`negativeLevel`、`campaignId`、`adGroupId`。没有分页、没有总条数、没有系列名、没有消耗。不传参数就是三级都查,含暂停,不含已移除的系列和广告组。`--start` / `--end` 会被忽略。
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
siluzan-tso ad keywords -a <accountId> --negative [--level Account|Campaign|AdGroup] \
|
|
214
|
+
[--keyword <文本>] [--match-type Broad|Phrase|Exact] [--exclude-paused] [--read-deleted] \
|
|
215
|
+
[--json-out ./snap]
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`--exclude-paused` 去掉暂停的否词,以及暂停系列、暂停广告组上的否词。账户级没有暂停,不受影响。`--read-deleted` 把已移除的系列、广告组也算进来;否词本身被移除的仍然不返回。两个开关可以一起用。`--keyword` 是包含匹配,不区分大小写。`--match-type` 按返回的 `matchTypeV2` 在本地过滤,不要自己给这个接口拼 `matchtype=BROAD`:网关用旧枚举去比,会得到空列表。
|
|
219
|
+
|
|
220
|
+
改、删先用这条精简记录组请求体:`keywordText` 要写成单元素数组,再带 `id`、`matchTypeV2`、`negativeLevel`,系列级加 `campaignId`,广告组级再加 `adGroupId`。
|
|
221
|
+
|
|
210
222
|
**支持空格多词短语**(如 `how to make`、`home cooking`)。匹配类型与 Web 一致:裸词=广泛,`"短语"`=词组,`[词]`=完全;也可用 `--match-type`。多词/带引号时推荐 `--keywords-file`,避免 shell 转义。
|
|
211
223
|
|
|
212
224
|
**三级层级:** Campaign(系列)/ AdGroup(组)/ **Account(账户级,Sammamish 2026-08 新增)**。不传 `--level` 时按是否传 `--adgroup-id` 自动判断 Campaign / AdGroup(兼容旧用法);**账户级必须显式传 `--level Account`**,且不能传 `--campaign-id` / `--adgroup-id`。账户级否词一次生效于整个账户下所有系列(对应 Google Ads「Account Settings → Negative keywords」),**无需再去 Google 后台手工应用**——这是本次新增前的旧限制,现已由接口直接支持。
|
|
@@ -74,7 +74,7 @@ siluzan-tso google-analysis render \
|
|
|
74
74
|
| 6 | 地域分布(国家/地区 消耗占比) | `google-analysis --sections geographic` |
|
|
75
75
|
| 7 | 受众分布(意向/兴趣/再营销/客户匹配等列表表现) | `google-analysis --sections audience`(默认不传 `--audience-type`,系统+自定义一起拉) |
|
|
76
76
|
| 8 | 关键词表现(词/消耗/CTR/CPC 排行) | `google-analysis --sections keywords`(这个命令获取的关键词数据会来自多个系列,不能合并去重) |
|
|
77
|
-
| 9 | 否词(当前账户已有否定关键词:词面 / 匹配 / 级别 / 系列 /
|
|
77
|
+
| 9 | 否词(当前账户已有否定关键词:词面 / 匹配 / 级别 / 系列 id / 广告组 id) | `google-analysis --sections negative-keywords`(精简列表;0 条也要落盘;不按本周期消耗过滤) |
|
|
78
78
|
| 10 | 优化建议(根据以上数据给出可执行改进建议) | 不额外拉数,基于已有数据撰写 |
|
|
79
79
|
|
|
80
80
|
**在执行任何数据拉取之前**,先向用户展示以下可选维度,询问是否需要追加:
|
|
@@ -143,9 +143,9 @@ siluzan-tso google-analysis -a <id> --start <s> --end <e> --json-out ./snap-goog
|
|
|
143
143
|
|
|
144
144
|
## 否词(必写)
|
|
145
145
|
|
|
146
|
-
`negative-keywords-*.json` → `items[]`,合并后是 `tables.negativeKeywords[]`。这是**当前账户已经存在的否定关键词**(系列 / 广告组 /
|
|
146
|
+
`negative-keywords-*.json` → `items[]`,合并后是 `tables.negativeKeywords[]`。这是**当前账户已经存在的否定关键词**(系列 / 广告组 / 账户三级),来自精简列表,不是本周期的消耗或搜索词。
|
|
147
147
|
|
|
148
|
-
- **呈现**:词面、匹配方式、级别(`levelZh`:账户 / 系列 /
|
|
148
|
+
- **呈现**:词面、匹配方式、级别(`levelZh`:账户 / 系列 / 广告组)、`campaignId`、`adGroupId`。精简列表没有系列名和广告组名。账户级没有 `campaignId`,只有广告组级才有 `adGroupId`。旧快照若还有 `campaignName` / `adGroupName` 可以照用。0 条时表格写「当前账户否词 0 条」。
|
|
149
149
|
- **分析** → `narrative.sections.negativeKeywords.{analysis,suggestions}`(≥2 / ≥1):
|
|
150
150
|
- 先写总条数,再按级别拆开。有词就点名几条;0 条就写「当前否词 0 条」。
|
|
151
151
|
- 其他章节如果建议加否词、出否词清单,必须引用这张表的条数。禁止在没拉到名单时写「账户没有否词」或只在建议里提否词。
|
|
@@ -74,7 +74,7 @@ siluzan-tso google-analysis render \
|
|
|
74
74
|
| 6 | 地域分布(国家/地区 消耗占比) | `google-analysis --sections geographic` |
|
|
75
75
|
| 7 | 受众分布(意向/兴趣/再营销/客户匹配等列表表现) | `google-analysis --sections audience`(默认不传 `--audience-type`,系统+自定义一起拉) |
|
|
76
76
|
| 8 | 关键词表现(词/消耗/CTR/CPC 排行) | `google-analysis --sections keywords`(这个命令获取的关键词数据会来自多个系列,不能合并去重) |
|
|
77
|
-
| 9 | 否词(当前账户已有否定关键词:词面 / 匹配 / 级别 / 系列 /
|
|
77
|
+
| 9 | 否词(当前账户已有否定关键词:词面 / 匹配 / 级别 / 系列 id / 广告组 id) | `google-analysis --sections negative-keywords`(精简列表;0 条也要落盘;不按本周期消耗过滤) |
|
|
78
78
|
| 10 | 优化建议(根据以上数据给出可执行改进建议) | 不额外拉数,基于已有数据撰写 |
|
|
79
79
|
|
|
80
80
|
**在执行任何数据拉取之前**,先向用户展示以下可选维度,询问是否需要追加:
|
|
@@ -143,9 +143,9 @@ siluzan-tso google-analysis -a <id> --start <s> --end <e> --json-out ./snap-goog
|
|
|
143
143
|
|
|
144
144
|
## 否词(必写)
|
|
145
145
|
|
|
146
|
-
`negative-keywords-*.json` → `items[]`,合并后是 `tables.negativeKeywords[]`。这是**当前账户已经存在的否定关键词**(系列 / 广告组 /
|
|
146
|
+
`negative-keywords-*.json` → `items[]`,合并后是 `tables.negativeKeywords[]`。这是**当前账户已经存在的否定关键词**(系列 / 广告组 / 账户三级),来自精简列表,不是本周期的消耗或搜索词。
|
|
147
147
|
|
|
148
|
-
- **呈现**:词面、匹配方式、级别(`levelZh`:账户 / 系列 /
|
|
148
|
+
- **呈现**:词面、匹配方式、级别(`levelZh`:账户 / 系列 / 广告组)、`campaignId`、`adGroupId`。精简列表没有系列名和广告组名。账户级没有 `campaignId`,只有广告组级才有 `adGroupId`。旧快照若还有 `campaignName` / `adGroupName` 可以照用。0 条时表格写「当前账户否词 0 条」。
|
|
149
149
|
- **分析** → `narrative.sections.negativeKeywords.{analysis,suggestions}`(≥2 / ≥1):
|
|
150
150
|
- 先写总条数,再按级别拆开。有词就点名几条;0 条就写「当前否词 0 条」。
|
|
151
151
|
- 其他章节如果建议加否词、出否词清单,必须引用这张表的条数。禁止在没拉到名单时写「账户没有否词」或只在建议里提否词。
|
|
@@ -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.64-beta.
|
|
12
|
+
$PKG_VERSION = '1.1.64-beta.2'
|
|
13
13
|
$CLI_BIN = 'siluzan-tso'
|
|
14
14
|
$SKILL_LABEL = 'Siluzan TSO'
|
|
15
15
|
$INSTALL_CMD = 'npm install -g siluzan-tso-cli@beta'
|
|
@@ -9,7 +9,7 @@ set -euo pipefail
|
|
|
9
9
|
# -- Package info (injected at build time) ------------------------------------
|
|
10
10
|
readonly PKG_NAME="siluzan-tso-cli"
|
|
11
11
|
# PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
|
|
12
|
-
readonly PKG_VERSION="1.1.64-beta.
|
|
12
|
+
readonly PKG_VERSION="1.1.64-beta.2"
|
|
13
13
|
readonly CLI_BIN="siluzan-tso"
|
|
14
14
|
readonly SKILL_LABEL="Siluzan TSO"
|
|
15
15
|
readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
|