siluzan-tso-cli 1.1.63 → 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 +2 -1
- package/dist/index.js +135 -61
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/accounts/accounts-list.md +1 -1
- package/dist/skill/references/accounts/accounts-permissions.md +2 -2
- package/dist/skill/references/accounts/finance.md +1 -1
- package/dist/skill/references/accounts/open-account-by-media.md +1 -1
- package/dist/skill/references/accounts/open-account-google-ui.md +1 -1
- package/dist/skill/references/analytics/account-analytics.md +1 -1
- package/dist/skill/references/analytics/rag.md +1 -1
- package/dist/skill/references/analytics/reporting.md +5 -5
- package/dist/skill/references/core/setup.md +5 -5
- package/dist/skill/references/google-ads/google-ads-read.md +14 -2
- package/dist/skill/references/misc/tso-home.md +2 -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 +3 -3
- package/dist/skill/scripts/install.sh +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ HTML 报告模板外部资源:**ECharts** 统一使用 `https://staticpn.siluz
|
|
|
43
43
|
在**用户的目标项目根目录**执行(根据用户使用的助手选择 `--ai`):
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
npm install -g siluzan-tso-cli
|
|
46
|
+
npm install -g siluzan-tso-cli@beta
|
|
47
47
|
siluzan-tso init --ai cursor # 写入 Cursor(默认)
|
|
48
48
|
siluzan-tso init --ai cursor,claude # 同时写入多个平台
|
|
49
49
|
siluzan-tso init --ai all # 写入所有支持的平台
|
|
@@ -51,6 +51,7 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
|
|
|
51
51
|
siluzan-tso init --force # 强制覆盖已存在文件
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
> **注意**:当前为测试版(1.1.64-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
54
55
|
|
|
55
56
|
| 助手 | 建议 `--ai` |
|
|
56
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -2337,7 +2337,7 @@ function rawRequest(url, options) {
|
|
|
2337
2337
|
const parsed = new URL(url);
|
|
2338
2338
|
const transport = parsed.protocol === "https:" ? https : http;
|
|
2339
2339
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
2340
|
-
const maxBytes = options.maxResponseBytes ??
|
|
2340
|
+
const maxBytes = options.maxResponseBytes ?? 0;
|
|
2341
2341
|
const isHttps = parsed.protocol === "https:";
|
|
2342
2342
|
const headers = {
|
|
2343
2343
|
...buildSiluzanClientIdentityHeaders(),
|
|
@@ -2375,7 +2375,10 @@ function rawRequest(url, options) {
|
|
|
2375
2375
|
} else if (encoding === "br") {
|
|
2376
2376
|
stream = res.pipe(zlib.createBrotliDecompress());
|
|
2377
2377
|
}
|
|
2378
|
-
|
|
2378
|
+
let failed = false;
|
|
2379
|
+
const fail5 = (err) => {
|
|
2380
|
+
if (failed) return;
|
|
2381
|
+
failed = true;
|
|
2379
2382
|
res.destroy();
|
|
2380
2383
|
if (perfOn) {
|
|
2381
2384
|
emitPerf({
|
|
@@ -2385,43 +2388,37 @@ function rawRequest(url, options) {
|
|
|
2385
2388
|
elapsedMs: performance.now() - t0,
|
|
2386
2389
|
bytes: 0,
|
|
2387
2390
|
ok: false,
|
|
2388
|
-
error:
|
|
2391
|
+
error: err.message
|
|
2389
2392
|
});
|
|
2390
2393
|
}
|
|
2391
|
-
reject(
|
|
2394
|
+
reject(err);
|
|
2392
2395
|
};
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
+
const onStreamError = (err) => {
|
|
2397
|
+
fail5(new Error(`\u54CD\u5E94\u89E3\u538B\u5931\u8D25\uFF08${encoding || "identity"}\uFF09\uFF1A${err.message}`));
|
|
2398
|
+
};
|
|
2399
|
+
if (stream !== res) stream.on("error", onStreamError);
|
|
2400
|
+
res.on("error", onStreamError);
|
|
2401
|
+
const chunks = [];
|
|
2396
2402
|
let byteLen = 0;
|
|
2397
|
-
stream.setEncoding("utf8");
|
|
2398
2403
|
stream.on("data", (chunk) => {
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
method,
|
|
2405
|
-
url,
|
|
2406
|
-
status: res.statusCode ?? 0,
|
|
2407
|
-
elapsedMs: performance.now() - t0,
|
|
2408
|
-
bytes: byteLen,
|
|
2409
|
-
ok: false,
|
|
2410
|
-
error: "response body exceeded limit"
|
|
2411
|
-
});
|
|
2412
|
-
}
|
|
2413
|
-
reject(
|
|
2404
|
+
if (failed) return;
|
|
2405
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
2406
|
+
byteLen += buf.length;
|
|
2407
|
+
if (maxBytes > 0 && byteLen > maxBytes) {
|
|
2408
|
+
fail5(
|
|
2414
2409
|
new Error(`\u54CD\u5E94\u4F53\u8D85\u8FC7\u4E0A\u9650\uFF08${(maxBytes / 1024 / 1024).toFixed(0)} MB\uFF09\uFF0C\u5DF2\u4E2D\u65AD\u8FDE\u63A5`)
|
|
2415
2410
|
);
|
|
2416
2411
|
return;
|
|
2417
2412
|
}
|
|
2418
|
-
|
|
2413
|
+
chunks.push(buf);
|
|
2419
2414
|
});
|
|
2420
2415
|
stream.on("end", () => {
|
|
2421
|
-
|
|
2416
|
+
if (failed) return;
|
|
2417
|
+
const text3 = Buffer.concat(chunks).toString("utf8");
|
|
2418
|
+
const responseHeaders = {};
|
|
2422
2419
|
for (const [key, val] of Object.entries(res.headers)) {
|
|
2423
2420
|
if (val !== void 0) {
|
|
2424
|
-
|
|
2421
|
+
responseHeaders[key.toLowerCase()] = Array.isArray(val) ? val[0] : val;
|
|
2425
2422
|
}
|
|
2426
2423
|
}
|
|
2427
2424
|
const status = res.statusCode ?? 0;
|
|
@@ -2435,7 +2432,7 @@ function rawRequest(url, options) {
|
|
|
2435
2432
|
ok: status >= 200 && status < 300
|
|
2436
2433
|
});
|
|
2437
2434
|
}
|
|
2438
|
-
resolve52({ status, text:
|
|
2435
|
+
resolve52({ status, text: text3, headers: responseHeaders });
|
|
2439
2436
|
});
|
|
2440
2437
|
});
|
|
2441
2438
|
req.on("timeout", () => {
|
|
@@ -3924,7 +3921,7 @@ async function issueApiKeyWithPhoneCode(opts) {
|
|
|
3924
3921
|
});
|
|
3925
3922
|
return apiKey;
|
|
3926
3923
|
}
|
|
3927
|
-
var import_semver, SILUZAN_DIR, CONFIG_FILE, inProcessLockChain, ALLOWED_HOSTNAME_SUFFIXES, EXACT_ID_FIELD_NAMES, SILUZAN_AGENT_COMPANY_SOURCE_TYPE, DEERFLOW_AGENT_ENV, _clientProduct, _resolveVersion, DEFAULT_TIMEOUT_MS,
|
|
3924
|
+
var import_semver, SILUZAN_DIR, CONFIG_FILE, inProcessLockChain, ALLOWED_HOSTNAME_SUFFIXES, EXACT_ID_FIELD_NAMES, SILUZAN_AGENT_COMPANY_SOURCE_TYPE, DEERFLOW_AGENT_ENV, _clientProduct, _resolveVersion, DEFAULT_TIMEOUT_MS, httpsAgent, httpAgent, PERF_PREFIX, DEFAULT_DETAIL_MAX, VERBOSE_DETAIL_MAX, GET_RETRY_DELAYS_MS, TABLE_CELL_PIPE, ASCII_TABLE_CHARS, CLI_SNAPSHOT_MANIFEST_FILE, SCHEMA_VERSION, FALLBACK_FIELD_GUIDE_REFS, _cliPackage, _resolveCliVersion, _fieldGuideRefs, OUTLINE_AGENT_HINT, TS_LIKE_ARRAY_SAMPLE_MAX, MAX_INVOCATION_CHARS, currentInvocation, MAX_COMMIT_CHARS, WRITE_AUDIT_AGENT_AUTO_COMMIT, cliCommitOverride, WRITE_AUDIT_SCHEMA_VERSION, MUTATING, BEIJING_TZ, AUDIT_LOG_FILENAME_RE, DEFAULT_MAX_FILE_MB, MIN_SEGMENT_BYTES, MAX_SEGMENT_BYTES, DEFAULT_RETENTION_DAYS, MIN_RETENTION_DAYS, MAX_RETENTION_DAYS, PRUNE_THROTTLE_MS, lastPruneAtMs, MAX_BODY_CHARS, MAX_ERROR_CHARS, DEFAULT_FIND_AUDIT_MAX_DAYS, MAX_SNAPSHOT_FILE_BYTES, API_KEY_SERVICE_VALUES;
|
|
3928
3925
|
var init_dist = __esm({
|
|
3929
3926
|
"../common/dist/index.js"() {
|
|
3930
3927
|
"use strict";
|
|
@@ -3974,7 +3971,6 @@ var init_dist = __esm({
|
|
|
3974
3971
|
DEERFLOW_AGENT_ENV = "DEERFLOW_AGENT";
|
|
3975
3972
|
_clientProduct = "siluzan-cli";
|
|
3976
3973
|
DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
3977
|
-
MAX_RESPONSE_BYTES = 50 * 1024 * 1024;
|
|
3978
3974
|
httpsAgent = new https.Agent({
|
|
3979
3975
|
keepAlive: true,
|
|
3980
3976
|
keepAliveMsecs: 3e4,
|
|
@@ -4051,7 +4047,7 @@ var DEFAULT_API_BASE;
|
|
|
4051
4047
|
var init_defaults = __esm({
|
|
4052
4048
|
"src/config/defaults.ts"() {
|
|
4053
4049
|
"use strict";
|
|
4054
|
-
DEFAULT_API_BASE = "https://tso-api.siluzan.com";
|
|
4050
|
+
DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
|
|
4055
4051
|
}
|
|
4056
4052
|
});
|
|
4057
4053
|
|
|
@@ -5564,6 +5560,17 @@ function resolveAdAccountId(account, label2 = "-a, --account") {
|
|
|
5564
5560
|
function withResolvedAdAccount(opts) {
|
|
5565
5561
|
return { ...opts, account: resolveAdAccountId(opts.account) };
|
|
5566
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
|
+
}
|
|
5567
5574
|
async function findItemInList(listUrl, config, id, verbose) {
|
|
5568
5575
|
const res = await apiFetch2(listUrl, config, {}, verbose);
|
|
5569
5576
|
const item = (res.data ?? []).find((x) => x["id"] === id);
|
|
@@ -6402,6 +6409,20 @@ function coerceToRowArray(value) {
|
|
|
6402
6409
|
}
|
|
6403
6410
|
return [];
|
|
6404
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
|
+
}
|
|
6405
6426
|
function asRecord(value) {
|
|
6406
6427
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
|
|
6407
6428
|
return value;
|
|
@@ -6436,7 +6457,7 @@ function wrapGoogleAnalysisSnapshotPayload(section, payload) {
|
|
|
6436
6457
|
const bag = asRecord(payload);
|
|
6437
6458
|
if (bag) {
|
|
6438
6459
|
const r = readGatewayList(bag);
|
|
6439
|
-
items3 = r.items;
|
|
6460
|
+
items3 = section === "negative-keywords" ? dedupeRowsById(r.items) : r.items;
|
|
6440
6461
|
meta = r.meta;
|
|
6441
6462
|
}
|
|
6442
6463
|
} else if (ROOT_ARRAY_SECTIONS.has(section)) {
|
|
@@ -8124,9 +8145,10 @@ var init_sections = __esm({
|
|
|
8124
8145
|
},
|
|
8125
8146
|
{
|
|
8126
8147
|
name: "negative-keywords",
|
|
8127
|
-
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 是为了快照文件名仍带报告区间,避免和上一次落盘互相覆盖。
|
|
8128
8150
|
dateMode: "range",
|
|
8129
|
-
path: (id) => `/negativekeywordmanagement/v2/list/${id}`
|
|
8151
|
+
path: (id) => `/negativekeywordmanagement/v2/list/${id}/slim`
|
|
8130
8152
|
},
|
|
8131
8153
|
{
|
|
8132
8154
|
name: "search-terms",
|
|
@@ -104346,6 +104368,8 @@ function mapNegativeKeywordRows(rows) {
|
|
|
104346
104368
|
if (id && seen.has(id)) continue;
|
|
104347
104369
|
if (id) seen.add(id);
|
|
104348
104370
|
const level = String(row.negativeLevel ?? "").trim();
|
|
104371
|
+
const campaignId = String(row.campaignId ?? "").trim();
|
|
104372
|
+
const adGroupId = String(row.adGroupId ?? "").trim();
|
|
104349
104373
|
const campaignName = String(row.campaignName ?? "").trim();
|
|
104350
104374
|
const adGroupName = String(row.adGroupName ?? "").trim();
|
|
104351
104375
|
mapped.push({
|
|
@@ -104353,6 +104377,8 @@ function mapNegativeKeywordRows(rows) {
|
|
|
104353
104377
|
matchType: String(row.matchTypeV2 ?? row.matchType ?? "\u2014"),
|
|
104354
104378
|
level: level || "\u2014",
|
|
104355
104379
|
levelZh: NEGATIVE_LEVEL_ZH[level] ?? (level || "\u2014"),
|
|
104380
|
+
campaignId: campaignId || "\u2014",
|
|
104381
|
+
adGroupId: adGroupId || "\u2014",
|
|
104356
104382
|
campaignName: campaignName || "\u2014",
|
|
104357
104383
|
adGroupName: adGroupName || "\u2014"
|
|
104358
104384
|
});
|
|
@@ -115261,6 +115287,7 @@ function resolveKeywordTexts(opts) {
|
|
|
115261
115287
|
}
|
|
115262
115288
|
function firstKeywordTextFromRecord(k) {
|
|
115263
115289
|
const kt = k["keywordText"];
|
|
115290
|
+
if (typeof kt === "string") return kt;
|
|
115264
115291
|
if (Array.isArray(kt) && kt.length > 0 && typeof kt[0] === "string") {
|
|
115265
115292
|
return kt[0];
|
|
115266
115293
|
}
|
|
@@ -115270,9 +115297,27 @@ function firstKeywordTextFromRecord(k) {
|
|
|
115270
115297
|
}
|
|
115271
115298
|
return "";
|
|
115272
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
|
+
}
|
|
115273
115314
|
function toNegativeKeywordAppliedLevelQuery(level) {
|
|
115274
115315
|
return level === "AdGroup" ? "Ad group" : level;
|
|
115275
115316
|
}
|
|
115317
|
+
function filterNegativeKeywordsByMatchType(items3, matchType) {
|
|
115318
|
+
const want = matchType.toLowerCase();
|
|
115319
|
+
return items3.filter((item) => String(item["matchTypeV2"] ?? "").toLowerCase() === want);
|
|
115320
|
+
}
|
|
115276
115321
|
function keywordRowToYuan(item) {
|
|
115277
115322
|
const { maxCPC: rawMaxCpc, ...rest } = item;
|
|
115278
115323
|
if (rawMaxCpc == null) return item;
|
|
@@ -115282,14 +115327,20 @@ async function runAdKeywords(opts) {
|
|
|
115282
115327
|
opts = withResolvedAdAccount(opts);
|
|
115283
115328
|
const config = loadConfig(opts.token);
|
|
115284
115329
|
const googleApiUrl = requireGoogleApi(config);
|
|
115285
|
-
|
|
115286
|
-
|
|
115287
|
-
|
|
115288
|
-
|
|
115289
|
-
|
|
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}`;
|
|
115290
115343
|
}
|
|
115291
|
-
const endpoint = opts.negative ? "negativekeywordmanagement" : "keywordmanagement";
|
|
115292
|
-
const url = `${googleApiUrl}/${endpoint}/v2/list/${opts.account}?${params}`;
|
|
115293
115344
|
let data;
|
|
115294
115345
|
try {
|
|
115295
115346
|
data = await apiFetch2(url, config, {}, opts.verbose);
|
|
@@ -115301,12 +115352,15 @@ async function runAdKeywords(opts) {
|
|
|
115301
115352
|
}
|
|
115302
115353
|
const rawItems = data.data ?? [];
|
|
115303
115354
|
const seen = /* @__PURE__ */ new Set();
|
|
115304
|
-
|
|
115355
|
+
let items3 = rawItems.filter((item) => {
|
|
115305
115356
|
const key = item["id"];
|
|
115306
115357
|
if (key !== void 0 && seen.has(key)) return false;
|
|
115307
115358
|
if (key !== void 0) seen.add(key);
|
|
115308
115359
|
return true;
|
|
115309
115360
|
});
|
|
115361
|
+
if (opts.negative && opts.matchType) {
|
|
115362
|
+
items3 = filterNegativeKeywordsByMatchType(items3, opts.matchType);
|
|
115363
|
+
}
|
|
115310
115364
|
const label2 = opts.negative ? "\u5426\u5B9A\u5173\u952E\u8BCD" : "\u5173\u952E\u8BCD";
|
|
115311
115365
|
const n = items3.length;
|
|
115312
115366
|
const displayItems = opts.negative ? items3 : items3.map((row) => keywordRowToYuan(row));
|
|
@@ -115314,7 +115368,7 @@ async function runAdKeywords(opts) {
|
|
|
115314
115368
|
wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items: displayItems })
|
|
115315
115369
|
);
|
|
115316
115370
|
if (await emitCliJsonOrSnapshot(opts, {
|
|
115317
|
-
section: `ad-keywords-${opts.
|
|
115371
|
+
section: `ad-keywords-${opts.negative ? "negative" : "positive"}`,
|
|
115318
115372
|
commandLabel: "ad keywords",
|
|
115319
115373
|
commandHint: opts.account,
|
|
115320
115374
|
payload: kwPayload,
|
|
@@ -115336,7 +115390,11 @@ ${label2}\uFF08\u8D26\u6237\uFF1A${opts.account}\uFF0C\u7B2C 1 \u9875\uFF0C\u672
|
|
|
115336
115390
|
const kwText = Array.isArray(item["keywordText"]) ? item["keywordText"].join(", ") : String(item["text"] ?? item["keywordText"] ?? item["id"] ?? "\u2014");
|
|
115337
115391
|
const matchType = item["matchTypeV2"] ?? "\u2014";
|
|
115338
115392
|
const level = item["negativeLevel"] ?? "\u2014";
|
|
115339
|
-
|
|
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
|
+
);
|
|
115340
115398
|
});
|
|
115341
115399
|
} else {
|
|
115342
115400
|
const kwW = Math.min(
|
|
@@ -115520,10 +115578,7 @@ async function runAdNegativeKeywordCreate(opts) {
|
|
|
115520
115578
|
`);
|
|
115521
115579
|
process.exit(1);
|
|
115522
115580
|
}
|
|
115523
|
-
const
|
|
115524
|
-
dateParams.set("startDate", toGoogleDate(void 0, -30));
|
|
115525
|
-
dateParams.set("endDate", toGoogleDate(void 0, 0));
|
|
115526
|
-
const listUrl = `${googleApiUrl}/negativekeywordmanagement/v2/list/${opts.account}?${dateParams}`;
|
|
115581
|
+
const listUrl = negativeKeywordSlimListUrl(googleApiUrl, opts.account);
|
|
115527
115582
|
const readList = async () => {
|
|
115528
115583
|
try {
|
|
115529
115584
|
const listed = await apiFetch2(
|
|
@@ -115797,10 +115852,7 @@ async function runAdNegativeKeywordEdit(opts) {
|
|
|
115797
115852
|
opts = withResolvedAdAccount(opts);
|
|
115798
115853
|
const config = loadConfig(opts.token);
|
|
115799
115854
|
const googleApiUrl = requireGoogleApi(config);
|
|
115800
|
-
const
|
|
115801
|
-
dateParams.set("startDate", toGoogleDate(opts.startDate, -30));
|
|
115802
|
-
dateParams.set("endDate", toGoogleDate(opts.endDate, 0));
|
|
115803
|
-
const listUrl = `${googleApiUrl}/negativekeywordmanagement/v2/list/${opts.account}?${dateParams}`;
|
|
115855
|
+
const listUrl = negativeKeywordSlimListUrl(googleApiUrl, opts.account);
|
|
115804
115856
|
let keyword;
|
|
115805
115857
|
try {
|
|
115806
115858
|
keyword = await findItemInList(listUrl, config, opts.id, opts.verbose);
|
|
@@ -115810,7 +115862,7 @@ async function runAdNegativeKeywordEdit(opts) {
|
|
|
115810
115862
|
`);
|
|
115811
115863
|
process.exit(1);
|
|
115812
115864
|
}
|
|
115813
|
-
const body =
|
|
115865
|
+
const body = negativeKeywordWriteBody(keyword);
|
|
115814
115866
|
if (opts.matchType !== void 0) {
|
|
115815
115867
|
const base2 = opts.text !== void 0 ? opts.text : firstKeywordTextFromRecord(keyword);
|
|
115816
115868
|
if (!String(base2).trim()) {
|
|
@@ -115853,10 +115905,7 @@ async function runAdNegativeKeywordDelete(opts) {
|
|
|
115853
115905
|
opts = withResolvedAdAccount(opts);
|
|
115854
115906
|
const config = loadConfig(opts.token);
|
|
115855
115907
|
const googleApiUrl = requireGoogleApi(config);
|
|
115856
|
-
const
|
|
115857
|
-
dateParams.set("startDate", toGoogleDate(opts.startDate, -30));
|
|
115858
|
-
dateParams.set("endDate", toGoogleDate(opts.endDate, 0));
|
|
115859
|
-
const listUrl = `${googleApiUrl}/negativekeywordmanagement/v2/list/${opts.account}?${dateParams}`;
|
|
115908
|
+
const listUrl = negativeKeywordSlimListUrl(googleApiUrl, opts.account);
|
|
115860
115909
|
let keyword;
|
|
115861
115910
|
try {
|
|
115862
115911
|
keyword = await findItemInList(listUrl, config, opts.id, opts.verbose);
|
|
@@ -115868,7 +115917,10 @@ async function runAdNegativeKeywordDelete(opts) {
|
|
|
115868
115917
|
}
|
|
115869
115918
|
const url = `${googleApiUrl}/negativekeywordmanagement/negativekeyword/${opts.account}/${opts.id}`;
|
|
115870
115919
|
try {
|
|
115871
|
-
await apiDeleteRaw(url, config, {
|
|
115920
|
+
await apiDeleteRaw(url, config, {
|
|
115921
|
+
body: JSON.stringify(negativeKeywordWriteBody(keyword)),
|
|
115922
|
+
verbose: opts.verbose
|
|
115923
|
+
});
|
|
115872
115924
|
} catch (err) {
|
|
115873
115925
|
console.error(`
|
|
115874
115926
|
\u274C \u5220\u9664\u5426\u5B9A\u5173\u952E\u8BCD\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
@@ -121146,7 +121198,7 @@ async function fetchLiveSnapshotForCampaign(config, accountId, campaignId, campa
|
|
|
121146
121198
|
),
|
|
121147
121199
|
fetchList(
|
|
121148
121200
|
config,
|
|
121149
|
-
|
|
121201
|
+
negativeKeywordSlimListUrl(googleApiUrl, accountId),
|
|
121150
121202
|
verbose
|
|
121151
121203
|
),
|
|
121152
121204
|
fetchList(
|
|
@@ -127005,11 +127057,18 @@ function register21(program2) {
|
|
|
127005
127057
|
}
|
|
127006
127058
|
);
|
|
127007
127059
|
adCmd.command("keywords").alias("keyword-list").description(
|
|
127008
|
-
"\u67E5\u8BE2\u5173\u952E\u8BCD\u5217\u8868\uFF08--negative \
|
|
127009
|
-
).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(
|
|
127010
127062
|
"--level <level>",
|
|
127011
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"
|
|
127012
|
-
).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(
|
|
127013
127072
|
"--json-out <path>",
|
|
127014
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",
|
|
127015
127074
|
void 0
|
|
@@ -127019,11 +127078,26 @@ function register21(program2) {
|
|
|
127019
127078
|
console.error("\n\u274C --level \u53EA\u63A5\u53D7 Account | Campaign | AdGroup\n");
|
|
127020
127079
|
process.exit(1);
|
|
127021
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
|
+
}
|
|
127022
127092
|
await runAdKeywords({
|
|
127023
127093
|
token: opts.token,
|
|
127024
127094
|
account: opts.account,
|
|
127025
127095
|
negative: opts.negative,
|
|
127026
127096
|
level: opts.level,
|
|
127097
|
+
keywordText: opts.keyword,
|
|
127098
|
+
matchType: opts.matchType,
|
|
127099
|
+
excludePaused: opts.excludePaused,
|
|
127100
|
+
readDeleted: opts.readDeleted,
|
|
127027
127101
|
startDate: opts.start,
|
|
127028
127102
|
endDate: opts.end,
|
|
127029
127103
|
jsonOut: opts.jsonOut,
|
package/dist/skill/_meta.json
CHANGED
|
@@ -170,7 +170,7 @@ siluzan-tso account-history --start 2026-03-01 --end 2026-03-31 --json-out ./sna
|
|
|
170
170
|
| 状态 | 含义 | 下一步操作 |
|
|
171
171
|
| ---------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
172
172
|
| `Pending` | 审核中 | 等待,可反复运行此命令轮询;审核周期因媒体而异 |
|
|
173
|
-
| `Approved` | 审核通过 | 运行 `list-accounts -m <媒体>` 确认账户已出现;引导用户充值激活(`config show` 取 `webUrl`,按 `finance.md` 打开对应媒体充值页;例如 Google 为 `https://www.siluzan.com/v3/foreign_trade/tso/recharge/pay?mediaType=Google`;Yandex 当前没有对应充值界面) |
|
|
173
|
+
| `Approved` | 审核通过 | 运行 `list-accounts -m <媒体>` 确认账户已出现;引导用户充值激活(`config show` 取 `webUrl`,按 `finance.md` 打开对应媒体充值页;例如 Google 为 `https://www-ci.siluzan.com/v3/foreign_trade/tso/recharge/pay?mediaType=Google`;Yandex 当前没有对应充值界面) |
|
|
174
174
|
| `Rejected` | 被拒 | 查看 `--json-out` 落盘中的 `reason` 字段了解拒绝原因;修改资料后重新提交;若原因不明,引导用户联系丝路赞客服 |
|
|
175
175
|
|
|
176
176
|
---
|
|
@@ -490,7 +490,7 @@ siluzan-tso account email-deauth -c <mediaCustomerId> --invitation-id <id> --res
|
|
|
490
490
|
|
|
491
491
|
| 功能 | 媒体 | 网页路径 |
|
|
492
492
|
| --------------------------------------- | ------ | ------------------------------------------------- |
|
|
493
|
-
| **账户激活**(邀请他人激活 / 充值激活) | Google | `https://www.siluzan.com/v3/foreign_trade/tso/manageAccounts` |
|
|
493
|
+
| **账户激活**(邀请他人激活 / 充值激活) | Google | `https://www-ci.siluzan.com/v3/foreign_trade/tso/manageAccounts` |
|
|
494
494
|
|
|
495
495
|
**Agent 建议话术**:
|
|
496
496
|
|
|
@@ -499,5 +499,5 @@ siluzan-tso account email-deauth -c <mediaCustomerId> --invitation-id <id> --res
|
|
|
499
499
|
siluzan-tso config show # 查看 webUrl 字段
|
|
500
500
|
|
|
501
501
|
# 账户激活(Google)→ 引导至账户管理页
|
|
502
|
-
# https://www.siluzan.com/v3/foreign_trade/tso/manageAccounts
|
|
502
|
+
# https://www-ci.siluzan.com/v3/foreign_trade/tso/manageAccounts
|
|
503
503
|
```
|
|
@@ -126,7 +126,7 @@ siluzan-tso config show
|
|
|
126
126
|
|
|
127
127
|
### 充值页链接(按媒体 × 类型)
|
|
128
128
|
|
|
129
|
-
链接模式:`https://www.siluzan.com/v3/foreign_trade/tso/recharge/<page>?mediaType=<mediaType>`;丝路赞钱包:`https://www.siluzan.com/v3/foreign_trade/tso/recharge/siluzanWallet`(无媒体参数)。
|
|
129
|
+
链接模式:`https://www-ci.siluzan.com/v3/foreign_trade/tso/recharge/<page>?mediaType=<mediaType>`;丝路赞钱包:`https://www-ci.siluzan.com/v3/foreign_trade/tso/recharge/siluzanWallet`(无媒体参数)。
|
|
130
130
|
|
|
131
131
|
| 充值类型 | `<page>` | 支持媒体(`mediaType` 参数) |
|
|
132
132
|
| ------------------------- | --------------------- | ------------------------------------------- |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 各媒体开户
|
|
2
2
|
|
|
3
|
-
> 网页链接:`https://www.siluzan.com/v3/foreign_trade/tso/accountOpeningHistory?tso=%2Fv3umijs%2Ftso%2FaccountOpeningHistory`
|
|
3
|
+
> 网页链接:`https://www-ci.siluzan.com/v3/foreign_trade/tso/accountOpeningHistory?tso=%2Fv3umijs%2Ftso%2FaccountOpeningHistory`
|
|
4
4
|
> 多命令串联见 `references/core/workflows.md` § 流程一。
|
|
5
5
|
|
|
6
6
|
## Contents
|
|
@@ -65,7 +65,7 @@ siluzan-tso open-account google-wizard
|
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
67
|
siluzan-tso account-history -m Google
|
|
68
|
-
# 审核通过后:config show → https://www.siluzan.com/v3/foreign_trade/tso/recharge/pay?mediaType=Google
|
|
68
|
+
# 审核通过后:config show → https://www-ci.siluzan.com/v3/foreign_trade/tso/recharge/pay?mediaType=Google
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
---
|
|
@@ -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
|
# 单维度
|
|
@@ -190,9 +190,9 @@ siluzan-tso report push receive-emails -m Google [--json-out ./snap]
|
|
|
190
190
|
|
|
191
191
|
| 媒体 | 报告类型 | URL 模板 |
|
|
192
192
|
| ------ | ---------------- | ----------------------------------------------------------- |
|
|
193
|
-
| Google | 日报(Daily) | `https://www.siluzan.com/media-report/publish/{entityId}?culture=zh-CN` |
|
|
194
|
-
| Google | 小时报(Hourly) | `https://www.siluzan.com/media-report/hour/{entityId}?culture=zh-CN` |
|
|
195
|
-
| TikTok | 日报 | `https://www.siluzan.com/media-report/publish/{entityId}?culture=zh-CN` |
|
|
193
|
+
| Google | 日报(Daily) | `https://www-ci.siluzan.com/media-report/publish/{entityId}?culture=zh-CN` |
|
|
194
|
+
| Google | 小时报(Hourly) | `https://www-ci.siluzan.com/media-report/hour/{entityId}?culture=zh-CN` |
|
|
195
|
+
| TikTok | 日报 | `https://www-ci.siluzan.com/media-report/publish/{entityId}?culture=zh-CN` |
|
|
196
196
|
|
|
197
197
|
`entityId` 来自 `siluzan-tso report list --json-out ./snap` 中每条记录的 `entityId` 字段。
|
|
198
198
|
|
|
@@ -205,8 +205,8 @@ siluzan-tso report list -m Google --json-out ./snap
|
|
|
205
205
|
|
|
206
206
|
# 第二步:查看 webUrl
|
|
207
207
|
siluzan-tso config show
|
|
208
|
-
# webUrl: https://www.siluzan.com
|
|
208
|
+
# webUrl: https://www-ci.siluzan.com
|
|
209
209
|
|
|
210
210
|
# 第三步:拼接链接(Google 日报)
|
|
211
|
-
# https://www.siluzan.com/media-report/publish/rpt_abc123?culture=zh-CN
|
|
211
|
+
# https://www-ci.siluzan.com/media-report/publish/rpt_abc123?culture=zh-CN
|
|
212
212
|
```
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
## 安装 CLI
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
npm install -g siluzan-tso-cli
|
|
13
|
+
npm install -g siluzan-tso-cli@beta
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
---
|
|
@@ -64,7 +64,7 @@ siluzan-tso config set --api-key <Key> # 或 config 直接写入
|
|
|
64
64
|
siluzan-tso config set --token <Token> # 备用:设置 JWT Token
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
API Key 获取入口:`https://www.siluzan.com/v3/foreign_trade/settings/apiKeyManagement`
|
|
67
|
+
API Key 获取入口:`https://www-ci.siluzan.com/v3/foreign_trade/settings/apiKeyManagement`
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
70
|
# 第 1 步:让用户报出手机号后,立刻发码(命令立即返回,不会等待输入)
|
|
@@ -127,9 +127,9 @@ siluzan-tso config show
|
|
|
127
127
|
|
|
128
128
|
```
|
|
129
129
|
构建环境 : production
|
|
130
|
-
apiBaseUrl : https://tso-api.siluzan.com
|
|
131
|
-
googleApiUrl : https://googleapi.mysiluzan.com
|
|
132
|
-
webUrl : https://www.siluzan.com
|
|
130
|
+
apiBaseUrl : https://tso-api-ci.siluzan.com
|
|
131
|
+
googleApiUrl : https://googleapi-ci.mysiluzan.com
|
|
132
|
+
webUrl : https://www-ci.siluzan.com
|
|
133
133
|
apiKey : abcd****1234
|
|
134
134
|
```
|
|
135
135
|
|
|
@@ -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 后台手工应用**——这是本次新增前的旧限制,现已由接口直接支持。
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
siluzan-tso config show # 取 webUrl
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
首页地址:`https://www.siluzan.com/v3/foreign_trade/tso/home`
|
|
9
|
+
首页地址:`https://www-ci.siluzan.com/v3/foreign_trade/tso/home`
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -25,7 +25,7 @@ siluzan-tso config show # 取 webUrl
|
|
|
25
25
|
|
|
26
26
|
## 推荐话术
|
|
27
27
|
|
|
28
|
-
1. **「和首页一样的总览」** → 打开 `https://www.siluzan.com/v3/foreign_trade/tso/home`。
|
|
28
|
+
1. **「和首页一样的总览」** → 打开 `https://www-ci.siluzan.com/v3/foreign_trade/tso/home`。
|
|
29
29
|
2. **「某个 Google 账户昨天花了多少」** → `list-accounts -m Google` + `stats -m Google -a <id>`。
|
|
30
30
|
3. **「有待充值账户」** → 说明聚合数据在首页;CLI 可 `list-accounts` + `balance` 逐户排查,或引导充值页。
|
|
31
31
|
|
|
@@ -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,11 +9,11 @@ $ErrorActionPreference = 'Stop'
|
|
|
9
9
|
# -- Package info (injected at build time) ------------------------------------
|
|
10
10
|
$PKG_NAME = 'siluzan-tso-cli'
|
|
11
11
|
# PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
|
|
12
|
-
$PKG_VERSION = '1.1.
|
|
12
|
+
$PKG_VERSION = '1.1.64-beta.2'
|
|
13
13
|
$CLI_BIN = 'siluzan-tso'
|
|
14
14
|
$SKILL_LABEL = 'Siluzan TSO'
|
|
15
|
-
$INSTALL_CMD = 'npm install -g siluzan-tso-cli'
|
|
16
|
-
$WEB_BASE = 'https://www.siluzan.com'
|
|
15
|
+
$INSTALL_CMD = 'npm install -g siluzan-tso-cli@beta'
|
|
16
|
+
$WEB_BASE = 'https://www-ci.siluzan.com'
|
|
17
17
|
|
|
18
18
|
# -- Constants ----------------------------------------------------------------
|
|
19
19
|
$NODE_MAJOR_MIN = 18
|
|
@@ -9,11 +9,11 @@ set -euo pipefail
|
|
|
9
9
|
# -- Package info (injected at build time) ------------------------------------
|
|
10
10
|
readonly PKG_NAME="siluzan-tso-cli"
|
|
11
11
|
# PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
|
|
12
|
-
readonly PKG_VERSION="1.1.
|
|
12
|
+
readonly PKG_VERSION="1.1.64-beta.2"
|
|
13
13
|
readonly CLI_BIN="siluzan-tso"
|
|
14
14
|
readonly SKILL_LABEL="Siluzan TSO"
|
|
15
|
-
readonly INSTALL_CMD="npm install -g siluzan-tso-cli"
|
|
16
|
-
readonly WEB_BASE="https://www.siluzan.com"
|
|
15
|
+
readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
|
|
16
|
+
readonly WEB_BASE="https://www-ci.siluzan.com"
|
|
17
17
|
|
|
18
18
|
# -- Constants ----------------------------------------------------------------
|
|
19
19
|
readonly NODE_MAJOR_MIN=18
|