siluzan-tso-cli 1.1.37-beta.11 → 1.1.37-beta.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -51,7 +51,7 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
51
51
  siluzan-tso init --force # 强制覆盖已存在文件
52
52
  ```
53
53
 
54
- > **注意**:当前为测试版(1.1.37-beta.11),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.37-beta.13),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -2202,6 +2202,11 @@ function skipAuthSetupInAgentEnv(commandLabel) {
2202
2202
  }
2203
2203
  return true;
2204
2204
  }
2205
+ function blockMediaAccountOAuthInAgentEnv(_commandLabel) {
2206
+ if (!isSiluzanAgentEnv()) return false;
2207
+ console.log("\u8BF7\u4F7F\u7528 present_reauth \u5B8C\u6210\u6388\u6743");
2208
+ return true;
2209
+ }
2205
2210
  function trimOrUndefined(value) {
2206
2211
  const trimmed = value?.trim();
2207
2212
  return trimmed ? trimmed : void 0;
@@ -4380,6 +4385,40 @@ var init_currency_display = __esm({
4380
4385
  }
4381
4386
  });
4382
4387
 
4388
+ // src/utils/media-customer-id.ts
4389
+ function stripMediaCustomerIdDecorators(raw) {
4390
+ return raw.trim().replace(/[\s-]+/g, "");
4391
+ }
4392
+ function looksLikeNumericMediaCustomerId(raw) {
4393
+ return /^\d+$/.test(stripMediaCustomerIdDecorators(raw));
4394
+ }
4395
+ function normalizeNumericMediaCustomerId(raw) {
4396
+ if (!looksLikeNumericMediaCustomerId(raw)) return null;
4397
+ return stripMediaCustomerIdDecorators(raw);
4398
+ }
4399
+ function parseNumericMediaCustomerIds(input) {
4400
+ if (!input || !input.trim()) return [];
4401
+ const seen = /* @__PURE__ */ new Set();
4402
+ const out = [];
4403
+ for (const raw of input.split(",")) {
4404
+ const piece = raw.trim();
4405
+ if (!piece) continue;
4406
+ const id = normalizeNumericMediaCustomerId(piece);
4407
+ if (!id) {
4408
+ throw new Error(`mediaCustomerId \u5FC5\u987B\u4E3A\u7EAF\u6570\u5B57\uFF08\u53EF\u542B\u8FDE\u5B57\u7B26\uFF09\uFF1A${piece}`);
4409
+ }
4410
+ if (seen.has(id)) continue;
4411
+ seen.add(id);
4412
+ out.push(id);
4413
+ }
4414
+ return out;
4415
+ }
4416
+ var init_media_customer_id = __esm({
4417
+ "src/utils/media-customer-id.ts"() {
4418
+ "use strict";
4419
+ }
4420
+ });
4421
+
4383
4422
  // src/utils/balance.ts
4384
4423
  function fmt(d) {
4385
4424
  return d.toISOString().slice(0, 10);
@@ -103121,20 +103160,7 @@ function parseSections(input) {
103121
103160
  return list;
103122
103161
  }
103123
103162
  function parseAccountIds(input) {
103124
- if (!input || !input.trim()) return [];
103125
- const seen = /* @__PURE__ */ new Set();
103126
- const out = [];
103127
- for (const raw of input.split(",")) {
103128
- const id = raw.trim();
103129
- if (!id) continue;
103130
- if (!/^\d+$/.test(id)) {
103131
- throw new Error(`mediaCustomerId \u5FC5\u987B\u4E3A\u7EAF\u6570\u5B57\uFF1A${id}`);
103132
- }
103133
- if (seen.has(id)) continue;
103134
- seen.add(id);
103135
- out.push(id);
103136
- }
103137
- return out;
103163
+ return parseNumericMediaCustomerIds(input);
103138
103164
  }
103139
103165
  function parseDeadline(s) {
103140
103166
  if (!s) return void 0;
@@ -103639,6 +103665,7 @@ var init_google_analysis_batch = __esm({
103639
103665
  init_google_analysis3();
103640
103666
  init_balance();
103641
103667
  init_version();
103668
+ init_media_customer_id();
103642
103669
  DEFAULT_SECTIONS = ["campaigns", "geographic", "keywords"];
103643
103670
  DEFAULT_ACCOUNT_CONCURRENCY = 4;
103644
103671
  DEFAULT_SECTION_CONCURRENCY = 6;
@@ -103790,23 +103817,15 @@ function finiteOpt(n) {
103790
103817
  return typeof n === "number" && Number.isFinite(n) ? n : void 0;
103791
103818
  }
103792
103819
  function parseAccountIdsForAnalysis(input) {
103793
- const out = [];
103794
- const seen = /* @__PURE__ */ new Set();
103795
- for (const raw of input.split(",")) {
103796
- const id = raw.trim();
103797
- if (!id) continue;
103798
- if (!/^\d+$/.test(id)) {
103799
- console.error(`
103800
- \u274C --account \u987B\u4E3A\u6570\u5B57 mediaCustomerId\uFF08\u4E0D\u5408\u6CD5\uFF1A${id}\uFF09\u3002
103820
+ try {
103821
+ return parseNumericMediaCustomerIds(input);
103822
+ } catch (err) {
103823
+ const message = err instanceof Error ? err.message : String(err);
103824
+ console.error(`
103825
+ \u274C --account \u987B\u4E3A\u6570\u5B57 mediaCustomerId\uFF1A${message}
103801
103826
  `);
103802
- process.exit(1);
103803
- }
103804
- if (!seen.has(id)) {
103805
- seen.add(id);
103806
- out.push(id);
103807
- }
103827
+ process.exit(1);
103808
103828
  }
103809
- return out;
103810
103829
  }
103811
103830
  async function runMultiAccountViaBatch(accountIds, opts) {
103812
103831
  const targets = resolveSectionList(opts.sections, opts.exclude);
@@ -103992,6 +104011,7 @@ var init_register_cli = __esm({
103992
104011
  init_auth();
103993
104012
  init_google_analysis_snapshot();
103994
104013
  init_version();
104014
+ init_media_customer_id();
103995
104015
  init_http_retry();
103996
104016
  init_sections();
103997
104017
  init_fetch();
@@ -105958,6 +105978,7 @@ function wrapListJson(params) {
105958
105978
  }
105959
105979
 
105960
105980
  // src/commands/list-accounts/main.ts
105981
+ init_media_customer_id();
105961
105982
  async function runListAccounts(opts) {
105962
105983
  let config = loadConfig(opts.token);
105963
105984
  if (opts.refreshDp) {
@@ -105988,9 +106009,9 @@ async function runListAccounts(opts) {
105988
106009
  params.set("status", opts.status);
105989
106010
  }
105990
106011
  if (opts.keyword) {
105991
- const isNumeric = /^\d+$/.test(opts.keyword.trim());
105992
- if (isNumeric) {
105993
- params.set(platformCfg.idSearchParam, opts.keyword.trim());
106012
+ const numericId = normalizeNumericMediaCustomerId(opts.keyword);
106013
+ if (numericId) {
106014
+ params.set(platformCfg.idSearchParam, numericId);
105994
106015
  } else {
105995
106016
  params.set("mediaCustomerName", opts.keyword.trim());
105996
106017
  }
@@ -106028,7 +106049,9 @@ async function runListAccounts(opts) {
106028
106049
  }
106029
106050
  } else {
106030
106051
  const params = new URLSearchParams();
106031
- if (opts.keyword) params.set("keyword", opts.keyword);
106052
+ if (opts.keyword) {
106053
+ params.set("keyword", normalizeNumericMediaCustomerId(opts.keyword) ?? opts.keyword.trim());
106054
+ }
106032
106055
  if (opts.status && opts.status !== "all") params.set("status", opts.status);
106033
106056
  params.set("pageNo", String(page));
106034
106057
  params.set("pageSize", String(pageSize));
@@ -106275,6 +106298,7 @@ init_auth();
106275
106298
  init_cli_json_snapshot();
106276
106299
  init_cli_table();
106277
106300
  init_balance();
106301
+ init_media_customer_id();
106278
106302
  var VALID_MEDIA_TYPES2 = ["Google", "TikTok", "Yandex", "MetaAd", "BingV2", "Kwai"];
106279
106303
  async function runBalance(opts) {
106280
106304
  const config = loadConfig(opts.token);
@@ -106311,7 +106335,7 @@ async function runBalance(opts) {
106311
106335
  `);
106312
106336
  process.exit(1);
106313
106337
  }
106314
- const accountIds = opts.accounts.split(",").map((id) => id.trim()).filter(Boolean);
106338
+ const accountIds = opts.accounts.split(",").map((id) => id.trim()).filter(Boolean).map((id) => normalizeNumericMediaCustomerId(id) ?? id);
106315
106339
  if (accountIds.length === 0) {
106316
106340
  console.error("\n\u274C \u8BF7\u901A\u8FC7 --accounts \u63D0\u4F9B\u81F3\u5C11\u4E00\u4E2A\u8D26\u6237 ID\uFF08\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF09\n");
106317
106341
  process.exit(1);
@@ -106869,7 +106893,10 @@ init_cli_json_snapshot();
106869
106893
  init_cli_table();
106870
106894
  init_balance();
106871
106895
  init_media_account_lookup();
106896
+ init_media_customer_id();
106872
106897
  var VALID_MEDIA_TYPES3 = ["Google", "TikTok", "Yandex", "MetaAd", "BingV2", "Kwai"];
106898
+ var MAX_BY_DAY_SPAN = 366;
106899
+ var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
106873
106900
  function defaultDateRange2() {
106874
106901
  const end = /* @__PURE__ */ new Date();
106875
106902
  end.setDate(end.getDate() - 1);
@@ -106878,6 +106905,77 @@ function defaultDateRange2() {
106878
106905
  const fmt2 = (d) => d.toISOString().slice(0, 10);
106879
106906
  return { startDate: fmt2(start), endDate: fmt2(end) };
106880
106907
  }
106908
+ function eachDateInclusive(startDate, endDate) {
106909
+ if (!DATE_RE.test(startDate) || !DATE_RE.test(endDate)) {
106910
+ throw new Error(`\u65E5\u671F\u683C\u5F0F\u987B\u4E3A YYYY-MM-DD\uFF0C\u6536\u5230\uFF1A${startDate} ~ ${endDate}`);
106911
+ }
106912
+ const [ys, ms, ds] = startDate.split("-").map(Number);
106913
+ const [ye, me, de] = endDate.split("-").map(Number);
106914
+ const cur = new Date(Date.UTC(ys, ms - 1, ds));
106915
+ const end = new Date(Date.UTC(ye, me - 1, de));
106916
+ if (Number.isNaN(cur.getTime()) || Number.isNaN(end.getTime())) {
106917
+ throw new Error(`\u65E0\u6548\u65E5\u671F\uFF1A${startDate} ~ ${endDate}`);
106918
+ }
106919
+ if (cur > end) {
106920
+ throw new Error(`\u5F00\u59CB\u65E5\u671F\u4E0D\u80FD\u665A\u4E8E\u7ED3\u675F\u65E5\u671F\uFF1A${startDate} > ${endDate}`);
106921
+ }
106922
+ const out = [];
106923
+ while (cur <= end) {
106924
+ out.push(cur.toISOString().slice(0, 10));
106925
+ cur.setUTCDate(cur.getUTCDate() + 1);
106926
+ }
106927
+ if (out.length > MAX_BY_DAY_SPAN) {
106928
+ throw new Error(
106929
+ `--by-day \u8DE8\u5EA6\u6700\u591A ${MAX_BY_DAY_SPAN} \u5929\uFF08\u542B\u8D77\u6B62\uFF09\uFF0C\u5F53\u524D ${out.length} \u5929\uFF1B\u8BF7\u7F29\u5C0F\u533A\u95F4\u6216\u5206\u6BB5\u62C9\u53D6`
106930
+ );
106931
+ }
106932
+ return out;
106933
+ }
106934
+ async function fetchStatsWindow(media, ids, config, startDate, endDate, verbose) {
106935
+ const isGoogle = media === "Google";
106936
+ const isMetaAd = media === "MetaAd";
106937
+ if (isGoogle) {
106938
+ const params = new URLSearchParams({ startDate, endDate, mediaCustomerIds: ids.join("|") });
106939
+ const url = `${config.apiBaseUrl}/report/media-account/google/account-spend-overview?${params.toString()}`;
106940
+ const raw = await apiFetch2(url, config, {}, verbose);
106941
+ return parseGoogleAccountSpendOverviewRows(raw).map((row) => ({
106942
+ mediaAccountId: row.mediaAccountId,
106943
+ mediaCustomerName: row.mediaCustomerName,
106944
+ spend: row.spend,
106945
+ impressions: row.impressions,
106946
+ clicks: row.clicks,
106947
+ conversions: row.conversions,
106948
+ costPerClick: row.costPerClick,
106949
+ currencyCode: row.currencyCode,
106950
+ remainingAccountBudget: row.remainingAccountBudget,
106951
+ status: row.status
106952
+ }));
106953
+ }
106954
+ if (isMetaAd) {
106955
+ const accountMediaTypes = await lookupMetaMediaAccountTypes(config, ids, verbose);
106956
+ const items2 = [];
106957
+ for (const [route, routeIds] of groupAccountIdsByOverviewRoute(
106958
+ "MetaAd",
106959
+ ids,
106960
+ accountMediaTypes
106961
+ )) {
106962
+ const map2 = await fetchOverviewMap(route, routeIds, config, startDate, endDate, verbose);
106963
+ for (const item of map2.values()) {
106964
+ items2.push(item);
106965
+ }
106966
+ }
106967
+ return items2;
106968
+ }
106969
+ const map = await fetchOverviewMap(
106970
+ media,
106971
+ ids,
106972
+ config,
106973
+ startDate,
106974
+ endDate,
106975
+ verbose
106976
+ );
106977
+ return [...map.values()];
106978
+ }
106881
106979
  async function runStats(opts) {
106882
106980
  const config = loadConfig(opts.token);
106883
106981
  if (!VALID_MEDIA_TYPES3.includes(opts.media)) {
@@ -106904,57 +107002,35 @@ async function runStats(opts) {
106904
107002
  );
106905
107003
  process.exit(1);
106906
107004
  }
106907
- const ids = opts.accounts.split(",").map((id) => id.trim()).filter(Boolean);
106908
- const isGoogle = opts.media === "Google";
106909
- const isMetaAd = opts.media === "MetaAd";
107005
+ const ids = opts.accounts.split(",").map((id) => id.trim()).filter(Boolean).map((id) => normalizeNumericMediaCustomerId(id) ?? id);
106910
107006
  let items2;
106911
107007
  try {
106912
- if (isGoogle) {
106913
- const params = new URLSearchParams({ startDate, endDate, mediaCustomerIds: ids.join("|") });
106914
- const url = `${config.apiBaseUrl}/report/media-account/google/account-spend-overview?${params.toString()}`;
106915
- const raw = await apiFetch2(url, config, {}, opts.verbose);
106916
- items2 = parseGoogleAccountSpendOverviewRows(raw).map((row) => ({
106917
- mediaAccountId: row.mediaAccountId,
106918
- mediaCustomerName: row.mediaCustomerName,
106919
- spend: row.spend,
106920
- impressions: row.impressions,
106921
- clicks: row.clicks,
106922
- conversions: row.conversions,
106923
- costPerClick: row.costPerClick,
106924
- currencyCode: row.currencyCode,
106925
- remainingAccountBudget: row.remainingAccountBudget,
106926
- status: row.status
106927
- }));
106928
- } else if (isMetaAd) {
106929
- const accountMediaTypes = await lookupMetaMediaAccountTypes(config, ids, opts.verbose);
107008
+ if (opts.byDay) {
107009
+ const days = eachDateInclusive(startDate, endDate);
106930
107010
  items2 = [];
106931
- for (const [route, routeIds] of groupAccountIdsByOverviewRoute(
106932
- "MetaAd",
106933
- ids,
106934
- accountMediaTypes
106935
- )) {
106936
- const map = await fetchOverviewMap(
106937
- route,
106938
- routeIds,
107011
+ console.error(
107012
+ `\u23F3 [stats --by-day] ${opts.media} ${ids.length} \u6237 \xD7 ${days.length} \u5929\uFF08${startDate} ~ ${endDate}\uFF09\u2026`
107013
+ );
107014
+ for (let i = 0; i < days.length; i++) {
107015
+ const day = days[i];
107016
+ if (opts.verbose || days.length > 7) {
107017
+ console.error(` \u2192 [${i + 1}/${days.length}] ${day}`);
107018
+ }
107019
+ const dayItems = await fetchStatsWindow(
107020
+ opts.media,
107021
+ ids,
106939
107022
  config,
106940
- startDate,
106941
- endDate,
107023
+ day,
107024
+ day,
106942
107025
  opts.verbose
106943
107026
  );
106944
- for (const item of map.values()) {
106945
- items2.push(item);
107027
+ for (const row of dayItems) {
107028
+ items2.push({ ...row, date: day });
106946
107029
  }
106947
107030
  }
107031
+ console.error(` \u2713 \u5B8C\u6210\uFF1A${items2.length} \u6761\u6309\u65E5\u884C`);
106948
107032
  } else {
106949
- const map = await fetchOverviewMap(
106950
- opts.media,
106951
- ids,
106952
- config,
106953
- startDate,
106954
- endDate,
106955
- opts.verbose
106956
- );
106957
- items2 = [...map.values()];
107033
+ items2 = await fetchStatsWindow(opts.media, ids, config, startDate, endDate, opts.verbose);
106958
107034
  }
106959
107035
  } catch (err) {
106960
107036
  const message = err instanceof Error ? err.message : String(err);
@@ -106973,17 +107049,18 @@ async function runStats(opts) {
106973
107049
  process.exit(1);
106974
107050
  }
106975
107051
  const n = items2.length;
107052
+ const section = opts.byDay ? `stats-${opts.media}-by-day` : `stats-${opts.media}`;
106976
107053
  const statsPayload = wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items: items2 });
106977
107054
  if (await emitCliJsonOrSnapshot(opts, {
106978
- section: `stats-${opts.media}`,
107055
+ section,
106979
107056
  commandLabel: "stats",
106980
- commandHint: opts.media,
107057
+ commandHint: opts.byDay ? `${opts.media}-by-day` : opts.media,
106981
107058
  payload: statsPayload,
106982
- idSuffix: opts.media
107059
+ idSuffix: opts.byDay ? `${opts.media}-by-day` : opts.media
106983
107060
  })) {
106984
107061
  return;
106985
107062
  }
106986
- const rangeLabel = `${startDate} ~ ${endDate}`;
107063
+ const rangeLabel = opts.byDay ? `${startDate} ~ ${endDate}\uFF08\u6309\u65E5\uFF09` : `${startDate} ~ ${endDate}`;
106987
107064
  if (items2.length === 0) {
106988
107065
  console.log(`
106989
107066
  ${opts.media} \u6295\u653E\u6570\u636E\uFF08${rangeLabel}\uFF09\uFF08\u7B2C 1 \u9875\uFF0C\u672C\u9875 0 \u6761\uFF0C\u5171 0 \u6761\uFF09
@@ -106996,7 +107073,16 @@ ${opts.media} \u6295\u653E\u6570\u636E\uFF08${rangeLabel}\uFF09\uFF08\u7B2C 1 \u
106996
107073
  ${opts.media} \u6295\u653E\u6570\u636E\uFF08${rangeLabel}\uFF09\uFF08\u7B2C 1 \u9875\uFF0C\u672C\u9875 ${items2.length} \u6761\uFF0C\u5171 ${items2.length} \u6761\uFF09
106997
107074
  `
106998
107075
  );
106999
- const cols = [
107076
+ const cols = opts.byDay ? [
107077
+ { key: "date", header: "\u65E5\u671F" },
107078
+ { key: "id", header: "\u8D26\u6237ID" },
107079
+ { key: "name", header: "\u8D26\u6237\u540D\u79F0" },
107080
+ { key: "spend", header: "\u6D88\u8017" },
107081
+ { key: "impressions", header: "\u5C55\u793A\u6B21\u6570" },
107082
+ { key: "clicks", header: "\u70B9\u51FB\u6B21\u6570" },
107083
+ { key: "conversions", header: "\u8F6C\u5316\u6B21\u6570" },
107084
+ { key: "cpc", header: "\u70B9\u51FB\u5747\u4EF7" }
107085
+ ] : [
107000
107086
  { key: "id", header: "\u8D26\u6237ID" },
107001
107087
  { key: "name", header: "\u8D26\u6237\u540D\u79F0" },
107002
107088
  { key: "spend", header: "\u6D88\u8017" },
@@ -107014,7 +107100,7 @@ ${opts.media} \u6295\u653E\u6570\u636E\uFF08${rangeLabel}\uFF09\uFF08\u7B2C 1 \u
107014
107100
  totalImpressions += item.impressions ?? 0;
107015
107101
  totalClicks += item.clicks ?? 0;
107016
107102
  totalConversions += item.conversions ?? 0;
107017
- return {
107103
+ const base = {
107018
107104
  id: item.mediaAccountId ?? "",
107019
107105
  name: item.mediaCustomerName ?? "",
107020
107106
  spend: item.spend?.toFixed(2) ?? "0.00",
@@ -107023,27 +107109,45 @@ ${opts.media} \u6295\u653E\u6570\u636E\uFF08${rangeLabel}\uFF09\uFF08\u7B2C 1 \u
107023
107109
  conversions: String(item.conversions ?? 0),
107024
107110
  cpc: item.costPerClick?.toFixed(4) ?? "0.0000"
107025
107111
  };
107112
+ return opts.byDay ? { date: item.date ?? "", ...base } : base;
107026
107113
  });
107027
107114
  if (items2.length > 1) {
107028
107115
  const avgCpc = totalClicks > 0 ? (totalSpend / totalClicks).toFixed(4) : "0.0000";
107029
- rows.push({
107030
- id: "\u5408\u8BA1",
107031
- name: "",
107032
- spend: totalSpend.toFixed(2),
107033
- impressions: String(totalImpressions),
107034
- clicks: String(totalClicks),
107035
- conversions: String(totalConversions),
107036
- cpc: avgCpc
107037
- });
107116
+ rows.push(
107117
+ opts.byDay ? {
107118
+ date: "\u5408\u8BA1",
107119
+ id: "",
107120
+ name: "",
107121
+ spend: totalSpend.toFixed(2),
107122
+ impressions: String(totalImpressions),
107123
+ clicks: String(totalClicks),
107124
+ conversions: String(totalConversions),
107125
+ cpc: avgCpc
107126
+ } : {
107127
+ id: "\u5408\u8BA1",
107128
+ name: "",
107129
+ spend: totalSpend.toFixed(2),
107130
+ impressions: String(totalImpressions),
107131
+ clicks: String(totalClicks),
107132
+ conversions: String(totalConversions),
107133
+ cpc: avgCpc
107134
+ }
107135
+ );
107038
107136
  }
107039
107137
  printCliTable(rows, cols);
107040
107138
  console.log();
107041
107139
  }
107042
107140
  function register11(program2) {
107043
- program2.command("stats").description("\u67E5\u8BE2\u5E7F\u544A\u6D88\u8017\u3001\u70B9\u51FB\u3001\u8F6C\u5316\u7B49\u6295\u653E\u6570\u636E\uFF08\u9ED8\u8BA4\u8FD1 7 \u5929\uFF0C\u4E0D\u542B\u4ECA\u5929\uFF09").requiredOption(
107141
+ program2.command("stats").description(
107142
+ "\u67E5\u8BE2\u5E7F\u544A\u6D88\u8017\u3001\u70B9\u51FB\u3001\u8F6C\u5316\u7B49\u6295\u653E\u6570\u636E\uFF08\u9ED8\u8BA4\u8FD1 7 \u5929\u533A\u95F4\u5408\u8BA1\uFF0C\u4E0D\u542B\u4ECA\u5929\uFF1B\u8981\u6309\u65E5\u660E\u7EC6\u52A0 --by-day\uFF09"
107143
+ ).requiredOption(
107044
107144
  "-m, --media <type>",
107045
107145
  "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | MetaAd | BingV2 | Kwai"
107046
- ).option("-a, --accounts <ids>", "\u8D26\u6237 ID\uFF0C\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF08\u7559\u7A7A\u5219\u67E5\u5168\u90E8\u8D26\u6237\uFF09").option("--start <date>", "\u5F00\u59CB\u65E5\u671F\uFF0C\u683C\u5F0F YYYY-MM-DD\uFF08\u9ED8\u8BA4 7 \u5929\u524D\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F\uFF0C\u683C\u5F0F YYYY-MM-DD\uFF08\u9ED8\u8BA4\u6628\u5929\uFF09").option("--start-date <date>", "\u540C --start\uFF08\u6587\u6863/Playbook \u517C\u5BB9\u522B\u540D\uFF09").option("--end-date <date>", "\u540C --end\uFF08\u6587\u6863/Playbook \u517C\u5BB9\u522B\u540D\uFF09").option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option(
107146
+ ).option("-a, --accounts <ids>", "\u8D26\u6237 mediaCustomerId\uFF0C\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF08\u5FC5\u586B\uFF09").option("--start <date>", "\u5F00\u59CB\u65E5\u671F\uFF0C\u683C\u5F0F YYYY-MM-DD\uFF08\u9ED8\u8BA4 7 \u5929\u524D\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F\uFF0C\u683C\u5F0F YYYY-MM-DD\uFF08\u9ED8\u8BA4\u6628\u5929\uFF09").option("--start-date <date>", "\u540C --start\uFF08\u6587\u6863/Playbook \u517C\u5BB9\u522B\u540D\uFF09").option("--end-date <date>", "\u540C --end\uFF08\u6587\u6863/Playbook \u517C\u5BB9\u522B\u540D\uFF09").option(
107147
+ "--by-day",
107148
+ "\u6309\u65E5\u660E\u7EC6\uFF1A\u5BF9\u533A\u95F4\u5185\u6BCF\u4E00\u5929\u5206\u522B\u62C9\u6570\uFF0Citems[] \u6BCF\u884C\u5E26 date\uFF1B\u5BFC\u51FA Excel/\u6309\u65E5\u8D8B\u52BF\u5FC5\u52A0\u3002\u9ED8\u8BA4\u4E0D\u52A0\u5219\u53EA\u8FD4\u56DE\u533A\u95F4\u5408\u8BA1\u4E00\u884C",
107149
+ false
107150
+ ).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option(
107047
107151
  "--json-out <path>",
107048
107152
  "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
107049
107153
  void 0
@@ -107055,6 +107159,7 @@ function register11(program2) {
107055
107159
  accounts: opts.accounts,
107056
107160
  startDate: opts.start ?? opts.startDate,
107057
107161
  endDate: opts.end ?? opts.endDate,
107162
+ byDay: opts.byDay,
107058
107163
  jsonOut: opts.jsonOut,
107059
107164
  verbose: opts.verbose
107060
107165
  });
@@ -121646,6 +121751,15 @@ async function runAccountMccUnbind(opts) {
121646
121751
  }
121647
121752
 
121648
121753
  // src/commands/account-manage/google-access-permission.ts
121754
+ function hintGoogleOAuth(kind) {
121755
+ if (isSiluzanAgentEnv()) {
121756
+ return "\u8BF7\u4F7F\u7528 present_reauth \u5B8C\u6210\u6388\u6743";
121757
+ }
121758
+ if (kind === "reauth") {
121759
+ return "list-accounts --json-out \u53D6 ma.entityId \u540E\u6267\u884C\uFF1Asiluzan-tso account reauth -m Google --id <entityId>";
121760
+ }
121761
+ return "\u6267\u884C siluzan-tso account auth -m Google \u5B8C\u6210 OAuth \u7ED1\u5B9A\u3002";
121762
+ }
121649
121763
  function assertGoogleMediaCustomerId(raw) {
121650
121764
  const id = raw.trim();
121651
121765
  if (!id) {
@@ -121692,7 +121806,7 @@ function interpretCustomerAccessPermission(mediaCustomerId, httpStatus, rawBody)
121692
121806
  status: "reauth_required",
121693
121807
  accessible: false,
121694
121808
  message: "\u8D26\u6237\u5DF2\u7ED1\u5B9A\u4F46 Google OAuth \u4E0D\u53EF\u7528\uFF0C\u9700\u91CD\u65B0\u6388\u6743\u3002",
121695
- hint: "list-accounts --json-out \u53D6 ma.entityId \u540E\u6267\u884C\uFF1Asiluzan-tso account reauth -m Google --id <entityId>"
121809
+ hint: hintGoogleOAuth("reauth")
121696
121810
  };
121697
121811
  }
121698
121812
  return {
@@ -121710,7 +121824,7 @@ function interpretCustomerAccessPermission(mediaCustomerId, httpStatus, rawBody)
121710
121824
  status: "google_not_bound",
121711
121825
  accessible: false,
121712
121826
  message: "\u5F53\u524D\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u672A\u7ED1\u5B9A Google \u5A92\u4F53\u6388\u6743\u3002",
121713
- hint: "\u6267\u884C siluzan-tso account auth -m Google \u5B8C\u6210 OAuth \u7ED1\u5B9A\u3002"
121827
+ hint: hintGoogleOAuth("auth")
121714
121828
  };
121715
121829
  }
121716
121830
  return {
@@ -121977,13 +122091,16 @@ async function runAccountShareDetail(opts) {
121977
122091
  }
121978
122092
 
121979
122093
  // src/commands/account-manage/oauth.ts
121980
- init_auth();
121981
122094
  init_dist();
122095
+ init_auth();
121982
122096
  function toApiMediaType(media) {
121983
122097
  return media === "Meta" ? "FacebookAds" : media;
121984
122098
  }
121985
122099
  var tryOpenBrowser = tryOpenHttpsUrlInBrowser;
121986
122100
  async function runAccountAuth(opts) {
122101
+ if (blockMediaAccountOAuthInAgentEnv("account auth")) {
122102
+ process.exit(1);
122103
+ }
121987
122104
  const config = loadConfig(opts.token);
121988
122105
  const apiMediaType = toApiMediaType(opts.media);
121989
122106
  const webUrl = deriveWebUrl(config.apiBaseUrl);
@@ -122015,6 +122132,9 @@ async function runAccountAuth(opts) {
122015
122132
  });
122016
122133
  }
122017
122134
  async function runAccountReauth(opts) {
122135
+ if (blockMediaAccountOAuthInAgentEnv("account reauth")) {
122136
+ process.exit(1);
122137
+ }
122018
122138
  if (!opts.id && (!opts.ids || opts.ids.length === 0)) {
122019
122139
  console.error(
122020
122140
  "\n\u274C \u91CD\u65B0\u6388\u6743\u987B\u6307\u5B9A\u8981\u89E3\u7ED1\u7684\u8D26\u6237 entityId\uFF1A--id <entityId> \u6216 --ids id1,id2\n entityId \u6765\u81EA list-accounts --json-out \u7684 ma.entityId\uFF08\u4E0D\u662F mediaCustomerId\uFF09\n"
@@ -38,6 +38,7 @@
38
38
  | 网站诊断 | `references/report-templates/website-diagnosis-report.md` |
39
39
  | 战略市场分析 | `references/report-templates/market-analysis-report.md` |
40
40
  | Google 周期 Excel | `references/report-templates/google-period-report-excel.md`(P4) |
41
+ | 账户按日 Excel | `references/report-templates/stats-daily-excel.md`(P4-DAILY) |
41
42
  | OKKI 周报 | `references/report-templates/okki-weekly-google-client.md` |
42
43
  | 询盘分析 | `references/report-templates/google-inquiry-analysis.md` |
43
44
 
@@ -98,6 +98,7 @@ siluzan-tso -h # 查看帮助
98
98
  | Google 账户 **周期** / **月度** / **季度** 报告 / 月报(**非** OKKI / **非** 询盘 / **非** 强调健康诊断) | **P4** | `references/report-templates/google-period-report.md` + `references/analytics/account-analytics.md`;要 **Excel** 加 `references/report-templates/google-period-report-excel.md` |
99
99
  | Meta/Facebook 周期或诊断报告 | **P4-FB** | `references/report-templates/meta-period-report.md` + `assets/meta-period-report-rules.md` + `references/analytics/facebook-analysis-guide.md`;要 Excel 加 `references/report-templates/meta-period-report-excel.md` |
100
100
  | TikTok / Bing 周期报告 | **P4** | `references/analytics/account-analytics.md`(tiktok-analysis/bing-analysis)+ 对应 `references/report-templates/*-period-report.md` |
101
+ | 账户级按日 Excel / 按天导出投放数据 | **P4-DAILY** | `references/report-templates/stats-daily-excel.md` |
101
102
  | 多账户 × 多维度批处理 | **P5** | `references/analytics/google-analysis-batch.md` + `references/analytics/account-analytics.md` |
102
103
  | OKKI 周报 | **P6** | `references/report-templates/okki-weekly-google-client.md`(全文) |
103
104
  | Google 询盘分析 | **P7** | `references/report-templates/google-inquiry-analysis.md`(全文)+ `references/analytics/geo-continents.json` |
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.37-beta.11",
4
- "publishedAt": 1784275362257
3
+ "version": "1.1.37-beta.13",
4
+ "publishedAt": 1784533653914
5
5
  }
@@ -85,6 +85,7 @@
85
85
  | ---------------------------------------------------------------- | ------------------------------ |
86
86
  | `google-period-report.md` | Google 账户周期/月度报告(P4) |
87
87
  | `google-period-report-excel.md` | P4 用户要 Excel |
88
+ | `stats-daily-excel.md` | P4-DAILY 按日 Excel |
88
89
  | `meta-period-report.md` / `meta-period-report-excel.md` | Meta 周期(P4-FB) |
89
90
  | `google-ads-diagnosis.md` / `google-account-diagnosis-report.md` | Google 诊断(P1) |
90
91
  | `website-diagnosis-report.md` | 网站诊断(P8) |
@@ -118,11 +118,8 @@ siluzan-tso account-active-bills -m <媒体> --id <entityId> [--json-out ./snap]
118
118
  # 从列表取 entityId
119
119
  siluzan-tso list-accounts -m Google --json-out ./snap
120
120
 
121
- # 查询该账户激活账单(将下方 UUID 换成实际 entityId)
122
- siluzan-tso account-active-bills -m Google --id 18176820-6204-43c2-9a1f-0d0f5e9eb957
123
-
124
- # 原始 JSON,便于脚本解析
125
- siluzan-tso account-active-bills -m Google --id 18176820-6204-43c2-9a1f-0d0f5e9eb957 --json-out ./snap
121
+ siluzan-tso account-active-bills -m Google --id <entityId>
122
+ siluzan-tso account-active-bills -m Google --id <entityId> --json-out ./snap
126
123
  ```
127
124
 
128
125
  > \*\*勿在文档或聊天中粘贴真实 JWT。
@@ -233,24 +230,19 @@ siluzan-tso stats -m <媒体类型> [选项]
233
230
  | `--start <YYYY-MM-DD>` | 开始日期 | 7 天前 |
234
231
  | `--end <YYYY-MM-DD>` | 结束日期 | 昨天 |
235
232
  | `--start-date` / `--end-date` | 与 `--start` / `--end` 同义(CLI 别名,与 SKILL Playbook 一致) | — |
233
+ | `--by-day` | 按日明细(`items[]` 带 `date`);按日 Excel 见 `stats-daily-excel.md` | 关 |
236
234
  | `--json-out` | 输出原始 JSON;**失败时 stdout 仍为 JSON**(`{"ok":false,"error":"..."}`) | — |
237
235
 
238
- **口径(易错)**:返回的 **`spend` = `--start`~`--end` 闭区间合计消耗**,默认近 7 天即约 **7 日之和**,**不是**「一天的消耗」。要日消耗须 `start=end`;要日均用 `spend/天数` 或 `balance-scan` 的 `dailySpend`。**禁止**把默认窗口的 `spend` 当成日消耗去比日预算或写「今天花了 X」。
236
+ **口径(易错)**:默认 **`spend` = 区间合计**(非日消耗)。按日加 `--by-day`;单日可用 `start=end`;日均用 `spend/天数` 或 `balance-scan` 的 `dailySpend`。
239
237
 
240
238
  **示例:**
241
239
 
242
240
  ```bash
243
- # 查询 Google 账户最近 7 天消耗
244
- siluzan-tso stats -m Google -a 6326027735
245
-
246
- # 查询 Google 指定月份消耗
247
- siluzan-tso stats -m Google -a 6326027735 --start 2026-03-01 --end 2026-03-31
248
-
249
- # 查询多个 Bing 账户消耗
250
- siluzan-tso stats -m BingV2 -a 1234567890,9876543210 --start 2026-03-01
251
-
252
- # JSON 输出
253
- siluzan-tso stats -m Google -a 6326027735 --json-out ./snap
241
+ siluzan-tso stats -m Google -a <id>
242
+ siluzan-tso stats -m Google -a <id> --start 2026-03-01 --end 2026-03-31
243
+ siluzan-tso stats -m Yandex -a <id> --start <S> --end <E> --by-day --json-out ./snap/daily.json
244
+ siluzan-tso stats -m BingV2 -a <id1>,<id2> --start 2026-03-01
245
+ siluzan-tso stats -m Google -a <id> --json-out ./snap
254
246
  ```
255
247
 
256
248
  ---
@@ -335,12 +327,6 @@ siluzan-tso account check-access -a 4256317784 --json-out ./snap-access
335
327
 
336
328
  ---
337
329
 
338
- ### auth — 添加媒体平台 OAuth 授权(首次绑定)
339
-
340
- 在浏览器中打开对应媒体的 OAuth 授权页面,授权后账户自动绑定到丝路赞。对应网页 **「添加授权」**。
341
-
342
- > **OAuth 已失效**(`invalidOAuthToken=true`)时**不要**用本命令,须走下方 **`reauth`**(重新授权必须先解绑)。
343
-
344
330
  ```bash
345
331
  siluzan-tso account auth -m <媒体类型>
346
332
  ```
@@ -349,7 +335,7 @@ siluzan-tso account auth -m <媒体类型>
349
335
  | -------------------- | ------------------------------------------------------------------------ |
350
336
  | `-m, --media <type>` | 媒体类型(必填):`Google \| TikTok \| Meta \| Yandex \| BingV2 \| Kwai` |
351
337
 
352
- **示例:**
338
+ **示例(仅本地 / 非 Siluzan Agent CLI):**
353
339
 
354
340
  ```bash
355
341
  # 首次授权 Google Ads 账户
@@ -362,9 +348,9 @@ siluzan-tso account auth -m TikTok
362
348
  siluzan-tso account auth -m Meta
363
349
  ```
364
350
 
365
- > CLI 会自动在系统默认浏览器中打开授权页;无法打开时输出 URL 供手动粘贴。授权完成后会跳回丝路赞,账户立即生效。
351
+ > CLI 会自动在系统默认浏览器中打开授权页;无法打开时输出 URL 供手动粘贴。授权完成后会跳回丝路赞 TSO 绑定页,账户立即生效。
366
352
  >
367
- > **Agent 硬约束(auth / reauth 共通)**:CLI stdout 会打印完整 `https://…` OAuth 链接(Agent 沙箱不会自动开浏览器)。**必须把该 URL 原样贴进给用户的回复**,禁止只说「链接已生成 / 我这边还有链接」却不贴出;用户无法从工具日志自行翻找。
353
+ > **非 Agent 环境**若仍用手跑 CLI:stdout 打印的完整 `https://…` OAuth 链接须原样贴给用户(沙箱不会自动开浏览器)。
368
354
 
369
355
  ---
370
356
 
@@ -372,6 +358,8 @@ siluzan-tso account auth -m Meta
372
358
 
373
359
  OAuth 失效时恢复授权。对齐 TSO 网页 **「重新授权」**:程序会先 **delink** 断开关联,再跳转媒体 OAuth。**禁止**对失效账户跳过解绑直接用 `account auth`。
374
360
 
361
+ > **Siluzan Agent**:同 `auth`——**禁止**执行;须走 **connectors / 平台授权工具**。CLI 拦截时不会先解绑。
362
+
375
363
  ```bash
376
364
  siluzan-tso account reauth -m <媒体类型> --id <entityId>
377
365
  siluzan-tso account reauth -m Google --ids <id1,id2>
@@ -383,7 +371,7 @@ siluzan-tso account reauth -m Google --ids <id1,id2>
383
371
  | `--id <entityId>` | 单个账户 `entityId`(来自 `list-accounts` 的 `ma.entityId`) |
384
372
  | `--ids <id1,id2>` | 批量 `entityId`,逗号分隔(与 `--id` 二选一) |
385
373
 
386
- **示例:**
374
+ **示例(仅本地 / 非 Siluzan Agent CLI):**
387
375
 
388
376
  ```bash
389
377
  # 1. 查失效账户的 entityId
@@ -396,13 +384,13 @@ siluzan-tso account reauth -m BingV2 --id <entityId> --commit "重新授权 Bing
396
384
  siluzan-tso list-accounts -m BingV2 -k <mediaCustomerId>
397
385
  ```
398
386
 
399
- **执行后 Agent 必须:**
387
+ **本地 CLI 执行后须:**
400
388
 
401
389
  1. 从 CLI 输出中提取 OAuth URL(含 `login.microsoftonline.com` / Google 等),**原样发给用户**并说明「请在浏览器打开完成授权」。
402
390
  2. 说明:步骤 1 已 delink,账户可能暂时从 `list-accounts` 消失;用户完成授权前无法拉数。
403
391
  3. 若用户之后才说要绑定回来、且当时未贴链接:再跑 **`account auth -m <媒体>`**(勿再 `reauth`——已无 entityId),把**新**链接贴出。勿声称「还有刚才的链接」却不粘贴。
404
392
 
405
- > 手动两步等价于 `reauth`:`account delink --id …` → `account auth -m …`(须用户确认解绑风险)。
393
+ > 手动两步等价于 `reauth`:`account delink --id …` → `account auth -m …`(须用户确认解绑风险;**Agent 环境仍勿用**)。
406
394
 
407
395
  ---
408
396
 
@@ -110,7 +110,7 @@
110
110
  - **数据时效性**:涉及「今天/当天/今日消耗」「实时消耗排行」前,必读 `references/analytics/account-analytics.md` 顶部「数据时效性」表。TikTok / Yandex / BingV2 / Kwai 是 `accountsoverview` 同步昨天数据,**不能查今天**。
111
111
  - **先查账户再操作**:`list-accounts -m [mediaType] -k [mediaCustomerId]`;用户给出的 `mediaCustomerId` 必须 `-k` 核验,无结果则告知用户并停止,**禁止**翻页 grep 自行换 ID(会导致报告错户);拉数、脚本、报告文件名全链路用同一 ID(以 stdout `accountId` 为准)。**Google 额外建议**:`list-accounts` 命中后、拉数前可 `account check-access -a <mediaCustomerId>` 校验 Google 网关访问权限;`no_permission`(403)通常表示账户不在当前丝路赞账号下。
112
112
  - **不猜测账户 ID**:`entityId` ≠ `mediaCustomerId`,两者均来自 `list-accounts`;**禁止**把 `entityId` 传给 `stats -a` / `balance -a`。
113
- - **媒体类型区分大小写**:`Google`、`TikTok`、`MetaAd`、`BingV2`、`Kwai`。
113
+ - **媒体类型区分大小写**:`Google`、`TikTok`、`Yandex`、`MetaAd`、`BingV2`、`Kwai`。
114
114
  - **CLI 输出忠实**:数值与 ID 须与本次落盘 JSON / stdout 一致,不编造示例 ID;`data` 为空时只说「当前返回无记录」并附 JSON 路径。
115
115
  - **破坏性操作必须确认 + `--commit`**:账户解绑/关闭/取消分享、BC/MCC 解绑、删除预警/报告/广告/关键词、发票申请、广告发布等。
116
116
  - **不确定时读文档**:先读对应 references 或用 `-h`,不要猜参数。
@@ -199,6 +199,7 @@
199
199
  | --------------------------- | -------------------------------------------------------------------------------------- | -------------------------------- |
200
200
  | 多账户余额 / 预算不足预警 | `balance-scan -m <媒体> --threshold-days 7`(用 **`dailySpend`** / `remainingDays`) | 逐账户 `balance --accounts ...`;把 `stats` 多日 **`spend` 当「日消耗」** |
201
201
  | 多账户投放画像 | `accounts-digest -m <媒体> [-a ...] --start --end --json-out`(**`spend`=区间合计**) | 逐账户 `stats`;把合计 `spend` 说成「每天花了」 |
202
+ | 单户按日 Excel | `stats … --by-day --json-out`(P4-DAILY) | 默认 `stats` 当按日 |
202
203
  | 多账户 × 多维度 Google 数据 | 全量:`google-analysis-batch run`(省略 `-a`);2~10:`google-analysis -a id1,id2,...` | 外层 for-loop;先 list 再拼 `-a` |
203
204
  | 多系列诊断 | `ad campaigns --json-out` + node 读文件过滤 | 逐系列 `ad campaign-get` |
204
205
  | 单日预算熔断 / 当日花费比日预算 | `ad campaigns --start <当日> --end <当日>`(**start=end**)后再比 `spend` 与 `budget` | 用默认近 7 天窗口的 `spend` 对比日预算 |
@@ -219,7 +220,7 @@
219
220
 
220
221
  先用位数判断类型,不确定再 `list-accounts -m [mediaType] -k [id]`:
221
222
 
222
- - Google: `454xxx5137` 或 `270-xxx-0720`(去掉 `-`)
223
+ - Google: `454xxx5137` 或 `270-xxx-0720`
223
224
  - TikTok: `70083497xxx59820033`
224
225
  - Meta(Facebook): `1716030xxx734076`
225
226
  - Bing: `138xxx763`
@@ -164,6 +164,7 @@
164
164
  | **P9** | **行业 / 市场 / 战略**(**无网址主诉求**) | 市场分析、**行业分析**、**行业分析报告**、市场调查报告、战略市场报告、KA 市场报告、竞品/GTM、「写一份 **XX 行业** 报告」(电商/制造/医疗…) | 客户名 / 行业 / 核心产品 **至少一项**(**不是** URL+诊断) | HTML(`market-analysis render`) |
165
165
  | **P1** | **单个 Google 广告账户** 的投放健康 / 诊断 | Google **广告**诊断、**账户**诊断报告、投放诊断、**账户**健康检查、**账户**体检(**且含 Google 账户 ID**) | 账户 ID + 统计区间 | HTML(`google-ads-diagnosis render`) |
166
166
  | **P4** | **单个媒体广告账户** 某区间的 **周期汇总** | 周期报告、月度报告、季度报告、月报、投放月报、Google 账户报告(**未强调「诊断/健康/网站/网址」**) | 媒体 + 账户 ID + 区间 | HTML(默认);用户要 Excel 见 P4 excel 纲要 |
167
+ | **P4-DAILY** | **账户级按日表**(无多维) | 按天导出、投放数据 Excel(非 Google/Meta 周期报告) | 媒体 + 账户 ID + 区间 | xlsx(`stats --by-day`) |
167
168
  | **P4-FB** | **Meta/Facebook 账户** 周期或诊断 | Facebook 报告、Meta 月报、FB **账户**诊断报告 | Meta 账户 ID + 区间 | HTML(`facebook-analysis render`) |
168
169
  | **P6** | **OKKI 周报**(固定客户模板) | 见 §三魔法短语 | 账户 ID + 区间 | 多 Sheet xlsx |
169
170
  | **P7** | **Google 账户 + 询盘** 效果 | 见 §三魔法短语 | 账户 + 分析月 + 询盘资料 | 8 Sheet xlsx |
@@ -190,7 +191,7 @@
190
191
  | 用户侧重点 | 正确工作流 |
191
192
  | ---------------------------------------------- | -------------------------------- |
192
193
  | 给了 **Google 账户 ID**,要健康/诊断/优化建议 | **P1**(`google-ads-diagnosis`) |
193
- | 给了 **账户 ID**,要某月/某季 **投放数据汇总** | **P4** |
194
+ | 给了 **账户 ID**,要某月/某季 **投放数据汇总** | **P4**(按日表无多维 → **P4-DAILY**) |
194
195
  | 给了 **URL / 网址 / 域名** + 诊断类词(§零) | **P8** |
195
196
  | 只给 **行业名**(无 URL),要行业分析 | **P9** |
196
197
 
@@ -73,6 +73,15 @@
73
73
  - **交付/确认**:按 conventions §七 自检;`accountId` 须与用户给的 `mediaCustomerId` 一致。
74
74
 
75
75
  > 用户用「OKKI 周报」固定话术 → 改走 **P6**,不按默认 8 维追问。
76
+ > 只要账户级按日 Excel(无多维章节)→ **P4-DAILY**。
77
+
78
+ ---
79
+
80
+ ## P4-DAILY · 按日投放 Excel
81
+
82
+ - **触发**:按天导出 / 投放数据做成 Excel(非 Google/Meta 多维报告)。
83
+ - **必读**:`references/report-templates/stats-daily-excel.md`。
84
+ - **步骤**:核验账户 → `stats … --by-day --json-out` → 脚本写 xlsx。
76
85
 
77
86
  ---
78
87
 
@@ -28,7 +28,7 @@
28
28
  - **步骤**:
29
29
  1. 列表/数量:`list-accounts -m <媒体> --page-size 999 --json-out ./snap`,脚本读 `list-accounts-*.json` 的 `total` / `items[]`(**禁止**默认 20 条再翻页)。
30
30
  2. 单户余额:`balance -m <媒体> -a <mediaCustomerId>`。
31
- 3. 单户消耗:`stats -m <媒体> -a <mediaCustomerId> --start <S> --end <D>`(多账户对比走 **P3**)。
31
+ 3. 单户消耗:`stats -m <媒体> -a <mediaCustomerId> --start <S> --end <D>`(按日 Excel → **P4-DAILY**;多账户对比 → **P3**)。
32
32
  4. 激活账单:先取 `entityId` → `account-active-bills -m <媒体> --id <entityId> --json-out ./snap`。
33
33
  - **交付/确认**:多账户余额预警走 **P2**、消耗汇总走 **P3**。
34
34
 
@@ -21,6 +21,7 @@
21
21
  | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
22
22
  | `google-period-report.md` | **Google 周期分析报告(默认)**:终稿=HTML,四步流程 → `google-analysis render` 出 HTML |
23
23
  | `google-period-report-excel.md` | **仅用户要 Excel 时**(P4 定制 Sheet):先 `*.outline.txt` 后脚本写 xlsx;账户须 `-k` 核验,无 CLI excel 子命令 |
24
+ | `stats-daily-excel.md` | **P4-DAILY**:账户级按日 Excel(`stats --by-day`) |
24
25
  | `google-account-diagnosis-report.md` | Google 账户深度诊断(健康度/转化/结构等) |
25
26
  | `google-ads-diagnosis.md` | Google **广告诊断**完整纲要(HTML 区块、**每日趋势 2 位小数**、**每模块必填分析/建议**) |
26
27
  | `meta-period-report.md` / `.html` | **Meta(Facebook)周期报告(默认)**:四步流程 → `facebook-analysis render` 出 HTML,见 P4-FB |
@@ -129,7 +129,7 @@ siluzan-tso google-ads-diagnosis render \
129
129
 
130
130
  ### 6. `landingPageAnalysis.desktop` / `mobile` 禁止被整体覆盖丢弃
131
131
 
132
- - `collect` 产出的 `landingPageAnalysis` 结构固定为 `{ desktop: {score, firstContentfulPaint, firstMeaningfulPaint, speedIndex}, mobile: {...}, source?, simpleDiagnosis? }`;Agent 撰写终稿时**只能在这个对象上追加** `analysis`/`suggestions`,**禁止**把整个 `landingPageAnalysis` 替换成仅剩 `{analysis, suggestions}`(真实案例:账户 1029225790 报告因此丢失了全部 Lighthouse 测速数字)。
132
+ - `collect` 产出的 `landingPageAnalysis` 结构固定为 `{ desktop: {score, firstContentfulPaint, firstMeaningfulPaint, speedIndex}, mobile: {...}, source?, simpleDiagnosis? }`;Agent 撰写终稿时**只能在这个对象上追加** `analysis`/`suggestions`,**禁止**把整个 `landingPageAnalysis` 替换成仅剩 `{analysis, suggestions}`(否则会丢掉全部 Lighthouse 测速数字)。
133
133
  - `render` 会在同目录存在 `google-ads-diagnosis-collect.json` 时自动从 collect 恢复缺失/损坏的 `desktop`/`mobile`(保留 Agent 写的 `analysis`/`suggestions`),但**不能依赖这个兜底**——终稿应本就保留完整字段。
134
134
  - 若 `source` 为 `simple`/`none`,或 `desktop`/`mobile` 全 0(未测到),`analysis` 须说明数据未获取/已降级,并引用 `simpleDiagnosis`(若有);**禁止**编造评分,**禁止**把「数据缺失」写进 `keyIssues`。
135
135
  - **`analysis` / `suggestions` 只能谈着陆页**(测速、HTTPS、表单、追踪、加载体验)。**禁止**在 §07 写关键词花费/CPA/匹配类型等(那些属于 §08);render 会拦截「只谈关键词、不谈着陆页」的 analysis。
@@ -22,16 +22,9 @@
22
22
 
23
23
  ---
24
24
 
25
- ## 账户 ID 纪律(来自真实踩坑)
25
+ ## 账户 ID
26
26
 
27
- 用户给出的 `mediaCustomerId`(如 `2702960720`)**必须**与后续全流程一致:
28
-
29
- - **必须**:`list-accounts -m Google -k <该 ID>` 单户查询;404 / 空列表 → 告知用户 ID 不存在,**停止**,不得换号继续。
30
- - **禁止**:`list-accounts` 翻页 + `grep` 猜账户;禁止把列表里「看起来像」的另一账户(如 `9322098303`)当作用户要的 ID。
31
- - **禁止**:目录名用用户 ID、但 `google-analysis -a` 或脚本里读文件用另一个 ID。
32
- - **禁止**:`entityId` 与 `mediaCustomerId` 混用(见 `agent-conventions.md`)。
33
-
34
- 核验通过后,在报告首行/封面写:`统计区间:YYYY-MM-DD ~ YYYY-MM-DD(货币:XXX)`,且账户 ID 与 `list-accounts` 输出一致。
27
+ `list-accounts -m Google -k <id>` 核验;全流程用同一 `mediaCustomerId`(见 `agent-conventions.md`)。报告首行:`统计区间:YYYY-MM-DD ~ YYYY-MM-DD(货币:XXX)`。
35
28
 
36
29
  ---
37
30
 
@@ -16,6 +16,7 @@
16
16
  2. **统计区间**:`--start` / `--end`(用户未给齐时按 SKILL 反问;授权默认时可用「上一完整自然周」白名单并写明)。
17
17
  3. **询盘口径**:默认使用区间内 `conversions`(转化次数,来自 `stats` 或 `overview`)。若运营定义为某类转化,须在首段脚注说明(必要时补拉 `google-analysis --sections conversion-actions` 仅作说明)。
18
18
  4. **日期写法**:`2026.4.1`、`2026/4/1` 等先规范为 `YYYY-MM-DD`(如 `2026-04-01`)再传 `--start` / `--end`。
19
+ 5. **交付**:用户要可复制话术 → 优先纯文本;否则默认话术 + Excel。
19
20
 
20
21
  ---
21
22
 
@@ -0,0 +1,10 @@
1
+ # 账户按日投放数据 Excel(stats --by-day)
2
+
3
+ > 账户级**按日**消耗表 → Excel。≠ P4 / P4-FB / P1。无 CLI excel 子命令。
4
+
5
+ ```bash
6
+ siluzan-tso list-accounts -m <媒体> -k <id> --json-out ./snap
7
+ siluzan-tso stats -m <媒体> -a <id> --start <S> --end <E> --by-day --json-out ./snap/daily.json
8
+ ```
9
+
10
+ 脚本读 `items[]`(含 `date`、`spend`、`impressions`、`clicks`、`conversions` 等)写 xlsx。建议 Sheet:每日明细 + 汇总。ID 列文本;CTR/CPA 由脚本派生。
@@ -21,6 +21,7 @@
21
21
  | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
22
22
  | `google-period-report.md` | **Google 周期分析报告(默认)**:终稿=HTML,四步流程 → `google-analysis render` 出 HTML |
23
23
  | `google-period-report-excel.md` | **仅用户要 Excel 时**(P4 定制 Sheet):先 `*.outline.txt` 后脚本写 xlsx;账户须 `-k` 核验,无 CLI excel 子命令 |
24
+ | `stats-daily-excel.md` | **P4-DAILY**:账户级按日 Excel(`stats --by-day`) |
24
25
  | `google-account-diagnosis-report.md` | Google 账户深度诊断(健康度/转化/结构等) |
25
26
  | `google-ads-diagnosis.md` | Google **广告诊断**完整纲要(HTML 区块、**每日趋势 2 位小数**、**每模块必填分析/建议**) |
26
27
  | `meta-period-report.md` / `.html` | **Meta(Facebook)周期报告(默认)**:四步流程 → `facebook-analysis render` 出 HTML,见 P4-FB |
@@ -129,7 +129,7 @@ siluzan-tso google-ads-diagnosis render \
129
129
 
130
130
  ### 6. `landingPageAnalysis.desktop` / `mobile` 禁止被整体覆盖丢弃
131
131
 
132
- - `collect` 产出的 `landingPageAnalysis` 结构固定为 `{ desktop: {score, firstContentfulPaint, firstMeaningfulPaint, speedIndex}, mobile: {...}, source?, simpleDiagnosis? }`;Agent 撰写终稿时**只能在这个对象上追加** `analysis`/`suggestions`,**禁止**把整个 `landingPageAnalysis` 替换成仅剩 `{analysis, suggestions}`(真实案例:账户 1029225790 报告因此丢失了全部 Lighthouse 测速数字)。
132
+ - `collect` 产出的 `landingPageAnalysis` 结构固定为 `{ desktop: {score, firstContentfulPaint, firstMeaningfulPaint, speedIndex}, mobile: {...}, source?, simpleDiagnosis? }`;Agent 撰写终稿时**只能在这个对象上追加** `analysis`/`suggestions`,**禁止**把整个 `landingPageAnalysis` 替换成仅剩 `{analysis, suggestions}`(否则会丢掉全部 Lighthouse 测速数字)。
133
133
  - `render` 会在同目录存在 `google-ads-diagnosis-collect.json` 时自动从 collect 恢复缺失/损坏的 `desktop`/`mobile`(保留 Agent 写的 `analysis`/`suggestions`),但**不能依赖这个兜底**——终稿应本就保留完整字段。
134
134
  - 若 `source` 为 `simple`/`none`,或 `desktop`/`mobile` 全 0(未测到),`analysis` 须说明数据未获取/已降级,并引用 `simpleDiagnosis`(若有);**禁止**编造评分,**禁止**把「数据缺失」写进 `keyIssues`。
135
135
  - **`analysis` / `suggestions` 只能谈着陆页**(测速、HTTPS、表单、追踪、加载体验)。**禁止**在 §07 写关键词花费/CPA/匹配类型等(那些属于 §08);render 会拦截「只谈关键词、不谈着陆页」的 analysis。
@@ -22,16 +22,9 @@
22
22
 
23
23
  ---
24
24
 
25
- ## 账户 ID 纪律(来自真实踩坑)
25
+ ## 账户 ID
26
26
 
27
- 用户给出的 `mediaCustomerId`(如 `2702960720`)**必须**与后续全流程一致:
28
-
29
- - **必须**:`list-accounts -m Google -k <该 ID>` 单户查询;404 / 空列表 → 告知用户 ID 不存在,**停止**,不得换号继续。
30
- - **禁止**:`list-accounts` 翻页 + `grep` 猜账户;禁止把列表里「看起来像」的另一账户(如 `9322098303`)当作用户要的 ID。
31
- - **禁止**:目录名用用户 ID、但 `google-analysis -a` 或脚本里读文件用另一个 ID。
32
- - **禁止**:`entityId` 与 `mediaCustomerId` 混用(见 `agent-conventions.md`)。
33
-
34
- 核验通过后,在报告首行/封面写:`统计区间:YYYY-MM-DD ~ YYYY-MM-DD(货币:XXX)`,且账户 ID 与 `list-accounts` 输出一致。
27
+ `list-accounts -m Google -k <id>` 核验;全流程用同一 `mediaCustomerId`(见 `agent-conventions.md`)。报告首行:`统计区间:YYYY-MM-DD ~ YYYY-MM-DD(货币:XXX)`。
35
28
 
36
29
  ---
37
30
 
@@ -16,6 +16,7 @@
16
16
  2. **统计区间**:`--start` / `--end`(用户未给齐时按 SKILL 反问;授权默认时可用「上一完整自然周」白名单并写明)。
17
17
  3. **询盘口径**:默认使用区间内 `conversions`(转化次数,来自 `stats` 或 `overview`)。若运营定义为某类转化,须在首段脚注说明(必要时补拉 `google-analysis --sections conversion-actions` 仅作说明)。
18
18
  4. **日期写法**:`2026.4.1`、`2026/4/1` 等先规范为 `YYYY-MM-DD`(如 `2026-04-01`)再传 `--start` / `--end`。
19
+ 5. **交付**:用户要可复制话术 → 优先纯文本;否则默认话术 + Excel。
19
20
 
20
21
  ---
21
22
 
@@ -0,0 +1,10 @@
1
+ # 账户按日投放数据 Excel(stats --by-day)
2
+
3
+ > 账户级**按日**消耗表 → Excel。≠ P4 / P4-FB / P1。无 CLI excel 子命令。
4
+
5
+ ```bash
6
+ siluzan-tso list-accounts -m <媒体> -k <id> --json-out ./snap
7
+ siluzan-tso stats -m <媒体> -a <id> --start <S> --end <E> --by-day --json-out ./snap/daily.json
8
+ ```
9
+
10
+ 脚本读 `items[]`(含 `date`、`spend`、`impressions`、`clicks`、`conversions` 等)写 xlsx。建议 Sheet:每日明细 + 汇总。ID 列文本;CTR/CPA 由脚本派生。
@@ -9,7 +9,7 @@ $ErrorActionPreference = 'Stop'
9
9
  # -- Package info (injected at build time) ------------------------------------
10
10
  $PKG_NAME = 'siluzan-tso-cli'
11
11
  # PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
12
- $PKG_VERSION = '1.1.37-beta.11'
12
+ $PKG_VERSION = '1.1.37-beta.13'
13
13
  $CLI_BIN = 'siluzan-tso'
14
14
  $SKILL_LABEL = 'Siluzan TSO'
15
15
  $INSTALL_CMD = 'npm install -g siluzan-tso-cli@beta'
@@ -9,7 +9,7 @@ set -euo pipefail
9
9
  # -- Package info (injected at build time) ------------------------------------
10
10
  readonly PKG_NAME="siluzan-tso-cli"
11
11
  # PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
12
- readonly PKG_VERSION="1.1.37-beta.11"
12
+ readonly PKG_VERSION="1.1.37-beta.13"
13
13
  readonly CLI_BIN="siluzan-tso"
14
14
  readonly SKILL_LABEL="Siluzan TSO"
15
15
  readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.37-beta.11",
3
+ "version": "1.1.37-beta.13",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",