siluzan-tso-cli 1.1.18-beta.6 → 1.1.18-beta.8
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
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.8),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -2412,23 +2412,28 @@ function createVersionNotifier(opts) {
|
|
|
2412
2412
|
mergeWriteConfig,
|
|
2413
2413
|
readConfigRaw
|
|
2414
2414
|
} = opts;
|
|
2415
|
-
const KEY_LAST_CHECK = `${cachePrefix}LastVersionCheck`;
|
|
2416
2415
|
const KEY_LATEST_STABLE = `${cachePrefix}LatestStable`;
|
|
2417
2416
|
const KEY_LATEST_BETA = `${cachePrefix}LatestBeta`;
|
|
2418
2417
|
const KEY_MIN_STABLE = `${cachePrefix}MinRequiredStable`;
|
|
2419
2418
|
const KEY_MIN_BETA = `${cachePrefix}MinRequiredBeta`;
|
|
2420
2419
|
const KEY_LAST_NOTIFIED = `${cachePrefix}LastNotified`;
|
|
2420
|
+
const KEY_FETCH_AT_MAIN = `${cachePrefix}VersionFetchAtMain`;
|
|
2421
|
+
const KEY_FETCH_AT_MIN = `${cachePrefix}VersionFetchAtMin`;
|
|
2421
2422
|
const HOURS_24 = 24 * 60 * 60 * 1e3;
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
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) {
|
|
2427
2430
|
const v = cfg[cacheKey];
|
|
2428
|
-
|
|
2431
|
+
const sv = typeof v === "string" && v ? v : null;
|
|
2432
|
+
return { version: sv, hitNetwork: false };
|
|
2429
2433
|
}
|
|
2430
2434
|
}
|
|
2431
|
-
|
|
2435
|
+
const version = await fetchNpmVersion(pkgName, tag);
|
|
2436
|
+
return { version, hitNetwork: true };
|
|
2432
2437
|
}
|
|
2433
2438
|
async function notifyIfOutdated2() {
|
|
2434
2439
|
try {
|
|
@@ -2439,15 +2444,20 @@ function createVersionNotifier(opts) {
|
|
|
2439
2444
|
const minCacheKey = isBeta ? KEY_MIN_BETA : KEY_MIN_STABLE;
|
|
2440
2445
|
const minTag = npmMinRequiredTagForBuildEnv(isBeta ? "test" : "production");
|
|
2441
2446
|
const cfg = readConfigRaw();
|
|
2442
|
-
const [
|
|
2443
|
-
fetchVersionByTag(tag, latestCacheKey, cfg),
|
|
2444
|
-
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)
|
|
2445
2450
|
]);
|
|
2446
|
-
|
|
2447
|
-
|
|
2451
|
+
const latest = mainRes.version;
|
|
2452
|
+
const minRequired = minRes.version;
|
|
2453
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
2454
|
+
const cacheUpdates = {
|
|
2448
2455
|
[latestCacheKey]: latest ?? "",
|
|
2449
2456
|
[minCacheKey]: minRequired ?? ""
|
|
2450
|
-
}
|
|
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);
|
|
2451
2461
|
const lastNotified = typeof cfg[KEY_LAST_NOTIFIED] === "string" ? new Date(cfg[KEY_LAST_NOTIFIED]).getTime() : 0;
|
|
2452
2462
|
if (Date.now() - lastNotified < HOURS_24) return;
|
|
2453
2463
|
const tagLabel = isBeta ? "\uFF08\u6D4B\u8BD5\u7248\uFF09" : "\uFF08\u6B63\u5F0F\u7248\uFF09";
|
|
@@ -2549,16 +2559,11 @@ function printAuthMissingHelp(binName) {
|
|
|
2549
2559
|
`
|
|
2550
2560
|
\u274C \u672A\u627E\u5230\u8BA4\u8BC1\u51ED\u636E\u3002\u8BF7\u9009\u62E9\u4EE5\u4E0B\u4EFB\u610F\u4E00\u79CD\u65B9\u5F0F\uFF1A
|
|
2551
2561
|
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
export SILUZAN_API_KEY=<YOUR_API_KEY>
|
|
2558
|
-
# \u6216 export SILUZAN_AUTH_TOKEN=<YOUR_TOKEN>
|
|
2559
|
-
|
|
2560
|
-
\u65B9\u5F0F\u4E09\uFF1AJWT Token
|
|
2561
|
-
${binName} login
|
|
2562
|
+
\u8BF7\u4F7F\u7528\u624B\u673A\u53F7\u91CD\u65B0\u767B\u5F55
|
|
2563
|
+
${binName} send-login-code --phone <YOUR_PHONE>
|
|
2564
|
+
`,
|
|
2565
|
+
`\u7136\u540E\u4F7F\u7528\u6536\u5230\u7684\u9A8C\u8BC1\u7801\u5B8C\u6210\u767B\u5F55
|
|
2566
|
+
${binName} login --phone <YOUR_PHONE> --code <YOUR_CODE>
|
|
2562
2567
|
`
|
|
2563
2568
|
);
|
|
2564
2569
|
process.exit(1);
|
|
@@ -3050,6 +3055,35 @@ function normalizeChinaPhone(input) {
|
|
|
3050
3055
|
function isValidChinaPhone(input) {
|
|
3051
3056
|
return /^\+861\d{10}$/.test(normalizeChinaPhone(input));
|
|
3052
3057
|
}
|
|
3058
|
+
async function sendPhoneLoginCode(opts) {
|
|
3059
|
+
const phone = normalizeChinaPhone(opts.phone);
|
|
3060
|
+
const url = `${opts.ssoBaseUrl}/Account/SendVaildCode?Phone=${encodeURIComponent(
|
|
3061
|
+
phone
|
|
3062
|
+
)}&RandStr=&Iicket=`;
|
|
3063
|
+
if (opts.verbose) {
|
|
3064
|
+
process.stderr.write(`[phone-login] GET ${url}
|
|
3065
|
+
`);
|
|
3066
|
+
}
|
|
3067
|
+
const res = await rawRequest(url, {
|
|
3068
|
+
method: "GET",
|
|
3069
|
+
headers: {
|
|
3070
|
+
Accept: "application/json",
|
|
3071
|
+
"Accept-Language": "zh-CN"
|
|
3072
|
+
}
|
|
3073
|
+
});
|
|
3074
|
+
if (res.status < 200 || res.status >= 300) {
|
|
3075
|
+
return { ok: false, message: `HTTP ${res.status}` };
|
|
3076
|
+
}
|
|
3077
|
+
let body;
|
|
3078
|
+
try {
|
|
3079
|
+
body = JSON.parse(res.text);
|
|
3080
|
+
} catch {
|
|
3081
|
+
return { ok: false, message: `\u54CD\u5E94\u975E JSON\uFF1A${res.text.slice(0, 120)}` };
|
|
3082
|
+
}
|
|
3083
|
+
const state = (body.State ?? body.state ?? "").toLowerCase();
|
|
3084
|
+
const message = body.Message ?? body.message ?? "";
|
|
3085
|
+
return { ok: state === "ok", message };
|
|
3086
|
+
}
|
|
3053
3087
|
async function loginByPhoneCode(opts) {
|
|
3054
3088
|
const phone = normalizeChinaPhone(opts.phone);
|
|
3055
3089
|
const url = `${opts.ssoBaseUrl}/Account/LoginByMiniCode?phone=${encodeURIComponent(
|
|
@@ -5965,6 +5999,11 @@ async function fetchJson(config, pathWithQuery, verbose) {
|
|
|
5965
5999
|
function assertNever(x, ctx) {
|
|
5966
6000
|
throw new Error(`${ctx}\uFF1A\u672A\u5904\u7406\u7684\u5206\u652F ${String(x)}`);
|
|
5967
6001
|
}
|
|
6002
|
+
function rowsFromAccountDailyReportsEnvelope(raw, mediaCustomerId) {
|
|
6003
|
+
const block = raw.accounts?.[mediaCustomerId];
|
|
6004
|
+
const list = block?.data;
|
|
6005
|
+
return Array.isArray(list) ? list : [];
|
|
6006
|
+
}
|
|
5968
6007
|
async function fetchGoogleAnalysisSectionJson(config, fullPath, verbose, name) {
|
|
5969
6008
|
switch (name) {
|
|
5970
6009
|
case "overview":
|
|
@@ -5995,8 +6034,6 @@ async function fetchGoogleAnalysisSectionJson(config, fullPath, verbose, name) {
|
|
|
5995
6034
|
return fetchJson(config, fullPath, verbose);
|
|
5996
6035
|
case "conversion-actions":
|
|
5997
6036
|
return fetchJson(config, fullPath, verbose);
|
|
5998
|
-
case "daily-metrics":
|
|
5999
|
-
return fetchJson(config, fullPath, verbose);
|
|
6000
6037
|
case "gold-account":
|
|
6001
6038
|
return fetchJson(config, fullPath, verbose);
|
|
6002
6039
|
case "ads-index":
|
|
@@ -6049,6 +6086,18 @@ async function fetchSectionPayload(def, opts, config, id) {
|
|
|
6049
6086
|
const merged = { images, videos };
|
|
6050
6087
|
return stripLegacyGoogleFieldsIfV2Present(merged);
|
|
6051
6088
|
}
|
|
6089
|
+
if (def.name === "daily-metrics") {
|
|
6090
|
+
const { startDate, endDate } = resolveDateRange2(opts.start, opts.end);
|
|
6091
|
+
const params = new URLSearchParams({
|
|
6092
|
+
mediaCustomerIds: id,
|
|
6093
|
+
startDate: `${startDate}T00:00:00+08:00`,
|
|
6094
|
+
endDate: `${endDate}T23:59:59+08:00`
|
|
6095
|
+
});
|
|
6096
|
+
const url = `${config.apiBaseUrl}/report/media-account/google/account-daily-reports?${params.toString()}`;
|
|
6097
|
+
const raw = await apiFetch2(url, config, {}, !!opts.verbose);
|
|
6098
|
+
const rows = rowsFromAccountDailyReportsEnvelope(raw, id);
|
|
6099
|
+
return stripLegacyGoogleFieldsIfV2Present(rows);
|
|
6100
|
+
}
|
|
6052
6101
|
const sectionPath = def.path(id);
|
|
6053
6102
|
const query = buildSearchParams(def, opts.start, opts.end, extras);
|
|
6054
6103
|
const data = await fetchGoogleAnalysisSectionJson(
|
|
@@ -6457,9 +6506,10 @@ var init_google_analysis2 = __esm({
|
|
|
6457
6506
|
},
|
|
6458
6507
|
{
|
|
6459
6508
|
name: "daily-metrics",
|
|
6460
|
-
description: "\u6309\u65E5\u6307\u6807\
|
|
6509
|
+
description: "\u6309\u65E5\u6307\u6807\uFF08\u4E3B\u5E73\u53F0 /report/media-account/google/account-daily-reports\uFF0C\u542B\u641C\u7D22\u4EFD\u989D\u7B49\uFF09",
|
|
6461
6510
|
dateMode: "range",
|
|
6462
|
-
|
|
6511
|
+
/** 仅用于 manifest endpointHint;实际请求走 fetchSectionPayload 专用分支(apiBaseUrl + 东八区起止时刻) */
|
|
6512
|
+
path: () => "/report/media-account/google/account-daily-reports"
|
|
6463
6513
|
},
|
|
6464
6514
|
{
|
|
6465
6515
|
name: "gold-account",
|
|
@@ -6594,17 +6644,38 @@ function validateAndNormalizePhone(rawInput) {
|
|
|
6594
6644
|
}
|
|
6595
6645
|
return normalizeChinaPhone(rawPhone);
|
|
6596
6646
|
}
|
|
6597
|
-
async function
|
|
6647
|
+
async function runSendLoginCode(opts) {
|
|
6598
6648
|
const phone = validateAndNormalizePhone(opts.phone);
|
|
6599
|
-
const
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6649
|
+
const tsoApiBase = process.env.SILUZAN_TSO_API_BASE ?? DEFAULT_API_BASE;
|
|
6650
|
+
const ssoBaseUrl = deriveSsoBaseUrl(tsoApiBase);
|
|
6651
|
+
console.log("\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
6652
|
+
console.log(" Siluzan \u53D1\u9001\u767B\u5F55\u77ED\u4FE1\u9A8C\u8BC1\u7801");
|
|
6653
|
+
console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n");
|
|
6654
|
+
console.log(` \u624B\u673A\u53F7 : ${phone}`);
|
|
6655
|
+
console.log("\u2192 \u6B63\u5728\u5411\u624B\u673A\u53D1\u9001\u9A8C\u8BC1\u7801...");
|
|
6656
|
+
const r = await sendPhoneLoginCode({ ssoBaseUrl, phone, verbose: opts.verbose });
|
|
6657
|
+
if (!r.ok) {
|
|
6658
|
+
console.error(`
|
|
6659
|
+
\u274C \u77ED\u4FE1\u9A8C\u8BC1\u7801\u53D1\u9001\u5931\u8D25\uFF1A${r.message || "(\u540E\u7AEF\u672A\u8FD4\u56DE\u539F\u56E0)"}
|
|
6604
6660
|
`);
|
|
6605
|
-
console.error("\uFF08\u62C6\u6210\u4E24\u6B65\u662F\u4E3A\u4E86\u907F\u514D AI Agent \u5361\u5728\u4EA4\u4E92\u8F93\u5165\u65F6\u53CD\u590D\u91CD\u8BD5\u5BFC\u81F4\u77ED\u4FE1\u8F70\u70B8\uFF09\n");
|
|
6606
6661
|
process.exit(1);
|
|
6607
6662
|
}
|
|
6663
|
+
console.log(`\u2713 \u9A8C\u8BC1\u7801\u5DF2\u53D1\u9001\uFF0810 \u5206\u949F\u5185\u6709\u6548\uFF09\u3002
|
|
6664
|
+
`);
|
|
6665
|
+
console.log("\u4E0B\u4E00\u6B65\uFF1A\u62FF\u5230 6 \u4F4D\u9A8C\u8BC1\u7801\u540E\uFF0C\u8FD0\u884C\u4E0B\u9762\u7684\u547D\u4EE4\u5B8C\u6210\u767B\u5F55\uFF1A\n");
|
|
6666
|
+
console.log(` siluzan-tso login --phone ${phone} --code <6\u4F4D\u9A8C\u8BC1\u7801>
|
|
6667
|
+
`);
|
|
6668
|
+
console.log("\u53EF\u9009\u53C2\u6570\uFF1A--name / --valid-days / --expires-at / --services");
|
|
6669
|
+
console.log("\uFF08\u9ED8\u8BA4\u521B\u5EFA 90 \u5929\u6709\u6548\u3001\u52FE\u9009 TSO + CUT \u670D\u52A1\u7684 API Key\uFF09\n");
|
|
6670
|
+
}
|
|
6671
|
+
function normalizeBearerTokenInput(raw) {
|
|
6672
|
+
const t = raw.trim();
|
|
6673
|
+
if (/^bearer\s+/i.test(t)) {
|
|
6674
|
+
return t.replace(/^bearer\s+/i, "").trim();
|
|
6675
|
+
}
|
|
6676
|
+
return t;
|
|
6677
|
+
}
|
|
6678
|
+
async function executePhoneLoginWithVerifiedCode(phone, code, opts) {
|
|
6608
6679
|
let allowedServices;
|
|
6609
6680
|
try {
|
|
6610
6681
|
allowedServices = parseAllowedServices(opts.services);
|
|
@@ -6676,9 +6747,10 @@ async function runPhoneLogin(opts) {
|
|
|
6676
6747
|
`
|
|
6677
6748
|
\u8BE5\u624B\u673A\u53F7\u5C1A\u672A\u6CE8\u518C\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u3002\u8BF7\u5148\u5728\u7F51\u9875\u7AEF\u6CE8\u518C\uFF1A
|
|
6678
6749
|
${WEB_BASE_URL}
|
|
6679
|
-
\u6CE8\u518C\u6210\u529F\u540E\u518D\u56DE\u5230 CLI \u91CD\u8BD5\
|
|
6750
|
+
\u6CE8\u518C\u6210\u529F\u540E\u518D\u56DE\u5230 CLI \u91CD\u8BD5\uFF1A
|
|
6680
6751
|
siluzan-tso send-login-code --phone ${phone}
|
|
6681
6752
|
siluzan-tso login --phone ${phone} --code <6\u4F4D\u9A8C\u8BC1\u7801>
|
|
6753
|
+
\u6216\u5728\u7EC8\u7AEF\u6267\u884C\u65E0\u53C2 siluzan-tso login \u540E\u9009\u62E9\u300C\u624B\u673A\u53F7\u767B\u5F55\u300D\u3002
|
|
6682
6754
|
`
|
|
6683
6755
|
);
|
|
6684
6756
|
} else if (/验证码错误|验证码/.test(msg)) {
|
|
@@ -6692,34 +6764,72 @@ async function runPhoneLogin(opts) {
|
|
|
6692
6764
|
process.exit(1);
|
|
6693
6765
|
}
|
|
6694
6766
|
}
|
|
6695
|
-
async function
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6767
|
+
async function runPhoneLogin(opts) {
|
|
6768
|
+
const phone = validateAndNormalizePhone(opts.phone);
|
|
6769
|
+
const code = opts.code?.trim() ?? "";
|
|
6770
|
+
if (!code) {
|
|
6771
|
+
console.error("\n\u274C \u7F3A\u5C11 --code \u53C2\u6570\u3002\u624B\u673A\u53F7\u767B\u5F55\u662F\u4E24\u6BB5\u5F0F\u8C03\u7528\uFF0C\u8BF7\u6309\u987A\u5E8F\u6267\u884C\uFF1A\n");
|
|
6772
|
+
console.error(` 1) siluzan-tso send-login-code --phone ${phone}`);
|
|
6773
|
+
console.error(` 2) siluzan-tso login --phone ${phone} --code <\u6536\u5230\u76846\u4F4D\u9A8C\u8BC1\u7801>
|
|
6774
|
+
`);
|
|
6775
|
+
console.error("\uFF08\u62C6\u6210\u4E24\u6B65\u662F\u4E3A\u4E86\u907F\u514D AI Agent \u5361\u5728\u4EA4\u4E92\u8F93\u5165\u65F6\u53CD\u590D\u91CD\u8BD5\u5BFC\u81F4\u77ED\u4FE1\u8F70\u70B8\uFF09\n");
|
|
6776
|
+
console.error("\u6216\u5728\u7EC8\u7AEF\u6267\u884C\uFF1Asiluzan-tso login\uFF08\u65E0\u53C2\u6570\uFF09\uFF0C\u9009\u62E9\u300C\u624B\u673A\u53F7 + \u77ED\u4FE1\u9A8C\u8BC1\u7801\u300D\u7531\u672C CLI \u53D1\u7801\u3002\n");
|
|
6777
|
+
process.exit(1);
|
|
6778
|
+
}
|
|
6779
|
+
await executePhoneLoginWithVerifiedCode(phone, code, opts);
|
|
6780
|
+
}
|
|
6781
|
+
async function runInteractiveJwtLogin() {
|
|
6782
|
+
const shared = readSharedConfig();
|
|
6783
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
6784
|
+
const prompt = (q) => new Promise((res) => rl.question(q, (a) => res(a.trim())));
|
|
6785
|
+
try {
|
|
6786
|
+
if (shared.apiKey) {
|
|
6787
|
+
console.log(`
|
|
6788
|
+
\u5DF2\u4FDD\u5B58 API Key\uFF08${maskSecret(shared.apiKey)}\uFF09\u3002\u6539\u7528 JWT \u5C06\u6E05\u9664 API Key \u5E76\u4F18\u5148\u4F7F\u7528 Bearer Token\u3002`);
|
|
6789
|
+
const ans = await prompt("\u662F\u5426\u7EE7\u7EED\uFF1F(y/N) ");
|
|
6790
|
+
if (ans.toLowerCase() !== "y") {
|
|
6791
|
+
console.log("\n\u5DF2\u53D6\u6D88\u3002\n");
|
|
6792
|
+
return;
|
|
6793
|
+
}
|
|
6794
|
+
}
|
|
6795
|
+
console.log("\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
6796
|
+
console.log(" Siluzan \u767B\u5F55\uFF08JWT Token\uFF09");
|
|
6797
|
+
console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
6798
|
+
console.log("\n\u8BF7\u7C98\u8D34 access token\uFF08\u53EF\u5E26\u6216\u4E0D\u5E26 `Bearer ` \u524D\u7F00\uFF09\uFF1A\n");
|
|
6799
|
+
let token = "";
|
|
6800
|
+
for (let i = 0; i < 3; i++) {
|
|
6801
|
+
const input = await prompt("Token\uFF1A");
|
|
6802
|
+
const normalized = normalizeBearerTokenInput(input);
|
|
6803
|
+
if (normalized) {
|
|
6804
|
+
token = normalized;
|
|
6805
|
+
break;
|
|
6806
|
+
}
|
|
6807
|
+
console.log("\u274C Token \u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u8BF7\u91CD\u8BD5");
|
|
6808
|
+
}
|
|
6809
|
+
if (!token) {
|
|
6810
|
+
console.error("\n\u274C \u591A\u6B21\u8F93\u5165\u65E0\u6548\u3002\n");
|
|
6700
6811
|
process.exit(1);
|
|
6701
6812
|
}
|
|
6702
|
-
writeSharedConfig({ apiKey:
|
|
6813
|
+
writeSharedConfig({ authToken: token, apiKey: "" });
|
|
6703
6814
|
console.log(`
|
|
6704
|
-
\u2705
|
|
6815
|
+
\u2705 JWT \u5DF2\u4FDD\u5B58\uFF08${maskSecret(token)}\uFF09`);
|
|
6705
6816
|
console.log(` \u914D\u7F6E\u6587\u4EF6\uFF1A${CONFIG_FILE}`);
|
|
6706
6817
|
printPostLoginReminderBanner();
|
|
6707
6818
|
console.log("\u73B0\u5728\u53EF\u4EE5\u8FD0\u884C siluzan-tso -h \u547D\u4EE4\u67E5\u770B\u5E2E\u52A9\u4E86\n");
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
if (opts.phone !== void 0) {
|
|
6711
|
-
await runPhoneLogin(opts);
|
|
6712
|
-
return;
|
|
6819
|
+
} finally {
|
|
6820
|
+
rl.close();
|
|
6713
6821
|
}
|
|
6822
|
+
}
|
|
6823
|
+
async function runInteractiveApiKeyLogin() {
|
|
6714
6824
|
const shared = readSharedConfig();
|
|
6715
6825
|
const currentKey = shared.apiKey ?? "";
|
|
6716
6826
|
if (currentKey) {
|
|
6717
6827
|
console.log(`
|
|
6718
6828
|
\u5DF2\u68C0\u6D4B\u5230\u5DF2\u4FDD\u5B58\u7684 API Key\uFF08${maskSecret(currentKey)}\uFF09\u3002`);
|
|
6719
|
-
const
|
|
6829
|
+
const rl0 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
6720
6830
|
const answer = await new Promise(
|
|
6721
|
-
(res) =>
|
|
6722
|
-
|
|
6831
|
+
(res) => rl0.question("\u662F\u5426\u8986\u76D6\uFF1F(y/N) ", (a) => {
|
|
6832
|
+
rl0.close();
|
|
6723
6833
|
res(a.trim());
|
|
6724
6834
|
})
|
|
6725
6835
|
);
|
|
@@ -6730,46 +6840,164 @@ async function runLogin(opts = {}) {
|
|
|
6730
6840
|
}
|
|
6731
6841
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
6732
6842
|
const prompt = (q) => new Promise((res) => rl.question(q, (a) => res(a.trim())));
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6843
|
+
try {
|
|
6844
|
+
console.log("\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
6845
|
+
console.log(" Siluzan \u767B\u5F55\uFF08API Key\uFF09");
|
|
6846
|
+
console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
6847
|
+
console.log("\n\u8BF7\u6309\u4EE5\u4E0B\u6B65\u9AA4\u83B7\u53D6 API Key\uFF1A");
|
|
6848
|
+
console.log("\n 1. \u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u4EE5\u4E0B\u5730\u5740\uFF08\u9700\u5DF2\u767B\u5F55\u4E1D\u8DEF\u8D5E\u8D26\u53F7\uFF09\uFF1A");
|
|
6849
|
+
console.log(`
|
|
6739
6850
|
${API_KEY_MANAGEMENT_URL}
|
|
6740
6851
|
`);
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6852
|
+
console.log(
|
|
6853
|
+
" 2. \u70B9\u51FB\u300C\u521B\u5EFA API Key\u300D\u6309\u94AE\u751F\u6210\u4E00\u4E2A\u65B0\u7684 Key, \u52FE\u9009TSO (\u5E7F\u544A\u6295\u653E\u670D\u52A1)\u4E0ESUCAI (\u7D20\u6750\u4E2D\u5FC3\u670D\u52A1)"
|
|
6854
|
+
);
|
|
6855
|
+
console.log(" 3. \u590D\u5236\u751F\u6210\u7684 API Key");
|
|
6856
|
+
console.log(" 4. \u7C98\u8D34\u5230\u4E0B\u65B9\u5E76\u6309\u56DE\u8F66\n");
|
|
6857
|
+
let apiKey = "";
|
|
6858
|
+
for (let i = 0; i < 3; i++) {
|
|
6859
|
+
const input = await prompt("\u7C98\u8D34 API Key\uFF1A");
|
|
6860
|
+
if (input) {
|
|
6861
|
+
apiKey = input;
|
|
6862
|
+
break;
|
|
6863
|
+
}
|
|
6864
|
+
console.log("\u274C API Key \u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u8BF7\u91CD\u8BD5");
|
|
6865
|
+
}
|
|
6866
|
+
if (!apiKey) {
|
|
6867
|
+
console.error("\n\u274C \u591A\u6B21\u8F93\u5165\u65E0\u6548\uFF0C\u8BF7\u91CD\u8BD5\u3002\n");
|
|
6868
|
+
process.exit(1);
|
|
6869
|
+
}
|
|
6870
|
+
writeSharedConfig({ apiKey });
|
|
6871
|
+
console.log(`
|
|
6872
|
+
\u2705 API Key \u5DF2\u4FDD\u5B58\uFF08${maskSecret(apiKey)}\uFF09`);
|
|
6873
|
+
printPostLoginReminderBanner();
|
|
6874
|
+
console.log("\u73B0\u5728\u53EF\u4EE5\u8FD0\u884C\uFF1A");
|
|
6875
|
+
console.log(" siluzan-tso list-accounts \u67E5\u770B\u5E7F\u544A\u8D26\u6237\u5217\u8868");
|
|
6876
|
+
console.log(" siluzan-tso balance -m Google \u67E5\u770B\u8D26\u6237\u4F59\u989D\n");
|
|
6877
|
+
} finally {
|
|
6878
|
+
rl.close();
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
6881
|
+
async function runInteractivePhoneLogin(menuOpts) {
|
|
6882
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
6883
|
+
const prompt = (q) => new Promise((res) => rl.question(q, (a) => res(a.trim())));
|
|
6884
|
+
try {
|
|
6885
|
+
const phoneRaw = await prompt("\n\u8BF7\u8F93\u5165\u624B\u673A\u53F7\uFF08\u5927\u9646\u53F7\u7801\uFF0C\u53EF\u5E26 +86\uFF09\uFF1A");
|
|
6886
|
+
const phone = validateAndNormalizePhone(phoneRaw);
|
|
6887
|
+
const tsoApiBase = process.env.SILUZAN_TSO_API_BASE ?? DEFAULT_API_BASE;
|
|
6888
|
+
const ssoBaseUrl = deriveSsoBaseUrl(tsoApiBase);
|
|
6889
|
+
console.log("\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
6890
|
+
console.log(" \u53D1\u9001\u767B\u5F55\u77ED\u4FE1\u9A8C\u8BC1\u7801");
|
|
6891
|
+
console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
6892
|
+
console.log(` \u624B\u673A\u53F7 : ${phone}`);
|
|
6893
|
+
console.log("\u2192 \u6B63\u5728\u5411\u624B\u673A\u53D1\u9001\u9A8C\u8BC1\u7801...");
|
|
6894
|
+
const sendRes = await sendPhoneLoginCode({ ssoBaseUrl, phone, verbose: menuOpts.verbose });
|
|
6895
|
+
if (!sendRes.ok) {
|
|
6896
|
+
console.error(`
|
|
6897
|
+
\u274C \u77ED\u4FE1\u9A8C\u8BC1\u7801\u53D1\u9001\u5931\u8D25\uFF1A${sendRes.message || "(\u540E\u7AEF\u672A\u8FD4\u56DE\u539F\u56E0)"}
|
|
6898
|
+
`);
|
|
6899
|
+
process.exit(1);
|
|
6900
|
+
}
|
|
6901
|
+
console.log("\u2713 \u9A8C\u8BC1\u7801\u5DF2\u53D1\u9001\uFF0810 \u5206\u949F\u5185\u6709\u6548\uFF09\u3002\n");
|
|
6902
|
+
const code = await prompt("\u8BF7\u8F93\u5165 6 \u4F4D\u9A8C\u8BC1\u7801\uFF1A");
|
|
6903
|
+
if (!code.trim()) {
|
|
6904
|
+
console.error("\n\u274C \u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A\u3002\n");
|
|
6905
|
+
process.exit(1);
|
|
6906
|
+
}
|
|
6907
|
+
await executePhoneLoginWithVerifiedCode(phone, code.trim(), menuOpts);
|
|
6908
|
+
} finally {
|
|
6909
|
+
rl.close();
|
|
6910
|
+
}
|
|
6911
|
+
}
|
|
6912
|
+
async function runLoginMethodMenu(menuOpts) {
|
|
6913
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
6914
|
+
const prompt = (q) => new Promise((res) => rl.question(q, (a) => res(a.trim())));
|
|
6915
|
+
console.log("\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
6916
|
+
console.log(" Siluzan \u767B\u5F55");
|
|
6917
|
+
console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
6918
|
+
console.log("\n\u8BF7\u9009\u62E9\u767B\u5F55\u65B9\u5F0F\uFF1A");
|
|
6919
|
+
console.log(" 1) JWT Token\uFF08Bearer\uFF0C\u7C98\u8D34 access token\uFF09");
|
|
6920
|
+
console.log(" 2) API Key\uFF08\u7F51\u9875\u300C\u8BBE\u7F6E \u2192 API Key \u7BA1\u7406\u300D\u521B\u5EFA\u540E\u7C98\u8D34\uFF09");
|
|
6921
|
+
console.log(" 3) \u624B\u673A\u53F7 + \u77ED\u4FE1\u9A8C\u8BC1\u7801\uFF08\u5C06\u5411\u624B\u673A\u53D1\u9001\u9A8C\u8BC1\u7801\uFF0C\u9A8C\u8BC1\u540E\u81EA\u52A8\u521B\u5EFA API Key\uFF09");
|
|
6922
|
+
console.log(" 0) \u9000\u51FA\n");
|
|
6923
|
+
let choice = "";
|
|
6924
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
6925
|
+
choice = await prompt("\u8BF7\u8F93\u5165\u9009\u9879 [0-3]\uFF1A");
|
|
6926
|
+
if (choice === "0" || choice === "1" || choice === "2" || choice === "3") {
|
|
6751
6927
|
break;
|
|
6752
6928
|
}
|
|
6753
|
-
console.log("\
|
|
6929
|
+
console.log("\u65E0\u6548\u9009\u62E9\uFF0C\u8BF7\u8F93\u5165 0\u30011\u30012 \u6216 3\u3002");
|
|
6754
6930
|
}
|
|
6755
6931
|
rl.close();
|
|
6756
|
-
if (
|
|
6757
|
-
console.
|
|
6758
|
-
|
|
6932
|
+
if (choice === "0" || choice === "") {
|
|
6933
|
+
console.log("\n\u5DF2\u53D6\u6D88\u3002\n");
|
|
6934
|
+
return;
|
|
6759
6935
|
}
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6936
|
+
if (choice === "1") {
|
|
6937
|
+
await runInteractiveJwtLogin();
|
|
6938
|
+
return;
|
|
6939
|
+
}
|
|
6940
|
+
if (choice === "2") {
|
|
6941
|
+
await runInteractiveApiKeyLogin();
|
|
6942
|
+
return;
|
|
6943
|
+
}
|
|
6944
|
+
await runInteractivePhoneLogin(menuOpts);
|
|
6945
|
+
}
|
|
6946
|
+
async function runLogin(opts = {}) {
|
|
6947
|
+
if (opts.apiKey !== void 0) {
|
|
6948
|
+
const key = opts.apiKey.trim();
|
|
6949
|
+
if (!key) {
|
|
6950
|
+
console.error("\n\u274C API Key \u4E0D\u80FD\u4E3A\u7A7A\u3002\n");
|
|
6951
|
+
process.exit(1);
|
|
6952
|
+
}
|
|
6953
|
+
writeSharedConfig({ apiKey: key });
|
|
6954
|
+
console.log(`
|
|
6955
|
+
\u2705 API Key \u5DF2\u4FDD\u5B58\uFF08${maskSecret(key)}\uFF09`);
|
|
6956
|
+
console.log(` \u914D\u7F6E\u6587\u4EF6\uFF1A${CONFIG_FILE}`);
|
|
6957
|
+
printPostLoginReminderBanner();
|
|
6958
|
+
console.log("\u73B0\u5728\u53EF\u4EE5\u8FD0\u884C siluzan-tso -h \u547D\u4EE4\u67E5\u770B\u5E2E\u52A9\u4E86\n");
|
|
6959
|
+
return;
|
|
6960
|
+
}
|
|
6961
|
+
if (opts.phone !== void 0) {
|
|
6962
|
+
await runPhoneLogin(opts);
|
|
6963
|
+
return;
|
|
6964
|
+
}
|
|
6965
|
+
if (opts.manual) {
|
|
6966
|
+
await runInteractiveJwtLogin();
|
|
6967
|
+
return;
|
|
6968
|
+
}
|
|
6969
|
+
await runLoginMethodMenu(opts);
|
|
6768
6970
|
}
|
|
6769
6971
|
function register(program2) {
|
|
6770
|
-
program2.command("login").description("\
|
|
6771
|
-
await
|
|
6972
|
+
program2.command("send-login-code").description("\u5411\u624B\u673A\u53F7\u53D1\u9001\u4E1D\u8DEF\u8D5E\u767B\u5F55\u77ED\u4FE1\u9A8C\u8BC1\u7801\uFF08\u65E0\u4EA4\u4E92\uFF0C\u9002\u5408 Agent\uFF1A\u5148\u53D1\u7801\u518D login --phone --code\uFF09").requiredOption("--phone <phone>", "\u4E2D\u56FD\u5927\u9646\u624B\u673A\u53F7\uFF08\u53EF\u5E26 +86\uFF09").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
|
|
6973
|
+
await runSendLoginCode({ phone: opts.phone, verbose: opts.verbose });
|
|
6772
6974
|
});
|
|
6975
|
+
program2.command("login").description(
|
|
6976
|
+
"\u4FDD\u5B58\u8BA4\u8BC1\u51ED\u636E\u5230 ~/.siluzan/config.json\uFF1B\u65E0\u53C2\u6570\u65F6\u5C55\u5F00 JWT / API Key / \u624B\u673A\u53F7\u4E09\u79CD\u4EA4\u4E92\u767B\u5F55"
|
|
6977
|
+
).option("--api-key <key>", "\u76F4\u63A5\u4FDD\u5B58 API Key\uFF08\u7F51\u9875\u300C\u8BBE\u7F6E \u2192 API Key \u7BA1\u7406\u300D\u521B\u5EFA\uFF09").option("--phone <phone>", "\u624B\u673A\u53F7\uFF08\u987B\u4E0E --code \u540C\u4F20\uFF1B\u6216\u7EC8\u7AEF\u65E0\u53C2 login \u9009 3 \u7531 CLI \u53D1\u7801\uFF09").option("--code <code>", "\u77ED\u4FE1\u9A8C\u8BC1\u7801\uFF08\u987B\u4E0E --phone \u540C\u4F20\uFF09").option("--name <text>", "\u624B\u673A\u53F7\u767B\u5F55\u65F6\u81EA\u52A8\u521B\u5EFA\u7684 API Key \u540D\u79F0").option("--valid-days <n>", "API Key \u6709\u6548\u5929\u6570\uFF0C\u9ED8\u8BA4 90").option("--expires-at <iso>", "API Key \u7EDD\u5BF9\u8FC7\u671F\u65F6\u95F4 ISO8601\uFF08\u4E0E --valid-days \u4E8C\u9009\u4E00\uFF09").option("--services <csv>", "API Key \u670D\u52A1\uFF1ACSO,TSO,CUT \u9017\u53F7\u5206\u9694\uFF0C\u9ED8\u8BA4 TSO,CUT").option("--manual", "\u8DF3\u8FC7\u83DC\u5355\uFF0C\u76F4\u63A5\u7C98\u8D34 JWT\uFF08\u4E0E\u65E0\u53C2\u83DC\u5355\u9009\u9879 1 \u7B49\u4EF7\uFF09").option("--verbose", "\u8BE6\u7EC6 HTTP", false).action(
|
|
6978
|
+
async (opts) => {
|
|
6979
|
+
let validDays;
|
|
6980
|
+
if (opts.validDays !== void 0 && String(opts.validDays).trim() !== "") {
|
|
6981
|
+
const n = parseInt(String(opts.validDays), 10);
|
|
6982
|
+
if (Number.isNaN(n)) {
|
|
6983
|
+
console.error("\n\u274C --valid-days \u987B\u4E3A\u6574\u6570\n");
|
|
6984
|
+
process.exit(1);
|
|
6985
|
+
}
|
|
6986
|
+
validDays = n;
|
|
6987
|
+
}
|
|
6988
|
+
await runLogin({
|
|
6989
|
+
apiKey: opts.apiKey,
|
|
6990
|
+
phone: opts.phone,
|
|
6991
|
+
code: opts.code,
|
|
6992
|
+
name: opts.name,
|
|
6993
|
+
validDays,
|
|
6994
|
+
expiresAt: opts.expiresAt,
|
|
6995
|
+
services: opts.services,
|
|
6996
|
+
verbose: opts.verbose,
|
|
6997
|
+
manual: opts.manual
|
|
6998
|
+
});
|
|
6999
|
+
}
|
|
7000
|
+
);
|
|
6773
7001
|
}
|
|
6774
7002
|
|
|
6775
7003
|
// src/commands/config.ts
|
|
@@ -14368,6 +14596,42 @@ init_auth();
|
|
|
14368
14596
|
init_cli_json_snapshot();
|
|
14369
14597
|
init_strip_legacy_google_fields();
|
|
14370
14598
|
init_cli_table();
|
|
14599
|
+
function unwrapKeywordDisplayTextForEdit(raw) {
|
|
14600
|
+
const t = raw.trim();
|
|
14601
|
+
if (t.length >= 2 && t.startsWith('"') && t.endsWith('"')) {
|
|
14602
|
+
return t.slice(1, -1);
|
|
14603
|
+
}
|
|
14604
|
+
if (t.length >= 2 && t.startsWith("[") && t.endsWith("]")) {
|
|
14605
|
+
return t.slice(1, -1);
|
|
14606
|
+
}
|
|
14607
|
+
return t;
|
|
14608
|
+
}
|
|
14609
|
+
function formatKeywordTextForMatchType(rawCoreOrDisplay, matchType) {
|
|
14610
|
+
const core = unwrapKeywordDisplayTextForEdit(rawCoreOrDisplay);
|
|
14611
|
+
switch (matchType) {
|
|
14612
|
+
case "Broad":
|
|
14613
|
+
return core;
|
|
14614
|
+
case "Phrase":
|
|
14615
|
+
return `"${core}"`;
|
|
14616
|
+
case "Exact":
|
|
14617
|
+
return `[${core}]`;
|
|
14618
|
+
default: {
|
|
14619
|
+
const _x = matchType;
|
|
14620
|
+
return _x;
|
|
14621
|
+
}
|
|
14622
|
+
}
|
|
14623
|
+
}
|
|
14624
|
+
function firstKeywordTextFromRecord(k) {
|
|
14625
|
+
const kt = k["keywordText"];
|
|
14626
|
+
if (Array.isArray(kt) && kt.length > 0 && typeof kt[0] === "string") {
|
|
14627
|
+
return kt[0];
|
|
14628
|
+
}
|
|
14629
|
+
const t = k["text"];
|
|
14630
|
+
if (typeof t === "string") {
|
|
14631
|
+
return t;
|
|
14632
|
+
}
|
|
14633
|
+
return "";
|
|
14634
|
+
}
|
|
14371
14635
|
async function runAdKeywords(opts) {
|
|
14372
14636
|
const config = loadConfig(opts.token);
|
|
14373
14637
|
const googleApiUrl = requireGoogleApi(config);
|
|
@@ -14559,8 +14823,17 @@ async function runAdKeywordEdit(opts) {
|
|
|
14559
14823
|
process.exit(1);
|
|
14560
14824
|
}
|
|
14561
14825
|
const body = { ...keyword };
|
|
14562
|
-
if (opts.
|
|
14563
|
-
|
|
14826
|
+
if (opts.matchType !== void 0) {
|
|
14827
|
+
const base = opts.text !== void 0 ? opts.text : firstKeywordTextFromRecord(keyword);
|
|
14828
|
+
if (!String(base).trim()) {
|
|
14829
|
+
console.error("\n\u274C \u65E0\u6CD5\u89E3\u6790\u5F53\u524D\u5173\u952E\u8BCD\u6587\u6848\uFF0C\u8BF7\u540C\u65F6\u4F20 --text <\u8BCD\u5E72>\n");
|
|
14830
|
+
process.exit(1);
|
|
14831
|
+
}
|
|
14832
|
+
body["keywordText"] = [formatKeywordTextForMatchType(base, opts.matchType)];
|
|
14833
|
+
body["matchTypeV2"] = opts.matchType;
|
|
14834
|
+
} else if (opts.text !== void 0) {
|
|
14835
|
+
body["keywordText"] = [opts.text];
|
|
14836
|
+
}
|
|
14564
14837
|
if (opts.maxCpc !== void 0) body["maxCPC"] = opts.maxCpc;
|
|
14565
14838
|
if (opts.finalUrl !== void 0) body["finalURL"] = opts.finalUrl;
|
|
14566
14839
|
const url = `${googleApiUrl}/keywordmanagement/Keyword/${opts.account}/batch`;
|
|
@@ -14607,8 +14880,17 @@ async function runAdNegativeKeywordEdit(opts) {
|
|
|
14607
14880
|
process.exit(1);
|
|
14608
14881
|
}
|
|
14609
14882
|
const body = { ...keyword };
|
|
14610
|
-
if (opts.
|
|
14611
|
-
|
|
14883
|
+
if (opts.matchType !== void 0) {
|
|
14884
|
+
const base = opts.text !== void 0 ? opts.text : firstKeywordTextFromRecord(keyword);
|
|
14885
|
+
if (!String(base).trim()) {
|
|
14886
|
+
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");
|
|
14887
|
+
process.exit(1);
|
|
14888
|
+
}
|
|
14889
|
+
body["keywordText"] = [formatKeywordTextForMatchType(base, opts.matchType)];
|
|
14890
|
+
body["matchTypeV2"] = opts.matchType;
|
|
14891
|
+
} else if (opts.text !== void 0) {
|
|
14892
|
+
body["keywordText"] = [opts.text];
|
|
14893
|
+
}
|
|
14612
14894
|
const url = `${googleApiUrl}/negativekeywordmanagement/negativekeyword/${opts.account}/${opts.id}`;
|
|
14613
14895
|
try {
|
|
14614
14896
|
await apiFetch2(url, config, { method: "PUT", body: JSON.stringify(body) }, opts.verbose);
|
|
@@ -15910,7 +16192,10 @@ function register20(program2) {
|
|
|
15910
16192
|
});
|
|
15911
16193
|
}
|
|
15912
16194
|
);
|
|
15913
|
-
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(
|
|
16195
|
+
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(
|
|
16196
|
+
"--match-type <type>",
|
|
16197
|
+
'\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'
|
|
16198
|
+
).option(
|
|
15914
16199
|
"--max-cpc <n>",
|
|
15915
16200
|
"\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"
|
|
15916
16201
|
).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(
|
|
@@ -15941,7 +16226,10 @@ function register20(program2) {
|
|
|
15941
16226
|
});
|
|
15942
16227
|
}
|
|
15943
16228
|
);
|
|
15944
|
-
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(
|
|
16229
|
+
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(
|
|
16230
|
+
"--match-type <type>",
|
|
16231
|
+
'\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'
|
|
16232
|
+
).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(
|
|
15945
16233
|
async (opts) => {
|
|
15946
16234
|
if (opts.matchType && !["Broad", "Phrase", "Exact"].includes(opts.matchType)) {
|
|
15947
16235
|
console.error("\n\u274C --match-type \u53EA\u63A5\u53D7 Broad | Phrase | Exact\n");
|
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> \
|
|
@@ -57,7 +57,8 @@ siluzan-tso init -d /path/to-your/skills # 写入自定义目录
|
|
|
57
57
|
## 其它登录方式(TTY 交互 / 已有 API Key / JWT)
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
siluzan-tso login #
|
|
60
|
+
siluzan-tso login # 无参:TTY 下展开菜单 → 1 JWT / 2 API Key / 3 手机号(发码+输码)
|
|
61
|
+
siluzan-tso login --manual # 跳过菜单,直接粘贴 JWT(会清除已存 API Key 以启用 Bearer)
|
|
61
62
|
siluzan-tso login --api-key <YOUR_API_KEY> # 直接设置 API Key(跳过交互)
|
|
62
63
|
siluzan-tso config set --api-key <Key> # 或 config 直接写入
|
|
63
64
|
siluzan-tso config set --token <Token> # 备用:设置 JWT Token
|
|
@@ -94,7 +95,7 @@ siluzan-tso login --phone 13800138000 --code 123456 \
|
|
|
94
95
|
>
|
|
95
96
|
> **验证码错误/过期**:返回明确提示,让用户重新跑 `send-login-code` 拿新验证码。
|
|
96
97
|
>
|
|
97
|
-
> **AI 助手用法**:先调 `send-login-code` 发码、立即返回继续对话;等用户报出收到的验证码后,再调 `login --phone xxx --code xxx` 完成登录。**永远不要在没拿到 `--code` 的情况下调用 `login --phone xxx
|
|
98
|
+
> **AI 助手用法**:先调 `send-login-code` 发码、立即返回继续对话;等用户报出收到的验证码后,再调 `login --phone xxx --code xxx` 完成登录。**永远不要在没拿到 `--code` 的情况下调用 `login --phone xxx`,那会直接报错。**(人类在本地终端可直接运行无参 `siluzan-tso login` 选 3,由 CLI 发码并读验证码,无需先记 `send-login-code`。)
|
|
98
99
|
|
|
99
100
|
### 通过环境变量传入凭据(CI/CD 推荐)
|
|
100
101
|
|