siluzan-tso-cli 1.1.34-beta.2 → 1.1.34-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/dist/index.js CHANGED
@@ -258,9 +258,9 @@ var require_semver = __commonJS({
258
258
  } else {
259
259
  this.prerelease = m[4].split(".").map((id) => {
260
260
  if (/^[0-9]+$/.test(id)) {
261
- const num5 = +id;
262
- if (num5 >= 0 && num5 < MAX_SAFE_INTEGER) {
263
- return num5;
261
+ const num6 = +id;
262
+ if (num6 >= 0 && num6 < MAX_SAFE_INTEGER) {
263
+ return num6;
264
264
  }
265
265
  }
266
266
  return id;
@@ -4361,7 +4361,15 @@ async function enrichOverviewRecordWithRealtimeBalance(media, accountId, config,
4361
4361
  const bal = balanceMap.get(accountId);
4362
4362
  if (bal == null) return record;
4363
4363
  const out = { ...record };
4364
- applyRealtimeBalanceIfNeeded(out, bal);
4364
+ const overviewBudget = typeof out.remainingAccountBudget === "number" ? out.remainingAccountBudget : void 0;
4365
+ const balanceTarget = out;
4366
+ applyRealtimeBalanceIfNeeded(balanceTarget, bal);
4367
+ if (typeof balanceTarget.remainingAccountBudget === "number") {
4368
+ out.balance = balanceTarget.remainingAccountBudget;
4369
+ if (overviewBudget === 0 && bal.remainingAccountBudget != null && bal.remainingAccountBudget > 0) {
4370
+ out._balanceSource = "GetMediaAccountInfo";
4371
+ }
4372
+ }
4365
4373
  if (bal.name && out["accountName"] == null && out["mediaCustomerName"] == null) {
4366
4374
  out["mediaCustomerName"] = bal.name;
4367
4375
  }
@@ -107794,6 +107802,133 @@ async function fetchBingJson(config, url, verbose) {
107794
107802
  // src/commands/report-bing-analysis/run.ts
107795
107803
  init_auth();
107796
107804
  init_balance();
107805
+
107806
+ // src/commands/report-bing-analysis/normalize-bing-overview.ts
107807
+ function num(v) {
107808
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
107809
+ }
107810
+ function roundMoney(v) {
107811
+ return Math.round(v * 100) / 100;
107812
+ }
107813
+ function countInclusiveCalendarDays(startDate, endDate) {
107814
+ const parts = (ymd) => ymd.trim().split("-").map((x) => parseInt(x, 10));
107815
+ const sp = parts(startDate);
107816
+ const ep = parts(endDate);
107817
+ if (sp.length !== 3 || ep.length !== 3 || sp.some((n) => !Number.isFinite(n)) || ep.some((n) => !Number.isFinite(n))) {
107818
+ return 0;
107819
+ }
107820
+ const s = new Date(sp[0], sp[1] - 1, sp[2]);
107821
+ const e = new Date(ep[0], ep[1] - 1, ep[2]);
107822
+ if (Number.isNaN(s.getTime()) || Number.isNaN(e.getTime()) || s > e) return 0;
107823
+ const msPerDay = 24 * 60 * 60 * 1e3;
107824
+ return Math.floor((e.getTime() - s.getTime()) / msPerDay) + 1;
107825
+ }
107826
+ function periodSpend(period) {
107827
+ if (!period || typeof period !== "object") return void 0;
107828
+ return num(period.spend);
107829
+ }
107830
+ function enrichBingOverviewDerivedMetrics(record, startDate, endDate) {
107831
+ const out = { ...record };
107832
+ const currentSpend = periodSpend(out.currentPeriod);
107833
+ let totalCost = num(out.totalCost);
107834
+ if (totalCost == null || totalCost <= 0) {
107835
+ if (currentSpend != null && currentSpend > 0) {
107836
+ out.totalCost = currentSpend;
107837
+ totalCost = currentSpend;
107838
+ }
107839
+ }
107840
+ let activeDays = num(out.activeDays);
107841
+ if (activeDays == null || activeDays <= 0) {
107842
+ const calendarDays = countInclusiveCalendarDays(startDate, endDate);
107843
+ if (calendarDays > 0) {
107844
+ out.activeDays = calendarDays;
107845
+ activeDays = calendarDays;
107846
+ }
107847
+ }
107848
+ const averageDailyCost = num(out.averageDailyCost);
107849
+ if ((averageDailyCost == null || averageDailyCost <= 0) && totalCost != null && totalCost > 0 && activeDays != null && activeDays > 0) {
107850
+ out.averageDailyCost = roundMoney(totalCost / activeDays);
107851
+ }
107852
+ return out;
107853
+ }
107854
+ function parseYmd(ymd) {
107855
+ const parts = ymd.trim().split("-").map((x) => parseInt(x, 10));
107856
+ if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n))) return null;
107857
+ const d = new Date(parts[0], parts[1] - 1, parts[2]);
107858
+ return Number.isNaN(d.getTime()) ? null : d;
107859
+ }
107860
+ function formatYmd(d) {
107861
+ const y = d.getFullYear();
107862
+ const m = String(d.getMonth() + 1).padStart(2, "0");
107863
+ const day = String(d.getDate()).padStart(2, "0");
107864
+ return `${y}-${m}-${day}`;
107865
+ }
107866
+ function calcBingPreviousPeriodRange(startDate, endDate) {
107867
+ const diffDays = countInclusiveCalendarDays(startDate, endDate);
107868
+ if (diffDays <= 0) return null;
107869
+ const start = parseYmd(startDate);
107870
+ if (!start) return null;
107871
+ const prevEnd = new Date(start);
107872
+ prevEnd.setDate(prevEnd.getDate() - 1);
107873
+ const prevStart = new Date(prevEnd);
107874
+ prevStart.setDate(prevStart.getDate() - (diffDays - 1));
107875
+ return { startDate: formatYmd(prevStart), endDate: formatYmd(prevEnd) };
107876
+ }
107877
+ function isBingPeriodBlockEmpty(period) {
107878
+ if (!period || typeof period !== "object") return true;
107879
+ const p = period;
107880
+ const spend = num(p.spend) ?? 0;
107881
+ const impressions = num(p.impressions) ?? 0;
107882
+ const clicks = num(p.clicks) ?? 0;
107883
+ return spend <= 0 && impressions <= 0 && clicks <= 0;
107884
+ }
107885
+ function syncOverviewBalanceFields(record) {
107886
+ const out = { ...record };
107887
+ const budget = num(out.remainingAccountBudget);
107888
+ if (budget != null && budget >= 0) {
107889
+ const balanceField = num(out.balance);
107890
+ if (balanceField == null || balanceField <= 0 && budget > 0) {
107891
+ out.balance = budget;
107892
+ }
107893
+ }
107894
+ return out;
107895
+ }
107896
+
107897
+ // src/commands/report-bing-analysis/run.ts
107898
+ async function enrichBingOverviewPreviousPeriod(config, accountId, record, startDate, endDate, verbose) {
107899
+ if (!isBingPeriodBlockEmpty(record.previousPeriod)) {
107900
+ return record;
107901
+ }
107902
+ const prevRange = calcBingPreviousPeriodRange(startDate, endDate);
107903
+ if (!prevRange) return record;
107904
+ if (bingRangeTouchesTodayOrYesterday(prevRange.startDate, prevRange.endDate)) {
107905
+ return record;
107906
+ }
107907
+ try {
107908
+ const params = new URLSearchParams({
107909
+ startDate: prevRange.startDate,
107910
+ endDate: prevRange.endDate
107911
+ });
107912
+ const url = reportingUrl2(config, accountId, "OverviewSectionData", params.toString());
107913
+ const prevRaw = await fetchBingJson(config, url, verbose);
107914
+ if (!prevRaw || typeof prevRaw !== "object" || Array.isArray(prevRaw)) {
107915
+ return record;
107916
+ }
107917
+ const prevRecord = prevRaw;
107918
+ const prevPeriod = prevRecord.currentPeriod;
107919
+ if (isBingPeriodBlockEmpty(prevPeriod)) {
107920
+ return record;
107921
+ }
107922
+ return {
107923
+ ...record,
107924
+ previousPeriod: prevPeriod,
107925
+ previousPeriodDateRange: prevRange,
107926
+ _previousPeriodSource: "OverviewSectionData-second-fetch"
107927
+ };
107928
+ } catch {
107929
+ return record;
107930
+ }
107931
+ }
107797
107932
  async function runReportBingOverview(opts) {
107798
107933
  const id = assertBingAccountId(opts.account);
107799
107934
  const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
@@ -107802,13 +107937,26 @@ async function runReportBingOverview(opts) {
107802
107937
  const url = reportingUrl2(config, id, "OverviewSectionData", params.toString());
107803
107938
  try {
107804
107939
  const raw = await fetchBingJson(config, url, !!opts.verbose);
107805
- const data = raw && typeof raw === "object" && !Array.isArray(raw) ? await enrichOverviewRecordWithRealtimeBalance(
107806
- "BingV2",
107807
- id,
107808
- config,
107809
- raw,
107810
- opts.verbose
107811
- ) : raw;
107940
+ let data = raw;
107941
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
107942
+ const withBalance = await enrichOverviewRecordWithRealtimeBalance(
107943
+ "BingV2",
107944
+ id,
107945
+ config,
107946
+ raw,
107947
+ opts.verbose
107948
+ );
107949
+ const withDerived = enrichBingOverviewDerivedMetrics(withBalance, startDate, endDate);
107950
+ const withPrevious = await enrichBingOverviewPreviousPeriod(
107951
+ config,
107952
+ id,
107953
+ withDerived,
107954
+ startDate,
107955
+ endDate,
107956
+ !!opts.verbose
107957
+ );
107958
+ data = syncOverviewBalanceFields(withPrevious);
107959
+ }
107812
107960
  if (opts.jsonOut) {
107813
107961
  await emitBingSnapshot(opts, "bing-overview", id, startDate, endDate, data);
107814
107962
  return;
@@ -120851,6 +120999,309 @@ async function runAccountMe(opts) {
120851
120999
  }
120852
121000
  }
120853
121001
 
121002
+ // src/commands/account-manage/google-access-permission.ts
121003
+ init_dist();
121004
+ init_auth();
121005
+ init_cli_json_snapshot();
121006
+
121007
+ // src/commands/account-manage/google-mcc.ts
121008
+ init_auth();
121009
+ init_cli_json_snapshot();
121010
+ function parseMccManagerIds(input) {
121011
+ const normalized = input.replace(/[,,;、]/g, ";");
121012
+ return normalized.split(";").map((part) => {
121013
+ const digits = part.replace(/\D/g, "");
121014
+ return digits ? +digits : NaN;
121015
+ }).filter((n) => Number.isFinite(n) && n > 0);
121016
+ }
121017
+ var MCC_AGENT_TYPES = ["ChengGongYi", "SiGeUS", "SiGeCN", "Dee"];
121018
+ function googleGatewayBase(config) {
121019
+ const raw = (config.googleApiUrl ?? "").replace(/\/$/, "");
121020
+ if (!raw) {
121021
+ console.error("\n\u274C Google \u7F51\u5173\u5730\u5740\u672A\u80FD\u63A8\u5BFC\uFF0C\u8BF7\u68C0\u67E5 tsoApiBaseUrl \u914D\u7F6E\u3002\n");
121022
+ process.exit(1);
121023
+ }
121024
+ const err = validateBaseUrl(raw);
121025
+ if (err) {
121026
+ console.error(`
121027
+ \u274C googleApiUrl \u4E0D\u5408\u6CD5\uFF1A${err}
121028
+ `);
121029
+ process.exit(1);
121030
+ }
121031
+ return raw;
121032
+ }
121033
+ function mccResponseHasSuccess(res, managerId) {
121034
+ if (!Array.isArray(res)) return false;
121035
+ return res.some((item) => String(item) === String(managerId));
121036
+ }
121037
+ async function runAccountMccBind(opts) {
121038
+ const config = loadConfig(opts.token);
121039
+ const base = googleGatewayBase(config);
121040
+ const managerIds = parseMccManagerIds(opts.mcc);
121041
+ if (managerIds.length === 0) {
121042
+ console.error("\n\u274C --mcc \u4E2D\u672A\u89E3\u6790\u51FA\u6709\u6548\u7684\u6570\u5B57\u578B MCC \u5BA2\u6237 ID\n");
121043
+ process.exit(1);
121044
+ }
121045
+ if (opts.customers.length === 0) {
121046
+ console.error("\n\u274C \u8BF7\u901A\u8FC7 --customers \u6307\u5B9A\u81F3\u5C11\u4E00\u4E2A Google \u5B50\u8D26\u6237 mediaCustomerId\n");
121047
+ process.exit(1);
121048
+ }
121049
+ const body = { agentTypes: [...MCC_AGENT_TYPES], managerIds };
121050
+ const results = [];
121051
+ for (const customerId of opts.customers) {
121052
+ const url = `${base}/command/media-account/${customerId}/CustomerLinks`;
121053
+ try {
121054
+ const response = await apiFetch2(
121055
+ url,
121056
+ config,
121057
+ { method: "POST", body: JSON.stringify(body) },
121058
+ opts.verbose
121059
+ );
121060
+ results.push({ customerId, response });
121061
+ } catch (err) {
121062
+ console.error(
121063
+ `
121064
+ \u274C \u8D26\u6237 ${customerId} MCC \u7ED1\u5B9A\u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
121065
+ `
121066
+ );
121067
+ process.exit(1);
121068
+ }
121069
+ }
121070
+ if (await emitCliJsonOrSnapshot(opts, {
121071
+ section: "account-mcc-bind",
121072
+ commandLabel: "account mcc-bind",
121073
+ payload: results,
121074
+ idSuffix: opts.mcc
121075
+ })) {
121076
+ return;
121077
+ }
121078
+ let hasFailure = false;
121079
+ for (const { customerId, response } of results) {
121080
+ const failed = managerIds.filter((mid) => !mccResponseHasSuccess(response, mid));
121081
+ if (failed.length > 0) {
121082
+ hasFailure = true;
121083
+ console.error(` \u274C \u8D26\u6237 ${customerId}\uFF1A\u4EE5\u4E0B MCC \u672A\u5728\u6210\u529F\u5217\u8868\u4E2D \u2192 ${failed.join("\uFF0C")}`);
121084
+ } else {
121085
+ console.log(
121086
+ ` \u2705 \u8D26\u6237 ${customerId}\uFF1A\u5DF2\u5411 ${managerIds.join("\u3001")} \u53D1\u8D77\u7ED1\u5B9A\uFF08\u63A5\u53E3\u8FD4\u56DE\u6210\u529F\uFF09`
121087
+ );
121088
+ }
121089
+ }
121090
+ console.log(
121091
+ hasFailure ? "\n\u26A0\uFE0F \u90E8\u5206 MCC \u7ED1\u5B9A\u53EF\u80FD\u672A\u751F\u6548\uFF0C\u8BF7\u7ED3\u5408\u7F51\u9875\u6216 --json-out \u6838\u5BF9\u3002\n" : "\n\u2705 MCC \u7ED1\u5B9A\u6D41\u7A0B\u5DF2\u5B8C\u6210\u3002\n"
121092
+ );
121093
+ }
121094
+ async function runAccountMccUnbind(opts) {
121095
+ const config = loadConfig(opts.token);
121096
+ const base = googleGatewayBase(config);
121097
+ const managerIds = parseMccManagerIds(opts.mcc);
121098
+ if (managerIds.length === 0) {
121099
+ console.error("\n\u274C --mcc \u4E2D\u672A\u89E3\u6790\u51FA\u6709\u6548\u7684\u6570\u5B57\u578B MCC \u5BA2\u6237 ID\n");
121100
+ process.exit(1);
121101
+ }
121102
+ if (opts.customers.length === 0) {
121103
+ console.error("\n\u274C \u8BF7\u901A\u8FC7 --customers \u6307\u5B9A\u81F3\u5C11\u4E00\u4E2A Google \u5B50\u8D26\u6237 mediaCustomerId\n");
121104
+ process.exit(1);
121105
+ }
121106
+ const results = [];
121107
+ for (const customerId of opts.customers) {
121108
+ const url = `${base}/command/media-account/${customerId}/CustomerUnlinks`;
121109
+ try {
121110
+ const response = await apiFetch2(
121111
+ url,
121112
+ config,
121113
+ { method: "POST", body: JSON.stringify(managerIds) },
121114
+ opts.verbose
121115
+ );
121116
+ results.push({ customerId, response });
121117
+ } catch (err) {
121118
+ console.error(
121119
+ `
121120
+ \u274C \u8D26\u6237 ${customerId} MCC \u89E3\u7ED1\u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
121121
+ `
121122
+ );
121123
+ process.exit(1);
121124
+ }
121125
+ }
121126
+ if (await emitCliJsonOrSnapshot(opts, {
121127
+ section: "account-mcc-unbind",
121128
+ commandLabel: "account mcc-unbind",
121129
+ payload: results,
121130
+ idSuffix: opts.mcc
121131
+ })) {
121132
+ return;
121133
+ }
121134
+ let hasFailure = false;
121135
+ for (const { customerId, response } of results) {
121136
+ const failed = managerIds.filter((mid) => !mccResponseHasSuccess(response, mid));
121137
+ if (failed.length > 0) {
121138
+ hasFailure = true;
121139
+ console.error(` \u274C \u8D26\u6237 ${customerId}\uFF1A\u4EE5\u4E0B MCC \u672A\u5728\u6210\u529F\u89E3\u7ED1\u5217\u8868\u4E2D \u2192 ${failed.join("\uFF0C")}`);
121140
+ } else {
121141
+ console.log(
121142
+ ` \u2705 \u8D26\u6237 ${customerId}\uFF1A\u5DF2\u5411 ${managerIds.join("\u3001")} \u53D1\u8D77\u89E3\u7ED1\uFF08\u63A5\u53E3\u8FD4\u56DE\u6210\u529F\uFF09`
121143
+ );
121144
+ }
121145
+ }
121146
+ console.log(
121147
+ hasFailure ? "\n\u26A0\uFE0F \u90E8\u5206 MCC \u89E3\u7ED1\u53EF\u80FD\u672A\u751F\u6548\uFF0C\u8BF7\u7ED3\u5408\u7F51\u9875\u6216 --json-out \u6838\u5BF9\u3002\n" : "\n\u2705 MCC \u89E3\u7ED1\u6D41\u7A0B\u5DF2\u5B8C\u6210\u3002\n"
121148
+ );
121149
+ }
121150
+
121151
+ // src/commands/account-manage/google-access-permission.ts
121152
+ function assertGoogleMediaCustomerId(raw) {
121153
+ const id = raw.trim();
121154
+ if (!id) {
121155
+ console.error("\n\u274C --account \u4E0D\u80FD\u4E3A\u7A7A\u3002\n");
121156
+ process.exit(1);
121157
+ }
121158
+ if (!/^\d+$/.test(id)) {
121159
+ console.error("\n\u274C --account \u987B\u4E3A Google mediaCustomerId\uFF08\u7EAF\u6570\u5B57\uFF0C\u4E0E list-accounts -m Google \u4E00\u81F4\uFF09\u3002\n");
121160
+ process.exit(1);
121161
+ }
121162
+ return id;
121163
+ }
121164
+ function parseCustomerAccessPermissionBody(text) {
121165
+ const trimmed = text.trim();
121166
+ if (!trimmed) return null;
121167
+ try {
121168
+ const parsed = JSON.parse(trimmed);
121169
+ if (typeof parsed === "boolean") return parsed;
121170
+ if (typeof parsed === "string") return parsed;
121171
+ } catch {
121172
+ }
121173
+ return trimmed;
121174
+ }
121175
+ function interpretCustomerAccessPermission(mediaCustomerId, httpStatus, rawBody) {
121176
+ const base = {
121177
+ mediaCustomerId,
121178
+ httpStatus,
121179
+ rawBody
121180
+ };
121181
+ if (httpStatus === 200) {
121182
+ if (rawBody === true) {
121183
+ return {
121184
+ ...base,
121185
+ status: "accessible",
121186
+ accessible: true,
121187
+ message: "\u5F53\u524D\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u53EF\u8BBF\u95EE\u8BE5 Google \u5E7F\u544A\u8D26\u6237\u3002"
121188
+ };
121189
+ }
121190
+ if (rawBody === false) {
121191
+ return {
121192
+ ...base,
121193
+ status: "reauth_required",
121194
+ accessible: false,
121195
+ message: "\u8D26\u6237\u5DF2\u7ED1\u5B9A\u4F46 Google OAuth \u4E0D\u53EF\u7528\uFF0C\u9700\u91CD\u65B0\u6388\u6743\u3002",
121196
+ hint: "list-accounts --json-out \u53D6 ma.entityId \u540E\u6267\u884C\uFF1Asiluzan-tso account reauth -m Google --id <entityId>"
121197
+ };
121198
+ }
121199
+ return {
121200
+ ...base,
121201
+ status: "unknown",
121202
+ accessible: false,
121203
+ message: `HTTP 200 \u4F46\u54CD\u5E94\u4F53\u975E\u5E38\u89C4\uFF08${String(rawBody)}\uFF09\uFF0C\u8BF7\u7528 --verbose \u67E5\u770B\u539F\u59CB\u54CD\u5E94\u3002`
121204
+ };
121205
+ }
121206
+ if (httpStatus === 403) {
121207
+ const text = typeof rawBody === "string" ? rawBody : rawBody == null ? "" : String(rawBody);
121208
+ if (text.includes("token\u4E0D\u80FD\u4E3A\u7A7A") || text.toLowerCase().includes("token") && text.includes("\u7A7A")) {
121209
+ return {
121210
+ ...base,
121211
+ status: "google_not_bound",
121212
+ accessible: false,
121213
+ message: "\u5F53\u524D\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u672A\u7ED1\u5B9A Google \u5A92\u4F53\u6388\u6743\u3002",
121214
+ hint: "\u6267\u884C siluzan-tso account auth -m Google \u5B8C\u6210 OAuth \u7ED1\u5B9A\u3002"
121215
+ };
121216
+ }
121217
+ return {
121218
+ ...base,
121219
+ status: "no_permission",
121220
+ accessible: false,
121221
+ message: "\u5F53\u524D\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u5BF9\u8BE5 Google \u5E7F\u544A\u8D26\u6237\u65E0\u8BBF\u95EE\u6743\u9650\uFF1B\u901A\u5E38\u8868\u793A\u8BE5\u8D26\u6237\u4E0D\u5728\u672C\u8D26\u53F7\u4E0B\uFF08\u6216\u672A\u5206\u4EAB\u7ED9\u4F60\uFF09\u3002",
121222
+ hint: "\u8BF7\u786E\u8BA4 mediaCustomerId \u662F\u5426\u6B63\u786E\uFF0C\u6216\u5207\u6362\u5230\u62E5\u6709\u8BE5\u8D26\u6237\u7684\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u767B\u5F55\uFF08send-login-code + login\uFF09\u3002"
121223
+ };
121224
+ }
121225
+ if (httpStatus === 401) {
121226
+ return {
121227
+ ...base,
121228
+ status: "siluzan_token_invalid",
121229
+ accessible: false,
121230
+ message: "\u4E1D\u8DEF\u8D5E\u767B\u5F55\u51ED\u636E\u65E0\u6548\u6216\u5DF2\u8FC7\u671F\u3002",
121231
+ hint: "\u6267\u884C send-login-code + login --phone --code \u91CD\u65B0\u767B\u5F55\uFF0C\u6216 siluzan-tso config show \u68C0\u67E5 Token\u3002"
121232
+ };
121233
+ }
121234
+ return {
121235
+ ...base,
121236
+ status: "unknown",
121237
+ accessible: false,
121238
+ message: `\u672A\u9884\u671F\u7684 HTTP ${httpStatus}\uFF1B\u8BF7\u7528 --verbose \u67E5\u770B\u539F\u59CB\u54CD\u5E94\u3002`
121239
+ };
121240
+ }
121241
+ function buildCustomerAccessPermissionUrl(googleApiUrl, mediaCustomerId) {
121242
+ const base = googleApiUrl.replace(/\/$/, "");
121243
+ return `${base}/query/media-account/${mediaCustomerId}/customer-access-permission`;
121244
+ }
121245
+ async function fetchGoogleCustomerAccessPermission(config, mediaCustomerId, verbose) {
121246
+ await ensureDataPermission(config);
121247
+ const url = buildCustomerAccessPermissionUrl(googleGatewayBase(config), mediaCustomerId);
121248
+ const res = await rawRequest(url, {
121249
+ method: "GET",
121250
+ headers: {
121251
+ "Content-Type": "application/json",
121252
+ "Accept-Language": "zh-CN",
121253
+ Accept: "application/json",
121254
+ ...buildSiluzanAuthHeaders(config),
121255
+ Datapermission: config.dataPermission ?? ""
121256
+ }
121257
+ });
121258
+ if (verbose && res.text) {
121259
+ console.error(` [verbose] HTTP ${res.status} body: ${res.text.slice(0, 300)}`);
121260
+ }
121261
+ const rawBody = parseCustomerAccessPermissionBody(res.text);
121262
+ return interpretCustomerAccessPermission(mediaCustomerId, res.status, rawBody);
121263
+ }
121264
+ async function runAccountCheckAccess(opts) {
121265
+ const mediaCustomerId = assertGoogleMediaCustomerId(opts.account);
121266
+ const config = loadConfig(opts.token);
121267
+ const payload = await fetchGoogleCustomerAccessPermission(
121268
+ config,
121269
+ mediaCustomerId,
121270
+ opts.verbose
121271
+ );
121272
+ if (await emitCliJsonOrSnapshot(opts, {
121273
+ section: "account-check-access",
121274
+ commandLabel: "account check-access",
121275
+ payload
121276
+ })) {
121277
+ if (!payload.accessible) {
121278
+ process.exit(1);
121279
+ }
121280
+ return;
121281
+ }
121282
+ const statusLabel = {
121283
+ accessible: "\u53EF\u8BBF\u95EE",
121284
+ reauth_required: "\u9700\u91CD\u65B0\u6388\u6743",
121285
+ no_permission: "\u65E0\u6743\u9650",
121286
+ google_not_bound: "\u672A\u7ED1\u5B9A Google",
121287
+ siluzan_token_invalid: "\u4E1D\u8DEF\u8D5E\u51ED\u636E\u5931\u6548",
121288
+ unknown: "\u672A\u77E5"
121289
+ };
121290
+ console.log(`
121291
+ Google \u8D26\u6237\u8BBF\u95EE\u6743\u9650\u6821\u9A8C ${mediaCustomerId}`);
121292
+ console.log(` \u72B6\u6001 : ${statusLabel[payload.status]} (${payload.status})`);
121293
+ console.log(` HTTP : ${payload.httpStatus}`);
121294
+ console.log(` \u53EF\u8BBF\u95EE : ${payload.accessible ? "\u662F" : "\u5426"}`);
121295
+ console.log(` \u8BF4\u660E : ${payload.message}`);
121296
+ if (payload.hint) {
121297
+ console.log(` \u5EFA\u8BAE : ${payload.hint}`);
121298
+ }
121299
+ console.log();
121300
+ if (!payload.accessible) {
121301
+ process.exit(1);
121302
+ }
121303
+ }
121304
+
120854
121305
  // src/commands/account-manage/share.ts
120855
121306
  init_auth();
120856
121307
  init_cli_json_snapshot();
@@ -121075,150 +121526,6 @@ async function runAccountReauth(opts) {
121075
121526
  });
121076
121527
  }
121077
121528
 
121078
- // src/commands/account-manage/google-mcc.ts
121079
- init_auth();
121080
- init_cli_json_snapshot();
121081
- function parseMccManagerIds(input) {
121082
- const normalized = input.replace(/[,,;、]/g, ";");
121083
- return normalized.split(";").map((part) => {
121084
- const digits = part.replace(/\D/g, "");
121085
- return digits ? +digits : NaN;
121086
- }).filter((n) => Number.isFinite(n) && n > 0);
121087
- }
121088
- var MCC_AGENT_TYPES = ["ChengGongYi", "SiGeUS", "SiGeCN", "Dee"];
121089
- function googleGatewayBase(config) {
121090
- const raw = (config.googleApiUrl ?? "").replace(/\/$/, "");
121091
- if (!raw) {
121092
- console.error("\n\u274C Google \u7F51\u5173\u5730\u5740\u672A\u80FD\u63A8\u5BFC\uFF0C\u8BF7\u68C0\u67E5 tsoApiBaseUrl \u914D\u7F6E\u3002\n");
121093
- process.exit(1);
121094
- }
121095
- const err = validateBaseUrl(raw);
121096
- if (err) {
121097
- console.error(`
121098
- \u274C googleApiUrl \u4E0D\u5408\u6CD5\uFF1A${err}
121099
- `);
121100
- process.exit(1);
121101
- }
121102
- return raw;
121103
- }
121104
- function mccResponseHasSuccess(res, managerId) {
121105
- if (!Array.isArray(res)) return false;
121106
- return res.some((item) => String(item) === String(managerId));
121107
- }
121108
- async function runAccountMccBind(opts) {
121109
- const config = loadConfig(opts.token);
121110
- const base = googleGatewayBase(config);
121111
- const managerIds = parseMccManagerIds(opts.mcc);
121112
- if (managerIds.length === 0) {
121113
- console.error("\n\u274C --mcc \u4E2D\u672A\u89E3\u6790\u51FA\u6709\u6548\u7684\u6570\u5B57\u578B MCC \u5BA2\u6237 ID\n");
121114
- process.exit(1);
121115
- }
121116
- if (opts.customers.length === 0) {
121117
- console.error("\n\u274C \u8BF7\u901A\u8FC7 --customers \u6307\u5B9A\u81F3\u5C11\u4E00\u4E2A Google \u5B50\u8D26\u6237 mediaCustomerId\n");
121118
- process.exit(1);
121119
- }
121120
- const body = { agentTypes: [...MCC_AGENT_TYPES], managerIds };
121121
- const results = [];
121122
- for (const customerId of opts.customers) {
121123
- const url = `${base}/command/media-account/${customerId}/CustomerLinks`;
121124
- try {
121125
- const response = await apiFetch2(
121126
- url,
121127
- config,
121128
- { method: "POST", body: JSON.stringify(body) },
121129
- opts.verbose
121130
- );
121131
- results.push({ customerId, response });
121132
- } catch (err) {
121133
- console.error(
121134
- `
121135
- \u274C \u8D26\u6237 ${customerId} MCC \u7ED1\u5B9A\u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
121136
- `
121137
- );
121138
- process.exit(1);
121139
- }
121140
- }
121141
- if (await emitCliJsonOrSnapshot(opts, {
121142
- section: "account-mcc-bind",
121143
- commandLabel: "account mcc-bind",
121144
- payload: results,
121145
- idSuffix: opts.mcc
121146
- })) {
121147
- return;
121148
- }
121149
- let hasFailure = false;
121150
- for (const { customerId, response } of results) {
121151
- const failed = managerIds.filter((mid) => !mccResponseHasSuccess(response, mid));
121152
- if (failed.length > 0) {
121153
- hasFailure = true;
121154
- console.error(` \u274C \u8D26\u6237 ${customerId}\uFF1A\u4EE5\u4E0B MCC \u672A\u5728\u6210\u529F\u5217\u8868\u4E2D \u2192 ${failed.join("\uFF0C")}`);
121155
- } else {
121156
- console.log(
121157
- ` \u2705 \u8D26\u6237 ${customerId}\uFF1A\u5DF2\u5411 ${managerIds.join("\u3001")} \u53D1\u8D77\u7ED1\u5B9A\uFF08\u63A5\u53E3\u8FD4\u56DE\u6210\u529F\uFF09`
121158
- );
121159
- }
121160
- }
121161
- console.log(
121162
- hasFailure ? "\n\u26A0\uFE0F \u90E8\u5206 MCC \u7ED1\u5B9A\u53EF\u80FD\u672A\u751F\u6548\uFF0C\u8BF7\u7ED3\u5408\u7F51\u9875\u6216 --json-out \u6838\u5BF9\u3002\n" : "\n\u2705 MCC \u7ED1\u5B9A\u6D41\u7A0B\u5DF2\u5B8C\u6210\u3002\n"
121163
- );
121164
- }
121165
- async function runAccountMccUnbind(opts) {
121166
- const config = loadConfig(opts.token);
121167
- const base = googleGatewayBase(config);
121168
- const managerIds = parseMccManagerIds(opts.mcc);
121169
- if (managerIds.length === 0) {
121170
- console.error("\n\u274C --mcc \u4E2D\u672A\u89E3\u6790\u51FA\u6709\u6548\u7684\u6570\u5B57\u578B MCC \u5BA2\u6237 ID\n");
121171
- process.exit(1);
121172
- }
121173
- if (opts.customers.length === 0) {
121174
- console.error("\n\u274C \u8BF7\u901A\u8FC7 --customers \u6307\u5B9A\u81F3\u5C11\u4E00\u4E2A Google \u5B50\u8D26\u6237 mediaCustomerId\n");
121175
- process.exit(1);
121176
- }
121177
- const results = [];
121178
- for (const customerId of opts.customers) {
121179
- const url = `${base}/command/media-account/${customerId}/CustomerUnlinks`;
121180
- try {
121181
- const response = await apiFetch2(
121182
- url,
121183
- config,
121184
- { method: "POST", body: JSON.stringify(managerIds) },
121185
- opts.verbose
121186
- );
121187
- results.push({ customerId, response });
121188
- } catch (err) {
121189
- console.error(
121190
- `
121191
- \u274C \u8D26\u6237 ${customerId} MCC \u89E3\u7ED1\u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
121192
- `
121193
- );
121194
- process.exit(1);
121195
- }
121196
- }
121197
- if (await emitCliJsonOrSnapshot(opts, {
121198
- section: "account-mcc-unbind",
121199
- commandLabel: "account mcc-unbind",
121200
- payload: results,
121201
- idSuffix: opts.mcc
121202
- })) {
121203
- return;
121204
- }
121205
- let hasFailure = false;
121206
- for (const { customerId, response } of results) {
121207
- const failed = managerIds.filter((mid) => !mccResponseHasSuccess(response, mid));
121208
- if (failed.length > 0) {
121209
- hasFailure = true;
121210
- console.error(` \u274C \u8D26\u6237 ${customerId}\uFF1A\u4EE5\u4E0B MCC \u672A\u5728\u6210\u529F\u89E3\u7ED1\u5217\u8868\u4E2D \u2192 ${failed.join("\uFF0C")}`);
121211
- } else {
121212
- console.log(
121213
- ` \u2705 \u8D26\u6237 ${customerId}\uFF1A\u5DF2\u5411 ${managerIds.join("\u3001")} \u53D1\u8D77\u89E3\u7ED1\uFF08\u63A5\u53E3\u8FD4\u56DE\u6210\u529F\uFF09`
121214
- );
121215
- }
121216
- }
121217
- console.log(
121218
- hasFailure ? "\n\u26A0\uFE0F \u90E8\u5206 MCC \u89E3\u7ED1\u53EF\u80FD\u672A\u751F\u6548\uFF0C\u8BF7\u7ED3\u5408\u7F51\u9875\u6216 --json-out \u6838\u5BF9\u3002\n" : "\n\u2705 MCC \u89E3\u7ED1\u6D41\u7A0B\u5DF2\u5B8C\u6210\u3002\n"
121219
- );
121220
- }
121221
-
121222
121529
  // src/commands/account-manage/tiktok-close.ts
121223
121530
  init_auth();
121224
121531
  init_cli_json_snapshot();
@@ -121988,6 +122295,21 @@ function register23(program2) {
121988
122295
  });
121989
122296
  }
121990
122297
  );
122298
+ accountCmd.command("check-access").description(
122299
+ "\u6821\u9A8C Google \u5E7F\u544A\u8D26\u6237\u8BBF\u95EE\u6743\u9650\uFF08GET googleapi \u2026/customer-access-permission\uFF09\uFF1B403 \u901A\u5E38\u8868\u793A\u8D26\u6237\u4E0D\u5728\u5F53\u524D\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u4E0B"
122300
+ ).requiredOption(
122301
+ "-a, --account <mediaCustomerId>",
122302
+ "Google mediaCustomerId\uFF08\u7EAF\u6570\u5B57\uFF0C\u4E0E list-accounts -m Google \u4E00\u81F4\uFF09"
122303
+ ).option("-t, --token <token>", "Auth Token").option("--json-out <path>", "\u843D\u76D8 JSON\uFF1Bstdout \u4E00\u884C\u6458\u8981\uFF1B\u4E0D\u53EF\u8BBF\u95EE\u65F6 exit 1").option("--verbose", "\u6253\u5370\u539F\u59CB HTTP \u54CD\u5E94\u7247\u6BB5", false).action(
122304
+ async (opts) => {
122305
+ await runAccountCheckAccess({
122306
+ account: opts.account,
122307
+ token: opts.token,
122308
+ jsonOut: opts.jsonOut,
122309
+ verbose: opts.verbose
122310
+ });
122311
+ }
122312
+ );
121991
122313
  accountCmd.command("delink").description(
121992
122314
  "\u89E3\u9664\u6388\u6743\uFF1A\u65AD\u5F00\u5E7F\u544A\u8D26\u6237\u4E0E\u5F53\u524D\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u7684\u5173\u8054\uFF08\u7F51\u9875 manageAccounts\u300C\u89E3\u9664\u6388\u6743\u300D\uFF1B--id \u6216 --ids\uFF09"
121993
122315
  ).option("--id <entityId>", "\u5355\u4E2A\u8D26\u6237 entityId\uFF08\u6765\u81EA list-accounts \u7684 ma.entityId \u5B57\u6BB5\uFF09").option("--ids <entityIds>", "\u6279\u91CF entityId\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u4E0E --id \u4E8C\u9009\u4E00\uFF0C\u53EF\u540C\u65F6\u4F20\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
@@ -125282,12 +125604,12 @@ function sectionRecord(env) {
125282
125604
  }
125283
125605
 
125284
125606
  // src/commands/google-ads-diagnosis/build-report-data.ts
125285
- function num(value) {
125607
+ function num2(value) {
125286
125608
  const n = Number(value);
125287
125609
  return Number.isFinite(n) ? n : 0;
125288
125610
  }
125289
125611
  function rowCost(row) {
125290
- return num(row.cost ?? row.spend);
125612
+ return num2(row.cost ?? row.spend);
125291
125613
  }
125292
125614
  function pct(value) {
125293
125615
  return `${(value * 100).toFixed(2)}%`;
@@ -125318,11 +125640,11 @@ function asPeriodRecord(value) {
125318
125640
  }
125319
125641
  function toKeyMetricRow(title, current, previous) {
125320
125642
  const currentCost = rowCost(current);
125321
- const currentClicks = num(current.clicks);
125322
- const currentCvr = num(current.conversionRate ?? current.cvr);
125643
+ const currentClicks = num2(current.clicks);
125644
+ const currentCvr = num2(current.conversionRate ?? current.cvr);
125323
125645
  const previousCost = previous ? rowCost(previous) : null;
125324
- const previousClicks = previous ? num(previous.clicks) : null;
125325
- const previousCvr = previous ? num(previous.conversionRate ?? previous.cvr) : null;
125646
+ const previousClicks = previous ? num2(previous.clicks) : null;
125647
+ const previousCvr = previous ? num2(previous.conversionRate ?? previous.cvr) : null;
125326
125648
  return {
125327
125649
  title,
125328
125650
  currentCost,
@@ -125352,13 +125674,13 @@ function buildComparisonItems(currentRows, previousRows, titleFn, keyFn) {
125352
125674
  }
125353
125675
  function toTargetingItem(row, labelKey, label) {
125354
125676
  const cost = rowCost(row);
125355
- const clicks = num(row.clicks);
125356
- const impressions = num(row.impressions);
125357
- const conversions = num(row.conversions);
125358
- const ctr = num(row.ctr);
125359
- const cvr = num(row.conversionRate ?? row.cvr);
125360
- const cpc = num(row.averageCpc ?? row.cpc);
125361
- const cpa = conversions > 0 ? cost / conversions : num(row.costPerConversion ?? row.cpa);
125677
+ const clicks = num2(row.clicks);
125678
+ const impressions = num2(row.impressions);
125679
+ const conversions = num2(row.conversions);
125680
+ const ctr = num2(row.ctr);
125681
+ const cvr = num2(row.conversionRate ?? row.cvr);
125682
+ const cpc = num2(row.averageCpc ?? row.cpc);
125683
+ const cpa = conversions > 0 ? cost / conversions : num2(row.costPerConversion ?? row.cpa);
125362
125684
  return {
125363
125685
  [labelKey]: label,
125364
125686
  cost,
@@ -125373,11 +125695,11 @@ function toTargetingItem(row, labelKey, label) {
125373
125695
  }
125374
125696
  function toDailyConversionItem(row) {
125375
125697
  const cost = rowCost(row);
125376
- const conversions = num(row.conversions);
125698
+ const conversions = num2(row.conversions);
125377
125699
  const rawDate = String(row.date ?? "");
125378
125700
  const date = rawDate.includes("T") ? rawDate.slice(0, 10) : rawDate.slice(0, 10);
125379
- const cpa = conversions > 0 ? cost / conversions : num(row.costPerConversion ?? row.cpa);
125380
- return { date, cost, conversions, cpa, clicks: num(row.clicks), impressions: num(row.impressions) };
125701
+ const cpa = conversions > 0 ? cost / conversions : num2(row.costPerConversion ?? row.cpa);
125702
+ return { date, cost, conversions, cpa, clicks: num2(row.clicks), impressions: num2(row.impressions) };
125381
125703
  }
125382
125704
  function loadCompanyNameFromSnap(snapDir) {
125383
125705
  const file = path21.join(snapDir, "list-accounts-google.json");
@@ -125414,8 +125736,8 @@ function inferBusinessProfile(campaignRows, adRows, website) {
125414
125736
  };
125415
125737
  }
125416
125738
  function primaryConversionLabel(rows) {
125417
- const sorted = [...rows].sort((a, b) => num(b.allConversions) - num(a.allConversions));
125418
- const top = sorted.find((r) => num(r.allConversions) > 0 && String(r.status) === "Enabled");
125739
+ const sorted = [...rows].sort((a, b) => num2(b.allConversions) - num2(a.allConversions));
125740
+ const top = sorted.find((r) => num2(r.allConversions) > 0 && String(r.status) === "Enabled");
125419
125741
  if (top) return String(top.name ?? "\u8BE2\u76D8/\u8F6C\u5316");
125420
125742
  const enabled = rows.find((r) => String(r.status) === "Enabled");
125421
125743
  return enabled ? String(enabled.name ?? "\u8BE2\u76D8/\u8F6C\u5316") : "\u8BE2\u76D8/\u8F6C\u5316";
@@ -125434,7 +125756,7 @@ function buildBiddingStrategyItems(campaignRows, endDate, gold) {
125434
125756
  const strategy = String(row.biddingStrategyTypeV2 ?? row.biddingStrategyType ?? "UNKNOWN");
125435
125757
  const status = String(row.campaignStatus ?? row.campaignStatusV2 ?? "");
125436
125758
  const isPaused = /paused/i.test(status);
125437
- const recommended = num(row.conversions) > 0 ? "MAXIMIZE_CONVERSIONS" : "TARGET_SPEND";
125759
+ const recommended = num2(row.conversions) > 0 ? "MAXIMIZE_CONVERSIONS" : "TARGET_SPEND";
125438
125760
  const isCorrect = policyOk && !isPaused && strategy === recommended;
125439
125761
  return {
125440
125762
  campaignName: String(row.campaignName ?? "\u672A\u547D\u540D\u7CFB\u5217"),
@@ -125447,16 +125769,16 @@ function buildBiddingStrategyItems(campaignRows, endDate, gold) {
125447
125769
  });
125448
125770
  }
125449
125771
  function buildStructureCounts(resourceRecord, campaignRows, keywordRows, searchRows) {
125450
- const campaignCount = Math.max(num(resourceRecord?.campaignCount), campaignRows.length);
125451
- const keywordCount = Math.max(num(resourceRecord?.keywordCount), keywordRows.length);
125772
+ const campaignCount = Math.max(num2(resourceRecord?.campaignCount), campaignRows.length);
125773
+ const keywordCount = Math.max(num2(resourceRecord?.keywordCount), keywordRows.length);
125452
125774
  return {
125453
125775
  campaignCount,
125454
- adGroupCount: num(resourceRecord?.adGroupCount),
125776
+ adGroupCount: num2(resourceRecord?.adGroupCount),
125455
125777
  keywordCount,
125456
- adCount: num(resourceRecord?.adCount),
125457
- extensionCount: num(resourceRecord?.extensionCount),
125458
- negativeKeywordCount: num(resourceRecord?.negativeKeywordCount),
125459
- countriesWithConversionsCount: num(resourceRecord?.countriesWithConversionsCount),
125778
+ adCount: num2(resourceRecord?.adCount),
125779
+ extensionCount: num2(resourceRecord?.extensionCount),
125780
+ negativeKeywordCount: num2(resourceRecord?.negativeKeywordCount),
125781
+ countriesWithConversionsCount: num2(resourceRecord?.countriesWithConversionsCount),
125460
125782
  searchTermCount: searchRows.length
125461
125783
  };
125462
125784
  }
@@ -125470,26 +125792,26 @@ function loadWebsiteFromAds(snapDir, accountId, start, end) {
125470
125792
  }
125471
125793
  function buildKeywordShareItems(rows) {
125472
125794
  const totalCost = rows.reduce((sum, row) => sum + rowCost(row), 0);
125473
- const totalConversions = rows.reduce((sum, row) => sum + num(row.conversions), 0);
125795
+ const totalConversions = rows.reduce((sum, row) => sum + num2(row.conversions), 0);
125474
125796
  return rows.map((row) => {
125475
125797
  const cost = rowCost(row);
125476
- const conversions = num(row.conversions);
125798
+ const conversions = num2(row.conversions);
125477
125799
  const keywordText = String(row.keywordText ?? row.keyword ?? "").trim();
125478
125800
  return {
125479
125801
  keyword: keywordText || "\u672A\u547D\u540D",
125480
- cpc: num(row.averageCpc ?? row.cpc),
125802
+ cpc: num2(row.averageCpc ?? row.cpc),
125481
125803
  matchType: String(
125482
125804
  row.keywordMatchTypeZh ?? row.keywordMatchType ?? row.matchType ?? ""
125483
125805
  ),
125484
- impressions: num(row.impressions),
125485
- clicks: num(row.clicks),
125806
+ impressions: num2(row.impressions),
125807
+ clicks: num2(row.clicks),
125486
125808
  cost,
125487
125809
  conversions,
125488
125810
  costShare: totalCost > 0 ? cost / totalCost * 100 : 0,
125489
125811
  conversionsShare: totalConversions > 0 ? conversions / totalConversions * 100 : 0,
125490
125812
  cpa: conversions > 0 ? cost / conversions : 0,
125491
- ctr: num(row.ctr),
125492
- cvr: num(row.conversionRate ?? row.cvr)
125813
+ ctr: num2(row.ctr),
125814
+ cvr: num2(row.conversionRate ?? row.cvr)
125493
125815
  };
125494
125816
  });
125495
125817
  }
@@ -125497,17 +125819,17 @@ function buildSearchTermItems(rows) {
125497
125819
  const sorted = [...rows].sort((a, b) => rowCost(b) - rowCost(a));
125498
125820
  return sorted.slice(0, 50).map((row) => {
125499
125821
  const cost = rowCost(row);
125500
- const conversions = num(row.conversions);
125501
- const cpa = conversions > 0 ? num(row.costPerConversion) || cost / conversions : void 0;
125822
+ const conversions = num2(row.conversions);
125823
+ const cpa = conversions > 0 ? num2(row.costPerConversion) || cost / conversions : void 0;
125502
125824
  return {
125503
125825
  searchTerm: String(row.searchTermText ?? row.searchTerm ?? "\u672A\u547D\u540D"),
125504
125826
  keyword: String(row.keywordText ?? row.keyword ?? ""),
125505
125827
  cost,
125506
125828
  conversions,
125507
- clicks: num(row.clicks),
125508
- impressions: num(row.impressions),
125509
- ctr: num(row.ctr),
125510
- cvr: num(row.conversionRate ?? row.cvr),
125829
+ clicks: num2(row.clicks),
125830
+ impressions: num2(row.impressions),
125831
+ ctr: num2(row.ctr),
125832
+ cvr: num2(row.conversionRate ?? row.cvr),
125511
125833
  cpa
125512
125834
  };
125513
125835
  });
@@ -125549,18 +125871,18 @@ function buildAdCreativeItems(rows) {
125549
125871
  }
125550
125872
  function buildBudgetCompetitiveness(overviewRecord, dimensionRecord) {
125551
125873
  const current = asPeriodRecord(overviewRecord?.currentPeriod);
125552
- const totalCost = num(overviewRecord?.totalCost ?? current?.spend);
125553
- const activeDays = num(overviewRecord?.activeDays);
125554
- const avgDaily = num(overviewRecord?.averageDailyCost ?? (activeDays > 0 ? totalCost / activeDays : 0));
125555
- const remainingBudget = num(overviewRecord?.remainingAccountBudget);
125874
+ const totalCost = num2(overviewRecord?.totalCost ?? current?.spend);
125875
+ const activeDays = num2(overviewRecord?.activeDays);
125876
+ const avgDaily = num2(overviewRecord?.averageDailyCost ?? (activeDays > 0 ? totalCost / activeDays : 0));
125877
+ const remainingBudget = num2(overviewRecord?.remainingAccountBudget);
125556
125878
  const utilization = remainingBudget > 0 && avgDaily > 0 ? Math.min(100, avgDaily / (avgDaily + remainingBudget / Math.max(activeDays, 1)) * 100) : avgDaily > 0 ? 85 : 0;
125557
- const sis = num(
125879
+ const sis = num2(
125558
125880
  dimensionRecord?.searchImpressionShare ?? current?.searchImpressionShare
125559
125881
  );
125560
- const budgetLost = num(
125882
+ const budgetLost = num2(
125561
125883
  dimensionRecord?.searchBudgetLostImpressionShare ?? current?.searchBudgetLostImpressionShare
125562
125884
  );
125563
- const rankLost = num(
125885
+ const rankLost = num2(
125564
125886
  dimensionRecord?.searchRankLostImpressionShare ?? current?.searchRankLostImpressionShare
125565
125887
  );
125566
125888
  return [
@@ -125646,17 +125968,17 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
125646
125968
  };
125647
125969
  data.reportDate = end;
125648
125970
  const cost = rowCost(currentPeriod ?? overviewRecord ?? {});
125649
- const impressions = num(currentPeriod?.impressions);
125650
- const clicks = num(currentPeriod?.clicks);
125651
- const conversions = num(currentPeriod?.conversions);
125652
- const ctr = num(currentPeriod?.ctr);
125653
- const cvr = num(currentPeriod?.conversionRate ?? currentPeriod?.cvr);
125654
- const cpa = conversions > 0 ? cost / conversions : num(currentPeriod?.costPerConversion);
125971
+ const impressions = num2(currentPeriod?.impressions);
125972
+ const clicks = num2(currentPeriod?.clicks);
125973
+ const conversions = num2(currentPeriod?.conversions);
125974
+ const ctr = num2(currentPeriod?.ctr);
125975
+ const cvr = num2(currentPeriod?.conversionRate ?? currentPeriod?.cvr);
125976
+ const cpa = conversions > 0 ? cost / conversions : num2(currentPeriod?.costPerConversion);
125655
125977
  const prevCost = previousPeriod ? rowCost(previousPeriod) : 0;
125656
- const prevConversions = previousPeriod ? num(previousPeriod.conversions) : 0;
125657
- const prevCtr = previousPeriod ? num(previousPeriod.ctr) : 0;
125658
- const prevCvr = previousPeriod ? num(previousPeriod.conversionRate ?? previousPeriod.cvr) : 0;
125659
- const prevCpa = prevConversions > 0 ? rowCost(previousPeriod) / prevConversions : num(previousPeriod?.costPerConversion);
125978
+ const prevConversions = previousPeriod ? num2(previousPeriod.conversions) : 0;
125979
+ const prevCtr = previousPeriod ? num2(previousPeriod.ctr) : 0;
125980
+ const prevCvr = previousPeriod ? num2(previousPeriod.conversionRate ?? previousPeriod.cvr) : 0;
125981
+ const prevCpa = prevConversions > 0 ? rowCost(previousPeriod) / prevConversions : num2(previousPeriod?.costPerConversion);
125660
125982
  data.metrics = {
125661
125983
  cost,
125662
125984
  impressions,
@@ -125692,8 +126014,8 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
125692
126014
  items: conversionRows.map((row) => ({
125693
126015
  name: String(row.name ?? row.eventName ?? "\u672A\u77E5\u4E8B\u4EF6"),
125694
126016
  status: String(row.status ?? ""),
125695
- allConversions: num(row.allConversions),
125696
- allConversionsValue: num(row.allConversionsValue)
126017
+ allConversions: num2(row.allConversions),
126018
+ allConversionsValue: num2(row.allConversionsValue)
125697
126019
  }))
125698
126020
  };
125699
126021
  const campaignPrevious = sectionItems(
@@ -125777,16 +126099,16 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
125777
126099
  const lighthouse = opts.landingPage ?? {};
125778
126100
  data.landingPageAnalysis = {
125779
126101
  desktop: {
125780
- score: num(lighthouse.desktop?.score),
125781
- firstContentfulPaint: num(lighthouse.desktop?.firstContentfulPaint),
125782
- firstMeaningfulPaint: num(lighthouse.desktop?.firstMeaningfulPaint),
125783
- speedIndex: num(lighthouse.desktop?.speedIndex)
126102
+ score: num2(lighthouse.desktop?.score),
126103
+ firstContentfulPaint: num2(lighthouse.desktop?.firstContentfulPaint),
126104
+ firstMeaningfulPaint: num2(lighthouse.desktop?.firstMeaningfulPaint),
126105
+ speedIndex: num2(lighthouse.desktop?.speedIndex)
125784
126106
  },
125785
126107
  mobile: {
125786
- score: num(lighthouse.mobile?.score),
125787
- firstContentfulPaint: num(lighthouse.mobile?.firstContentfulPaint),
125788
- firstMeaningfulPaint: num(lighthouse.mobile?.firstMeaningfulPaint),
125789
- speedIndex: num(lighthouse.mobile?.speedIndex)
126108
+ score: num2(lighthouse.mobile?.score),
126109
+ firstContentfulPaint: num2(lighthouse.mobile?.firstContentfulPaint),
126110
+ firstMeaningfulPaint: num2(lighthouse.mobile?.firstMeaningfulPaint),
126111
+ speedIndex: num2(lighthouse.mobile?.speedIndex)
125790
126112
  }
125791
126113
  };
125792
126114
  const keywordShareItems = buildKeywordShareItems(kwCurrent);
@@ -125820,7 +126142,7 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
125820
126142
  data.newFeatures = {
125821
126143
  items: nfItems.map((item) => {
125822
126144
  const strategy = String(item.strategy ?? "");
125823
- const enabled = strategy === "PerformanceMax" ? num(campaignTypes?.PerformanceMax) > 0 : strategy === "KwMatch" ? num(campaignTypes?.Search) > 0 : strategy === "DemandGen" ? num(campaignTypes?.DemandGen) > 0 : strategy === "Youtube" ? num(campaignTypes?.Video) > 0 || num(campaignTypes?.Youtube) > 0 : false;
126145
+ const enabled = strategy === "PerformanceMax" ? num2(campaignTypes?.PerformanceMax) > 0 : strategy === "KwMatch" ? num2(campaignTypes?.Search) > 0 : strategy === "DemandGen" ? num2(campaignTypes?.DemandGen) > 0 : strategy === "Youtube" ? num2(campaignTypes?.Video) > 0 || num2(campaignTypes?.Youtube) > 0 : false;
125824
126146
  return {
125825
126147
  ...item,
125826
126148
  accountStatus: enabled,
@@ -125830,7 +126152,7 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
125830
126152
  ...emptyModuleNarrative()
125831
126153
  };
125832
126154
  data.summary = emptySummary();
125833
- const zeroConvKw = kwCurrent.filter((r) => rowCost(r) > 50 && num(r.conversions) === 0);
126155
+ const zeroConvKw = kwCurrent.filter((r) => rowCost(r) > 50 && num2(r.conversions) === 0);
125834
126156
  const agentBrief = buildGoogleAdsDiagnosisAgentBrief({
125835
126157
  accountId,
125836
126158
  period: formatPeriod(start, end),
@@ -125854,9 +126176,9 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
125854
126176
  prevCpa
125855
126177
  },
125856
126178
  dimension: {
125857
- searchImpressionShare: num(dimensionRecord?.searchImpressionShare),
125858
- searchBudgetLostImpressionShare: num(dimensionRecord?.searchBudgetLostImpressionShare),
125859
- searchRankLostImpressionShare: num(dimensionRecord?.searchRankLostImpressionShare)
126179
+ searchImpressionShare: num2(dimensionRecord?.searchImpressionShare),
126180
+ searchBudgetLostImpressionShare: num2(dimensionRecord?.searchBudgetLostImpressionShare),
126181
+ searchRankLostImpressionShare: num2(dimensionRecord?.searchRankLostImpressionShare)
125860
126182
  },
125861
126183
  structure: structureCounts,
125862
126184
  goldAccount: goldRecord,
@@ -126502,7 +126824,7 @@ var PHASE_LABEL = {
126502
126824
  "find-winner": "\u627E\u8D62\u5BB6",
126503
126825
  scale: "\u653E\u91CF"
126504
126826
  };
126505
- function num2(value) {
126827
+ function num3(value) {
126506
126828
  const n = Number(value);
126507
126829
  return Number.isFinite(n) ? n : void 0;
126508
126830
  }
@@ -126513,35 +126835,35 @@ function money(value, currency = "USD") {
126513
126835
  }
126514
126836
  function adSetsWithSpend(tables) {
126515
126837
  const rows = Array.isArray(tables?.adSets) ? tables.adSets : [];
126516
- return rows.filter((r) => (num2(r.spend) ?? 0) > 0);
126838
+ return rows.filter((r) => (num3(r.spend) ?? 0) > 0);
126517
126839
  }
126518
126840
  function topAdSetConcentration(adSets) {
126519
126841
  if (!adSets.length) return null;
126520
- const totalSpend = adSets.reduce((s, r) => s + (num2(r.spend) ?? 0), 0);
126521
- const totalResults = adSets.reduce((s, r) => s + (num2(r.results) ?? 0), 0);
126842
+ const totalSpend = adSets.reduce((s, r) => s + (num3(r.spend) ?? 0), 0);
126843
+ const totalResults = adSets.reduce((s, r) => s + (num3(r.results) ?? 0), 0);
126522
126844
  let best = adSets[0];
126523
- let bestSpend = num2(best.spend) ?? 0;
126845
+ let bestSpend = num3(best.spend) ?? 0;
126524
126846
  for (const r of adSets) {
126525
- const sp = num2(r.spend) ?? 0;
126847
+ const sp = num3(r.spend) ?? 0;
126526
126848
  if (sp > bestSpend) {
126527
126849
  best = r;
126528
126850
  bestSpend = sp;
126529
126851
  }
126530
126852
  }
126531
- const results = num2(best.results) ?? 0;
126853
+ const results = num3(best.results) ?? 0;
126532
126854
  return {
126533
126855
  name: String(best.adGroupName ?? best.name ?? "\u2014"),
126534
126856
  spendShare: totalSpend > 0 ? bestSpend / totalSpend : 0,
126535
126857
  resultsShare: totalResults > 0 ? results / totalResults : 0,
126536
- cpl: num2(best.costPerResult)
126858
+ cpl: num3(best.costPerResult)
126537
126859
  };
126538
126860
  }
126539
126861
  function winnerSpendShare(adSets, avgCpl, totalSpend) {
126540
126862
  if (totalSpend <= 0 || avgCpl <= 0) return 0;
126541
126863
  const winnerSpend = adSets.filter((r) => {
126542
- const cpl = num2(r.costPerResult);
126864
+ const cpl = num3(r.costPerResult);
126543
126865
  return cpl != null && cpl <= avgCpl * 0.9;
126544
- }).reduce((s, r) => s + (num2(r.spend) ?? 0), 0);
126866
+ }).reduce((s, r) => s + (num3(r.spend) ?? 0), 0);
126545
126867
  return winnerSpend / totalSpend;
126546
126868
  }
126547
126869
  function countActiveDimensions(charts, tables) {
@@ -126558,7 +126880,7 @@ function asArray(value) {
126558
126880
  }
126559
126881
  function inferLifecyclePhase(input) {
126560
126882
  const { avgCpl, currency = "USD" } = input;
126561
- const spend = num2(input.spend) ?? 0;
126883
+ const spend = num3(input.spend) ?? 0;
126562
126884
  const adSets = adSetsWithSpend(input.tables);
126563
126885
  const activeDims = countActiveDimensions(input.charts, input.tables);
126564
126886
  const winShare = winnerSpendShare(adSets, avgCpl, spend);
@@ -126617,7 +126939,7 @@ function ensureLifecycleFromSnapshot(healthDiagnosis, merged) {
126617
126939
  }
126618
126940
 
126619
126941
  // src/commands/facebook-analysis/scorecard-autofill.ts
126620
- function num3(value) {
126942
+ function num4(value) {
126621
126943
  const n = Number(value);
126622
126944
  return Number.isFinite(n) ? n : void 0;
126623
126945
  }
@@ -126657,8 +126979,8 @@ function buildScorecardFromMergedData(input) {
126657
126979
  if (pLabels.length) {
126658
126980
  const indexed = pLabels.map((label, i) => ({
126659
126981
  label: String(label),
126660
- cpl: num3(pCpl[i]),
126661
- spend: num3(pSpend[i]) ?? 0
126982
+ cpl: num4(pCpl[i]),
126983
+ spend: num4(pSpend[i]) ?? 0
126662
126984
  })).filter((x) => x.spend > 0 || x.cpl != null && x.cpl > 0);
126663
126985
  const sorted = [...indexed].sort((a, b) => (a.cpl ?? Infinity) - (b.cpl ?? Infinity));
126664
126986
  if (sorted[0]) {
@@ -126683,7 +127005,7 @@ function buildScorecardFromMergedData(input) {
126683
127005
  const cLabels = Array.isArray(country?.labels) ? country.labels : [];
126684
127006
  const cCpl = Array.isArray(country?.cpl) ? country.cpl : [];
126685
127007
  if (cLabels.length) {
126686
- const indexed = cLabels.map((label, i) => ({ label: String(label), cpl: num3(cCpl[i]) })).filter((x) => x.cpl != null && x.cpl > 0);
127008
+ const indexed = cLabels.map((label, i) => ({ label: String(label), cpl: num4(cCpl[i]) })).filter((x) => x.cpl != null && x.cpl > 0);
126687
127009
  const sorted = [...indexed].sort((a, b) => (a.cpl ?? 0) - (b.cpl ?? 0));
126688
127010
  if (sorted[0]) {
126689
127011
  const s = cplSignal(sorted[0].cpl, avgCpl);
@@ -126704,24 +127026,24 @@ function buildScorecardFromMergedData(input) {
126704
127026
  }
126705
127027
  }
126706
127028
  const adSets = Array.isArray(tables.adSets) ? tables.adSets : [];
126707
- const adWithSpend = adSets.filter((g) => (num3(g.spend) ?? 0) > 0);
127029
+ const adWithSpend = adSets.filter((g) => (num4(g.spend) ?? 0) > 0);
126708
127030
  if (adWithSpend.length) {
126709
127031
  const sorted = [...adWithSpend].sort(
126710
- (a, b) => (num3(a.costPerResult) ?? Infinity) - (num3(b.costPerResult) ?? Infinity)
127032
+ (a, b) => (num4(a.costPerResult) ?? Infinity) - (num4(b.costPerResult) ?? Infinity)
126711
127033
  );
126712
127034
  const best = sorted[0];
126713
- const sBest = cplSignal(num3(best.costPerResult), avgCpl);
127035
+ const sBest = cplSignal(num4(best.costPerResult), avgCpl);
126714
127036
  pushRow(rows, seen, {
126715
127037
  item: String(best.name ?? "\u5E7F\u544A\u7EC4"),
126716
- data: `CPL ${money2(num3(best.costPerResult), currency)}`,
127038
+ data: `CPL ${money2(num4(best.costPerResult), currency)}`,
126717
127039
  ...sBest
126718
127040
  });
126719
127041
  if (sorted.length > 1) {
126720
127042
  const worst = sorted[sorted.length - 1];
126721
- const sWorst = cplSignal(num3(worst.costPerResult), avgCpl);
127043
+ const sWorst = cplSignal(num4(worst.costPerResult), avgCpl);
126722
127044
  pushRow(rows, seen, {
126723
127045
  item: String(worst.name ?? "\u5E7F\u544A\u7EC4"),
126724
- data: `CPL ${money2(num3(worst.costPerResult), currency)}`,
127046
+ data: `CPL ${money2(num4(worst.costPerResult), currency)}`,
126725
127047
  ...sWorst
126726
127048
  });
126727
127049
  }
@@ -126729,18 +127051,18 @@ function buildScorecardFromMergedData(input) {
126729
127051
  const topAud = Array.isArray(tables.audienceTop) ? tables.audienceTop : [];
126730
127052
  const bottomAud = Array.isArray(tables.audienceBottom) ? tables.audienceBottom : [];
126731
127053
  if (topAud[0]) {
126732
- const s = cplSignal(num3(topAud[0].costPerResult), avgCpl);
127054
+ const s = cplSignal(num4(topAud[0].costPerResult), avgCpl);
126733
127055
  pushRow(rows, seen, {
126734
127056
  item: `\u53D7\u4F17\xB7${String(topAud[0].label ?? "Top")}`,
126735
- data: `CPL ${money2(num3(topAud[0].costPerResult), currency)}`,
127057
+ data: `CPL ${money2(num4(topAud[0].costPerResult), currency)}`,
126736
127058
  ...s
126737
127059
  });
126738
127060
  }
126739
127061
  if (bottomAud[0]) {
126740
- const s = cplSignal(num3(bottomAud[0].costPerResult), avgCpl);
127062
+ const s = cplSignal(num4(bottomAud[0].costPerResult), avgCpl);
126741
127063
  pushRow(rows, seen, {
126742
127064
  item: `\u53D7\u4F17\xB7${String(bottomAud[0].label ?? "Bottom")}`,
126743
- data: `CPL ${money2(num3(bottomAud[0].costPerResult), currency)}`,
127065
+ data: `CPL ${money2(num4(bottomAud[0].costPerResult), currency)}`,
126744
127066
  ...s
126745
127067
  });
126746
127068
  }
@@ -126784,7 +127106,7 @@ function asRecord8(value) {
126784
127106
  }
126785
127107
  return null;
126786
127108
  }
126787
- function num4(value) {
127109
+ function num5(value) {
126788
127110
  const n = Number(value);
126789
127111
  return Number.isFinite(n) ? n : void 0;
126790
127112
  }
@@ -126838,8 +127160,8 @@ function aggregatePlatform(networks) {
126838
127160
  const platform = String(row.publisherPlatform ?? row.network ?? "unknown");
126839
127161
  const prev = map.get(platform) ?? { spend: 0, results: 0 };
126840
127162
  map.set(platform, {
126841
- spend: prev.spend + (num4(row.spend) ?? 0),
126842
- results: prev.results + (num4(row.results) ?? 0)
127163
+ spend: prev.spend + (num5(row.spend) ?? 0),
127164
+ results: prev.results + (num5(row.results) ?? 0)
126843
127165
  });
126844
127166
  }
126845
127167
  const labels = [];
@@ -126855,10 +127177,10 @@ function aggregatePlatform(networks) {
126855
127177
  function buildAudienceRows(audiences) {
126856
127178
  return audiences.map((a) => ({
126857
127179
  label: audienceLabel(a.age, a.gender),
126858
- spend: num4(a.spend) ?? 0,
126859
- results: num4(a.results) ?? 0,
126860
- costPerResult: num4(a.costPerResult) ?? (num4(a.results) ? (num4(a.spend) ?? 0) / (num4(a.results) ?? 1) : 0),
126861
- frequency: num4(a.frequency)
127180
+ spend: num5(a.spend) ?? 0,
127181
+ results: num5(a.results) ?? 0,
127182
+ costPerResult: num5(a.costPerResult) ?? (num5(a.results) ? (num5(a.spend) ?? 0) / (num5(a.results) ?? 1) : 0),
127183
+ frequency: num5(a.frequency)
126862
127184
  })).filter((r) => r.spend > 0 || r.results > 0);
126863
127185
  }
126864
127186
  async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
@@ -126897,7 +127219,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
126897
127219
  }
126898
127220
  if (!meta.resultType && current?.resultType) meta.resultType = String(current.resultType);
126899
127221
  const charts = { ...payload.charts ?? {} };
126900
- const avgCpl = num4(kpis.costPerResult) ?? 0;
127222
+ const avgCpl = num5(kpis.costPerResult) ?? 0;
126901
127223
  if (!charts.platform) {
126902
127224
  const platform = sectionMap.get("platform");
126903
127225
  const networks = Array.isArray(platform?.networks) ? platform.networks : [];
@@ -126907,10 +127229,10 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
126907
127229
  const country = sectionMap.get("country");
126908
127230
  const countries = Array.isArray(country?.countries) ? country.countries : [];
126909
127231
  if (countries.length) {
126910
- const sorted = [...countries].sort((a, b) => (num4(b.spend) ?? 0) - (num4(a.spend) ?? 0));
127232
+ const sorted = [...countries].sort((a, b) => (num5(b.spend) ?? 0) - (num5(a.spend) ?? 0));
126911
127233
  charts.country = {
126912
127234
  labels: sorted.map((c) => String(c.countryOrRegion ?? "\u2014")),
126913
- cpl: sorted.map((c) => num4(c.costPerResult) ?? 0)
127235
+ cpl: sorted.map((c) => num5(c.costPerResult) ?? 0)
126914
127236
  };
126915
127237
  }
126916
127238
  }
@@ -126926,7 +127248,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
126926
127248
  };
126927
127249
  }
126928
127250
  }
126929
- if (!charts.funnel && num4(kpis.reach) != null) {
127251
+ if (!charts.funnel && num5(kpis.reach) != null) {
126930
127252
  charts.funnel = { reach: kpis.reach, results: kpis.results ?? 0 };
126931
127253
  }
126932
127254
  const tables = { ...payload.tables ?? {} };
@@ -126935,14 +127257,14 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
126935
127257
  const groups = Array.isArray(adSets?.adGroups) ? adSets.adGroups : [];
126936
127258
  if (groups.length) {
126937
127259
  tables.adSets = groups.map((g) => {
126938
- const cpl = num4(g.costPerResult);
126939
- const freq = num4(g.frequency);
127260
+ const cpl = num5(g.costPerResult);
127261
+ const freq = num5(g.frequency);
126940
127262
  const fatigue = fatigueFromFrequency(freq);
126941
127263
  const status = statusFromCpl(cpl, avgCpl);
126942
127264
  return {
126943
127265
  name: String(g.adGroupName ?? g.campaignName ?? "\u2014"),
126944
- spend: num4(g.spend) ?? 0,
126945
- results: num4(g.results) ?? 0,
127266
+ spend: num5(g.spend) ?? 0,
127267
+ results: num5(g.results) ?? 0,
126946
127268
  costPerResult: cpl ?? 0,
126947
127269
  frequency: freq,
126948
127270
  fatigueLevel: fatigue.level,
@@ -126966,7 +127288,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
126966
127288
  }
126967
127289
  const currency = typeof meta.currency === "string" ? meta.currency : "USD";
126968
127290
  const mergeCtx = {
126969
- spend: num4(kpis.spend),
127291
+ spend: num5(kpis.spend),
126970
127292
  avgCpl,
126971
127293
  currency,
126972
127294
  charts,