siluzan-tso-cli 1.1.30-beta.6 → 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.
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.6),供内部测试使用。正式发布后安装命令将改为 `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
 
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.30-beta.6",
4
- "publishedAt": 1782372193290
3
+ "version": "1.1.30-beta.7",
4
+ "publishedAt": 1782384217103
5
5
  }
@@ -128,7 +128,7 @@ siluzan-tso balance -m <媒体类型> -a <账户ID列表>
128
128
 
129
129
  | 选项 | 说明 |
130
130
  | ---------------------- | ------------------------------------------------------------------------------------ |
131
- | `-m, --media <type>` | 媒体类型(必填) |
131
+ | `-m, --media <type>` | 媒体类型(必填):`Google \| TikTok \| Yandex \| MetaAd \| BingV2 \| Kwai`(MetaAd 走 `GetMediaAccountInfo`,余额字段为 `spend_cap`) |
132
132
  | `-a, --accounts <ids>` | 账户 `mediaCustomerId`(数字 ID),多个用逗号分隔(必填)。**注意:不是 `entityId`** |
133
133
  | `--json-out` | 输出原始 JSON;不支持或查询失败时 stdout 为 `{"ok":false,"error":"..."}` |
134
134
 
@@ -141,6 +141,9 @@ siluzan-tso balance -m Google -a 6326027735
141
141
  # 查询多个 TikTok 账户余额
142
142
  siluzan-tso balance -m TikTok -a 1234567890,9876543210
143
143
 
144
+ # 查询 Meta 广告账户余额
145
+ siluzan-tso balance -m MetaAd -a <mediaCustomerId>
146
+
144
147
  # JSON 输出,供脚本使用
145
148
  siluzan-tso balance -m Google -a 6326027735 --json-out ./snap
146
149
  ```
@@ -153,7 +156,7 @@ siluzan-tso balance -m Google -a 6326027735 --json-out ./snap
153
156
 
154
157
  一条命令替代 AI 对每个账户循环 `list-accounts -k` + `stats`。**多账户汇总表、对比消耗、跨账户巡检** 应优先本命令,禁止外层 for-loop 逐户 `stats`。
155
158
 
156
- > **数据时效性**:与 `stats` / `balance-scan` 相同(Google `account-spend-overview` 分流;TikTok/Yandex/BingV2/Kwai 为截至昨天的 `accountsoverview`)。完整表见 `references/analytics/account-analytics.md` 顶部。
159
+ > **数据时效性**:与 `stats` / `balance-scan` 相同(Google `account-spend-overview` 分流;TikTok/Yandex/BingV2/Kwai/**MetaAd** 为截至昨天的 `accountsoverview`)。完整表见 `references/analytics/account-analytics.md` 顶部。
157
160
 
158
161
  ```bash
159
162
  siluzan-tso accounts-digest -m <媒体类型> [选项]
@@ -161,7 +164,7 @@ siluzan-tso accounts-digest -m <媒体类型> [选项]
161
164
 
162
165
  | 选项 | 说明 | 默认 |
163
166
  | ---------------------- | ------------------------------------------------------------------------------------------------------------- | ------- |
164
- | `-m, --media <type>` | 媒体类型(必填):`Google \| TikTok \| Yandex \| BingV2 \| Kwai`(**MetaAd 无消耗汇总接口**) | — |
167
+ | `-m, --media <type>` | 媒体类型(必填):`Google \| TikTok \| Yandex \| MetaAd \| BingV2 \| Kwai` | — |
165
168
  | `-a, --accounts <ids>` | 指定 `mediaCustomerId`,逗号分隔;**留空**则翻页拉该媒体全部账户 | — |
166
169
  | `--start <YYYY-MM-DD>` | 统计开始日期(SKILL 要求 AI 先与用户确认区间) | 近 7 天 |
167
170
  | `--end <YYYY-MM-DD>` | 统计结束日期 | 昨天 |
@@ -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.30-beta.6'
12
+ $PKG_VERSION = '1.1.30-beta.7'
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.30-beta.6"
12
+ readonly PKG_VERSION="1.1.30-beta.7"
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.30-beta.6",
3
+ "version": "1.1.30-beta.7",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",