siluzan-tso-cli 1.1.54-beta.3 → 1.1.54-beta.4

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.54-beta.3),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.54-beta.4),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -2582,6 +2582,27 @@ async function parseApiResponse(status, text2, verbose) {
2582
2582
  return text2;
2583
2583
  }
2584
2584
  }
2585
+ function isTransientNetworkError(err) {
2586
+ const msg = err instanceof Error ? err.message : String(err);
2587
+ const code = err?.code;
2588
+ if (typeof code === "string" && ["ECONNRESET", "EPIPE", "ETIMEDOUT", "ECONNABORTED"].includes(code)) {
2589
+ return true;
2590
+ }
2591
+ return /响应解压失败|aborted|socket hang up|ECONNRESET|unexpected end of file/i.test(msg);
2592
+ }
2593
+ async function requestWithReadRetry(url, options) {
2594
+ const method = (options.method ?? "GET").toUpperCase();
2595
+ for (let attempt = 0; ; attempt++) {
2596
+ try {
2597
+ return await rawRequest(url, options);
2598
+ } catch (err) {
2599
+ if (method !== "GET" || attempt >= GET_RETRY_DELAYS_MS.length || !isTransientNetworkError(err)) {
2600
+ throw err;
2601
+ }
2602
+ await new Promise((r) => setTimeout(r, GET_RETRY_DELAYS_MS[attempt]));
2603
+ }
2604
+ }
2605
+ }
2585
2606
  async function apiFetch(url, config, options = {}, verbose = false) {
2586
2607
  const method = options.method ?? "GET";
2587
2608
  const reqHeaders = {
@@ -2595,7 +2616,7 @@ async function apiFetch(url, config, options = {}, verbose = false) {
2595
2616
  ...options.headers ?? {}
2596
2617
  };
2597
2618
  const body = typeof options.body === "string" ? options.body : void 0;
2598
- const res = await rawRequest(url, {
2619
+ const res = await requestWithReadRetry(url, {
2599
2620
  method,
2600
2621
  headers: reqHeaders,
2601
2622
  body
@@ -2613,7 +2634,7 @@ async function apiFetchWithHeaders(url, config, options = {}, verbose = false) {
2613
2634
  ...options.headers ?? {}
2614
2635
  };
2615
2636
  const body = typeof options.body === "string" ? options.body : void 0;
2616
- const res = await rawRequest(url, { method, headers: reqHeaders, body });
2637
+ const res = await requestWithReadRetry(url, { method, headers: reqHeaders, body });
2617
2638
  const text2 = res.text;
2618
2639
  if (res.status < 200 || res.status >= 300) {
2619
2640
  throw new Error(formatHttpError(res.status, text2, { verbose }));
@@ -3901,7 +3922,7 @@ async function issueApiKeyWithPhoneCode(opts) {
3901
3922
  });
3902
3923
  return apiKey;
3903
3924
  }
3904
- 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, MAX_RESPONSE_BYTES, httpsAgent, httpAgent, PERF_PREFIX, DEFAULT_DETAIL_MAX, VERBOSE_DETAIL_MAX, 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;
3925
+ 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, MAX_RESPONSE_BYTES, 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;
3905
3926
  var init_dist = __esm({
3906
3927
  "../common/dist/index.js"() {
3907
3928
  "use strict";
@@ -3969,6 +3990,7 @@ var init_dist = __esm({
3969
3990
  PERF_PREFIX = "[SILUZAN_HTTP_PERF]";
3970
3991
  DEFAULT_DETAIL_MAX = 400;
3971
3992
  VERBOSE_DETAIL_MAX = 800;
3993
+ GET_RETRY_DELAYS_MS = [1e3, 3e3];
3972
3994
  TABLE_CELL_PIPE = "\uFF5C";
3973
3995
  ASCII_TABLE_CHARS = {
3974
3996
  top: "-",
@@ -113858,7 +113880,10 @@ async function runAdKeywordEdit(opts) {
113858
113880
  if (opts.maxCpc !== void 0) body["maxCPC"] = toCentAmount(opts.maxCpc);
113859
113881
  if (opts.finalUrl !== void 0) body["finalURL"] = opts.finalUrl;
113860
113882
  if (opts.status !== void 0) body["userStatusV2"] = opts.status;
113883
+ const notAppliedHint = ` \u8BF7\u52FF\u6539\u7528\u300C\u5220\u9664\u540E\u91CD\u5EFA\u300D\u7ED5\u8FC7\uFF08\u4F1A\u4E22\u5931\u5173\u952E\u8BCD id \u4E0E\u5386\u53F2\u6570\u636E\uFF09\uFF0C\u8BF7\u53CD\u9988\u5E73\u53F0\u6392\u67E5\u3002
113884
+ `;
113861
113885
  const url = `${googleApiUrl}/keywordmanagement/Keyword/${opts.account}/batch`;
113886
+ let currentId = opts.id;
113862
113887
  try {
113863
113888
  const raw = await apiFetch2(
113864
113889
  url,
@@ -113869,6 +113894,7 @@ async function runAdKeywordEdit(opts) {
113869
113894
  const first = Array.isArray(raw) && raw.length > 0 && raw[0] && typeof raw[0] === "object" ? raw[0] : null;
113870
113895
  const returnedId = first && typeof first["id"] === "string" ? first["id"] : void 0;
113871
113896
  if (returnedId && returnedId !== opts.id) {
113897
+ currentId = returnedId;
113872
113898
  console.log(
113873
113899
  `
113874
113900
  \u2139\uFE0F \u7F51\u5173\u8FD4\u56DE\u7684\u5173\u952E\u8BCD id \u5DF2\u53D8\u66F4\u4E3A ${returnedId}\uFF08\u8BF7\u6C42\u65F6 id \u4E3A ${opts.id}\uFF09\uFF0C\u540E\u7EED\u67E5\u8BE2\u8BF7\u4F7F\u7528\u65B0 id\u3002
@@ -113881,8 +113907,7 @@ async function runAdKeywordEdit(opts) {
113881
113907
  `
113882
113908
  \u274C \u7F51\u5173\u8FD4\u56DE\u6210\u529F\uFF0C\u4F46\u4EE5\u4E0B\u5B57\u6BB5\u672A\u751F\u6548\uFF1A
113883
113909
  ${mismatches.map((m) => ` - ${m}`).join("\n")}
113884
- \u8BF7\u52FF\u6539\u7528\u300C\u5220\u9664\u540E\u91CD\u5EFA\u300D\u7ED5\u8FC7\uFF08\u4F1A\u4E22\u5931\u5173\u952E\u8BCD id \u4E0E\u5386\u53F2\u6570\u636E\uFF09\uFF0C\u8BF7\u53CD\u9988\u5E73\u53F0\u6392\u67E5\u3002
113885
- `
113910
+ ` + notAppliedHint
113886
113911
  );
113887
113912
  process.exit(1);
113888
113913
  }
@@ -113892,6 +113917,35 @@ ${mismatches.map((m) => ` - ${m}`).join("\n")}
113892
113917
  `);
113893
113918
  process.exit(1);
113894
113919
  }
113920
+ if (opts.finalUrl !== void 0 || opts.maxCpc !== void 0 || opts.status !== void 0) {
113921
+ let fresh;
113922
+ try {
113923
+ fresh = await findItemInList(
113924
+ listUrl,
113925
+ config,
113926
+ currentId,
113927
+ opts.verbose
113928
+ );
113929
+ } catch (err) {
113930
+ console.error(
113931
+ `
113932
+ \u274C \u5199\u5165\u8BF7\u6C42\u5DF2\u53D1\u9001\uFF0C\u4F46\u56DE\u8BFB\u6821\u9A8C\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
113933
+ \u7A0D\u540E\u7528 ad keywords \u6838\u5BF9 id=${currentId} \u7684\u5B9E\u9645\u503C\uFF1B\u672A\u751F\u6548\u65F6\u91CD\u8DD1 keyword-edit \u5373\u53EF\uFF08\u5E42\u7B49\uFF09\u3002
113934
+ ` + notAppliedHint
113935
+ );
113936
+ process.exit(1);
113937
+ }
113938
+ const mismatches = findKeywordEditMismatches(body, fresh, opts);
113939
+ if (mismatches.length > 0) {
113940
+ console.error(
113941
+ `
113942
+ \u274C \u7F51\u5173\u8FD4\u56DE\u6210\u529F\uFF0C\u4F46\u56DE\u8BFB\u540E\u4EE5\u4E0B\u5B57\u6BB5\u672A\u751F\u6548\uFF1A
113943
+ ${mismatches.map((m) => ` - ${m}`).join("\n")}
113944
+ ` + notAppliedHint
113945
+ );
113946
+ process.exit(1);
113947
+ }
113948
+ }
113895
113949
  console.log(`
113896
113950
  \u2705 \u5173\u952E\u8BCD\u5DF2\u66F4\u65B0
113897
113951
  `);
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.54-beta.3",
4
- "publishedAt": 1791958328486
3
+ "version": "1.1.54-beta.4",
4
+ "publishedAt": 1791960108857
5
5
  }
@@ -336,7 +336,7 @@ siluzan-tso ad keyword-delete -a <accountId> --id <keywordId> --adgroup-id <adGr
336
336
 
337
337
  ## ad keyword-edit — 搜索关键词编辑
338
338
 
339
- 先用 `ad keywords --json-out ./snap` 取完整对象,再提交修改。只改 `--final-url` / `--max-cpc` / `--status` 时原地更新,`id` 与历史数据保留;改 `--text` / `--match-type`(Google 侧词面与匹配类型不可变)会新建并删除旧词,`id` 变化时 CLI 会提示。若回执显示字段未生效,CLI 报错退出:**不要**自行改用 `keyword-delete` + `keyword-create` 重建(丢失 id 与历史数据),应反馈平台。
339
+ 先用 `ad keywords --json-out ./snap` 取完整对象,再提交修改。只改 `--final-url` / `--max-cpc` / `--status` 时原地更新,`id` 与历史数据保留;改 `--text` / `--match-type`(Google 侧词面与匹配类型不可变)会新建并删除旧词,`id` 变化时 CLI 会提示。改 `--final-url` / `--max-cpc` / `--status` 后 CLI 会重新拉列表回读实际值,只有回读一致才输出 ✅;未生效或回读失败则报错退出。此时**禁止**自行改用 `keyword-delete` + `keyword-create`,也禁止直调网关删除重建(会丢失 id 与历史数据),哪怕只改一条也不行。应停下来告知用户「平台未生效、需反馈平台」,可另给 Google Ads Editor 导入清单作为替代方案。
340
340
 
341
341
  ```bash
342
342
  siluzan-tso ad keyword-edit \
@@ -9,7 +9,7 @@ $ErrorActionPreference = 'Stop'
9
9
  # -- Package info (injected at build time) ------------------------------------
10
10
  $PKG_NAME = 'siluzan-tso-cli'
11
11
  # PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
12
- $PKG_VERSION = '1.1.54-beta.3'
12
+ $PKG_VERSION = '1.1.54-beta.4'
13
13
  $CLI_BIN = 'siluzan-tso'
14
14
  $SKILL_LABEL = 'Siluzan TSO'
15
15
  $INSTALL_CMD = 'npm install -g siluzan-tso-cli@beta'
@@ -9,7 +9,7 @@ set -euo pipefail
9
9
  # -- Package info (injected at build time) ------------------------------------
10
10
  readonly PKG_NAME="siluzan-tso-cli"
11
11
  # PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
12
- readonly PKG_VERSION="1.1.54-beta.3"
12
+ readonly PKG_VERSION="1.1.54-beta.4"
13
13
  readonly CLI_BIN="siluzan-tso"
14
14
  readonly SKILL_LABEL="Siluzan TSO"
15
15
  readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.54-beta.3",
3
+ "version": "1.1.54-beta.4",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",