siluzan-tso-cli 1.1.30-beta.5 → 1.1.30-beta.7

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.
Files changed (37) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +282 -74
  3. package/dist/skill/SKILL.md +34 -34
  4. package/dist/skill/_meta.json +2 -2
  5. package/dist/skill/assets/campaign-create-template.md +1 -1
  6. package/dist/skill/references/README.md +35 -35
  7. package/dist/skill/references/accounts/accounts.md +10 -7
  8. package/dist/skill/references/accounts/currency.md +2 -2
  9. package/dist/skill/references/accounts/open-account-google-ui.md +1 -1
  10. package/dist/skill/references/analytics/account-analytics.md +2 -2
  11. package/dist/skill/references/analytics/keyword-planner-workflows.md +3 -3
  12. package/dist/skill/references/analytics/rag.md +4 -4
  13. package/dist/skill/references/analytics/website-diagnosis-guide.md +1 -1
  14. package/dist/skill/references/core/agent-conventions.md +4 -4
  15. package/dist/skill/references/core/playbooks.md +11 -11
  16. package/dist/skill/references/core/subagent-orchestration.md +5 -5
  17. package/dist/skill/references/core/tips.md +1 -1
  18. package/dist/skill/references/core/workflows.md +25 -25
  19. package/dist/skill/references/google-ads/google-ads-campaign-plan.md +18 -18
  20. package/dist/skill/references/google-ads/google-ads.md +4 -4
  21. package/dist/skill/references/google-ads/rules/README.md +1 -1
  22. package/dist/skill/references/operations/hosted-automation-monitoring-json.md +4 -4
  23. package/dist/skill/references/operations/hosted-automation-optimize-ab-winner.md +2 -2
  24. package/dist/skill/references/operations/hosted-automation-optimize-index.md +5 -5
  25. package/dist/skill/references/operations/hosted-automation-optimize-scale.md +1 -1
  26. package/dist/skill/references/operations/hosted-automation-optimize-weak-downbid.md +5 -5
  27. package/dist/skill/references/operations/hosted-automation-scenarios.md +4 -4
  28. package/dist/skill/references/operations/hosted-automation-self-control.md +7 -7
  29. package/dist/skill/references/operations/hosted-automation-user-catalog.md +12 -12
  30. package/dist/skill/references/report-templates/google-inquiry-analysis.md +2 -2
  31. package/dist/skill/references/report-templates/google-period-report-excel.md +3 -3
  32. package/dist/skill/report-templates/google-inquiry-analysis.md +2 -2
  33. package/dist/skill/report-templates/google-period-report-excel.md +3 -3
  34. package/dist/skill/report-templates/website-diagnosis-report.html +0 -25
  35. package/dist/skill/scripts/install.ps1 +1 -1
  36. package/dist/skill/scripts/install.sh +1 -1
  37. package/package.json +1 -1
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.30-beta.5),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.30-beta.7),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -4170,11 +4170,27 @@ function defaultDateRange() {
4170
4170
  start.setDate(start.getDate() - 6);
4171
4171
  return { startDate: fmt(start), endDate: fmt(end) };
4172
4172
  }
4173
+ function resolveAccountOverviewMediaRoute(cliMedia, accountMediaAccountType) {
4174
+ if (cliMedia === "MetaAd" && accountMediaAccountType === "FacebookAds") {
4175
+ return "FacebookAds";
4176
+ }
4177
+ return cliMedia;
4178
+ }
4179
+ function groupAccountIdsByOverviewRoute(cliMedia, accountIds, accountMediaTypes) {
4180
+ const groups = /* @__PURE__ */ new Map();
4181
+ for (const id of accountIds) {
4182
+ const route = resolveAccountOverviewMediaRoute(cliMedia, accountMediaTypes?.get(id));
4183
+ const list = groups.get(route) ?? [];
4184
+ list.push(id);
4185
+ groups.set(route, list);
4186
+ }
4187
+ return groups;
4188
+ }
4173
4189
  async function fetchBalanceMap(media, accountIds, config, startDate, endDate, verbose) {
4174
4190
  const result = /* @__PURE__ */ new Map();
4175
- if (accountIds.length === 0 || media === "MetaAd") return result;
4191
+ if (accountIds.length === 0) return result;
4176
4192
  try {
4177
- if (["Google", "Yandex", "TikTok", "BingV2"].includes(media)) {
4193
+ if (["Google", "Yandex", "TikTok", "BingV2", "MetaAd"].includes(media)) {
4178
4194
  const params = new URLSearchParams({ ids: accountIds.join("|") });
4179
4195
  const url = `${config.apiBaseUrl}/query/media-account/${media}/GetMediaAccountInfo?${params}`;
4180
4196
  const raw = await apiFetch2(
@@ -4284,39 +4300,87 @@ function parseGoogleAccountSpendOverviewRows(raw) {
4284
4300
  const items = Array.isArray(r.items) ? r.items : [];
4285
4301
  return items.map(fromDbItem).filter((row) => row !== null);
4286
4302
  }
4287
- async function fetchOverviewMap(media, accountIds, config, startDate, endDate, verbose) {
4303
+ async function fetchOverviewMap(route, accountIds, config, startDate, endDate, verbose) {
4288
4304
  const result = /* @__PURE__ */ new Map();
4289
- if (accountIds.length === 0 || media === "MetaAd") return result;
4305
+ if (accountIds.length === 0) return result;
4290
4306
  const range = defaultDateRange();
4291
4307
  const start = startDate ?? range.startDate;
4292
4308
  const end = endDate ?? range.endDate;
4293
4309
  try {
4294
- if (media === "Google") {
4295
- const params2 = new URLSearchParams({
4310
+ if (route === "Google") {
4311
+ const params = new URLSearchParams({
4296
4312
  startDate: start,
4297
4313
  endDate: end,
4298
4314
  mediaCustomerIds: accountIds.join("|")
4299
4315
  });
4300
- const url2 = `${config.apiBaseUrl}/report/media-account/google/account-spend-overview?${params2}`;
4301
- const raw2 = await apiFetch2(url2, config, {}, verbose);
4302
- const rows = parseGoogleAccountSpendOverviewRows(raw2);
4316
+ const url = `${config.apiBaseUrl}/report/media-account/google/account-spend-overview?${params}`;
4317
+ const raw = await apiFetch2(url, config, {}, verbose);
4318
+ const rows = parseGoogleAccountSpendOverviewRows(raw);
4303
4319
  for (const row of rows) {
4304
4320
  result.set(row.mediaAccountId, row);
4305
4321
  }
4306
4322
  return result;
4307
4323
  }
4308
- const params = new URLSearchParams({
4309
- period: "true",
4310
- startDate: start,
4311
- endDate: end,
4312
- mediaCustomerIds: accountIds.join(",")
4313
- });
4314
- const url = `${config.apiBaseUrl}/report/media-account/${media}/accountsoverview?${params}`;
4315
- const raw = await apiFetch2(url, config, {}, verbose);
4316
- const items = Array.isArray(raw) ? raw : [];
4317
- for (const item of items) {
4318
- if (item.mediaAccountId != null) {
4319
- result.set(String(item.mediaAccountId), item);
4324
+ const fetchLegacyBatch = async (ids) => {
4325
+ const batch = /* @__PURE__ */ new Map();
4326
+ const params = new URLSearchParams({
4327
+ period: "true",
4328
+ startDate: start,
4329
+ endDate: end,
4330
+ mediaCustomerIds: ids.join(",")
4331
+ });
4332
+ const url = `${config.apiBaseUrl}/report/media-account/${route}/accountsoverview?${params}`;
4333
+ const raw = await apiFetch2(url, config, {}, verbose);
4334
+ const items = Array.isArray(raw) ? raw : [];
4335
+ for (const item of items) {
4336
+ if (item.mediaAccountId != null) {
4337
+ batch.set(String(item.mediaAccountId), item);
4338
+ }
4339
+ }
4340
+ return batch;
4341
+ };
4342
+ const mergeOverview = (batch) => {
4343
+ for (const [k, v] of batch) result.set(k, v);
4344
+ };
4345
+ if (accountIds.length === 1) {
4346
+ mergeOverview(await fetchLegacyBatch(accountIds));
4347
+ return result;
4348
+ }
4349
+ try {
4350
+ const batch = await fetchLegacyBatch(accountIds);
4351
+ mergeOverview(batch);
4352
+ if (batch.size === 0) {
4353
+ for (const id of accountIds) {
4354
+ try {
4355
+ mergeOverview(await fetchLegacyBatch([id]));
4356
+ } catch (singleErr) {
4357
+ if (verbose) {
4358
+ process.stderr.write(
4359
+ `[fetchOverviewMap] \u9010\u6237\u91CD\u8BD5\u5931\u8D25 ${id}\uFF1A${singleErr instanceof Error ? singleErr.message : String(singleErr)}
4360
+ `
4361
+ );
4362
+ }
4363
+ }
4364
+ }
4365
+ }
4366
+ } catch (err) {
4367
+ if (verbose) {
4368
+ process.stderr.write(
4369
+ `[fetchOverviewMap] \u6279\u91CF\u5F02\u5E38\uFF0C\u9010\u6237\u91CD\u8BD5\uFF1A${err instanceof Error ? err.message : String(err)}
4370
+ `
4371
+ );
4372
+ }
4373
+ for (const id of accountIds) {
4374
+ try {
4375
+ mergeOverview(await fetchLegacyBatch([id]));
4376
+ } catch (singleErr) {
4377
+ if (verbose) {
4378
+ process.stderr.write(
4379
+ `[fetchOverviewMap] \u9010\u6237\u91CD\u8BD5\u5931\u8D25 ${id}\uFF1A${singleErr instanceof Error ? singleErr.message : String(singleErr)}
4380
+ `
4381
+ );
4382
+ }
4383
+ }
4320
4384
  }
4321
4385
  }
4322
4386
  } catch (err) {
@@ -4339,7 +4403,8 @@ var init_balance = __esm({
4339
4403
  "Yandex",
4340
4404
  "TikTok",
4341
4405
  "BingV2",
4342
- "Kwai"
4406
+ "Kwai",
4407
+ "MetaAd"
4343
4408
  ];
4344
4409
  }
4345
4410
  });
@@ -4373,16 +4438,25 @@ async function fetchBalanceAndOverviewSequential(opts) {
4373
4438
  const ids = chunks[chunkIdx];
4374
4439
  const oneBased = chunkIdx + 1;
4375
4440
  opts.onChunkStart?.(oneBased, chunks.length, ids);
4376
- const overviewChunk = await fetchOverviewMap(
4441
+ const routeGroups = groupAccountIdsByOverviewRoute(
4377
4442
  media,
4378
4443
  ids,
4379
- config,
4380
- startDate,
4381
- endDate,
4382
- verbose
4444
+ opts.accountMediaTypes
4383
4445
  );
4384
- opts.onOverviewDone?.(oneBased, chunks.length, ids, overviewChunk.size);
4385
- for (const [k, v] of overviewChunk) overviewMap.set(k, v);
4446
+ let chunkOverviewSize = 0;
4447
+ for (const [route, routeIds] of routeGroups) {
4448
+ const overviewChunk = await fetchOverviewMap(
4449
+ route,
4450
+ routeIds,
4451
+ config,
4452
+ startDate,
4453
+ endDate,
4454
+ verbose
4455
+ );
4456
+ chunkOverviewSize += overviewChunk.size;
4457
+ for (const [k, v] of overviewChunk) overviewMap.set(k, v);
4458
+ }
4459
+ opts.onOverviewDone?.(oneBased, chunks.length, ids, chunkOverviewSize);
4386
4460
  const balanceChunk = await fetchBalanceMap(
4387
4461
  media,
4388
4462
  ids,
@@ -4403,6 +4477,143 @@ var init_media_account_batch = __esm({
4403
4477
  }
4404
4478
  });
4405
4479
 
4480
+ // src/utils/media-account-lookup.ts
4481
+ function pickMediaCustomerName(items, mediaCustomerId) {
4482
+ const list = Array.isArray(items) ? items : [];
4483
+ const target = mediaCustomerId.trim();
4484
+ for (const item of list) {
4485
+ const cid = String(item.ma?.mediaCustomerId ?? "");
4486
+ if (cid === target) {
4487
+ const name2 = String(item.ma?.mediaCustomerName ?? "").trim();
4488
+ return name2 || null;
4489
+ }
4490
+ }
4491
+ return null;
4492
+ }
4493
+ function metaAccountIdDigits(raw) {
4494
+ const t = raw.trim();
4495
+ if (t.startsWith("act_")) return t.slice(4);
4496
+ return t;
4497
+ }
4498
+ function idsMatch(a, b) {
4499
+ return metaAccountIdDigits(a) === metaAccountIdDigits(b);
4500
+ }
4501
+ async function lookupFromMediaAccountQuery(apiBaseUrl, config, mediaType, mediaCustomerId, verbose) {
4502
+ const params = new URLSearchParams({
4503
+ MediaType: mediaType,
4504
+ mediaAccountState: "Approved,Linked",
4505
+ pageNo: "1",
4506
+ pageSize: "20",
4507
+ mediaCustomerId: mediaCustomerId.trim()
4508
+ });
4509
+ const data = await apiFetch2(
4510
+ `${apiBaseUrl}/query/media-account/?${params}`,
4511
+ config,
4512
+ {},
4513
+ verbose
4514
+ );
4515
+ return pickMediaCustomerName(data, mediaCustomerId);
4516
+ }
4517
+ async function lookupMediaCustomerName(apiBaseUrl, config, mediaType, mediaCustomerId, verbose = false) {
4518
+ const id = mediaCustomerId.trim();
4519
+ if (!id) return null;
4520
+ if (mediaType === "Google" || mediaType === "Yandex") {
4521
+ return lookupFromMediaAccountQuery(apiBaseUrl, config, mediaType, id, verbose);
4522
+ }
4523
+ return null;
4524
+ }
4525
+ async function lookupMetaMediaAccountTypes(config, accountIds, verbose = false) {
4526
+ const result = /* @__PURE__ */ new Map();
4527
+ const pending = new Set(accountIds.map((id) => id.trim()).filter(Boolean));
4528
+ if (pending.size === 0) return result;
4529
+ const CHUNK = 50;
4530
+ const ids = [...pending];
4531
+ for (let i = 0; i < ids.length; i += CHUNK) {
4532
+ const chunk = ids.slice(i, i + CHUNK);
4533
+ const params = new URLSearchParams({
4534
+ MediaTypes: "MetaAd|FacebookAds",
4535
+ advStatus: "",
4536
+ mediaAccountState: "Approved,Linked",
4537
+ isForce: "false",
4538
+ pageNum: "1",
4539
+ pageSize: String(Math.max(20, chunk.length)),
4540
+ mediaCustomerIds: chunk.join(",")
4541
+ });
4542
+ try {
4543
+ const data = await apiFetch2(
4544
+ `${config.apiBaseUrl}/query/media-account/SearchMediaAcountByCriteria?${params}`,
4545
+ config,
4546
+ {},
4547
+ verbose
4548
+ );
4549
+ for (const item of data?.mas ?? []) {
4550
+ const ma = item.ma ?? {};
4551
+ const cid = String(ma.mediaCustomerId ?? "");
4552
+ const typ = String(ma.mediaAccountType ?? "").trim();
4553
+ if (!cid || !typ) continue;
4554
+ for (const inputId of chunk) {
4555
+ if (idsMatch(inputId, cid)) {
4556
+ result.set(inputId, typ);
4557
+ pending.delete(inputId);
4558
+ }
4559
+ }
4560
+ }
4561
+ } catch {
4562
+ }
4563
+ }
4564
+ for (const inputId of [...pending]) {
4565
+ for (const idCandidate of [inputId, `act_${metaAccountIdDigits(inputId)}`]) {
4566
+ const params = new URLSearchParams({
4567
+ MediaTypes: "MetaAd|FacebookAds",
4568
+ advStatus: "",
4569
+ mediaAccountState: "Approved,Linked",
4570
+ isForce: "false",
4571
+ pageNum: "1",
4572
+ pageSize: "20",
4573
+ mediaCustomerIds: idCandidate
4574
+ });
4575
+ try {
4576
+ const data = await apiFetch2(
4577
+ `${config.apiBaseUrl}/query/media-account/SearchMediaAcountByCriteria?${params}`,
4578
+ config,
4579
+ {},
4580
+ verbose
4581
+ );
4582
+ for (const item of data?.mas ?? []) {
4583
+ const ma = item.ma ?? {};
4584
+ const cid = String(ma.mediaCustomerId ?? "");
4585
+ if (idsMatch(inputId, cid)) {
4586
+ result.set(inputId, String(ma.mediaAccountType ?? "FacebookAds"));
4587
+ pending.delete(inputId);
4588
+ break;
4589
+ }
4590
+ }
4591
+ } catch {
4592
+ }
4593
+ if (!pending.has(inputId)) break;
4594
+ }
4595
+ if (!result.has(inputId)) {
4596
+ result.set(inputId, "FacebookAds");
4597
+ }
4598
+ }
4599
+ return result;
4600
+ }
4601
+ function buildAccountMediaTypesMap(items) {
4602
+ const map = /* @__PURE__ */ new Map();
4603
+ for (const item of items) {
4604
+ const id = item.ma?.mediaCustomerId;
4605
+ const typ = item.ma?.mediaAccountType;
4606
+ if (id && typ) map.set(String(id), String(typ));
4607
+ }
4608
+ return map;
4609
+ }
4610
+ var init_media_account_lookup = __esm({
4611
+ "src/utils/media-account-lookup.ts"() {
4612
+ "use strict";
4613
+ init_auth();
4614
+ }
4615
+ });
4616
+
4406
4617
  // src/commands/accounts-digest/list-fetch.ts
4407
4618
  async function fetchAccountsByMedia(media, config, opts) {
4408
4619
  const cfg = DIGEST_PLATFORM_CONFIG[media];
@@ -4491,6 +4702,17 @@ var init_list_fetch = __esm({
4491
4702
  isForce: false
4492
4703
  },
4493
4704
  responseType: "mas"
4705
+ },
4706
+ MetaAd: {
4707
+ path: "/query/media-account/SearchMediaAcountByCriteria",
4708
+ pageParam: "pageNum",
4709
+ fixedParams: {
4710
+ MediaTypes: "MetaAd|FacebookAds",
4711
+ advStatus: "",
4712
+ mediaAccountState: "Approved,Linked",
4713
+ isForce: false
4714
+ },
4715
+ responseType: "mas"
4494
4716
  }
4495
4717
  };
4496
4718
  }
@@ -4509,7 +4731,7 @@ async function runAccountsDigest(opts) {
4509
4731
  console.error(
4510
4732
  `
4511
4733
  \u274C accounts-digest \u6682\u4E0D\u652F\u6301\u5A92\u4F53\uFF1A${opts.media}
4512
- \u53EF\u9009\uFF1A${BALANCE_SUPPORTED_MEDIA.join(" | ")}\uFF08MetaAd \u65E0\u4F59\u989D/\u6D88\u8017\u6C47\u603B\u63A5\u53E3\uFF09
4734
+ \u53EF\u9009\uFF1A${BALANCE_SUPPORTED_MEDIA.join(" | ")}
4513
4735
  `
4514
4736
  );
4515
4737
  process.exit(1);
@@ -4523,12 +4745,16 @@ async function runAccountsDigest(opts) {
4523
4745
  let accountsList;
4524
4746
  let scannedFromList = false;
4525
4747
  if (filterSet.size > 0) {
4748
+ let mediaTypes = /* @__PURE__ */ new Map();
4749
+ if (media === "MetaAd") {
4750
+ mediaTypes = await lookupMetaMediaAccountTypes(config, filterIds, opts.verbose);
4751
+ }
4526
4752
  accountsList = filterIds.map((id) => ({
4527
4753
  ma: {
4528
4754
  entityId: "",
4529
4755
  mediaCustomerId: id,
4530
4756
  mediaCustomerName: null,
4531
- mediaAccountType: media,
4757
+ mediaAccountType: mediaTypes.get(id) ?? media,
4532
4758
  invalidOAuthToken: false
4533
4759
  },
4534
4760
  accepted: true,
@@ -4566,6 +4792,7 @@ async function runAccountsDigest(opts) {
4566
4792
  startDate: opts.startDate,
4567
4793
  endDate: opts.endDate,
4568
4794
  verbose: opts.verbose,
4795
+ accountMediaTypes: buildAccountMediaTypesMap(accountsList),
4569
4796
  onChunkStart: (chunkIdx, totalChunks, ids) => {
4570
4797
  process.stderr.write(
4571
4798
  ` \u2192 [\u6D88\u8017] \u7B2C ${chunkIdx}/${totalChunks} \u6279\uFF08${ids.length} \u6237\uFF09\u2026
@@ -4725,6 +4952,7 @@ var init_run = __esm({
4725
4952
  init_cli_table();
4726
4953
  init_currency_display();
4727
4954
  init_media_account_batch();
4955
+ init_media_account_lookup();
4728
4956
  init_list_fetch();
4729
4957
  }
4730
4958
  });
@@ -4735,7 +4963,7 @@ function register10(program2) {
4735
4963
  "\u591A\u8D26\u6237\u6295\u653E\u753B\u50CF\uFF1A\u4E00\u6761\u547D\u4EE4\u62FF\u9F50\u6D88\u8017/\u70B9\u51FB/\u8F6C\u5316/\u4F59\u989D/\u6D3E\u751F\u6307\u6807\uFF08CTR/CPC/CPA\uFF09\uFF0C\u66FF\u4EE3 AI \u9010\u8D26\u6237 `list-accounts` + `stats` \u7684\u7F16\u6392\u3002\u5178\u578B\u573A\u666F\uFF1A\u300C\u8FD9 10 \u4E2A\u8D26\u6237\uFF08\u6216\u67D0\u5A92\u4F53\u5168\u90E8\u8D26\u6237\uFF09\u5E2E\u6211\u6574\u7406\u4E00\u4E0B\u300D\u3002"
4736
4964
  ).requiredOption(
4737
4965
  "-m, --media <type>",
4738
- "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | BingV2 | Kwai\uFF08MetaAd \u65E0\u6D88\u8017\u6C47\u603B\u63A5\u53E3\uFF09"
4966
+ "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | MetaAd | BingV2 | Kwai"
4739
4967
  ).option(
4740
4968
  "-a, --accounts <ids>",
4741
4969
  "\u6307\u5B9A\u8D26\u6237 mediaCustomerId\uFF0C\u9017\u53F7\u5206\u9694\uFF1B\u7559\u7A7A\u5219\u626B\u63CF\u8BE5\u5A92\u4F53\u4E0B\u5168\u90E8\u8D26\u6237"
@@ -105320,6 +105548,7 @@ init_balance();
105320
105548
  init_cli_table();
105321
105549
  init_currency_display();
105322
105550
  init_media_account_batch();
105551
+ init_media_account_lookup();
105323
105552
 
105324
105553
  // src/commands/balance-scan/list-fetch.ts
105325
105554
  init_auth();
@@ -105369,6 +105598,17 @@ var SCAN_PLATFORM_CONFIG = {
105369
105598
  isForce: false
105370
105599
  },
105371
105600
  responseType: "mas"
105601
+ },
105602
+ MetaAd: {
105603
+ path: "/query/media-account/SearchMediaAcountByCriteria",
105604
+ pageParam: "pageNum",
105605
+ fixedParams: {
105606
+ MediaTypes: "MetaAd|FacebookAds",
105607
+ advStatus: "",
105608
+ mediaAccountState: "Approved,Linked",
105609
+ isForce: false
105610
+ },
105611
+ responseType: "mas"
105372
105612
  }
105373
105613
  };
105374
105614
  async function fetchOnePage(media, page, pageSize, config, verbose) {
@@ -105472,7 +105712,7 @@ async function runBalanceScan(opts) {
105472
105712
  console.error(
105473
105713
  `
105474
105714
  \u274C balance scan \u6682\u4E0D\u652F\u6301\u5A92\u4F53\uFF1A${opts.media}
105475
- \u53EF\u9009\uFF1A${BALANCE_SUPPORTED_MEDIA.join(" | ")}\uFF08MetaAd \u63A5\u53E3\u672A\u5F00\u653E\u4F59\u989D\u67E5\u8BE2\uFF09
105715
+ \u53EF\u9009\uFF1A${BALANCE_SUPPORTED_MEDIA.join(" | ")}
105476
105716
  `
105477
105717
  );
105478
105718
  process.exit(1);
@@ -105489,12 +105729,16 @@ async function runBalanceScan(opts) {
105489
105729
  let allItems;
105490
105730
  let total;
105491
105731
  if (isSubset) {
105732
+ let mediaTypes = /* @__PURE__ */ new Map();
105733
+ if (media === "MetaAd") {
105734
+ mediaTypes = await lookupMetaMediaAccountTypes(config, filterIds, opts.verbose);
105735
+ }
105492
105736
  allItems = filterIds.map((id) => ({
105493
105737
  ma: {
105494
105738
  entityId: "",
105495
105739
  mediaCustomerId: id,
105496
105740
  mediaCustomerName: null,
105497
- mediaAccountType: media,
105741
+ mediaAccountType: mediaTypes.get(id) ?? media,
105498
105742
  invalidOAuthToken: false
105499
105743
  },
105500
105744
  accepted: true,
@@ -105532,6 +105776,7 @@ async function runBalanceScan(opts) {
105532
105776
  accountIds: validIds,
105533
105777
  config,
105534
105778
  verbose: opts.verbose,
105779
+ accountMediaTypes: buildAccountMediaTypesMap(allItems),
105535
105780
  onChunkStart: (chunkIdx, totalChunks, ids) => {
105536
105781
  process.stderr.write(
105537
105782
  ` \u2192 [\u8FD17\u65E5\u6D88\u8017] \u7B2C ${chunkIdx}/${totalChunks} \u6279\u8BF7\u6C42\u4E2D\uFF08${ids.length} \u6237\uFF09\u2026
@@ -105691,7 +105936,7 @@ function register9(program2) {
105691
105936
  "\u4E00\u952E\u626B\u63CF\u67D0\u5A92\u4F53\u4E0B\u6240\u6709\u8D26\u6237\u7684\u4F59\u989D\uFF0C\u7B5B\u51FA\u7EED\u822A\u5929\u6570\u4E0D\u8DB3\u7684\u8D26\u6237\u3002\u66FF\u4EE3\u5FAA\u73AF\u8C03\u7528 balance \u7684\u4F20\u7EDF\u505A\u6CD5\uFF0C\u5178\u578B\u573A\u666F\uFF1A\u300C\u5E2E\u6211\u627E\u51FA 117 \u4E2A Bing \u8D26\u6237\u91CC\u4F59\u989D\u4E0D\u8DB3 7 \u5929\u7684\u300D\u3002"
105692
105937
  ).requiredOption(
105693
105938
  "-m, --media <type>",
105694
- "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | BingV2 | Kwai\uFF08MetaAd \u63A5\u53E3\u672A\u5F00\u653E\u4F59\u989D\u67E5\u8BE2\uFF09"
105939
+ "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | MetaAd | BingV2 | Kwai"
105695
105940
  ).option(
105696
105941
  "-a, --accounts <ids>",
105697
105942
  "\u6307\u5B9A mediaCustomerId\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF1B\u8DF3\u8FC7\u6E05\u5355\u7FFB\u9875\uFF0C\u62C9\u53D6\u540E\u5168\u90E8\u8F93\u51FA\uFF08\u4E0D\u6309\u9608\u503C\u8FC7\u6EE4\uFF09"
@@ -111664,46 +111909,8 @@ function tryLoadCampaignCreateConfig(configFile) {
111664
111909
  return stripMetaKeys(raw);
111665
111910
  }
111666
111911
 
111667
- // src/utils/media-account-lookup.ts
111668
- init_auth();
111669
- function pickMediaCustomerName(items, mediaCustomerId) {
111670
- const list = Array.isArray(items) ? items : [];
111671
- const target = mediaCustomerId.trim();
111672
- for (const item of list) {
111673
- const cid = String(item.ma?.mediaCustomerId ?? "");
111674
- if (cid === target) {
111675
- const name2 = String(item.ma?.mediaCustomerName ?? "").trim();
111676
- return name2 || null;
111677
- }
111678
- }
111679
- return null;
111680
- }
111681
- async function lookupFromMediaAccountQuery(apiBaseUrl, config, mediaType, mediaCustomerId, verbose) {
111682
- const params = new URLSearchParams({
111683
- MediaType: mediaType,
111684
- mediaAccountState: "Approved,Linked",
111685
- pageNo: "1",
111686
- pageSize: "20",
111687
- mediaCustomerId: mediaCustomerId.trim()
111688
- });
111689
- const data = await apiFetch2(
111690
- `${apiBaseUrl}/query/media-account/?${params}`,
111691
- config,
111692
- {},
111693
- verbose
111694
- );
111695
- return pickMediaCustomerName(data, mediaCustomerId);
111696
- }
111697
- async function lookupMediaCustomerName(apiBaseUrl, config, mediaType, mediaCustomerId, verbose = false) {
111698
- const id = mediaCustomerId.trim();
111699
- if (!id) return null;
111700
- if (mediaType === "Google" || mediaType === "Yandex") {
111701
- return lookupFromMediaAccountQuery(apiBaseUrl, config, mediaType, id, verbose);
111702
- }
111703
- return null;
111704
- }
111705
-
111706
111912
  // src/commands/ad/campaign-customer-name.ts
111913
+ init_media_account_lookup();
111707
111914
  var CampaignCustomerNameResolveError = class extends Error {
111708
111915
  constructor(accountId) {
111709
111916
  super(
@@ -113932,6 +114139,7 @@ async function runAdGeoSetBid(opts) {
113932
114139
 
113933
114140
  // src/commands/ad/ai-creation.ts
113934
114141
  init_auth();
114142
+ init_media_account_lookup();
113935
114143
  init_cli_json_snapshot();
113936
114144
  init_cli_table();
113937
114145
 
@@ -65,55 +65,55 @@ siluzan-tso -h # 查看帮助
65
65
 
66
66
  | 用户意图(关键词) | 工作流 | 必读文档 |
67
67
  | ------------------ | ------ | -------- |
68
- | 账户列表 / 有哪些 / 有多少 / 列出全部某媒体 | W1 | `accounts/accounts.md`(§ list-accounts 意图速查) |
69
- | 单户实时余额 | W1 | `accounts/accounts.md`(balance) |
70
- | 多账户余额 / 续航不足预警 | **P2** | `accounts/accounts.md`(balance-scan) |
71
- | 单户消耗 / 投放数据 | W1 / **P1** | `accounts/accounts.md`(stats) |
72
- | 多账户消耗/对比汇总 | **P3** | `accounts/accounts.md`(accounts-digest) |
73
- | 激活/充值账单明细 | W1 | `accounts/accounts.md`(account-active-bills) |
74
- | 开户(六大媒体)/ 开户进度 | W2 | `accounts/open-account-by-media.md`(**首次须列全必填项**;Google 加 `accounts/open-account-google-ui.md`) |
75
- | 账户权限:分享/取消/解绑/重授权/MCC/BC/BM/关闭/提现/邮箱授权 | W9 | `accounts/accounts.md`(account 子命令) |
76
- | 充值 / 钱包 | W8 | `accounts/finance.md`(recharge 引导网页) |
77
- | 转账记录 / 账户间转账 | W8 | `accounts/finance.md`(transfer) |
78
- | 发票 / 开票 / 发票抬头 | W8 | `accounts/finance.md`(invoice / invoice-info) |
79
- | 审计 / 误操作恢复 | — | `accounts/write-audit-restore.md` |
68
+ | 账户列表 / 有哪些 / 有多少 / 列出全部某媒体 | W1 | `references/accounts/accounts.md`(§ list-accounts 意图速查) |
69
+ | 单户实时余额 | W1 | `references/accounts/accounts.md`(balance) |
70
+ | 多账户余额 / 续航不足预警 | **P2** | `references/accounts/accounts.md`(balance-scan) |
71
+ | 单户消耗 / 投放数据 | W1 / **P1** | `references/accounts/accounts.md`(stats) |
72
+ | 多账户消耗/对比汇总 | **P3** | `references/accounts/accounts.md`(accounts-digest) |
73
+ | 激活/充值账单明细 | W1 | `references/accounts/accounts.md`(account-active-bills) |
74
+ | 开户(六大媒体)/ 开户进度 | W2 | `references/accounts/open-account-by-media.md`(**首次须列全必填项**;Google 加 `references/accounts/open-account-google-ui.md`) |
75
+ | 账户权限:分享/取消/解绑/重授权/MCC/BC/BM/关闭/提现/邮箱授权 | W9 | `references/accounts/accounts.md`(account 子命令) |
76
+ | 充值 / 钱包 | W8 | `references/accounts/finance.md`(recharge 引导网页) |
77
+ | 转账记录 / 账户间转账 | W8 | `references/accounts/finance.md`(transfer) |
78
+ | 发票 / 开票 / 发票抬头 | W8 | `references/accounts/finance.md`(invoice / invoice-info) |
79
+ | 审计 / 误操作恢复 | — | `references/accounts/write-audit-restore.md` |
80
80
 
81
81
  ### Google 广告投放
82
82
 
83
83
  | 用户意图(关键词) | 工作流 | 必读文档 |
84
84
  | ------------------ | ------ | -------- |
85
- | 新建搜索系列 / 出投放方案 / **根据官网·网站·URL 生成 Google 搜索广告** / 搜索广告文案·关键词·**表格** / 写搜索广告计划 | **W3** | `google-ads/google-ads-campaign-plan.md` + **`assets/campaign-create-template.json`**(结构真相源,先 Read)+ `assets/campaign-create-template.md`(字段说明);表格交付读 `google-ads/rules/google-ads-launch-plan-template.md`。**≠ P8 网站诊断 / ≠ P9 市场分析 / ≠ W5 仅拓词** |
86
- | 广告系列/组/广告/关键词 CRUD / 拒审 | W3 | `google-ads/google-ads.md` |
87
- | PMax 系列 | W3 | **`assets/pmax-create-template.json`**(先 Read)+ `assets/pmax-create-template.md` + `google-ads/pmax-api.md` |
88
- | AI 智投草稿 list/get/update/publish | W4 | `google-ads/google-ads.md`(§ ad batch) |
89
- | 拓词 / keyword / 关键词规划 | W5 | `analytics/keyword-planner-workflows.md`(客户背景先 `analytics/rag.md`) |
90
- | AI 广告优化记录 / 建议 | W6 | `operations/optimize.md` |
91
- | 优化/合规 SOP | W3 / W6 | `google-ads/rules/README.md` |
85
+ | 新建搜索系列 / 出投放方案 / **根据官网·网站·URL 生成 Google 搜索广告** / 搜索广告文案·关键词·**表格** / 写搜索广告计划 | **W3** | `references/google-ads/google-ads-campaign-plan.md` + **`assets/campaign-create-template.json`**(结构真相源,先 Read)+ `assets/campaign-create-template.md`(字段说明);表格交付读 `references/google-ads/rules/google-ads-launch-plan-template.md`。**≠ P8 网站诊断 / ≠ P9 市场分析 / ≠ W5 仅拓词** |
86
+ | 广告系列/组/广告/关键词 CRUD / 拒审 | W3 | `references/google-ads/google-ads.md` |
87
+ | PMax 系列 | W3 | **`assets/pmax-create-template.json`**(先 Read)+ `assets/pmax-create-template.md` + `references/google-ads/pmax-api.md` |
88
+ | AI 智投草稿 list/get/update/publish | W4 | `references/google-ads/google-ads.md`(§ ad batch) |
89
+ | 拓词 / keyword / 关键词规划 | W5 | `references/analytics/keyword-planner-workflows.md`(客户背景先 `references/analytics/rag.md`) |
90
+ | AI 广告优化记录 / 建议 | W6 | `references/operations/optimize.md` |
91
+ | 优化/合规 SOP | W3 / W6 | `references/google-ads/rules/README.md` |
92
92
 
93
93
  ### 分析与报告(CLI 拉数 + Agent 撰稿)
94
94
 
95
95
  | 用户意图(关键词) | 工作流 | 必读文档 |
96
96
  | ------------------ | ------ | -------- |
97
- | 单账户投放画像 / **Google 广告账户诊断** / 账户健康检查 / 投放诊断报告 / 账户体检(**含账户 ID**) | **P1** | `analytics/account-analytics.md`;完整 HTML 诊断加 `references/report-templates/google-ads-diagnosis.md` |
98
- | Google 账户 **周期** / **月度** / **季度** 报告 / 月报(**非** OKKI / **非** 询盘 / **非** 强调健康诊断) | **P4** | `references/report-templates/google-period-report.md` + `analytics/account-analytics.md`;要 **Excel** 加 `references/report-templates/google-period-report-excel.md` |
99
- | Meta/Facebook 周期或诊断报告 | **P4-FB** | `references/report-templates/meta-period-report.md` + `assets/meta-period-report-rules.md` + `analytics/facebook-analysis-guide.md`;要 Excel 加 `references/report-templates/meta-period-report-excel.md` |
100
- | TikTok / Bing 周期报告 | **P4** | `analytics/account-analytics.md`(report tiktok-*/bing-*)+ 对应 `references/report-templates/*-period-report.md` |
101
- | 多账户 × 多维度批处理 | **P5** | `analytics/google-analysis-batch.md` + `analytics/account-analytics.md` |
97
+ | 单账户投放画像 / **Google 广告账户诊断** / 账户健康检查 / 投放诊断报告 / 账户体检(**含账户 ID**) | **P1** | `references/analytics/account-analytics.md`;完整 HTML 诊断加 `references/report-templates/google-ads-diagnosis.md` |
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
+ | 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
+ | TikTok / Bing 周期报告 | **P4** | `references/analytics/account-analytics.md`(report tiktok-*/bing-*)+ 对应 `references/report-templates/*-period-report.md` |
101
+ | 多账户 × 多维度批处理 | **P5** | `references/analytics/google-analysis-batch.md` + `references/analytics/account-analytics.md` |
102
102
  | OKKI 周报 | **P6** | `references/report-templates/okki-weekly-google-client.md`(全文) |
103
- | Google 询盘分析 | **P7** | `references/report-templates/google-inquiry-analysis.md`(全文)+ `analytics/geo-continents.json` |
104
- | 网站诊断 / **网站诊断·检测·监测报告** / 落地页报告 / 网站质量评分 / 官网体检(**要 URL**) | **P8** | `analytics/website-diagnosis-guide.md` + `assets/website-diagnosis-rules.md`(**必须先** `website-diagnosis collect` + `render`,**默认交付 HTML**;**≠ P9 ≠ W3**) |
105
- | 市场分析 / **行业分析** / **行业分析报告** / 市场调查 / 生成 **XX 行业** 报告(如电商)/ 战略市场报告 / KA 市场报告 | **P9** | `analytics/market-analysis-guide.md` + `assets/market-analysis-rules.md`(**必须先** `market-analysis collect` + `render`,**默认交付 HTML**;**禁止**纯 WebSearch 交 Markdown 终稿;**≠ P8**) |
106
- | TSO 平台优化报告:列表/生成/推送 | W7 | `analytics/reporting.md` |
107
- | RAG 知识库检索 | W5 | `analytics/rag.md` |
103
+ | Google 询盘分析 | **P7** | `references/report-templates/google-inquiry-analysis.md`(全文)+ `references/analytics/geo-continents.json` |
104
+ | 网站诊断 / **网站诊断·检测·监测报告** / 落地页报告 / 网站质量评分 / 官网体检(**要 URL**) | **P8** | `references/analytics/website-diagnosis-guide.md` + `assets/website-diagnosis-rules.md`(**必须先** `website-diagnosis collect` + `render`,**默认交付 HTML**;**≠ P9 ≠ W3**) |
105
+ | 市场分析 / **行业分析** / **行业分析报告** / 市场调查 / 生成 **XX 行业** 报告(如电商)/ 战略市场报告 / KA 市场报告 | **P9** | `references/analytics/market-analysis-guide.md` + `assets/market-analysis-rules.md`(**必须先** `market-analysis collect` + `render`,**默认交付 HTML**;**禁止**纯 WebSearch 交 Markdown 终稿;**≠ P8**) |
106
+ | TSO 平台优化报告:列表/生成/推送 | W7 | `references/analytics/reporting.md` |
107
+ | RAG 知识库检索 | W5 | `references/analytics/rag.md` |
108
108
 
109
109
  ### 工具与运营
110
110
 
111
111
  | 用户意图(关键词) | 工作流 | 必读文档 |
112
112
  | ------------------ | ------ | -------- |
113
- | 智能预警:创建/查询/启停/触发记录 | W10 | `operations/forewarning.md` |
114
- | TikTok / Meta 线索表单 | W11 | `operations/clue.md` |
115
- | 日/周巡检 | W12 | `core/workflows.md`(W12)+ `accounts/accounts.md` |
116
- | 宿主编排 / 投放自控 / 异常监控 / 自动优化 | — | `operations/hosted-automation-user-catalog.md`(索引)→ `operations/hosted-automation-*.md` |
113
+ | 智能预警:创建/查询/启停/触发记录 | W10 | `references/operations/forewarning.md` |
114
+ | TikTok / Meta 线索表单 | W11 | `references/operations/clue.md` |
115
+ | 日/周巡检 | W12 | `references/core/workflows.md`(W12)+ `references/accounts/accounts.md` |
116
+ | 宿主编排 / 投放自控 / 异常监控 / 自动优化 | — | `references/operations/hosted-automation-user-catalog.md`(索引)→ `operations/hosted-automation-*.md` |
117
117
 
118
118
  ---
119
119
 
@@ -132,4 +132,4 @@ siluzan-tso -h # 查看帮助
132
132
  | 金额、ID、字段口径一致 | 触达(钉钉/飞书)与工单 |
133
133
  | references 与命令对齐 | 批处理循环、限速、重试;**可选** subagent 并行 |
134
134
 
135
- 详见 `operations/hosted-automation-self-control.md`、`operations/hosted-automation-monitoring-json.md`、`operations/hosted-automation-optimize-index.md`。
135
+ 详见 `references/operations/hosted-automation-self-control.md`、`references/operations/hosted-automation-monitoring-json.md`、`references/operations/hosted-automation-optimize-index.md`。
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.30-beta.5",
4
- "publishedAt": 1782370125040
3
+ "version": "1.1.30-beta.7",
4
+ "publishedAt": 1782384217103
5
5
  }
@@ -37,7 +37,7 @@ JSON 模板:同目录 [`campaign-create-template.json`](campaign-create-templa
37
37
 
38
38
  | 命令 | 支持 `--json-out`? | 推荐用法 |
39
39
  | -------------------------------- | :-----------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
40
- | `ad campaign-validate` | **是** | **推荐** `--json-out ./snap-campaign`(落盘 `ad-campaign-validate-<account>.json`,含 `lengthViolations`);。人读:`--config-file` 即可;词面规范化:`--write-normalized`。超长勿自动截断,见 `google-ads/google-ads-campaign-plan.md` § 超长人工确认 |
40
+ | `ad campaign-validate` | **是** | **推荐** `--json-out ./snap-campaign`(落盘 `ad-campaign-validate-<account>.json`,含 `lengthViolations`);。人读:`--config-file` 即可;词面规范化:`--write-normalized`。超长勿自动截断,见 `references/google-ads/google-ads-campaign-plan.md` § 超长人工确认 |
41
41
  | `ad campaign-create` | **是** | 落盘任务响应:`--json-out ./snap-campaign`; |
42
42
  | `ad batch get` / `ad batch diff` | **是** | 轮询与 diff 结果落盘,见 `references/core/tips.md` |
43
43