siluzan-tso-cli 1.1.18-beta.5 → 1.1.18-beta.7
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 +16 -5
- package/dist/index.js +157 -31
- package/dist/skill/SKILL.md +4 -4
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/account-analytics.md +5 -5
- package/dist/skill/references/accounts.md +0 -2
- package/dist/skill/references/google-ads.md +4 -0
- package/dist/skill/references/setup.md +8 -6
- 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.18-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.18-beta.7),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
|
@@ -69,7 +69,18 @@ siluzan-tso init --force # 强制覆盖已存在文件
|
|
|
69
69
|
|
|
70
70
|
**若用户已安装 `siluzan-cso` 并完成登录,无需重复操作,直接跳到第 4 步。**
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
**推荐顺序**:① **手机号 + 验证码**(首选)完整参数与排错见 `references/setup.md`(随 Skill 安装到本地)。
|
|
73
|
+
|
|
74
|
+
### 方式 A:手机号 + 验证码(**推荐**)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
siluzan-tso send-login-code --phone <手机号>
|
|
78
|
+
siluzan-tso login --phone <手机号> --code <6位验证码>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
不向终端索要交互输入;Agent 先发码、用户回填验证码后再执行第二步。手机号须已在丝路赞注册。
|
|
82
|
+
|
|
83
|
+
### 方式 B:交互式登录(需真人 TTY)
|
|
73
84
|
|
|
74
85
|
```bash
|
|
75
86
|
siluzan-tso login
|
|
@@ -81,13 +92,13 @@ siluzan-tso login
|
|
|
81
92
|
siluzan-tso login --api-key <YOUR_API_KEY>
|
|
82
93
|
```
|
|
83
94
|
|
|
84
|
-
### 方式
|
|
95
|
+
### 方式 C:直接设置(适合自动化场景)
|
|
85
96
|
|
|
86
97
|
```bash
|
|
87
98
|
siluzan-tso config set --api-key <你的ApiKey>
|
|
88
99
|
```
|
|
89
100
|
|
|
90
|
-
### 方式
|
|
101
|
+
### 方式 D:Token 登录(已有 siluzan-cso 账号)
|
|
91
102
|
|
|
92
103
|
```bash
|
|
93
104
|
npm install -g siluzan-cso-cli
|
|
@@ -137,4 +148,4 @@ siluzan-tso init --ai xxx --force
|
|
|
137
148
|
|
|
138
149
|
## 6. Token 续期
|
|
139
150
|
|
|
140
|
-
Token
|
|
151
|
+
Token 过期后:**优先**再走一遍**方式 A**(手机验证码)签发新 API Key;或 `siluzan-cso login` / `siluzan-tso config set --api-key <新Key>`,`siluzan-tso` 自动读取新凭据。
|
package/dist/index.js
CHANGED
|
@@ -1974,6 +1974,7 @@ import { performance } from "perf_hooks";
|
|
|
1974
1974
|
import * as fs2 from "fs";
|
|
1975
1975
|
import * as path2 from "path";
|
|
1976
1976
|
import { fileURLToPath } from "url";
|
|
1977
|
+
import { spawn } from "child_process";
|
|
1977
1978
|
import Table from "cli-table3";
|
|
1978
1979
|
import * as path3 from "path";
|
|
1979
1980
|
import { chmod, mkdir, open, readdir, stat, unlink } from "fs/promises";
|
|
@@ -2375,6 +2376,29 @@ async function fetchNpmVersion(pkgName, tag, timeoutMs = 4e3) {
|
|
|
2375
2376
|
return null;
|
|
2376
2377
|
}
|
|
2377
2378
|
}
|
|
2379
|
+
async function defaultRunMinRequiredGlobalInstall(ctx) {
|
|
2380
|
+
if (process.env.SILUZAN_SKIP_AUTO_GLOBAL_INSTALL === "1") {
|
|
2381
|
+
return { ok: false, stderr: "SILUZAN_SKIP_AUTO_GLOBAL_INSTALL=1" };
|
|
2382
|
+
}
|
|
2383
|
+
const spec = `${ctx.pkgName}@${ctx.tag}`;
|
|
2384
|
+
return await new Promise((resolve32) => {
|
|
2385
|
+
const child = spawn("npm", ["install", "-g", spec, "--no-fund", "--no-audit"], {
|
|
2386
|
+
stdio: "inherit",
|
|
2387
|
+
shell: true
|
|
2388
|
+
});
|
|
2389
|
+
child.on("error", (err) => {
|
|
2390
|
+
resolve32({ ok: false, stderr: err instanceof Error ? err.message : String(err) });
|
|
2391
|
+
});
|
|
2392
|
+
child.on("close", (code, signal) => {
|
|
2393
|
+
if (code === 0) {
|
|
2394
|
+
resolve32({ ok: true });
|
|
2395
|
+
} else {
|
|
2396
|
+
const sig = signal ? ` signal=${signal}` : "";
|
|
2397
|
+
resolve32({ ok: false, stderr: `exit code ${code}${sig}` });
|
|
2398
|
+
}
|
|
2399
|
+
});
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
2378
2402
|
function createVersionNotifier(opts) {
|
|
2379
2403
|
const {
|
|
2380
2404
|
pkgName,
|
|
@@ -2383,27 +2407,33 @@ function createVersionNotifier(opts) {
|
|
|
2383
2407
|
resolveTag,
|
|
2384
2408
|
forceUpdateExtra = "",
|
|
2385
2409
|
updateAvailableExtra = "",
|
|
2410
|
+
runMinRequiredGlobalInstall = defaultRunMinRequiredGlobalInstall,
|
|
2386
2411
|
getCurrentVersion: getCurrentVersion22,
|
|
2387
2412
|
mergeWriteConfig,
|
|
2388
2413
|
readConfigRaw
|
|
2389
2414
|
} = opts;
|
|
2390
|
-
const KEY_LAST_CHECK = `${cachePrefix}LastVersionCheck`;
|
|
2391
2415
|
const KEY_LATEST_STABLE = `${cachePrefix}LatestStable`;
|
|
2392
2416
|
const KEY_LATEST_BETA = `${cachePrefix}LatestBeta`;
|
|
2393
2417
|
const KEY_MIN_STABLE = `${cachePrefix}MinRequiredStable`;
|
|
2394
2418
|
const KEY_MIN_BETA = `${cachePrefix}MinRequiredBeta`;
|
|
2395
2419
|
const KEY_LAST_NOTIFIED = `${cachePrefix}LastNotified`;
|
|
2420
|
+
const KEY_FETCH_AT_MAIN = `${cachePrefix}VersionFetchAtMain`;
|
|
2421
|
+
const KEY_FETCH_AT_MIN = `${cachePrefix}VersionFetchAtMin`;
|
|
2396
2422
|
const HOURS_24 = 24 * 60 * 60 * 1e3;
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2423
|
+
const TTL_MAIN_TAG_MS = 60 * 60 * 1e3;
|
|
2424
|
+
const TTL_MIN_REQUIRED_MS = HOURS_24;
|
|
2425
|
+
async function fetchVersionByTag(tag, cacheKey, fetchAtKey, cfg, maxAgeMs) {
|
|
2426
|
+
const lastAt = cfg[fetchAtKey];
|
|
2427
|
+
if (typeof lastAt === "string" && cacheKey in cfg) {
|
|
2428
|
+
const lastMs = new Date(lastAt).getTime();
|
|
2429
|
+
if (Date.now() - lastMs < maxAgeMs) {
|
|
2402
2430
|
const v = cfg[cacheKey];
|
|
2403
|
-
|
|
2431
|
+
const sv = typeof v === "string" && v ? v : null;
|
|
2432
|
+
return { version: sv, hitNetwork: false };
|
|
2404
2433
|
}
|
|
2405
2434
|
}
|
|
2406
|
-
|
|
2435
|
+
const version = await fetchNpmVersion(pkgName, tag);
|
|
2436
|
+
return { version, hitNetwork: true };
|
|
2407
2437
|
}
|
|
2408
2438
|
async function notifyIfOutdated2() {
|
|
2409
2439
|
try {
|
|
@@ -2414,15 +2444,20 @@ function createVersionNotifier(opts) {
|
|
|
2414
2444
|
const minCacheKey = isBeta ? KEY_MIN_BETA : KEY_MIN_STABLE;
|
|
2415
2445
|
const minTag = npmMinRequiredTagForBuildEnv(isBeta ? "test" : "production");
|
|
2416
2446
|
const cfg = readConfigRaw();
|
|
2417
|
-
const [
|
|
2418
|
-
fetchVersionByTag(tag, latestCacheKey, cfg),
|
|
2419
|
-
fetchVersionByTag(minTag, minCacheKey, cfg)
|
|
2447
|
+
const [mainRes, minRes] = await Promise.all([
|
|
2448
|
+
fetchVersionByTag(tag, latestCacheKey, KEY_FETCH_AT_MAIN, cfg, TTL_MAIN_TAG_MS),
|
|
2449
|
+
fetchVersionByTag(minTag, minCacheKey, KEY_FETCH_AT_MIN, cfg, TTL_MIN_REQUIRED_MS)
|
|
2420
2450
|
]);
|
|
2421
|
-
|
|
2422
|
-
|
|
2451
|
+
const latest = mainRes.version;
|
|
2452
|
+
const minRequired = minRes.version;
|
|
2453
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
2454
|
+
const cacheUpdates = {
|
|
2423
2455
|
[latestCacheKey]: latest ?? "",
|
|
2424
2456
|
[minCacheKey]: minRequired ?? ""
|
|
2425
|
-
}
|
|
2457
|
+
};
|
|
2458
|
+
if (mainRes.hitNetwork) cacheUpdates[KEY_FETCH_AT_MAIN] = nowIso;
|
|
2459
|
+
if (minRes.hitNetwork) cacheUpdates[KEY_FETCH_AT_MIN] = nowIso;
|
|
2460
|
+
await mergeWriteConfig(cacheUpdates);
|
|
2426
2461
|
const lastNotified = typeof cfg[KEY_LAST_NOTIFIED] === "string" ? new Date(cfg[KEY_LAST_NOTIFIED]).getTime() : 0;
|
|
2427
2462
|
if (Date.now() - lastNotified < HOURS_24) return;
|
|
2428
2463
|
const tagLabel = isBeta ? "\uFF08\u6D4B\u8BD5\u7248\uFF09" : "\uFF08\u6B63\u5F0F\u7248\uFF09";
|
|
@@ -2430,11 +2465,26 @@ function createVersionNotifier(opts) {
|
|
|
2430
2465
|
await mergeWriteConfig({ [KEY_LAST_NOTIFIED]: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2431
2466
|
console.log(
|
|
2432
2467
|
`
|
|
2433
|
-
[SILUZAN_FORCE_UPDATE] \u5F53\u524D\u7248\u672C ${current} \u5DF2\
|
|
2434
|
-
\u26D4 \
|
|
2435
|
-
\u6216\u624B\u52A8\uFF1Anpm install -g ${pkgName}@${tag}
|
|
2468
|
+
[SILUZAN_FORCE_UPDATE] \u5F53\u524D\u7248\u672C ${current} \u5DF2\u4F4E\u4E8E\u6700\u4F4E\u8981\u6C42 ${minRequired}${tagLabel}
|
|
2469
|
+
\u26D4 \u6B63\u5728\u81EA\u52A8\u6267\u884C\uFF1Anpm install -g ${pkgName}@${tag}
|
|
2436
2470
|
` + (forceUpdateExtra ? forceUpdateExtra + "\n" : "")
|
|
2437
2471
|
);
|
|
2472
|
+
const installResult = await runMinRequiredGlobalInstall({ pkgName, tag });
|
|
2473
|
+
if (installResult.ok) {
|
|
2474
|
+
console.log(
|
|
2475
|
+
`
|
|
2476
|
+
[SILUZAN_AUTO_GLOBAL_INSTALL_OK] \u5168\u5C40\u5B89\u88C5\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C\u672C\u547D\u4EE4\u4EE5\u52A0\u8F7D\u65B0\u7248\u672C ${pkgName}@${tag}\u3002
|
|
2477
|
+
`
|
|
2478
|
+
);
|
|
2479
|
+
} else {
|
|
2480
|
+
console.log(
|
|
2481
|
+
`
|
|
2482
|
+
[SILUZAN_AUTO_GLOBAL_INSTALL_FAILED] \u81EA\u52A8\u5168\u5C40\u5B89\u88C5\u5931\u8D25\uFF1A${installResult.stderr ?? "unknown"}
|
|
2483
|
+
\u8BF7\u624B\u52A8\u6267\u884C\uFF1Anpm install -g ${pkgName}@${tag}
|
|
2484
|
+
\u6216\uFF1A${binName} update
|
|
2485
|
+
` + (forceUpdateExtra ? forceUpdateExtra + "\n" : "")
|
|
2486
|
+
);
|
|
2487
|
+
}
|
|
2438
2488
|
return;
|
|
2439
2489
|
}
|
|
2440
2490
|
if (latest && isNewer(current, latest)) {
|
|
@@ -5925,6 +5975,11 @@ async function fetchJson(config, pathWithQuery, verbose) {
|
|
|
5925
5975
|
function assertNever(x, ctx) {
|
|
5926
5976
|
throw new Error(`${ctx}\uFF1A\u672A\u5904\u7406\u7684\u5206\u652F ${String(x)}`);
|
|
5927
5977
|
}
|
|
5978
|
+
function rowsFromAccountDailyReportsEnvelope(raw, mediaCustomerId) {
|
|
5979
|
+
const block = raw.accounts?.[mediaCustomerId];
|
|
5980
|
+
const list = block?.data;
|
|
5981
|
+
return Array.isArray(list) ? list : [];
|
|
5982
|
+
}
|
|
5928
5983
|
async function fetchGoogleAnalysisSectionJson(config, fullPath, verbose, name) {
|
|
5929
5984
|
switch (name) {
|
|
5930
5985
|
case "overview":
|
|
@@ -5955,8 +6010,6 @@ async function fetchGoogleAnalysisSectionJson(config, fullPath, verbose, name) {
|
|
|
5955
6010
|
return fetchJson(config, fullPath, verbose);
|
|
5956
6011
|
case "conversion-actions":
|
|
5957
6012
|
return fetchJson(config, fullPath, verbose);
|
|
5958
|
-
case "daily-metrics":
|
|
5959
|
-
return fetchJson(config, fullPath, verbose);
|
|
5960
6013
|
case "gold-account":
|
|
5961
6014
|
return fetchJson(config, fullPath, verbose);
|
|
5962
6015
|
case "ads-index":
|
|
@@ -6009,6 +6062,18 @@ async function fetchSectionPayload(def, opts, config, id) {
|
|
|
6009
6062
|
const merged = { images, videos };
|
|
6010
6063
|
return stripLegacyGoogleFieldsIfV2Present(merged);
|
|
6011
6064
|
}
|
|
6065
|
+
if (def.name === "daily-metrics") {
|
|
6066
|
+
const { startDate, endDate } = resolveDateRange2(opts.start, opts.end);
|
|
6067
|
+
const params = new URLSearchParams({
|
|
6068
|
+
mediaCustomerIds: id,
|
|
6069
|
+
startDate: `${startDate}T00:00:00+08:00`,
|
|
6070
|
+
endDate: `${endDate}T23:59:59+08:00`
|
|
6071
|
+
});
|
|
6072
|
+
const url = `${config.apiBaseUrl}/report/media-account/google/account-daily-reports?${params.toString()}`;
|
|
6073
|
+
const raw = await apiFetch2(url, config, {}, !!opts.verbose);
|
|
6074
|
+
const rows = rowsFromAccountDailyReportsEnvelope(raw, id);
|
|
6075
|
+
return stripLegacyGoogleFieldsIfV2Present(rows);
|
|
6076
|
+
}
|
|
6012
6077
|
const sectionPath = def.path(id);
|
|
6013
6078
|
const query = buildSearchParams(def, opts.start, opts.end, extras);
|
|
6014
6079
|
const data = await fetchGoogleAnalysisSectionJson(
|
|
@@ -6417,9 +6482,10 @@ var init_google_analysis2 = __esm({
|
|
|
6417
6482
|
},
|
|
6418
6483
|
{
|
|
6419
6484
|
name: "daily-metrics",
|
|
6420
|
-
description: "\u6309\u65E5\u6307\u6807\
|
|
6485
|
+
description: "\u6309\u65E5\u6307\u6807\uFF08\u4E3B\u5E73\u53F0 /report/media-account/google/account-daily-reports\uFF0C\u542B\u641C\u7D22\u4EFD\u989D\u7B49\uFF09",
|
|
6421
6486
|
dateMode: "range",
|
|
6422
|
-
|
|
6487
|
+
/** 仅用于 manifest endpointHint;实际请求走 fetchSectionPayload 专用分支(apiBaseUrl + 东八区起止时刻) */
|
|
6488
|
+
path: () => "/report/media-account/google/account-daily-reports"
|
|
6423
6489
|
},
|
|
6424
6490
|
{
|
|
6425
6491
|
name: "gold-account",
|
|
@@ -14328,6 +14394,42 @@ init_auth();
|
|
|
14328
14394
|
init_cli_json_snapshot();
|
|
14329
14395
|
init_strip_legacy_google_fields();
|
|
14330
14396
|
init_cli_table();
|
|
14397
|
+
function unwrapKeywordDisplayTextForEdit(raw) {
|
|
14398
|
+
const t = raw.trim();
|
|
14399
|
+
if (t.length >= 2 && t.startsWith('"') && t.endsWith('"')) {
|
|
14400
|
+
return t.slice(1, -1);
|
|
14401
|
+
}
|
|
14402
|
+
if (t.length >= 2 && t.startsWith("[") && t.endsWith("]")) {
|
|
14403
|
+
return t.slice(1, -1);
|
|
14404
|
+
}
|
|
14405
|
+
return t;
|
|
14406
|
+
}
|
|
14407
|
+
function formatKeywordTextForMatchType(rawCoreOrDisplay, matchType) {
|
|
14408
|
+
const core = unwrapKeywordDisplayTextForEdit(rawCoreOrDisplay);
|
|
14409
|
+
switch (matchType) {
|
|
14410
|
+
case "Broad":
|
|
14411
|
+
return core;
|
|
14412
|
+
case "Phrase":
|
|
14413
|
+
return `"${core}"`;
|
|
14414
|
+
case "Exact":
|
|
14415
|
+
return `[${core}]`;
|
|
14416
|
+
default: {
|
|
14417
|
+
const _x = matchType;
|
|
14418
|
+
return _x;
|
|
14419
|
+
}
|
|
14420
|
+
}
|
|
14421
|
+
}
|
|
14422
|
+
function firstKeywordTextFromRecord(k) {
|
|
14423
|
+
const kt = k["keywordText"];
|
|
14424
|
+
if (Array.isArray(kt) && kt.length > 0 && typeof kt[0] === "string") {
|
|
14425
|
+
return kt[0];
|
|
14426
|
+
}
|
|
14427
|
+
const t = k["text"];
|
|
14428
|
+
if (typeof t === "string") {
|
|
14429
|
+
return t;
|
|
14430
|
+
}
|
|
14431
|
+
return "";
|
|
14432
|
+
}
|
|
14331
14433
|
async function runAdKeywords(opts) {
|
|
14332
14434
|
const config = loadConfig(opts.token);
|
|
14333
14435
|
const googleApiUrl = requireGoogleApi(config);
|
|
@@ -14519,8 +14621,17 @@ async function runAdKeywordEdit(opts) {
|
|
|
14519
14621
|
process.exit(1);
|
|
14520
14622
|
}
|
|
14521
14623
|
const body = { ...keyword };
|
|
14522
|
-
if (opts.
|
|
14523
|
-
|
|
14624
|
+
if (opts.matchType !== void 0) {
|
|
14625
|
+
const base = opts.text !== void 0 ? opts.text : firstKeywordTextFromRecord(keyword);
|
|
14626
|
+
if (!String(base).trim()) {
|
|
14627
|
+
console.error("\n\u274C \u65E0\u6CD5\u89E3\u6790\u5F53\u524D\u5173\u952E\u8BCD\u6587\u6848\uFF0C\u8BF7\u540C\u65F6\u4F20 --text <\u8BCD\u5E72>\n");
|
|
14628
|
+
process.exit(1);
|
|
14629
|
+
}
|
|
14630
|
+
body["keywordText"] = [formatKeywordTextForMatchType(base, opts.matchType)];
|
|
14631
|
+
body["matchTypeV2"] = opts.matchType;
|
|
14632
|
+
} else if (opts.text !== void 0) {
|
|
14633
|
+
body["keywordText"] = [opts.text];
|
|
14634
|
+
}
|
|
14524
14635
|
if (opts.maxCpc !== void 0) body["maxCPC"] = opts.maxCpc;
|
|
14525
14636
|
if (opts.finalUrl !== void 0) body["finalURL"] = opts.finalUrl;
|
|
14526
14637
|
const url = `${googleApiUrl}/keywordmanagement/Keyword/${opts.account}/batch`;
|
|
@@ -14567,8 +14678,17 @@ async function runAdNegativeKeywordEdit(opts) {
|
|
|
14567
14678
|
process.exit(1);
|
|
14568
14679
|
}
|
|
14569
14680
|
const body = { ...keyword };
|
|
14570
|
-
if (opts.
|
|
14571
|
-
|
|
14681
|
+
if (opts.matchType !== void 0) {
|
|
14682
|
+
const base = opts.text !== void 0 ? opts.text : firstKeywordTextFromRecord(keyword);
|
|
14683
|
+
if (!String(base).trim()) {
|
|
14684
|
+
console.error("\n\u274C \u65E0\u6CD5\u89E3\u6790\u5F53\u524D\u5426\u5B9A\u5173\u952E\u8BCD\u6587\u6848\uFF0C\u8BF7\u540C\u65F6\u4F20 --text <\u8BCD\u5E72>\n");
|
|
14685
|
+
process.exit(1);
|
|
14686
|
+
}
|
|
14687
|
+
body["keywordText"] = [formatKeywordTextForMatchType(base, opts.matchType)];
|
|
14688
|
+
body["matchTypeV2"] = opts.matchType;
|
|
14689
|
+
} else if (opts.text !== void 0) {
|
|
14690
|
+
body["keywordText"] = [opts.text];
|
|
14691
|
+
}
|
|
14572
14692
|
const url = `${googleApiUrl}/negativekeywordmanagement/negativekeyword/${opts.account}/${opts.id}`;
|
|
14573
14693
|
try {
|
|
14574
14694
|
await apiFetch2(url, config, { method: "PUT", body: JSON.stringify(body) }, opts.verbose);
|
|
@@ -15870,7 +15990,10 @@ function register20(program2) {
|
|
|
15870
15990
|
});
|
|
15871
15991
|
}
|
|
15872
15992
|
);
|
|
15873
|
-
adCmd.command("keyword-edit").description("\u7F16\u8F91\u641C\u7D22\u5173\u952E\u8BCD\uFF08\u6570\u7EC4 body\uFF0C\u5148 list \u518D\u5408\u5E76\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <keywordId>", "\u5173\u952E\u8BCD ID\uFF08\u6765\u81EA ad keywords --json \u2192 id\uFF09").option("--text <text>", "\u65B0\u5173\u952E\u8BCD\u6587\u672C\uFF08\u5199\u5165 keywordText \u6570\u7EC4\uFF09").option(
|
|
15993
|
+
adCmd.command("keyword-edit").description("\u7F16\u8F91\u641C\u7D22\u5173\u952E\u8BCD\uFF08\u6570\u7EC4 body\uFF0C\u5148 list \u518D\u5408\u5E76\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <keywordId>", "\u5173\u952E\u8BCD ID\uFF08\u6765\u81EA ad keywords --json \u2192 id\uFF09").option("--text <text>", "\u65B0\u5173\u952E\u8BCD\u6587\u672C\uFF08\u5199\u5165 keywordText \u6570\u7EC4\uFF09").option(
|
|
15994
|
+
"--match-type <type>",
|
|
15995
|
+
'\u65B0\u5339\u914D\u7C7B\u578B\uFF1ABroad | Phrase | Exact\uFF08\u5199 matchTypeV2\uFF0C\u5E76\u9ED8\u8BA4\u540C\u6B65\u6539\u5199 keywordText \u4E3A\u8BCD\u5E72/"\u8BCD"/[\u8BCD] \u4EE5\u7B26\u5408\u7F51\u5173\u63A8\u65AD\uFF09'
|
|
15996
|
+
).option(
|
|
15874
15997
|
"--max-cpc <n>",
|
|
15875
15998
|
"\u6700\u9AD8\u6BCF\u6B21\u70B9\u51FB\u8D39\u7528 maxCPC\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08\u5982 5 \u8868\u793A \xA55\uFF1B\u26A0\uFE0F \u8FD9\u4E2A\u5B57\u6BB5\u540E\u7AEF\u5355\u4F4D\u5C31\u662F\u300C\u4E3B\u5E01\u79CD\u5143\u300D\uFF0CCLI \u76F4\u63A5\u900F\u4F20\u4E0D\u505A \xD7100\uFF0C\u4E0E budget / \u7EC4 maxCPCAmount \u4E0D\u540C\uFF1B0 \u8868\u793A\u6309\u5E73\u53F0/\u8BA1\u5212\u9ED8\u8BA4\uFF09"
|
|
15876
15999
|
).option("--final-url <url>", "\u5173\u952E\u8BCD\u7EA7\u6700\u7EC8\u5230\u8FBE\u7F51\u5740 finalURL").option("--start <date>", "\u5217\u8868\u67E5\u8BE2\u8D77\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u5217\u8868\u67E5\u8BE2\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
@@ -15901,7 +16024,10 @@ function register20(program2) {
|
|
|
15901
16024
|
});
|
|
15902
16025
|
}
|
|
15903
16026
|
);
|
|
15904
|
-
adCmd.command("keyword-negative-edit").description("\u7F16\u8F91\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u6587\u672C\u6216\u5339\u914D\u7C7B\u578B\uFF0C\u81F3\u5C11\u4F20\u4E00\u4E2A\u4FEE\u6539\u9879\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <keywordId>", "\u5426\u5B9A\u5173\u952E\u8BCD ID\uFF08\u6765\u81EA ad keywords --negative --json \u2192 id\uFF09").option("--text <text>", "\u65B0\u5173\u952E\u8BCD\u6587\u672C").option(
|
|
16027
|
+
adCmd.command("keyword-negative-edit").description("\u7F16\u8F91\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u6587\u672C\u6216\u5339\u914D\u7C7B\u578B\uFF0C\u81F3\u5C11\u4F20\u4E00\u4E2A\u4FEE\u6539\u9879\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <keywordId>", "\u5426\u5B9A\u5173\u952E\u8BCD ID\uFF08\u6765\u81EA ad keywords --negative --json \u2192 id\uFF09").option("--text <text>", "\u65B0\u5173\u952E\u8BCD\u6587\u672C").option(
|
|
16028
|
+
"--match-type <type>",
|
|
16029
|
+
'\u65B0\u5339\u914D\u7C7B\u578B\uFF1ABroad | Phrase | Exact\uFF08\u5199 matchTypeV2\uFF0C\u5E76\u9ED8\u8BA4\u540C\u6B65\u6539\u5199 keywordText \u4E3A\u8BCD\u5E72/"\u8BCD"/[\u8BCD]\uFF09'
|
|
16030
|
+
).option("--start <date>", "\u5217\u8868\u67E5\u8BE2\u8D77\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u5217\u8868\u67E5\u8BE2\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
15905
16031
|
async (opts) => {
|
|
15906
16032
|
if (opts.matchType && !["Broad", "Phrase", "Exact"].includes(opts.matchType)) {
|
|
15907
16033
|
console.error("\n\u274C --match-type \u53EA\u63A5\u53D7 Broad | Phrase | Exact\n");
|
|
@@ -16403,7 +16529,7 @@ async function runAccountShareDetail(opts) {
|
|
|
16403
16529
|
|
|
16404
16530
|
// src/commands/account-manage/oauth.ts
|
|
16405
16531
|
init_auth();
|
|
16406
|
-
import { spawn } from "child_process";
|
|
16532
|
+
import { spawn as spawn2 } from "child_process";
|
|
16407
16533
|
function toApiMediaType(media) {
|
|
16408
16534
|
return media === "Meta" ? "FacebookAds" : media;
|
|
16409
16535
|
}
|
|
@@ -16414,17 +16540,17 @@ function tryOpenBrowser(url) {
|
|
|
16414
16540
|
return false;
|
|
16415
16541
|
}
|
|
16416
16542
|
if (process.platform === "win32") {
|
|
16417
|
-
const child =
|
|
16543
|
+
const child = spawn2("rundll32", ["url.dll,FileProtocolHandler", parsed.toString()], {
|
|
16418
16544
|
detached: true,
|
|
16419
16545
|
stdio: "ignore",
|
|
16420
16546
|
windowsHide: true
|
|
16421
16547
|
});
|
|
16422
16548
|
child.unref();
|
|
16423
16549
|
} else if (process.platform === "darwin") {
|
|
16424
|
-
const child =
|
|
16550
|
+
const child = spawn2("open", [parsed.toString()], { detached: true, stdio: "ignore" });
|
|
16425
16551
|
child.unref();
|
|
16426
16552
|
} else {
|
|
16427
|
-
const child =
|
|
16553
|
+
const child = spawn2("xdg-open", [parsed.toString()], { detached: true, stdio: "ignore" });
|
|
16428
16554
|
child.unref();
|
|
16429
16555
|
}
|
|
16430
16556
|
return true;
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -26,7 +26,7 @@ allowed-tools: Bash(siluzan-tso:*) Read Write
|
|
|
26
26
|
|
|
27
27
|
Windows 注意:部分 Agent 客户端通过 PowerShell / cmd 代执行命令时存在兼容性问题。若上述命令异常失败,请先安装 [Git for Windows](https://git-scm.com/download/win),然后在 Git Bash 中执行 macOS / Linux / WSL 的 Bash 安装命令。
|
|
28
28
|
|
|
29
|
-
参照 `references/setup.md`
|
|
29
|
+
参照 `references/setup.md` 完成安装与配置。**需登录或我方 401 换凭据时,优先引导「手机号 + 验证码」**(`send-login-code` → `login --phone --code`),详见 `references/setup.md` 登录优先级说明。
|
|
30
30
|
|
|
31
31
|
---
|
|
32
32
|
|
|
@@ -34,7 +34,7 @@ Windows 注意:部分 Agent 客户端通过 PowerShell / cmd 代执行命令
|
|
|
34
34
|
|
|
35
35
|
| 文档 | 功能 |
|
|
36
36
|
|------|------|
|
|
37
|
-
| `references/setup.md` |
|
|
37
|
+
| `references/setup.md` | 安装、**登录(手机验证码优先)**、配置、环境切换、更新 |
|
|
38
38
|
| `references/workflows.md` | 多步骤业务流程、跨命令串联 |
|
|
39
39
|
| `references/tips.md` | **Agent 拉数一律 `--json-out`**(目录或 `*.json` 文件)+ 读 `cli-manifest[-<查询id>].json` / 各 `<section>[-<查询id>].json` + **`*.outline.txt`**(TS 式类型,**几百字节,描述完整字段结构**——写聚合脚本前先读它而不是 `Read` 整个 JSON,省 2~3 个数量级 context);stdout 一行摘要含 `manifestFile` / `writtenFiles` / `outlineFile` 等 |
|
|
40
40
|
| `references/accounts.md` | 账户列表、余额、消耗、开户记录、授权/解绑/分享/MCC/BC/BM/邮箱授权 |
|
|
@@ -147,7 +147,7 @@ Windows 注意:部分 Agent 客户端通过 PowerShell / cmd 代执行命令
|
|
|
147
147
|
2. 中断后**必须**用 `resume --run-id <id>` 续跑,**禁止**重新 `run`。
|
|
148
148
|
3. stdout 始终是单行 JSON(`kind=siluzan-tso-batch-summary`);进度读 `progress.json`、轨迹读 `state/tasks.jsonl`。
|
|
149
149
|
4. 退出码:`0` 全成功 / `2` 部分成功 / `3` 全失败或 Token 失效 / `4` 用法错误。
|
|
150
|
-
5. 401 响应 → 整批终止 + `tokenInvalidated:true
|
|
150
|
+
5. 401 响应 → 整批终止 + `tokenInvalidated:true`,提示用户按 `references/setup.md` **优先手机验证码**重新登录后再 `resume`。
|
|
151
151
|
|
|
152
152
|
若无批量命令(如 117 个 Bing 账户剩余天数计算):先 `list-accounts --json-out <dir>` 一次性拿全量 → `node -e` 本地计算 → 只对命中账户做后续操作。
|
|
153
153
|
|
|
@@ -244,7 +244,7 @@ siluzan-tso accounts-digest -m Google -a id1,id2,... --start <S> --end <D> --jso
|
|
|
244
244
|
### 常见 HTTP 状态码
|
|
245
245
|
|
|
246
246
|
- **400**:参数错误,查看对应 reference 或用 `-h` 了解命令用法
|
|
247
|
-
- **401
|
|
247
|
+
- **401**:平台方返回则需用户重新授权;**我方凭据失效**则优先 **`send-login-code` + `login --phone --code`**(或 TTY 下 `siluzan-tso login` / `config set …`),见 `references/setup.md`
|
|
248
248
|
- **500**:服务可能正在部署/升级,建议提交给 Siluzan 相关人员
|
|
249
249
|
|
|
250
250
|
### 报告模板外部资源
|
package/dist/skill/_meta.json
CHANGED
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
2. 确定报告维度(默认含:执行摘要、每日趋势、月度汇总、系列表现、设备分布、地域分布、关键词表现、优化建议),详见 `report-templates/README.md`。
|
|
34
34
|
3. **拉数**:使用 `google-analysis … --json-out <dir>`(Google)或对应 `report <media>-*` 命令落盘。
|
|
35
35
|
4. **编写并执行代码**从磁盘读取 `manifest-<accountId>.json` 与各 `<section>-<accountId>.json` 来完成筛选、聚合、排序等计算;**禁止**用 `Read` 看 JSON 后在对话里心算或手填报告数字。
|
|
36
|
-
- **写脚本前先读 `<section>-<accountId>.outline.txt`**(与 JSON 同 stem 的纯文本,最后一行是 TS 式类型字面量)了解字段结构;它体积只有几百字节、不含数据,比直接 `Read` 整个 `*.json`(动辄几 MB /
|
|
37
|
-
- 真实数据始终从 `<section>-<accountId>.json`
|
|
38
|
-
5. **由代码写出最终文件**(HTML/Excel/PDF/PPT/Markdown 等)。**禁止**在报告脚本中以源码字面量写死应从 JSON 读取的业务数据(消耗金额、系列名、日期区间等)。允许的常量仅限:快照目录路径、JSON 字段键名、版式/结构占位。
|
|
39
|
-
6. **报告首行**须标注:`统计区间:YYYY-MM-DD ~ YYYY-MM-DD(货币:XXX
|
|
36
|
+
- **写脚本前先读 `<section>-<accountId>.outline.txt`**(与 JSON 同 stem 的纯文本,最后一行是 TS 式类型字面量)了解字段结构;它体积只有几百字节、不含数据,比直接 `Read` 整个 `*.json`(动辄几 MB / 几万行)省**两到三个数量级**的上下文,不要 `require()`,用 `fs.readFileSync(outlineFile,'utf8')` 读最后一行即可。
|
|
37
|
+
- 真实数据始终从 `<section>-<accountId>.json` 通过代码获取,**不要**把 outline 当作业务数据贴给用户。
|
|
38
|
+
5. **由代码写出最终文件**(HTML/Excel/PDF/PPT/Markdown/word 等)。**禁止**在报告脚本中以源码字面量写死应从 JSON 读取的业务数据(消耗金额、系列名、日期区间等)。允许的常量仅限:快照目录路径、JSON 字段键名、版式/结构占位。
|
|
39
|
+
6. **报告首行**须标注:`统计区间:YYYY-MM-DD ~ YYYY-MM-DD(货币:XXX)`
|
|
40
40
|
7. 交付后帮用户打开报告文件。
|
|
41
41
|
|
|
42
42
|
---
|
|
@@ -139,7 +139,7 @@ siluzan-tso google-analysis -a <id> --exclude materials,gold-account --json-out
|
|
|
139
139
|
| `materials` | 合并图片+视频 `{ images, videos }` |
|
|
140
140
|
| `resource-counts` | 结构统计 |
|
|
141
141
|
| `conversion-actions` | 转化动作 |
|
|
142
|
-
| `daily-metrics` |
|
|
142
|
+
| `daily-metrics` | 按日指标(主平台 `GET …/report/media-account/google/account-daily-reports`,`--json` 根为按日数组) |
|
|
143
143
|
| `gold-account` | 黄金账户 |
|
|
144
144
|
| `ads-index` | 质量指标 |
|
|
145
145
|
| `final-urls` | 最终到达网址(不传 `--start`/`--end`) |
|
|
@@ -980,6 +980,8 @@ siluzan-tso ad keyword-delete -a 6326027735 --id 2464982882313 --adgroup-id 1955
|
|
|
980
980
|
|
|
981
981
|
**约束:** `--text`、`--match-type`、`--max-cpc`、`--final-url` 至少传一项。
|
|
982
982
|
|
|
983
|
+
**匹配类型与文案:** Google 网关 V2 根据 `keywordText` 上的 `"` / `[` `]` 推断实际 MatchType(会覆盖仅传的 `matchTypeV2`)。因此只要传 `--match-type`,CLI **默认**把 `keywordText` 规范为词干 / `"词干"` / `[词干]` 并写入 `matchTypeV2`,无需额外开关;仅改匹配时可不传 `--text`(用列表里的当前文案去外层括号后再包一层)。
|
|
984
|
+
|
|
983
985
|
```bash
|
|
984
986
|
siluzan-tso ad keyword-edit \
|
|
985
987
|
-a <accountId> \
|
|
@@ -1009,6 +1011,8 @@ siluzan-tso ad keyword-edit -a 6326027735 --id 2081924039951 \
|
|
|
1009
1011
|
|
|
1010
1012
|
## ad keyword-negative-edit — 否词编辑
|
|
1011
1013
|
|
|
1014
|
+
与搜索词相同:传 `--match-type` 时 CLI 会默认同步改写 `keywordText` 外层括号/引号。
|
|
1015
|
+
|
|
1012
1016
|
```bash
|
|
1013
1017
|
siluzan-tso ad keyword-negative-edit \
|
|
1014
1018
|
-a <accountId> \
|
|
@@ -40,8 +40,11 @@ siluzan-tso init -d /path/to-your/skills # 写入自定义目录
|
|
|
40
40
|
|
|
41
41
|
`siluzan-tso` 与 `siluzan-cso` **共用同一份凭据**,存储在 `~/.siluzan/config.json`,配置一次两个 CLI 均可使用。
|
|
42
42
|
|
|
43
|
+
> **登录方式优先级**
|
|
44
|
+
> 1. **首选**:**手机号 + 短信验证码**两段式(`send-login-code` → `login --phone --code`)——无 TTY 不卡死、不依赖浏览器里复制 API Key,**对话式 AI / OpenClaw / CI 日志旁路**均适用。
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
|
|
47
|
+
### 通过手机号 + 验证码登录(**首选**;对话式 AI / 无 TTY 与各 Agent 环境)
|
|
45
48
|
|
|
46
49
|
**两段式调用**,专为 AI Agent 设计——任何一步都不会进入交互等待,绝不会卡住 stdout。
|
|
47
50
|
拆分后单一职责:第 1 步只发码;第 2 步只用 code 换 API Key。这样 Agent 不会因为"看到 stdout 卡住就重试"而触发短信轰炸。
|
|
@@ -51,13 +54,12 @@ siluzan-tso init -d /path/to-your/skills # 写入自定义目录
|
|
|
51
54
|
| 1 | `siluzan-tso send-login-code --phone <手机号>` | 仅向手机发送 6 位验证码 |
|
|
52
55
|
| 2 | `siluzan-tso login --phone <手机号> --code <验证码>` | 用 code 完成登录并自动签发 API Key 写入 `~/.siluzan/config.json` |
|
|
53
56
|
|
|
54
|
-
##
|
|
57
|
+
## 其它登录方式(TTY 交互 / 已有 API Key / JWT)
|
|
58
|
+
|
|
55
59
|
```bash
|
|
56
|
-
siluzan-tso login #
|
|
60
|
+
siluzan-tso login # 交互式登录(需 TTY),按提示创建 API Key 后粘贴
|
|
57
61
|
siluzan-tso login --api-key <YOUR_API_KEY> # 直接设置 API Key(跳过交互)
|
|
58
|
-
siluzan-tso
|
|
59
|
-
siluzan-tso login --phone 138xxxx --code 123456 # 两段式登录第 2 步:用验证码完成登录
|
|
60
|
-
siluzan-tso config set --api-key <Key> # 或通过 config set 直接写入
|
|
62
|
+
siluzan-tso config set --api-key <Key> # 或 config 直接写入
|
|
61
63
|
siluzan-tso config set --token <Token> # 备用:设置 JWT Token
|
|
62
64
|
```
|
|
63
65
|
|