siluzan-tso-cli 1.1.34 → 1.1.35-beta.1
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 +347 -259
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/accounts/accounts.md +3 -3
- 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/rag.md +1 -1
- package/dist/skill/references/analytics/reporting.md +5 -5
- package/dist/skill/references/core/playbooks.md +1 -1
- package/dist/skill/references/core/setup.md +5 -5
- package/dist/skill/references/misc/tso-home.md +2 -2
- package/dist/skill/references/report-templates/google-ads-diagnosis.md +14 -1
- package/dist/skill/report-templates/google-ads-diagnosis.md +14 -1
- 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.35-beta.1),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
54
55
|
|
|
55
56
|
| 助手 | 建议 `--ai` |
|
|
56
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -3829,7 +3829,7 @@ var DEFAULT_API_BASE;
|
|
|
3829
3829
|
var init_defaults = __esm({
|
|
3830
3830
|
"src/config/defaults.ts"() {
|
|
3831
3831
|
"use strict";
|
|
3832
|
-
DEFAULT_API_BASE = "https://tso-api.siluzan.com";
|
|
3832
|
+
DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
|
|
3833
3833
|
}
|
|
3834
3834
|
});
|
|
3835
3835
|
|
|
@@ -5754,6 +5754,150 @@ var init_sections = __esm({
|
|
|
5754
5754
|
}
|
|
5755
5755
|
});
|
|
5756
5756
|
|
|
5757
|
+
// src/utils/http-retry.ts
|
|
5758
|
+
function classifyError(err) {
|
|
5759
|
+
const status = parseHttpStatusFromError(err);
|
|
5760
|
+
if (status === 401) return { class: "abort", status };
|
|
5761
|
+
if (status === void 0) {
|
|
5762
|
+
return { class: "retryable" };
|
|
5763
|
+
}
|
|
5764
|
+
if (status === 408 || status === 429) return { class: "retryable", status };
|
|
5765
|
+
if (status >= 500 && status <= 599) return { class: "retryable", status };
|
|
5766
|
+
if (status >= 400 && status <= 499) return { class: "permanent", status };
|
|
5767
|
+
return { class: "permanent", status };
|
|
5768
|
+
}
|
|
5769
|
+
function computeBackoffMs(attempt, policy) {
|
|
5770
|
+
const exp = policy.baseMs * 2 ** Math.max(0, attempt - 1);
|
|
5771
|
+
const capped = Math.min(exp, policy.maxBackoffMs);
|
|
5772
|
+
const jitter = Math.floor(Math.random() * policy.baseMs);
|
|
5773
|
+
return capped + jitter;
|
|
5774
|
+
}
|
|
5775
|
+
function sleep3(ms, signal) {
|
|
5776
|
+
return new Promise((resolve15, reject) => {
|
|
5777
|
+
if (signal?.aborted) {
|
|
5778
|
+
reject(new Error("retry sleep aborted"));
|
|
5779
|
+
return;
|
|
5780
|
+
}
|
|
5781
|
+
const timer = setTimeout(() => {
|
|
5782
|
+
signal?.removeEventListener("abort", onAbort);
|
|
5783
|
+
resolve15();
|
|
5784
|
+
}, ms);
|
|
5785
|
+
const onAbort = () => {
|
|
5786
|
+
clearTimeout(timer);
|
|
5787
|
+
reject(new Error("retry sleep aborted"));
|
|
5788
|
+
};
|
|
5789
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
5790
|
+
});
|
|
5791
|
+
}
|
|
5792
|
+
async function withTaskTimeout(factory, timeoutMs, label) {
|
|
5793
|
+
if (!timeoutMs || timeoutMs <= 0) return factory();
|
|
5794
|
+
let timer;
|
|
5795
|
+
try {
|
|
5796
|
+
return await Promise.race([
|
|
5797
|
+
factory(),
|
|
5798
|
+
new Promise((_, reject) => {
|
|
5799
|
+
timer = setTimeout(() => {
|
|
5800
|
+
reject(new Error(`\u8BF7\u6C42\u8D85\u65F6\uFF08${(timeoutMs / 1e3).toFixed(0)} \u79D2\uFF09\uFF1A${label}`));
|
|
5801
|
+
}, timeoutMs);
|
|
5802
|
+
})
|
|
5803
|
+
]);
|
|
5804
|
+
} finally {
|
|
5805
|
+
if (timer) clearTimeout(timer);
|
|
5806
|
+
}
|
|
5807
|
+
}
|
|
5808
|
+
async function fetchWithRetry(factory, options) {
|
|
5809
|
+
const policy = { ...DEFAULT_RETRY_POLICY, ...options.policy ?? {} };
|
|
5810
|
+
let lastErr;
|
|
5811
|
+
let lastStatus;
|
|
5812
|
+
for (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {
|
|
5813
|
+
if (options.signal?.aborted) {
|
|
5814
|
+
throw new AbortRunError({
|
|
5815
|
+
message: `\u4EFB\u52A1\u88AB\u5916\u90E8\u4FE1\u53F7\u4E2D\u6B62\uFF1A${options.label}`
|
|
5816
|
+
});
|
|
5817
|
+
}
|
|
5818
|
+
try {
|
|
5819
|
+
return await withTaskTimeout(factory, policy.taskTimeoutMs, options.label);
|
|
5820
|
+
} catch (raw) {
|
|
5821
|
+
const err = raw instanceof Error ? raw : new Error(String(raw));
|
|
5822
|
+
const cls = classifyError(err);
|
|
5823
|
+
lastErr = err;
|
|
5824
|
+
lastStatus = cls.status;
|
|
5825
|
+
if (cls.class === "abort") {
|
|
5826
|
+
throw new AbortRunError({
|
|
5827
|
+
message: `Token \u5931\u6548\u6216\u81F4\u547D\u9519\u8BEF\uFF08${options.label}\uFF09\uFF1A${err.message}`,
|
|
5828
|
+
status: cls.status,
|
|
5829
|
+
cause: err
|
|
5830
|
+
});
|
|
5831
|
+
}
|
|
5832
|
+
if (cls.class === "permanent" || attempt >= policy.maxAttempts) {
|
|
5833
|
+
throw new HttpRetryError({
|
|
5834
|
+
message: cls.class === "permanent" ? `\u6C38\u4E45\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${err.message}` : `\u91CD\u8BD5 ${attempt} \u6B21\u4ECD\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${err.message}`,
|
|
5835
|
+
class: cls.class === "permanent" ? "permanent" : "retryable",
|
|
5836
|
+
status: cls.status,
|
|
5837
|
+
attempts: attempt,
|
|
5838
|
+
cause: err
|
|
5839
|
+
});
|
|
5840
|
+
}
|
|
5841
|
+
const backoffMs = computeBackoffMs(attempt, policy);
|
|
5842
|
+
options.onRetry?.({ attempt, backoffMs, status: cls.status, error: err });
|
|
5843
|
+
try {
|
|
5844
|
+
await sleep3(backoffMs, options.signal);
|
|
5845
|
+
} catch {
|
|
5846
|
+
throw new AbortRunError({
|
|
5847
|
+
message: `\u91CD\u8BD5\u7B49\u5F85\u88AB\u4E2D\u6B62\uFF1A${options.label}`,
|
|
5848
|
+
cause: err
|
|
5849
|
+
});
|
|
5850
|
+
}
|
|
5851
|
+
}
|
|
5852
|
+
}
|
|
5853
|
+
throw new HttpRetryError({
|
|
5854
|
+
message: `\u672A\u77E5\u91CD\u8BD5\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${lastErr?.message ?? "no error"}`,
|
|
5855
|
+
class: "retryable",
|
|
5856
|
+
status: lastStatus,
|
|
5857
|
+
attempts: policy.maxAttempts,
|
|
5858
|
+
cause: lastErr
|
|
5859
|
+
});
|
|
5860
|
+
}
|
|
5861
|
+
var HttpRetryError, AbortRunError, DEFAULT_RETRY_POLICY;
|
|
5862
|
+
var init_http_retry = __esm({
|
|
5863
|
+
"src/utils/http-retry.ts"() {
|
|
5864
|
+
"use strict";
|
|
5865
|
+
init_dist();
|
|
5866
|
+
HttpRetryError = class extends Error {
|
|
5867
|
+
class;
|
|
5868
|
+
status;
|
|
5869
|
+
attempts;
|
|
5870
|
+
cause;
|
|
5871
|
+
constructor(opts) {
|
|
5872
|
+
super(opts.message);
|
|
5873
|
+
this.name = "HttpRetryError";
|
|
5874
|
+
this.class = opts.class;
|
|
5875
|
+
this.status = opts.status;
|
|
5876
|
+
this.attempts = opts.attempts;
|
|
5877
|
+
this.cause = opts.cause;
|
|
5878
|
+
}
|
|
5879
|
+
};
|
|
5880
|
+
AbortRunError = class extends HttpRetryError {
|
|
5881
|
+
constructor(opts) {
|
|
5882
|
+
super({
|
|
5883
|
+
message: opts.message,
|
|
5884
|
+
class: "abort",
|
|
5885
|
+
status: opts.status,
|
|
5886
|
+
attempts: 1,
|
|
5887
|
+
cause: opts.cause
|
|
5888
|
+
});
|
|
5889
|
+
this.name = "AbortRunError";
|
|
5890
|
+
}
|
|
5891
|
+
};
|
|
5892
|
+
DEFAULT_RETRY_POLICY = {
|
|
5893
|
+
maxAttempts: 3,
|
|
5894
|
+
baseMs: 1e3,
|
|
5895
|
+
maxBackoffMs: 3e4,
|
|
5896
|
+
taskTimeoutMs: 6e4
|
|
5897
|
+
};
|
|
5898
|
+
}
|
|
5899
|
+
});
|
|
5900
|
+
|
|
5757
5901
|
// src/commands/google-analysis/normalize-impression-shares.ts
|
|
5758
5902
|
function normalizeShareValue(value, competitiveMetrics, field) {
|
|
5759
5903
|
const cmVal = competitiveMetrics?.[field];
|
|
@@ -101399,6 +101543,28 @@ var init_translate_fields = __esm({
|
|
|
101399
101543
|
}
|
|
101400
101544
|
});
|
|
101401
101545
|
|
|
101546
|
+
// src/commands/google-analysis/daily-metrics-range.ts
|
|
101547
|
+
function pad2(n) {
|
|
101548
|
+
return String(n).padStart(2, "0");
|
|
101549
|
+
}
|
|
101550
|
+
function formatChinaOffsetDateTime(d) {
|
|
101551
|
+
const sh = new Date(d.getTime() + CHINA_OFFSET_MS);
|
|
101552
|
+
return `${sh.getUTCFullYear()}-${pad2(sh.getUTCMonth() + 1)}-${pad2(sh.getUTCDate())}T${pad2(sh.getUTCHours())}:${pad2(sh.getUTCMinutes())}:${pad2(sh.getUTCSeconds())}+08:00`;
|
|
101553
|
+
}
|
|
101554
|
+
function resolveDailyMetricsQueryTimes(startDate, endDate, now = /* @__PURE__ */ new Date()) {
|
|
101555
|
+
const startInstant = `${startDate}T00:00:00+08:00`;
|
|
101556
|
+
const endOfDay = /* @__PURE__ */ new Date(`${endDate}T23:59:59+08:00`);
|
|
101557
|
+
const endInstant = endOfDay.getTime() > now.getTime() ? formatChinaOffsetDateTime(now) : formatChinaOffsetDateTime(endOfDay);
|
|
101558
|
+
return { startInstant, endInstant };
|
|
101559
|
+
}
|
|
101560
|
+
var CHINA_OFFSET_MS;
|
|
101561
|
+
var init_daily_metrics_range = __esm({
|
|
101562
|
+
"src/commands/google-analysis/daily-metrics-range.ts"() {
|
|
101563
|
+
"use strict";
|
|
101564
|
+
CHINA_OFFSET_MS = 8 * 36e5;
|
|
101565
|
+
}
|
|
101566
|
+
});
|
|
101567
|
+
|
|
101402
101568
|
// src/commands/google-analysis/fetch.ts
|
|
101403
101569
|
function defaultDateRange3() {
|
|
101404
101570
|
const end = /* @__PURE__ */ new Date();
|
|
@@ -101432,9 +101598,19 @@ function buildSearchParams(def, start, end, extras) {
|
|
|
101432
101598
|
const s = params.toString();
|
|
101433
101599
|
return s ? `?${s}` : "";
|
|
101434
101600
|
}
|
|
101435
|
-
async function
|
|
101601
|
+
async function fetchGoogleAnalysisHttp(label, factory, http2) {
|
|
101602
|
+
return fetchWithRetry(factory, {
|
|
101603
|
+
label,
|
|
101604
|
+
policy: { ...DEFAULT_RETRY_POLICY, ...http2?.retry },
|
|
101605
|
+
signal: http2?.signal
|
|
101606
|
+
});
|
|
101607
|
+
}
|
|
101608
|
+
async function fetchJson(config, pathWithQuery, verbose, label = pathWithQuery, http2) {
|
|
101436
101609
|
const url = `${config.googleApiUrl}${pathWithQuery}`;
|
|
101437
|
-
return apiFetch2(url, config, {}, verbose);
|
|
101610
|
+
return fetchGoogleAnalysisHttp(label, () => apiFetch2(url, config, {}, verbose), http2);
|
|
101611
|
+
}
|
|
101612
|
+
async function fetchMainPlatformJson(url, config, verbose, label, http2) {
|
|
101613
|
+
return fetchGoogleAnalysisHttp(label, () => apiFetch2(url, config, {}, verbose), http2);
|
|
101438
101614
|
}
|
|
101439
101615
|
function deviceBidModifierLookupKey(campaignId, adGroupId, deviceType) {
|
|
101440
101616
|
return `${campaignId ?? ""}\0${adGroupId ?? ""}\0${deviceType ?? ""}`;
|
|
@@ -101482,54 +101658,55 @@ function rowsFromAccountDailyReportsEnvelope(raw, mediaCustomerId) {
|
|
|
101482
101658
|
const list = block?.data;
|
|
101483
101659
|
return Array.isArray(list) ? list : [];
|
|
101484
101660
|
}
|
|
101485
|
-
async function fetchGoogleAnalysisSectionJson(config, fullPath, verbose, name2) {
|
|
101661
|
+
async function fetchGoogleAnalysisSectionJson(config, fullPath, verbose, name2, label, http2) {
|
|
101662
|
+
const fetchSection = () => fetchJson(config, fullPath, verbose, label, http2);
|
|
101486
101663
|
switch (name2) {
|
|
101487
101664
|
case "overview":
|
|
101488
|
-
return
|
|
101665
|
+
return fetchSection();
|
|
101489
101666
|
case "keywords":
|
|
101490
|
-
return
|
|
101667
|
+
return fetchSection();
|
|
101491
101668
|
case "search-terms":
|
|
101492
|
-
return
|
|
101669
|
+
return fetchSection();
|
|
101493
101670
|
case "campaigns":
|
|
101494
|
-
return
|
|
101671
|
+
return fetchSection();
|
|
101495
101672
|
case "campaign-hour":
|
|
101496
|
-
return
|
|
101673
|
+
return fetchSection();
|
|
101497
101674
|
case "ads":
|
|
101498
|
-
return
|
|
101675
|
+
return fetchSection();
|
|
101499
101676
|
case "extensions":
|
|
101500
|
-
return
|
|
101677
|
+
return fetchSection();
|
|
101501
101678
|
case "devices":
|
|
101502
|
-
return
|
|
101679
|
+
return fetchSection();
|
|
101503
101680
|
case "geographic":
|
|
101504
|
-
return
|
|
101681
|
+
return fetchSection();
|
|
101505
101682
|
case "geo-matched":
|
|
101506
|
-
return
|
|
101683
|
+
return fetchSection();
|
|
101507
101684
|
case "campaign-geo":
|
|
101508
|
-
return
|
|
101685
|
+
return fetchSection();
|
|
101509
101686
|
case "campaign-geo-matched":
|
|
101510
|
-
return
|
|
101687
|
+
return fetchSection();
|
|
101511
101688
|
case "campaign-device":
|
|
101512
|
-
return
|
|
101689
|
+
return fetchSection();
|
|
101513
101690
|
case "audience":
|
|
101514
|
-
return
|
|
101691
|
+
return fetchSection();
|
|
101515
101692
|
case "asset-images":
|
|
101516
|
-
return
|
|
101693
|
+
return fetchSection();
|
|
101517
101694
|
case "videos":
|
|
101518
|
-
return
|
|
101695
|
+
return fetchSection();
|
|
101519
101696
|
case "resource-counts":
|
|
101520
|
-
return
|
|
101697
|
+
return fetchSection();
|
|
101521
101698
|
case "conversion-actions":
|
|
101522
|
-
return
|
|
101699
|
+
return fetchSection();
|
|
101523
101700
|
case "gold-account":
|
|
101524
|
-
return
|
|
101701
|
+
return fetchSection();
|
|
101525
101702
|
case "ads-index":
|
|
101526
|
-
return
|
|
101703
|
+
return fetchSection();
|
|
101527
101704
|
case "final-urls":
|
|
101528
|
-
return
|
|
101705
|
+
return fetchSection();
|
|
101529
101706
|
case "dimension-summary":
|
|
101530
|
-
return
|
|
101707
|
+
return fetchSection();
|
|
101531
101708
|
case "campaign-types":
|
|
101532
|
-
return
|
|
101709
|
+
return fetchSection();
|
|
101533
101710
|
default:
|
|
101534
101711
|
return assertNever(name2, "google-analysis");
|
|
101535
101712
|
}
|
|
@@ -101544,7 +101721,7 @@ function manifestDateRange(def, start, end) {
|
|
|
101544
101721
|
const { startDate, endDate } = resolveDateRange2(start, end);
|
|
101545
101722
|
return { mode: "range", start: startDate, end: endDate };
|
|
101546
101723
|
}
|
|
101547
|
-
async function fetchSectionPayload(def, opts, config, id) {
|
|
101724
|
+
async function fetchSectionPayload(def, opts, config, id, http2) {
|
|
101548
101725
|
const extras = {};
|
|
101549
101726
|
if (def.keywordOptions) {
|
|
101550
101727
|
if (typeof opts.limit === "number" && opts.limit > 0) {
|
|
@@ -101571,9 +101748,10 @@ async function fetchSectionPayload(def, opts, config, id) {
|
|
|
101571
101748
|
const q = `?${new URLSearchParams({ startDate, endDate }).toString()}`;
|
|
101572
101749
|
const videoPath = `/reporting/media-account/${id}/Videos${q}`;
|
|
101573
101750
|
const imagePath = `/reporting/media-account/${id}/CampaignAssetView${q}`;
|
|
101751
|
+
const verbose = !!opts.verbose;
|
|
101574
101752
|
const [images, videos] = await Promise.all([
|
|
101575
|
-
fetchJson(config, imagePath,
|
|
101576
|
-
fetchJson(config, videoPath,
|
|
101753
|
+
fetchJson(config, imagePath, verbose, `materials/images/${id}`, http2),
|
|
101754
|
+
fetchJson(config, videoPath, verbose, `materials/videos/${id}`, http2)
|
|
101577
101755
|
]);
|
|
101578
101756
|
const merged = { images, videos };
|
|
101579
101757
|
return annotateZhFields(
|
|
@@ -101587,12 +101765,21 @@ async function fetchSectionPayload(def, opts, config, id) {
|
|
|
101587
101765
|
if (def.name === "campaign-device") {
|
|
101588
101766
|
const sectionPath2 = def.path(id);
|
|
101589
101767
|
const query2 = buildSearchParams(def, opts.start, opts.end, extras);
|
|
101768
|
+
const verbose = !!opts.verbose;
|
|
101590
101769
|
const [report, modifiers] = await Promise.all([
|
|
101591
|
-
fetchJson(
|
|
101770
|
+
fetchJson(
|
|
101771
|
+
config,
|
|
101772
|
+
`${sectionPath2}${query2}`,
|
|
101773
|
+
verbose,
|
|
101774
|
+
`campaign-device/${id}`,
|
|
101775
|
+
http2
|
|
101776
|
+
),
|
|
101592
101777
|
fetchJson(
|
|
101593
101778
|
config,
|
|
101594
101779
|
`/campaignmanagement/${id}/BidModifiers/Devices`,
|
|
101595
|
-
|
|
101780
|
+
verbose,
|
|
101781
|
+
`campaign-device/bid-modifiers/${id}`,
|
|
101782
|
+
http2
|
|
101596
101783
|
)
|
|
101597
101784
|
]);
|
|
101598
101785
|
const data2 = mergeDeviceBidModifiersIntoReport(report, modifiers);
|
|
@@ -101606,13 +101793,20 @@ async function fetchSectionPayload(def, opts, config, id) {
|
|
|
101606
101793
|
}
|
|
101607
101794
|
if (def.name === "daily-metrics") {
|
|
101608
101795
|
const { startDate, endDate } = resolveDateRange2(opts.start, opts.end);
|
|
101796
|
+
const { startInstant, endInstant } = resolveDailyMetricsQueryTimes(startDate, endDate);
|
|
101609
101797
|
const params = new URLSearchParams({
|
|
101610
101798
|
mediaCustomerIds: id,
|
|
101611
|
-
startDate:
|
|
101612
|
-
endDate:
|
|
101799
|
+
startDate: startInstant,
|
|
101800
|
+
endDate: endInstant
|
|
101613
101801
|
});
|
|
101614
101802
|
const url = `${config.apiBaseUrl}/report/media-account/google/account-daily-reports?${params.toString()}`;
|
|
101615
|
-
const raw = await
|
|
101803
|
+
const raw = await fetchMainPlatformJson(
|
|
101804
|
+
url,
|
|
101805
|
+
config,
|
|
101806
|
+
!!opts.verbose,
|
|
101807
|
+
`daily-metrics/${id}`,
|
|
101808
|
+
http2
|
|
101809
|
+
);
|
|
101616
101810
|
const rows = rowsFromAccountDailyReportsEnvelope(raw, id);
|
|
101617
101811
|
return annotateZhFields(
|
|
101618
101812
|
normalizeMoneyScales(
|
|
@@ -101628,7 +101822,9 @@ async function fetchSectionPayload(def, opts, config, id) {
|
|
|
101628
101822
|
config,
|
|
101629
101823
|
`${sectionPath}${query}`,
|
|
101630
101824
|
!!opts.verbose,
|
|
101631
|
-
def.name
|
|
101825
|
+
def.name,
|
|
101826
|
+
`${def.name}/${id}`,
|
|
101827
|
+
http2
|
|
101632
101828
|
);
|
|
101633
101829
|
let payload = stripLegacyGoogleFieldsIfV2Present(data);
|
|
101634
101830
|
payload = normalizeRateScales(payload, def.name);
|
|
@@ -101665,7 +101861,8 @@ async function fetchOneGoogleSection(opts) {
|
|
|
101665
101861
|
conversionsGreater: opts.conversionsGreater
|
|
101666
101862
|
},
|
|
101667
101863
|
opts.config,
|
|
101668
|
-
opts.accountId
|
|
101864
|
+
opts.accountId,
|
|
101865
|
+
{ retry: opts.retry, signal: opts.signal }
|
|
101669
101866
|
);
|
|
101670
101867
|
return {
|
|
101671
101868
|
payload,
|
|
@@ -101679,156 +101876,14 @@ var init_fetch = __esm({
|
|
|
101679
101876
|
"use strict";
|
|
101680
101877
|
init_auth();
|
|
101681
101878
|
init_strip_legacy_google_fields();
|
|
101879
|
+
init_http_retry();
|
|
101682
101880
|
init_sections();
|
|
101683
101881
|
init_normalize_impression_shares();
|
|
101684
101882
|
init_normalize_rates();
|
|
101685
101883
|
init_normalize_money();
|
|
101686
101884
|
init_normalize_overview();
|
|
101687
101885
|
init_translate_fields();
|
|
101688
|
-
|
|
101689
|
-
});
|
|
101690
|
-
|
|
101691
|
-
// src/utils/http-retry.ts
|
|
101692
|
-
function classifyError(err) {
|
|
101693
|
-
const status = parseHttpStatusFromError(err);
|
|
101694
|
-
if (status === 401) return { class: "abort", status };
|
|
101695
|
-
if (status === void 0) {
|
|
101696
|
-
return { class: "retryable" };
|
|
101697
|
-
}
|
|
101698
|
-
if (status === 408 || status === 429) return { class: "retryable", status };
|
|
101699
|
-
if (status >= 500 && status <= 599) return { class: "retryable", status };
|
|
101700
|
-
if (status >= 400 && status <= 499) return { class: "permanent", status };
|
|
101701
|
-
return { class: "permanent", status };
|
|
101702
|
-
}
|
|
101703
|
-
function computeBackoffMs(attempt, policy) {
|
|
101704
|
-
const exp = policy.baseMs * 2 ** Math.max(0, attempt - 1);
|
|
101705
|
-
const capped = Math.min(exp, policy.maxBackoffMs);
|
|
101706
|
-
const jitter = Math.floor(Math.random() * policy.baseMs);
|
|
101707
|
-
return capped + jitter;
|
|
101708
|
-
}
|
|
101709
|
-
function sleep3(ms, signal) {
|
|
101710
|
-
return new Promise((resolve15, reject) => {
|
|
101711
|
-
if (signal?.aborted) {
|
|
101712
|
-
reject(new Error("retry sleep aborted"));
|
|
101713
|
-
return;
|
|
101714
|
-
}
|
|
101715
|
-
const timer = setTimeout(() => {
|
|
101716
|
-
signal?.removeEventListener("abort", onAbort);
|
|
101717
|
-
resolve15();
|
|
101718
|
-
}, ms);
|
|
101719
|
-
const onAbort = () => {
|
|
101720
|
-
clearTimeout(timer);
|
|
101721
|
-
reject(new Error("retry sleep aborted"));
|
|
101722
|
-
};
|
|
101723
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
101724
|
-
});
|
|
101725
|
-
}
|
|
101726
|
-
async function withTaskTimeout(factory, timeoutMs, label) {
|
|
101727
|
-
if (!timeoutMs || timeoutMs <= 0) return factory();
|
|
101728
|
-
let timer;
|
|
101729
|
-
try {
|
|
101730
|
-
return await Promise.race([
|
|
101731
|
-
factory(),
|
|
101732
|
-
new Promise((_, reject) => {
|
|
101733
|
-
timer = setTimeout(() => {
|
|
101734
|
-
reject(new Error(`\u8BF7\u6C42\u8D85\u65F6\uFF08${(timeoutMs / 1e3).toFixed(0)} \u79D2\uFF09\uFF1A${label}`));
|
|
101735
|
-
}, timeoutMs);
|
|
101736
|
-
})
|
|
101737
|
-
]);
|
|
101738
|
-
} finally {
|
|
101739
|
-
if (timer) clearTimeout(timer);
|
|
101740
|
-
}
|
|
101741
|
-
}
|
|
101742
|
-
async function fetchWithRetry(factory, options) {
|
|
101743
|
-
const policy = { ...DEFAULT_RETRY_POLICY, ...options.policy ?? {} };
|
|
101744
|
-
let lastErr;
|
|
101745
|
-
let lastStatus;
|
|
101746
|
-
for (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {
|
|
101747
|
-
if (options.signal?.aborted) {
|
|
101748
|
-
throw new AbortRunError({
|
|
101749
|
-
message: `\u4EFB\u52A1\u88AB\u5916\u90E8\u4FE1\u53F7\u4E2D\u6B62\uFF1A${options.label}`
|
|
101750
|
-
});
|
|
101751
|
-
}
|
|
101752
|
-
try {
|
|
101753
|
-
return await withTaskTimeout(factory, policy.taskTimeoutMs, options.label);
|
|
101754
|
-
} catch (raw) {
|
|
101755
|
-
const err = raw instanceof Error ? raw : new Error(String(raw));
|
|
101756
|
-
const cls = classifyError(err);
|
|
101757
|
-
lastErr = err;
|
|
101758
|
-
lastStatus = cls.status;
|
|
101759
|
-
if (cls.class === "abort") {
|
|
101760
|
-
throw new AbortRunError({
|
|
101761
|
-
message: `Token \u5931\u6548\u6216\u81F4\u547D\u9519\u8BEF\uFF08${options.label}\uFF09\uFF1A${err.message}`,
|
|
101762
|
-
status: cls.status,
|
|
101763
|
-
cause: err
|
|
101764
|
-
});
|
|
101765
|
-
}
|
|
101766
|
-
if (cls.class === "permanent" || attempt >= policy.maxAttempts) {
|
|
101767
|
-
throw new HttpRetryError({
|
|
101768
|
-
message: cls.class === "permanent" ? `\u6C38\u4E45\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${err.message}` : `\u91CD\u8BD5 ${attempt} \u6B21\u4ECD\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${err.message}`,
|
|
101769
|
-
class: cls.class === "permanent" ? "permanent" : "retryable",
|
|
101770
|
-
status: cls.status,
|
|
101771
|
-
attempts: attempt,
|
|
101772
|
-
cause: err
|
|
101773
|
-
});
|
|
101774
|
-
}
|
|
101775
|
-
const backoffMs = computeBackoffMs(attempt, policy);
|
|
101776
|
-
options.onRetry?.({ attempt, backoffMs, status: cls.status, error: err });
|
|
101777
|
-
try {
|
|
101778
|
-
await sleep3(backoffMs, options.signal);
|
|
101779
|
-
} catch {
|
|
101780
|
-
throw new AbortRunError({
|
|
101781
|
-
message: `\u91CD\u8BD5\u7B49\u5F85\u88AB\u4E2D\u6B62\uFF1A${options.label}`,
|
|
101782
|
-
cause: err
|
|
101783
|
-
});
|
|
101784
|
-
}
|
|
101785
|
-
}
|
|
101786
|
-
}
|
|
101787
|
-
throw new HttpRetryError({
|
|
101788
|
-
message: `\u672A\u77E5\u91CD\u8BD5\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${lastErr?.message ?? "no error"}`,
|
|
101789
|
-
class: "retryable",
|
|
101790
|
-
status: lastStatus,
|
|
101791
|
-
attempts: policy.maxAttempts,
|
|
101792
|
-
cause: lastErr
|
|
101793
|
-
});
|
|
101794
|
-
}
|
|
101795
|
-
var HttpRetryError, AbortRunError, DEFAULT_RETRY_POLICY;
|
|
101796
|
-
var init_http_retry = __esm({
|
|
101797
|
-
"src/utils/http-retry.ts"() {
|
|
101798
|
-
"use strict";
|
|
101799
|
-
init_dist();
|
|
101800
|
-
HttpRetryError = class extends Error {
|
|
101801
|
-
class;
|
|
101802
|
-
status;
|
|
101803
|
-
attempts;
|
|
101804
|
-
cause;
|
|
101805
|
-
constructor(opts) {
|
|
101806
|
-
super(opts.message);
|
|
101807
|
-
this.name = "HttpRetryError";
|
|
101808
|
-
this.class = opts.class;
|
|
101809
|
-
this.status = opts.status;
|
|
101810
|
-
this.attempts = opts.attempts;
|
|
101811
|
-
this.cause = opts.cause;
|
|
101812
|
-
}
|
|
101813
|
-
};
|
|
101814
|
-
AbortRunError = class extends HttpRetryError {
|
|
101815
|
-
constructor(opts) {
|
|
101816
|
-
super({
|
|
101817
|
-
message: opts.message,
|
|
101818
|
-
class: "abort",
|
|
101819
|
-
status: opts.status,
|
|
101820
|
-
attempts: 1,
|
|
101821
|
-
cause: opts.cause
|
|
101822
|
-
});
|
|
101823
|
-
this.name = "AbortRunError";
|
|
101824
|
-
}
|
|
101825
|
-
};
|
|
101826
|
-
DEFAULT_RETRY_POLICY = {
|
|
101827
|
-
maxAttempts: 3,
|
|
101828
|
-
baseMs: 1e3,
|
|
101829
|
-
maxBackoffMs: 3e4,
|
|
101830
|
-
taskTimeoutMs: 6e4
|
|
101831
|
-
};
|
|
101886
|
+
init_daily_metrics_range();
|
|
101832
101887
|
}
|
|
101833
101888
|
});
|
|
101834
101889
|
|
|
@@ -102260,24 +102315,19 @@ async function runBatch(opts) {
|
|
|
102260
102315
|
await recordTaskTransition(opts.paths, inner, task, "running", { attempts: 0 });
|
|
102261
102316
|
const t0 = performance2.now();
|
|
102262
102317
|
try {
|
|
102263
|
-
const result = await
|
|
102264
|
-
|
|
102265
|
-
|
|
102266
|
-
|
|
102267
|
-
|
|
102268
|
-
|
|
102269
|
-
|
|
102270
|
-
|
|
102271
|
-
|
|
102272
|
-
|
|
102273
|
-
|
|
102274
|
-
|
|
102275
|
-
|
|
102276
|
-
label: `${task.section}/${task.accountId}`,
|
|
102277
|
-
policy: opts.retry,
|
|
102278
|
-
signal: aborterToken.signal
|
|
102279
|
-
}
|
|
102280
|
-
);
|
|
102318
|
+
const result = await fetcher({
|
|
102319
|
+
config: opts.config,
|
|
102320
|
+
accountId: task.accountId,
|
|
102321
|
+
section: task.section,
|
|
102322
|
+
start: opts.start,
|
|
102323
|
+
end: opts.end,
|
|
102324
|
+
limit: opts.keywordLimit,
|
|
102325
|
+
costGreater: opts.campaignGeoCostGreater,
|
|
102326
|
+
clickGreater: opts.campaignGeoClickGreater,
|
|
102327
|
+
conversionsGreater: opts.campaignGeoConversionsGreater,
|
|
102328
|
+
retry: opts.retry,
|
|
102329
|
+
signal: aborterToken.signal
|
|
102330
|
+
});
|
|
102281
102331
|
await writer({
|
|
102282
102332
|
snapshotDir: path25.join(opts.paths.resultsDir, sanitizeAccountSegment(task.accountId)),
|
|
102283
102333
|
section: task.section,
|
|
@@ -103046,6 +103096,51 @@ async function runWithConcurrency(tasks, concurrency) {
|
|
|
103046
103096
|
await Promise.all(workers);
|
|
103047
103097
|
return results;
|
|
103048
103098
|
}
|
|
103099
|
+
function sectionFetchErrorMessage(err) {
|
|
103100
|
+
return err instanceof Error ? err.message : String(err);
|
|
103101
|
+
}
|
|
103102
|
+
async function fetchAndWriteSection(def, opts, config, id, cliVersion) {
|
|
103103
|
+
const t0 = performance4.now();
|
|
103104
|
+
try {
|
|
103105
|
+
const { payload } = await fetchOneGoogleSection({
|
|
103106
|
+
config,
|
|
103107
|
+
accountId: id,
|
|
103108
|
+
section: def.name,
|
|
103109
|
+
start: opts.start,
|
|
103110
|
+
end: opts.end,
|
|
103111
|
+
verbose: opts.verbose,
|
|
103112
|
+
limit: opts.limit,
|
|
103113
|
+
noOrderByCost: opts.noOrderByCost,
|
|
103114
|
+
level: opts.level,
|
|
103115
|
+
audienceType: opts.audienceType,
|
|
103116
|
+
costGreater: opts.costGreater,
|
|
103117
|
+
clickGreater: opts.clickGreater,
|
|
103118
|
+
conversionsGreater: opts.conversionsGreater
|
|
103119
|
+
});
|
|
103120
|
+
const summary = await writeGoogleAnalysisSnapshot({
|
|
103121
|
+
snapshotDir: opts.jsonOut,
|
|
103122
|
+
section: def.name,
|
|
103123
|
+
accountId: id,
|
|
103124
|
+
dateRange: manifestDateRange(def, opts.start, opts.end),
|
|
103125
|
+
payload,
|
|
103126
|
+
cliVersion,
|
|
103127
|
+
endpointHint: endpointHintForSection(def)
|
|
103128
|
+
});
|
|
103129
|
+
return {
|
|
103130
|
+
section: def.name,
|
|
103131
|
+
ok: true,
|
|
103132
|
+
elapsedMs: performance4.now() - t0,
|
|
103133
|
+
file: summary.writtenFiles[0]
|
|
103134
|
+
};
|
|
103135
|
+
} catch (err) {
|
|
103136
|
+
return {
|
|
103137
|
+
section: def.name,
|
|
103138
|
+
ok: false,
|
|
103139
|
+
elapsedMs: performance4.now() - t0,
|
|
103140
|
+
error: sectionFetchErrorMessage(err)
|
|
103141
|
+
};
|
|
103142
|
+
}
|
|
103143
|
+
}
|
|
103049
103144
|
async function runAllSections(opts) {
|
|
103050
103145
|
const config = loadConfig(opts.token);
|
|
103051
103146
|
const id = opts.account.split(",")[0].trim();
|
|
@@ -103070,35 +103165,20 @@ async function runAllSections(opts) {
|
|
|
103070
103165
|
);
|
|
103071
103166
|
const cliVersion = getCurrentVersion2();
|
|
103072
103167
|
const overallT0 = performance4.now();
|
|
103073
|
-
const tasks = targets.map(
|
|
103074
|
-
|
|
103075
|
-
|
|
103076
|
-
|
|
103077
|
-
|
|
103078
|
-
|
|
103079
|
-
|
|
103080
|
-
|
|
103081
|
-
|
|
103082
|
-
|
|
103083
|
-
|
|
103084
|
-
|
|
103085
|
-
});
|
|
103086
|
-
return {
|
|
103087
|
-
section: def.name,
|
|
103088
|
-
ok: true,
|
|
103089
|
-
elapsedMs: performance4.now() - t0,
|
|
103090
|
-
file: summary2.writtenFiles[0]
|
|
103091
|
-
};
|
|
103092
|
-
} catch (err) {
|
|
103093
|
-
return {
|
|
103094
|
-
section: def.name,
|
|
103095
|
-
ok: false,
|
|
103096
|
-
elapsedMs: performance4.now() - t0,
|
|
103097
|
-
error: err instanceof Error ? err.message : String(err)
|
|
103098
|
-
};
|
|
103168
|
+
const tasks = targets.map(
|
|
103169
|
+
(def) => () => fetchAndWriteSection(def, opts, config, id, cliVersion)
|
|
103170
|
+
);
|
|
103171
|
+
let results = await runWithConcurrency(tasks, concurrency);
|
|
103172
|
+
const failedIndices = results.map((r, i) => r.ok ? -1 : i).filter((i) => i >= 0);
|
|
103173
|
+
if (failedIndices.length > 0) {
|
|
103174
|
+
console.error(`
|
|
103175
|
+
\u26A0\uFE0F ${failedIndices.length} \u4E2A\u7EF4\u5EA6\u9996\u8F6E\u5931\u8D25\uFF0C\u4E32\u884C\u4E8C\u8F6E\u91CD\u8BD5\u2026
|
|
103176
|
+
`);
|
|
103177
|
+
for (const i of failedIndices) {
|
|
103178
|
+
const retry = await fetchAndWriteSection(targets[i], opts, config, id, cliVersion);
|
|
103179
|
+
if (retry.ok) results = [...results.slice(0, i), retry, ...results.slice(i + 1)];
|
|
103099
103180
|
}
|
|
103100
|
-
}
|
|
103101
|
-
const results = await runWithConcurrency(tasks, concurrency);
|
|
103181
|
+
}
|
|
103102
103182
|
const succeeded = results.filter((r) => r.ok).length;
|
|
103103
103183
|
const failed = results.length - succeeded;
|
|
103104
103184
|
const absoluteSnapshotDir = path27.resolve(opts.jsonOut);
|
|
@@ -126276,43 +126356,51 @@ async function runGoogleAdsDiagnosisCollect(opts) {
|
|
|
126276
126356
|
}
|
|
126277
126357
|
const snapDir = path22.resolve(opts.jsonOut);
|
|
126278
126358
|
await fs17.mkdir(snapDir, { recursive: true });
|
|
126279
|
-
|
|
126280
|
-
|
|
126359
|
+
let degraded = false;
|
|
126360
|
+
console.error(`
|
|
126281
126361
|
\u{1F4E5} google-analysis \u62C9\u6570\uFF1A\u8D26\u6237 ${accountId}\uFF0C${opts.start} ~ ${opts.end}
|
|
126282
126362
|
`);
|
|
126283
|
-
|
|
126363
|
+
const main = invokeGoogleAnalysisCli({
|
|
126364
|
+
account: accountId,
|
|
126365
|
+
start: opts.start,
|
|
126366
|
+
end: opts.end,
|
|
126367
|
+
jsonOut: snapDir,
|
|
126368
|
+
token: opts.token,
|
|
126369
|
+
verbose: opts.verbose
|
|
126370
|
+
});
|
|
126371
|
+
const mainPartial = !main.ok && main.status === 2;
|
|
126372
|
+
if (!main.ok && !mainPartial) {
|
|
126373
|
+
console.error(main.stderr || main.stdout);
|
|
126374
|
+
console.error("\n\u274C google-analysis \u62C9\u6570\u5931\u8D25\u3002\n");
|
|
126375
|
+
process.exit(main.status || 1);
|
|
126376
|
+
}
|
|
126377
|
+
if (mainPartial) {
|
|
126378
|
+
console.error(main.stderr || main.stdout);
|
|
126379
|
+
console.error(
|
|
126380
|
+
"\n\u26A0\uFE0F google-analysis \u90E8\u5206\u7EF4\u5EA6\u62C9\u53D6\u5931\u8D25\uFF0C\u5C06\u7EE7\u7EED\u62C9\u53D6\u5BF9\u6BD4\u5468\u671F\u5E76\u751F\u6210 collect\u3002\n"
|
|
126381
|
+
);
|
|
126382
|
+
degraded = true;
|
|
126383
|
+
}
|
|
126384
|
+
if (opts.fetchPreviousPeriod !== false) {
|
|
126385
|
+
const prev = previousDateRange2(opts.start, opts.end);
|
|
126386
|
+
console.error(
|
|
126387
|
+
`
|
|
126388
|
+
\u{1F4E5} \u62C9\u53D6\u5BF9\u6BD4\u5468\u671F campaigns/geographic/keywords\uFF1A${prev.start} ~ ${prev.end}
|
|
126389
|
+
`
|
|
126390
|
+
);
|
|
126391
|
+
const prevRun = invokeGoogleAnalysisCli({
|
|
126284
126392
|
account: accountId,
|
|
126285
|
-
start:
|
|
126286
|
-
end:
|
|
126393
|
+
start: prev.start,
|
|
126394
|
+
end: prev.end,
|
|
126287
126395
|
jsonOut: snapDir,
|
|
126396
|
+
sections: "campaigns,geographic,keywords",
|
|
126288
126397
|
token: opts.token,
|
|
126289
126398
|
verbose: opts.verbose
|
|
126290
126399
|
});
|
|
126291
|
-
if (!
|
|
126292
|
-
console.error(
|
|
126293
|
-
console.error("\n\
|
|
126294
|
-
|
|
126295
|
-
}
|
|
126296
|
-
if (opts.fetchPreviousPeriod !== false) {
|
|
126297
|
-
const prev = previousDateRange2(opts.start, opts.end);
|
|
126298
|
-
console.error(
|
|
126299
|
-
`
|
|
126300
|
-
\u{1F4E5} \u62C9\u53D6\u5BF9\u6BD4\u5468\u671F campaigns/geographic/keywords\uFF1A${prev.start} ~ ${prev.end}
|
|
126301
|
-
`
|
|
126302
|
-
);
|
|
126303
|
-
const prevRun = invokeGoogleAnalysisCli({
|
|
126304
|
-
account: accountId,
|
|
126305
|
-
start: prev.start,
|
|
126306
|
-
end: prev.end,
|
|
126307
|
-
jsonOut: snapDir,
|
|
126308
|
-
sections: "campaigns,geographic,keywords",
|
|
126309
|
-
token: opts.token,
|
|
126310
|
-
verbose: opts.verbose
|
|
126311
|
-
});
|
|
126312
|
-
if (!prevRun.ok) {
|
|
126313
|
-
console.error(prevRun.stderr || prevRun.stdout);
|
|
126314
|
-
console.error("\n\u26A0\uFE0F \u4E0A\u4E00\u5468\u671F\u5BF9\u6BD4\u7EF4\u5EA6\u62C9\u53D6\u5931\u8D25\uFF0C\u73AF\u6BD4\u5217\u53EF\u80FD\u4E3A\u7A7A\u3002\n");
|
|
126315
|
-
}
|
|
126400
|
+
if (!prevRun.ok) {
|
|
126401
|
+
console.error(prevRun.stderr || prevRun.stdout);
|
|
126402
|
+
console.error("\n\u26A0\uFE0F \u4E0A\u4E00\u5468\u671F\u5BF9\u6BD4\u7EF4\u5EA6\u62C9\u53D6\u5931\u8D25\uFF0C\u73AF\u6BD4\u5217\u53EF\u80FD\u4E3A\u7A7A\u3002\n");
|
|
126403
|
+
degraded = true;
|
|
126316
126404
|
}
|
|
126317
126405
|
}
|
|
126318
126406
|
const websiteUrl = await resolveWebsiteUrl(opts, snapDir, accountId);
|
|
@@ -126364,6 +126452,7 @@ async function runGoogleAdsDiagnosisCollect(opts) {
|
|
|
126364
126452
|
` \u7EC8\u7A3F\uFF1Asiluzan-tso google-ads-diagnosis render --data ${path22.join(snapDir, GOOGLE_ADS_DIAGNOSIS_REPORT_BASENAME)}
|
|
126365
126453
|
`
|
|
126366
126454
|
);
|
|
126455
|
+
if (degraded) process.exit(2);
|
|
126367
126456
|
}
|
|
126368
126457
|
|
|
126369
126458
|
// src/commands/google-ads-diagnosis/render-report.ts
|
|
@@ -126707,7 +126796,7 @@ function registerGoogleAdsDiagnosisCommands(program2) {
|
|
|
126707
126796
|
).requiredOption("-a, --account <id>", "Google mediaCustomerId").requiredOption("--start <date>", "\u8BCA\u65AD\u5F00\u59CB\u65E5\u671F YYYY-MM-DD").requiredOption("--end <date>", "\u8BCA\u65AD\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("--company-name <name>", "\u8D26\u6237\u540D\u79F0\uFF08\u9ED8\u8BA4\u4ECE list-accounts \u5FEB\u7167\u8BFB\u53D6\uFF09").option("--website <url>", "\u5B98\u7F51 URL\uFF08\u9ED8\u8BA4\u4ECE ads \u5FEB\u7167 finalurl \u8BFB\u53D6\uFF09").option(
|
|
126708
126797
|
"--json-out <dir>",
|
|
126709
126798
|
"\u843D\u76D8\u76EE\u5F55\uFF08google-analysis \u5FEB\u7167 + google-ads-diagnosis-collect.json\uFF09"
|
|
126710
|
-
).option("--skip-
|
|
126799
|
+
).option("--skip-landing-page", "\u4E0D\u62C9\u53D6 WebsiteDiagnosisReports/performance").option("--no-fetch-previous-period", "\u4E0D\u62C9\u4E0A\u4E00\u5468\u671F campaigns/geographic/keywords \u5BF9\u6BD4\u6570\u636E").option("--token <token>", "JWT").option("--verbose", "\u6253\u5370\u8BF7\u6C42\u8BE6\u60C5").action(
|
|
126711
126800
|
async (opts) => {
|
|
126712
126801
|
if (!opts.jsonOut?.trim()) {
|
|
126713
126802
|
console.error("\n\u274C collect \u987B\u6307\u5B9A --json-out <\u76EE\u5F55>\u3002\n");
|
|
@@ -126720,7 +126809,6 @@ function registerGoogleAdsDiagnosisCommands(program2) {
|
|
|
126720
126809
|
companyName: opts.companyName,
|
|
126721
126810
|
website: opts.website,
|
|
126722
126811
|
jsonOut: opts.jsonOut,
|
|
126723
|
-
skipFetch: opts.skipFetch,
|
|
126724
126812
|
skipLandingPage: opts.skipLandingPage,
|
|
126725
126813
|
fetchPreviousPeriod: opts.fetchPreviousPeriod,
|
|
126726
126814
|
token: opts.token,
|
package/dist/skill/_meta.json
CHANGED
|
@@ -280,7 +280,7 @@ siluzan-tso account-history --start 2026-03-01 --end 2026-03-31 --json-out ./sna
|
|
|
280
280
|
| 状态 | 含义 | 下一步操作 |
|
|
281
281
|
| ---------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
282
282
|
| `Pending` | 审核中 | 等待,可反复运行此命令轮询;审核周期因媒体而异 |
|
|
283
|
-
| `Approved` | 审核通过 | 运行 `list-accounts -m <媒体>` 确认账户已出现;引导用户充值激活(`config show` 取 `webUrl`,按 `finance.md` 打开对应媒体充值页;例如 Google 为 `https://www.siluzan.com/v3/foreign_trade/tso/recharge/pay?mediaType=Google`;Kwai、Yandex 当前没有对应充值界面) |
|
|
283
|
+
| `Approved` | 审核通过 | 运行 `list-accounts -m <媒体>` 确认账户已出现;引导用户充值激活(`config show` 取 `webUrl`,按 `finance.md` 打开对应媒体充值页;例如 Google 为 `https://www-ci.siluzan.com/v3/foreign_trade/tso/recharge/pay?mediaType=Google`;Kwai、Yandex 当前没有对应充值界面) |
|
|
284
284
|
| `Rejected` | 被拒 | 查看 `--json-out` 落盘中的 `reason` 字段了解拒绝原因;修改资料后重新提交;若原因不明,引导用户联系丝路赞客服 |
|
|
285
285
|
|
|
286
286
|
---
|
|
@@ -909,7 +909,7 @@ siluzan-tso account email-deauth -c <mediaCustomerId> --invitation-id <id> --res
|
|
|
909
909
|
|
|
910
910
|
| 功能 | 媒体 | 网页路径 |
|
|
911
911
|
| --------------------------------------- | ------ | ------------------------------------------------- |
|
|
912
|
-
| **账户激活**(邀请他人激活 / 充值激活) | Google | `https://www.siluzan.com/v3/foreign_trade/tso/manageAccounts` |
|
|
912
|
+
| **账户激活**(邀请他人激活 / 充值激活) | Google | `https://www-ci.siluzan.com/v3/foreign_trade/tso/manageAccounts` |
|
|
913
913
|
|
|
914
914
|
**Agent 建议话术**:
|
|
915
915
|
|
|
@@ -918,5 +918,5 @@ siluzan-tso account email-deauth -c <mediaCustomerId> --invitation-id <id> --res
|
|
|
918
918
|
siluzan-tso config show # 查看 webUrl 字段
|
|
919
919
|
|
|
920
920
|
# 账户激活(Google)→ 引导至账户管理页
|
|
921
|
-
# https://www.siluzan.com/v3/foreign_trade/tso/manageAccounts
|
|
921
|
+
# https://www-ci.siluzan.com/v3/foreign_trade/tso/manageAccounts
|
|
922
922
|
```
|
|
@@ -117,7 +117,7 @@ siluzan-tso config show
|
|
|
117
117
|
|
|
118
118
|
### 充值页链接(按媒体 × 类型)
|
|
119
119
|
|
|
120
|
-
链接模式:`https://www.siluzan.com/v3/foreign_trade/tso/recharge/<page>?mediaType=<mediaType>`;丝路赞钱包:`https://www.siluzan.com/v3/foreign_trade/tso/recharge/siluzanWallet`(无媒体参数)。
|
|
120
|
+
链接模式:`https://www-ci.siluzan.com/v3/foreign_trade/tso/recharge/<page>?mediaType=<mediaType>`;丝路赞钱包:`https://www-ci.siluzan.com/v3/foreign_trade/tso/recharge/siluzanWallet`(无媒体参数)。
|
|
121
121
|
|
|
122
122
|
| 充值类型 | `<page>` | 支持媒体(`mediaType` 参数) |
|
|
123
123
|
| ------------------------- | --------------------- | ------------------------------------------- |
|
|
@@ -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
|
## 首次响应硬规范(必读)
|
|
@@ -60,7 +60,7 @@ siluzan-tso open-account google-wizard
|
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
62
|
siluzan-tso account-history -m Google
|
|
63
|
-
# 审核通过后:config show → https://www.siluzan.com/v3/foreign_trade/tso/recharge/pay?mediaType=Google
|
|
63
|
+
# 审核通过后:config show → https://www-ci.siluzan.com/v3/foreign_trade/tso/recharge/pay?mediaType=Google
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
---
|
|
@@ -192,9 +192,9 @@ siluzan-tso report push receive-emails -m Google [--json-out ./snap]
|
|
|
192
192
|
|
|
193
193
|
| 媒体 | 报告类型 | URL 模板 |
|
|
194
194
|
| ------ | ---------------- | ----------------------------------------------------------- |
|
|
195
|
-
| Google | 日报(Daily) | `https://www.siluzan.com/media-report/publish/{entityId}?culture=zh-CN` |
|
|
196
|
-
| Google | 小时报(Hourly) | `https://www.siluzan.com/media-report/hour/{entityId}?culture=zh-CN` |
|
|
197
|
-
| TikTok | 日报 | `https://www.siluzan.com/media-report/publish/{entityId}?culture=zh-CN` |
|
|
195
|
+
| Google | 日报(Daily) | `https://www-ci.siluzan.com/media-report/publish/{entityId}?culture=zh-CN` |
|
|
196
|
+
| Google | 小时报(Hourly) | `https://www-ci.siluzan.com/media-report/hour/{entityId}?culture=zh-CN` |
|
|
197
|
+
| TikTok | 日报 | `https://www-ci.siluzan.com/media-report/publish/{entityId}?culture=zh-CN` |
|
|
198
198
|
|
|
199
199
|
`entityId` 来自 `siluzan-tso report list --json-out ./snap` 中每条记录的 `entityId` 字段。
|
|
200
200
|
|
|
@@ -207,8 +207,8 @@ siluzan-tso report list -m Google --json-out ./snap
|
|
|
207
207
|
|
|
208
208
|
# 第二步:查看 webUrl
|
|
209
209
|
siluzan-tso config show
|
|
210
|
-
# webUrl: https://www.siluzan.com
|
|
210
|
+
# webUrl: https://www-ci.siluzan.com
|
|
211
211
|
|
|
212
212
|
# 第三步:拼接链接(Google 日报)
|
|
213
|
-
# https://www.siluzan.com/media-report/publish/rpt_abc123?culture=zh-CN
|
|
213
|
+
# https://www-ci.siluzan.com/media-report/publish/rpt_abc123?culture=zh-CN
|
|
214
214
|
```
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
1. 确认统计区间(规则见 conventions §五)。
|
|
30
30
|
2. `list-accounts -m Google -k <mediaCustomerId> --json-out ./snap-p1`(取 `currencyCode`)。
|
|
31
31
|
3. `stats -m Google -a <mediaCustomerId> --start <S> --end <D> --json-out ./snap-p1`。
|
|
32
|
-
4. `google-ads-diagnosis collect -a <mediaCustomerId> --start <S> --end <D> --json-out ./snap-p1
|
|
32
|
+
4. `google-ads-diagnosis collect -a <mediaCustomerId> --start <S> --end <D> --json-out ./snap-p1`(含对比周期 campaigns/geographic/keywords + `daily-metrics` 按日趋势;产出 `google-ads-diagnosis-collect.json`,**仅事实**)。部分维度失败(exit 2)时 collect 仍产出 JSON,**禁止**用 `--skip-fetch` 或脚本绕过 CLI 重映射快照。
|
|
33
33
|
5. **outline 门禁**(若手写脚本读盘):对**每个** section Read 其 `<section>-<accountId>_*.outline.txt` 确认字段树——字段名以 outline 为准。
|
|
34
34
|
6. **Agent 撰写**:读 `google-ads-diagnosis-collect.json`(`reportData` + `agentBrief`)+ `google-ads-diagnosis.md`,填写全部 `analysis` / `suggestions` / `diagnosisOverview` / `summary` 等,保存 `google-ads-diagnosis.json`。
|
|
35
35
|
7. **渲染终稿**:`google-ads-diagnosis render --data ./snap-p1/google-ads-diagnosis.json --out ./snap-p1/google-ads-diagnosis-report.html`(模板与 MarkAI `GoogleAdsDiagnosisReport.html` 一致)。
|
|
@@ -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 步:让用户报出手机号后,立刻发码(命令立即返回,不会等待输入)
|
|
@@ -129,9 +129,9 @@ siluzan-tso config show
|
|
|
129
129
|
|
|
130
130
|
```
|
|
131
131
|
构建环境 : production
|
|
132
|
-
apiBaseUrl : https://tso-api.siluzan.com
|
|
133
|
-
googleApiUrl : https://googleapi.mysiluzan.com
|
|
134
|
-
webUrl : https://www.siluzan.com
|
|
132
|
+
apiBaseUrl : https://tso-api-ci.siluzan.com
|
|
133
|
+
googleApiUrl : https://googleapi-ci.mysiluzan.com
|
|
134
|
+
webUrl : https://www-ci.siluzan.com
|
|
135
135
|
apiKey : abcd****1234
|
|
136
136
|
```
|
|
137
137
|
|
|
@@ -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
|
|
|
@@ -30,9 +30,22 @@ siluzan-tso google-ads-diagnosis render \
|
|
|
30
30
|
|
|
31
31
|
| 子命令 | 说明 |
|
|
32
32
|
| -------- | -------------------------------------------------------------------- |
|
|
33
|
-
| `collect` | 拉 google-analysis + 可选 Lighthouse;输出 `google-ads-diagnosis-collect.json` |
|
|
33
|
+
| `collect` | 拉 google-analysis + 对比周期 campaigns/geographic/keywords + 可选 Lighthouse;输出 `google-ads-diagnosis-collect.json` |
|
|
34
34
|
| `render` | 读取 Agent 产出的 `google-ads-diagnosis.json`,注入 `GoogleAdsDiagnosisReport.html` |
|
|
35
35
|
|
|
36
|
+
**collect 选项**:
|
|
37
|
+
|
|
38
|
+
| 选项 | 说明 |
|
|
39
|
+
| ---- | ---- |
|
|
40
|
+
| `--no-fetch-previous-period` | 不拉上一周期 campaigns/geographic/keywords 对比数据(环比列将为空) |
|
|
41
|
+
| `--skip-landing-page` | 不拉取 WebsiteDiagnosisReports/performance |
|
|
42
|
+
|
|
43
|
+
**collect 行为约束**:
|
|
44
|
+
|
|
45
|
+
- **禁止**使用已移除的 `--skip-fetch`;环比对比数据须由 CLI 向 API 拉取上一周期快照(`campaigns/geographic/keywords` 的 `*_YYYYMMDD-YYYYMMDD.json`),不可仅靠目录内当期快照映射。
|
|
46
|
+
- `google-analysis` **部分维度失败**(exit 2,如 `daily-metrics` HTTP 400)时,collect **仍会继续**拉取对比周期并生成 `google-ads-diagnosis-collect.json`;Agent 可基于 collect 继续撰写,缺失维度在报告中注明。
|
|
47
|
+
- 若环比列全为 `null`,检查落盘目录是否存在上一周期文件(如 `campaigns-<id>_20260503-20260602.json`);不存在则**重新执行 collect**(勿用手动拼装或 Python 脚本绕过 CLI)。
|
|
48
|
+
|
|
36
49
|
- **render 自动合并(默认开启)**:若同目录存在 `google-ads-diagnosis-collect.json`,且 Agent 改坏了 `campaigns` / `geographic` / `keywords` 的 `items`(缺 `title`/`currentCost` 或环比写成字符串),CLI **自动从 collect.reportData 恢复 items**,保留 Agent 的 `analysis`/`suggestions`。可用 `--no-merge-collect` 关闭;`--collect <file>` 指定 collect 路径。
|
|
37
50
|
- **render 硬校验**:合并后对比表仍不完整则 **exit 1**(避免 HTML 表体全空)。
|
|
38
51
|
|
|
@@ -30,9 +30,22 @@ siluzan-tso google-ads-diagnosis render \
|
|
|
30
30
|
|
|
31
31
|
| 子命令 | 说明 |
|
|
32
32
|
| -------- | -------------------------------------------------------------------- |
|
|
33
|
-
| `collect` | 拉 google-analysis + 可选 Lighthouse;输出 `google-ads-diagnosis-collect.json` |
|
|
33
|
+
| `collect` | 拉 google-analysis + 对比周期 campaigns/geographic/keywords + 可选 Lighthouse;输出 `google-ads-diagnosis-collect.json` |
|
|
34
34
|
| `render` | 读取 Agent 产出的 `google-ads-diagnosis.json`,注入 `GoogleAdsDiagnosisReport.html` |
|
|
35
35
|
|
|
36
|
+
**collect 选项**:
|
|
37
|
+
|
|
38
|
+
| 选项 | 说明 |
|
|
39
|
+
| ---- | ---- |
|
|
40
|
+
| `--no-fetch-previous-period` | 不拉上一周期 campaigns/geographic/keywords 对比数据(环比列将为空) |
|
|
41
|
+
| `--skip-landing-page` | 不拉取 WebsiteDiagnosisReports/performance |
|
|
42
|
+
|
|
43
|
+
**collect 行为约束**:
|
|
44
|
+
|
|
45
|
+
- **禁止**使用已移除的 `--skip-fetch`;环比对比数据须由 CLI 向 API 拉取上一周期快照(`campaigns/geographic/keywords` 的 `*_YYYYMMDD-YYYYMMDD.json`),不可仅靠目录内当期快照映射。
|
|
46
|
+
- `google-analysis` **部分维度失败**(exit 2,如 `daily-metrics` HTTP 400)时,collect **仍会继续**拉取对比周期并生成 `google-ads-diagnosis-collect.json`;Agent 可基于 collect 继续撰写,缺失维度在报告中注明。
|
|
47
|
+
- 若环比列全为 `null`,检查落盘目录是否存在上一周期文件(如 `campaigns-<id>_20260503-20260602.json`);不存在则**重新执行 collect**(勿用手动拼装或 Python 脚本绕过 CLI)。
|
|
48
|
+
|
|
36
49
|
- **render 自动合并(默认开启)**:若同目录存在 `google-ads-diagnosis-collect.json`,且 Agent 改坏了 `campaigns` / `geographic` / `keywords` 的 `items`(缺 `title`/`currentCost` 或环比写成字符串),CLI **自动从 collect.reportData 恢复 items**,保留 Agent 的 `analysis`/`suggestions`。可用 `--no-merge-collect` 关闭;`--collect <file>` 指定 collect 路径。
|
|
37
50
|
- **render 硬校验**:合并后对比表仍不完整则 **exit 1**(避免 HTML 表体全空)。
|
|
38
51
|
|
|
@@ -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.35-beta.1'
|
|
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.35-beta.1"
|
|
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
|