siluzan-tso-cli 1.1.42-beta.4 → 1.1.42-beta.6

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.42-beta.4),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.42-beta.6),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -124394,6 +124394,52 @@ function logAccountOpeningHistoryWebUrl(apiBaseUrl, mediaType) {
124394
124394
  init_auth();
124395
124395
  init_cli_json_snapshot();
124396
124396
  init_cli_table();
124397
+
124398
+ // src/commands/open-account/google-open-account-body.ts
124399
+ function defaultGoogleTimezoneForCurrency(currency) {
124400
+ return currency.trim().toUpperCase() === "CNY" ? "Asia/Shanghai" : "Asia/Hong_Kong";
124401
+ }
124402
+ function buildGoogleMagIndustry(industry1, industry2) {
124403
+ return {
124404
+ level1: (industry1 ?? "").trim(),
124405
+ level2: (industry2 ?? "").trim()
124406
+ };
124407
+ }
124408
+ function buildGoogleCustomerIndustry(industry1, industry2) {
124409
+ const level1 = (industry1 ?? "").trim();
124410
+ const level2 = (industry2 ?? "").trim();
124411
+ if (!level1 && !level2) return "-";
124412
+ return `${level1}-${level2}`;
124413
+ }
124414
+ function resolveGoogleTimezoneForSubmit(currency, timezone) {
124415
+ const t = (timezone ?? "").trim();
124416
+ if (t) return t;
124417
+ return defaultGoogleTimezoneForCurrency(currency);
124418
+ }
124419
+ function buildGoogleAccountInfoItem(opts) {
124420
+ return {
124421
+ advertiser_info: {
124422
+ name: opts.accountName,
124423
+ currency: opts.currency,
124424
+ timezone: opts.timezone,
124425
+ accounttype: "Adwords",
124426
+ inviteduseremail: opts.inviteEmail,
124427
+ inviteduserrole: opts.inviteRole ?? "Standard"
124428
+ },
124429
+ customer_info: {
124430
+ company: opts.company.trim(),
124431
+ industry: buildGoogleCustomerIndustry(opts.industry1, opts.industry2)
124432
+ },
124433
+ qualification_info: {
124434
+ promotion_link: opts.promotionLink,
124435
+ promotion_type: opts.promotionType
124436
+ },
124437
+ auto_allot_mailbox: opts.autoAllotMailbox ?? false,
124438
+ counts: 1
124439
+ };
124440
+ }
124441
+
124442
+ // src/commands/open-account/groups.ts
124397
124443
  async function runListAdvertiserGroups(opts) {
124398
124444
  const config = loadConfig(opts.token);
124399
124445
  let data;
@@ -124535,6 +124581,7 @@ async function resolveGoogleMediaAccountGroupId(config, opts) {
124535
124581
  }
124536
124582
  const company = opts.company.trim();
124537
124583
  const promotionLink = normalizePromotionLinkForGoogle(opts.promotionLink);
124584
+ const industry = buildGoogleMagIndustry(opts.industry1, opts.industry2);
124538
124585
  let profile;
124539
124586
  try {
124540
124587
  profile = await apiFetch2(
@@ -124559,6 +124606,7 @@ async function resolveGoogleMediaAccountGroupId(config, opts) {
124559
124606
  const mediaAccountGroupPayload = {
124560
124607
  advertiserName: company,
124561
124608
  StorageProvider: storageFromGroup ?? "StorageAccount",
124609
+ industry,
124562
124610
  promotionLink,
124563
124611
  promotionType: opts.promotionType
124564
124612
  };
@@ -125257,9 +125305,6 @@ Google \u5F00\u6237\u53EF\u9009\u65F6\u533A\uFF08\u4E0E\u7F51\u9875 /openAnAccou
125257
125305
  "\n\u63D0\u793A\uFF1A\u9009\u5E01\u79CD\u540E\u7F51\u9875\u4F1A\u9ED8\u8BA4 \u2014\u2014 CNY \u2192 Asia/Shanghai\uFF0CUSD \u2192 Asia/Hong_Kong\uFF1B\u5176\u5B83\u65F6\u533A\u8BF7\u4ECE\u4E0A\u8868\u53D6 Code\u3002\n"
125258
125306
  );
125259
125307
  }
125260
- function defaultGoogleTimezoneForCurrency(currency) {
125261
- return currency.trim().toUpperCase() === "CNY" ? "Asia/Shanghai" : "Asia/Hong_Kong";
125262
- }
125263
125308
  async function runOpenAccountGoogleWizard(opts) {
125264
125309
  if (!stdinStream.isTTY || !stdoutStream.isTTY) {
125265
125310
  console.error(
@@ -125361,6 +125406,7 @@ async function runOpenAccountGoogleWizard(opts) {
125361
125406
  \u516C\u53F8\u540D\uFF1A ${company}
125362
125407
  \u63A8\u5E7F\u94FE\u63A5\uFF1A ${promotionLink}
125363
125408
  \u63A8\u5E7F\u7C7B\u578B\uFF1A ${promotionType}
125409
+ \u884C\u4E1A\uFF1A -\uFF08\u4E0E\u7F51\u9875\u672A\u586B\u4E00\u81F4\uFF09
125364
125410
  \u8D26\u6237\u540D\u79F0\uFF1A ${accountName}
125365
125411
  \u5E01\u79CD / \u65F6\u533A\uFF1A ${currency} / ${timezone}
125366
125412
  \u5F00\u6237\u6570\u91CF\uFF1A ${counts}
@@ -125397,36 +125443,56 @@ async function runOpenAccountGoogle(opts) {
125397
125443
  console.error("\n\u274C --invite-email \u4E0D\u662F\u6709\u6548\u7684\u90AE\u7BB1\u683C\u5F0F\uFF08\u9700\u542B @ \u4E0E\u57DF\u540D\uFF09\u3002\n");
125398
125444
  process.exit(1);
125399
125445
  }
125446
+ const industry1 = (opts.industry1 ?? "").trim();
125447
+ const industry2 = (opts.industry2 ?? "").trim();
125448
+ const timezone = resolveGoogleTimezoneForSubmit(opts.currency, opts.timezone);
125449
+ try {
125450
+ const tzRows = await fetchGoogleTimezoneRows(config, opts.verbose);
125451
+ const codes = new Set(tzRows.map((r) => String(r.Code ?? "").trim()).filter(Boolean));
125452
+ if (codes.size > 0 && !codes.has(timezone)) {
125453
+ console.error(
125454
+ `
125455
+ \u274C --timezone "${timezone}" \u4E0D\u5728 Google \u65F6\u533A\u5217\u8868\u7684 Code \u4E2D\u3002
125456
+ \u8BF7\u6267\u884C open-account google-timezones \u67E5\u770B\uFF1BUSD \u9ED8\u8BA4 Asia/Hong_Kong\uFF0CCNY \u9ED8\u8BA4 Asia/Shanghai\u3002
125457
+ `
125458
+ );
125459
+ process.exit(1);
125460
+ }
125461
+ } catch (err) {
125462
+ console.warn(
125463
+ ` \u26A0\uFE0F \u65E0\u6CD5\u6821\u9A8C\u65F6\u533A\u5217\u8868\uFF08${err instanceof Error ? err.message : String(err)}\uFF09\uFF0C\u5DF2\u6309 "${timezone}" \u7EE7\u7EED\u63D0\u4EA4\u3002`
125464
+ );
125465
+ }
125400
125466
  const counts = Math.min(Math.max(opts.counts ?? 1, 1), 3);
125401
125467
  const magKey = await resolveGoogleMediaAccountGroupId(config, {
125402
125468
  advertiserId: opts.advertiserId,
125403
125469
  company: opts.company,
125404
125470
  promotionLink: opts.promotionLink,
125405
125471
  promotionType: opts.promotionType,
125472
+ industry1,
125473
+ industry2,
125406
125474
  verbose: opts.verbose
125407
125475
  });
125408
125476
  const promotionLinkNorm = normalizePromotionLinkForGoogle(opts.promotionLink);
125409
125477
  const body = {
125410
- accountInfo: Array.from({ length: counts }, (_, i) => ({
125411
- advertiser_info: {
125412
- // 多账户时在名称末尾追加 -1, -2 … 以便区分
125413
- name: counts > 1 ? `${opts.accountName}-${i + 1}` : opts.accountName,
125478
+ accountInfo: Array.from({ length: counts }, (_, i) => {
125479
+ const base = buildGoogleAccountInfoItem({
125480
+ accountName: opts.accountName,
125414
125481
  currency: opts.currency,
125415
- timezone: opts.timezone,
125416
- accounttype: "Adwords",
125417
- inviteduseremail: inviteEmail,
125418
- inviteduserrole: opts.inviteRole ?? "Standard"
125419
- },
125420
- customer_info: {
125421
- company: opts.company.trim()
125422
- },
125423
- qualification_info: {
125424
- promotion_link: promotionLinkNorm,
125425
- promotion_type: opts.promotionType
125426
- },
125427
- auto_allot_mailbox: opts.autoAllotMailbox ?? false,
125428
- counts: 1
125429
- })),
125482
+ timezone,
125483
+ inviteEmail,
125484
+ inviteRole: opts.inviteRole,
125485
+ company: opts.company,
125486
+ industry1,
125487
+ industry2,
125488
+ promotionLink: promotionLinkNorm,
125489
+ promotionType: opts.promotionType,
125490
+ autoAllotMailbox: opts.autoAllotMailbox
125491
+ });
125492
+ const adv = base.advertiser_info;
125493
+ adv.name = counts > 1 ? `${opts.accountName}-${i + 1}` : opts.accountName;
125494
+ return base;
125495
+ }),
125430
125496
  MediaAccountGroupId: magKey,
125431
125497
  IsSaltAdd: false,
125432
125498
  ManagerCustomerId: opts.managerCustomerId ?? ""
@@ -125456,6 +125522,67 @@ async function runOpenAccountGoogle(opts) {
125456
125522
  process.exit(1);
125457
125523
  }
125458
125524
  }
125525
+ async function runOpenAccountGoogleIndustries(opts) {
125526
+ const config = loadConfig(opts.token);
125527
+ let rows;
125528
+ try {
125529
+ const raw = await apiFetch2(
125530
+ `${config.apiBaseUrl}/analysisreporting/media-account/industryDirectory`,
125531
+ config,
125532
+ {},
125533
+ opts.verbose
125534
+ );
125535
+ rows = Array.isArray(raw) ? raw : Array.isArray(raw.data) ? raw.data : [];
125536
+ } catch (err) {
125537
+ console.error(
125538
+ `
125539
+ \u274C \u62C9\u53D6 Google \u884C\u4E1A\u5217\u8868\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
125540
+ `
125541
+ );
125542
+ process.exit(1);
125543
+ }
125544
+ const kw = (opts.keyword ?? "").trim().toLowerCase();
125545
+ if (kw) {
125546
+ rows = rows.filter(
125547
+ (r) => String(r.industry1 ?? "").toLowerCase().includes(kw) || String(r.industry2 ?? "").toLowerCase().includes(kw)
125548
+ );
125549
+ }
125550
+ const n = rows.length;
125551
+ const payload = wrapListJson({
125552
+ page: 1,
125553
+ pageSize: Math.max(n, 1),
125554
+ total: n,
125555
+ items: rows
125556
+ });
125557
+ if (await emitCliJsonOrSnapshot(opts, {
125558
+ section: "open-account-google-industries",
125559
+ commandLabel: "open-account google-industries",
125560
+ payload
125561
+ })) {
125562
+ return;
125563
+ }
125564
+ console.log(
125565
+ `
125566
+ Google \u5F00\u6237\u884C\u4E1A\uFF08\u7B2C 1 \u9875\uFF0C\u672C\u9875 ${rows.length} \u6761\uFF0C\u5171 ${rows.length} \u6761\uFF1B--industry1/--industry2 \u4F20\u4E2D\u6587\u6587\u6848\uFF09
125567
+ `
125568
+ );
125569
+ if (rows.length === 0) {
125570
+ console.log(" \u65E0\u6570\u636E\u3002\n");
125571
+ return;
125572
+ }
125573
+ const cols = [
125574
+ { key: "industry1", header: "\u4E00\u7EA7\uFF08--industry1\uFF09" },
125575
+ { key: "industry2", header: "\u4E8C\u7EA7\uFF08--industry2\uFF09" }
125576
+ ];
125577
+ printCliTable(
125578
+ rows.map((r) => ({
125579
+ industry1: String(r.industry1 ?? ""),
125580
+ industry2: String(r.industry2 ?? "")
125581
+ })),
125582
+ cols
125583
+ );
125584
+ console.log();
125585
+ }
125459
125586
 
125460
125587
  // src/commands/open-account/tiktok.ts
125461
125588
  init_auth();
@@ -125996,18 +126123,40 @@ function register25(program2) {
125996
126123
  });
125997
126124
  }
125998
126125
  );
126126
+ openAccountCmd.command("google-industries").description(
126127
+ "\u5217\u51FA Google \u5F00\u6237\u884C\u4E1A\uFF08\u4E0E\u7F51\u9875\u884C\u4E1A\u7EA7\u8054\u540C\u6E90\uFF1B--industry1/--industry2 \u4F20\u4E2D\u6587\u6587\u6848\uFF0C\u52FF\u7528\u6570\u5B57 ID\uFF09"
126128
+ ).option("-k, --keyword <text>", "\u6309\u4E00\u7EA7/\u4E8C\u7EA7\u884C\u4E1A\u6587\u6848\u8FC7\u6EE4").option(
126129
+ "--json-out <path>",
126130
+ "\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",
126131
+ void 0
126132
+ ).option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
126133
+ async (opts) => {
126134
+ await runOpenAccountGoogleIndustries({
126135
+ token: opts.token,
126136
+ keyword: opts.keyword,
126137
+ jsonOut: opts.jsonOut,
126138
+ verbose: opts.verbose
126139
+ });
126140
+ }
126141
+ );
125999
126142
  openAccountCmd.command("google-wizard").description("\u4EA4\u4E92\u5F0F Google \u5F00\u6237\uFF08\u5BF9\u9F50\u7F51\u9875\u4E94\u6B65\u8BF4\u660E + \u4E24\u6B65\u8868\u5355\uFF1B\u9700\u7EC8\u7AEF TTY\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
126000
126143
  await runOpenAccountGoogleWizard({ token: opts.token, verbose: opts.verbose });
126001
126144
  });
126002
126145
  openAccountCmd.command("google").description(
126003
126146
  "\u63D0\u4EA4 Google \u5F00\u6237\u7533\u8BF7\uFF08\u4E0E\u7F51\u9875\u8868\u5355\u4E00\u81F4\uFF1A\u6309\u516C\u53F8\u540D\u79F0\u81EA\u52A8\u521B\u5EFA/\u5173\u8054\u5E7F\u544A\u4E3B\u7EC4\uFF0C\u65E0\u9700 magKey\uFF09"
126004
- ).option("--advertiser-id <magKey>", "\u53EF\u9009\uFF1A\u624B\u52A8\u6307\u5B9A\u5E7F\u544A\u4E3B\u7EC4 ID\uFF08\u4E00\u822C\u4E0D\u7528\uFF1B\u8C03\u8BD5\u7528\u6216\u7279\u6B8A\u573A\u666F\uFF09").requiredOption("--account-name <name>", "\u5E7F\u544A\u8D26\u6237\u540D\u79F0\uFF08\u5EFA\u8BAE 22 \u4E2A\u5B57\u7B26\u4EE5\u5185\uFF09").requiredOption("--currency <code>", "\u8D27\u5E01\uFF1AUSD | CNY").requiredOption(
126147
+ ).option("--advertiser-id <magKey>", "\u53EF\u9009\uFF1A\u624B\u52A8\u6307\u5B9A\u5E7F\u544A\u4E3B\u7EC4 ID\uFF08\u4E00\u822C\u4E0D\u7528\uFF1B\u8C03\u8BD5\u7528\u6216\u7279\u6B8A\u573A\u666F\uFF09").requiredOption("--account-name <name>", "\u5E7F\u544A\u8D26\u6237\u540D\u79F0\uFF08\u5EFA\u8BAE 22 \u4E2A\u5B57\u7B26\u4EE5\u5185\uFF09").requiredOption("--currency <code>", "\u8D27\u5E01\uFF1AUSD | CNY").option(
126005
126148
  "--timezone <tz>",
126006
- "\u65F6\u533A\uFF0C\u5982 Asia/Hong_Kong\uFF08\u53EF\u5148\u67E5 open-account google-timezones\uFF09"
126149
+ "\u65F6\u533A Code\uFF08\u53EF\u5148\u67E5 google-timezones\uFF09\uFF1B\u7701\u7565\u5219\u6309\u5E01\u79CD\u9ED8\u8BA4\uFF1AUSD\u2192Asia/Hong_Kong\uFF0CCNY\u2192Asia/Shanghai"
126007
126150
  ).requiredOption("--invite-email <email>", "\u53D7\u9080\u7528\u6237\u90AE\u7BB1\uFF08\u8D26\u6237\u9080\u8BF7\u5C06\u53D1\u5230\u6B64\u90AE\u7BB1\uFF09").requiredOption("--company <name>", "\u516C\u53F8\u540D\u79F0\uFF08\u7528\u4E8E\u5339\u914D\u6216\u521B\u5EFA\u5E7F\u544A\u4E3B\u7EC4\uFF0C\u4E0E\u7F51\u9875\u7B2C\u4E00\u6B65\u4E00\u81F4\uFF09").requiredOption(
126008
126151
  "--promotion-link <url>",
126009
126152
  "\u63A8\u5E7F\u94FE\u63A5\uFF08\u516C\u53F8\u5B98\u7F51\u6216\u4EA7\u54C1\u9875\uFF1B\u53EF\u5199\u57DF\u540D\uFF0C\u4F1A\u81EA\u52A8\u8865 https://\uFF09"
126010
- ).requiredOption("--promotion-type <type>", "\u63A8\u5E7F\u7C7B\u578B\uFF1Ab2b | b2c | app").option("--invite-role <role>", "\u53D7\u9080\u7528\u6237\u89D2\u8272\uFF1AStandard | Admin\uFF08\u9ED8\u8BA4 Standard\uFF09", "Standard").option("--auto-mailbox", "\u81EA\u52A8\u5206\u914D\u90AE\u7BB1", false).option("--counts <n>", "\u672C\u6B21\u5F00\u6237\u6570\u91CF\uFF081-3\uFF0C\u9ED8\u8BA4 1\uFF09", parseInt).option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
126153
+ ).requiredOption("--promotion-type <type>", "\u63A8\u5E7F\u7C7B\u578B\uFF1Ab2b | b2c | app").option(
126154
+ "--industry1 <name>",
126155
+ "\u4E00\u7EA7\u884C\u4E1A\u4E2D\u6587\u6587\u6848\uFF08\u53EF\u9009\uFF1B\u4E0D\u4F20\u5219 customer_info.industry \u4E3A -\uFF0C\u4E0E\u7F51\u9875\u4E00\u81F4\uFF09"
126156
+ ).option(
126157
+ "--industry2 <name>",
126158
+ "\u4E8C\u7EA7\u884C\u4E1A\u4E2D\u6587\u6587\u6848\uFF08\u53EF\u9009\uFF1B\u4E0E --industry1 \u6210\u5BF9\uFF1B\u7686\u7A7A\u65F6 industry \u4E3A -\uFF09"
126159
+ ).option("--invite-role <role>", "\u53D7\u9080\u7528\u6237\u89D2\u8272\uFF1AStandard | Admin\uFF08\u9ED8\u8BA4 Standard\uFF09", "Standard").option("--auto-mailbox", "\u81EA\u52A8\u5206\u914D\u90AE\u7BB1", false).option("--counts <n>", "\u672C\u6B21\u5F00\u6237\u6570\u91CF\uFF081-3\uFF0C\u9ED8\u8BA4 1\uFF09", parseInt).option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
126011
126160
  async (opts) => {
126012
126161
  await runOpenAccountGoogle({
126013
126162
  token: opts.token,
@@ -126019,6 +126168,8 @@ function register25(program2) {
126019
126168
  company: opts.company,
126020
126169
  promotionLink: opts.promotionLink,
126021
126170
  promotionType: opts.promotionType,
126171
+ industry1: opts.industry1,
126172
+ industry2: opts.industry2,
126022
126173
  inviteRole: opts.inviteRole,
126023
126174
  autoAllotMailbox: opts.autoMailbox,
126024
126175
  counts: opts.counts,
@@ -43,6 +43,7 @@ siluzan-tso -h
43
43
  - 多户余额用 `balance-scan`(P2),多户消耗用 `accounts-digest`(P3);禁止外层 for-loop 逐户拉数。
44
44
  - `stats` 的 `spend` = **区间合计**,不是日消耗。
45
45
  - 行业/市场报告禁止纯 WebSearch 当终稿;须 P9 `market-analysis collect`+`render`。
46
+ - **禁止编造平台网址**:给用户 `*.siluzan.com` / `mysiluzan.com` 链接前,**必须**当轮 `config show` 取 `webUrl`,路径**只**用已 Read 文档中的相对路径表拼接;CLI 输出的授权 URL **原样粘贴**;文档未给路径则**不拼链接**。细则见 `agent-conventions.md`。
46
47
 
47
48
  ---
48
49
 
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.42-beta.4",
4
- "publishedAt": 1785898691140
3
+ "version": "1.1.42-beta.6",
4
+ "publishedAt": 1785923784459
5
5
  }
@@ -139,7 +139,13 @@ siluzan-tso config show
139
139
 
140
140
  ### 引导用户的标准话术
141
141
 
142
- 当用户需要充值/查看钱包时,先取 `webUrl`,再按上表拼出**完整链接**。用户已指定媒体时,只给对应媒体的单笔充值链接(按需补批量/月结);未指定媒体时,先询问媒体,或列出全部支持媒体的单笔充值链接 + 丝路赞钱包链接,并附 Yandex/Kwai 的说明。
142
+ 当用户需要充值/查看钱包时:
143
+
144
+ 1. **必须**先跑 `siluzan-tso config show`,用输出中的 `webUrl`(禁止猜生产/测试域名)。
145
+ 2. 再按上表相对路径拼出**完整链接**贴给用户。
146
+ 3. **禁止**编造未出现在上表的路径(如 `/recharge`、`/wallet` 等「看起来像」的地址)。
147
+
148
+ 用户已指定媒体时,只给对应媒体的单笔充值链接(按需补批量/月结);未指定媒体时,先询问媒体,或列出全部支持媒体的单笔充值链接 + 丝路赞钱包链接,并附 Yandex/Kwai 的说明。
143
149
 
144
150
  ---
145
151
 
@@ -62,9 +62,11 @@ CLI 参数仅供 Agent **内部**组命令使用(见下表第三列)。用
62
62
  | 公司名称 | 用于匹配/创建广告主组 | `--company` |
63
63
  | 推广链接 | 可只写域名,提交时会自动补 `https://` | `--promotion-link` |
64
64
  | 推广类型 | `b2b`(企业对企业)/ `b2c`(企业对消费者)/ `app`(应用) | `--promotion-type` |
65
+ | 一级行业 | 可选;网页已隐藏;不传则 `customer_info.industry`=`-` | `--industry1` |
66
+ | 二级行业 | 可选;与一级成对;皆空时统一为 `-` | `--industry2` |
65
67
  | 账户名称 | 广告账户显示名,建议 ≤22 字 | `--account-name` |
66
68
  | 币种 | `USD`(美元)或 `CNY`(人民币) | `--currency` |
67
- | 时区 | IANA 时区,如北京时间、美东;可用常用时区表辅助 | `--timezone` |
69
+ | 时区 | 时区 Code;可省略(USD→香港、CNY→上海);其它先查列表 | `--timezone`(可选) |
68
70
  | 邀请邮箱 | 开通后接收账户邀请的邮箱 | `--invite-email` |
69
71
 
70
72
  ### TikTok(`open-account tiktok`,需营业执照图片)
@@ -162,6 +164,7 @@ siluzan-tso open-account google \
162
164
  --currency USD \
163
165
  --timezone "America/New_York" \
164
166
  --invite-email "marketing@brand-a.com"
167
+ # 不传行业时与网页一致:customer_info.industry = "-"
165
168
 
166
169
  siluzan-tso account-history -m Google
167
170
  siluzan-tso list-accounts -m Google
@@ -22,15 +22,17 @@
22
22
  | 公司名称 | 用于匹配/创建广告主组 | `--company` |
23
23
  | 推广网址 | 可只写域名,提交时会自动补协议 | `--promotion-link` |
24
24
  | 推广类型 | 企业对企业(b2b)/ 企业对消费者(b2c)/ 应用(app) | `--promotion-type`:`b2b` \| `b2c` \| `app` |
25
- | 行业(可选) | | `--industry1` / `--industry2` |
25
+ | 一级 / 二级行业 | **可选**;网页自 2025-04-22 起已隐藏 Google 行业下拉(后端字典不稳) | `--industry1` / `--industry2`(可选);列表:`google-industries` |
26
26
  | 账户名称 | 广告账户显示名 | `--account-name` |
27
27
  | 币种 | CNY 或 USD | `--currency` |
28
- | 时区 | IANA;可用常用时区表或查完整列表 | `--timezone`;列表:`open-account google-timezones` |
28
+ | 时区 | 时区 Code;可省略走币种默认;勿用展示名 | `--timezone`(可选);列表:`google-timezones` |
29
29
  | 开户数量 | 1~3 | `--counts` |
30
30
  | 邀请邮箱 | 开通后接收账户邀请的邮箱 | `--invite-email` |
31
31
 
32
32
  **币种默认时区**:CNY → `Asia/Shanghai`;USD(或其它非 CNY)→ `Asia/Hong_Kong`。其它时区从 `google-timezones` 取 **Code** 列。
33
33
 
34
+ **行业(与网页一致,非必填)**:不传时 MAG `industry.level1/level2` 为空串,账户 **`customer_info.industry` 统一为 `"-"`**(与网页未填抓包一致)。若传齐一级/二级则拼 `level1-level2`。
35
+
34
36
  提交时按公司名称自动查找/创建广告主组并拿 `magKey`,**无需**让用户填 magKey。
35
37
 
36
38
  ---
@@ -51,7 +53,7 @@ siluzan-tso open-account google \
51
53
  --counts 1
52
54
  ```
53
55
 
54
- 常用时区:`Asia/Shanghai`、`Asia/Hong_Kong`、`America/New_York`、`America/Los_Angeles`、`Europe/London`。完整列表:`open-account google-timezones [--keyword <关键词>]`
56
+ 常用时区:`Asia/Shanghai`、`Asia/Hong_Kong`、`America/New_York`、`America/Los_Angeles`、`Europe/London`。完整列表:`open-account google-timezones [--keyword <关键词>]`。行业可选:`google-industries`(CI 上字典接口可能 500,与网页隐藏行业同源问题)。
55
57
 
56
58
  ### 交互向导(需真实 TTY;Agent 不可用)
57
59
 
@@ -186,7 +186,7 @@ siluzan-tso report push receive-emails -m Google [--json-out ./snap]
186
186
 
187
187
  ### 查看报告(引导打开网页)
188
188
 
189
- 报告生成后,在网页查看。先用 `siluzan-tso config show` 获取 `webUrl`,再按以下规则拼接链接:
189
+ 报告生成后,在网页查看。先用 `siluzan-tso config show` 获取 `webUrl`,再按以下规则拼接链接(**禁止**凭记忆编造查看地址;`entityId` 须来自当次 `report list`):
190
190
 
191
191
  | 媒体 | 报告类型 | URL 模板 |
192
192
  | ------ | ---------------- | ----------------------------------------------------------- |
@@ -133,6 +133,12 @@
133
133
  - **不猜测账户 ID**:`entityId`(UUID,仅 delink/share/reauth/账单)≠ `mediaCustomerId`(`stats`/`balance`/`accounts-digest`/`ad` 的 `-a`)。两者均来自 `list-accounts` 的 `ma.*`。用户已给出媒体账户号时(Google 数字 CID、**Yandex `porg-…`**、Meta `act_…` 等)→ `-a` **原样使用**,再用 `-k` 核验;**禁止**改成 `entityId` / tokenId / 会话里其它 UUID。**禁止**把 `entityId` 传给 `stats -a` / `balance -a` / `accounts-digest -a`。
134
134
  - **媒体类型区分大小写**:`Google`、`TikTok`、`Yandex`、`MetaAd`、`BingV2`、`Kwai`。
135
135
  - **CLI 输出忠实**:数值与 ID 须与本次落盘 JSON / stdout 一致,不编造示例 ID;`data` 为空时只说「当前返回无记录」并附 JSON 路径。
136
+ - **禁止编造平台网页地址**(充值/钱包/开户进度/授权/报告查看等):
137
+ 1. **禁止**凭记忆、训练数据或「看起来像」写出任何完整 `https://…siluzan.com…` / `mysiluzan.com` 链接(含改域名、改路径、补 query)。
138
+ 2. 引导用户打开丝路赞网页时:**必须**当轮执行 `siluzan-tso config show`,用输出中的 **`webUrl`** 作基地址;相对路径**只**能来自当次已 Read 的 reference(如 `finance.md`、`reporting.md`、`setup.md`)中的路径表;拼出后完整链接贴给用户。
139
+ 3. CLI stdout / 工具返回的 OAuth、授权、注册等 URL:**原样整段粘贴**,禁止改写、截断后补全、或「根据印象」重写。
140
+ 4. 当次文档**没有**对应相对路径 → **禁止拼接**;用业务语言说明须在网页端完成,或请用户到平台自行找入口 / 联系客服(如 Yandex/Kwai 无充值页)。
141
+ 5. **`apiBaseUrl` / `googleApiUrl` 禁止**当作浏览器访问地址发给用户;用户官网/落地页仅使用用户提供或当次 CLI/WebFetch 得到的真实 URL,禁止臆造客户站点。
136
142
  - **破坏性操作必须确认 + `--commit`**:账户解绑/关闭/取消分享、BC/MCC 解绑、删除预警/报告/广告/关键词、发票申请、广告发布等。
137
143
  - **CLI 硬门控**:`account delink` / `unshare` / `reauth` / `mcc-unbind` / `bc-unbind` **缺少 `--i-confirm` 会直接失败**(不会发网关)。`--commit` 只是审计说明,**不能替代**用户确认。
138
144
  - **Agent**:先说明风险与将执行的命令 → **等待用户明确同意** → 才附加 `--i-confirm --commit "…"` 执行。用户说「别问我 / 直接做」也**不得**跳过确认;**禁止**未获同意就自行加 `--i-confirm`。
@@ -258,7 +264,7 @@
258
264
  ## 十一、常见 HTTP 状态码
259
265
 
260
266
  - **400**:参数错误,查看对应 reference 或 `-h`
261
- - **401 / OAuth 失效**:仅当 `list-accounts` 的 `invalidOAuthToken=true`(或表格「授权状态」为失效)且用户确认后——Siluzan Agent **优先**用平台重新授权工具(如 `present_reauth`);备选 `account reauth -m <媒体> --id <entityId> --i-confirm --commit "…"`(内置 delink→OAuth,见 W9 / `accounts-permissions.md`)。走 CLI 时**必须把完整授权 URL 贴给用户**,禁止只说「链接已生成」。解绑后若列表已无该户,恢复用平台授权工具或 `account auth -m <媒体>`。**丝路赞登录凭据失效** → `send-login-code` + `login --phone --code`,见 `references/core/setup.md`
267
+ - **401 / OAuth 失效**:仅当 `list-accounts` 的 `invalidOAuthToken=true`(或表格「授权状态」为失效)且用户确认后——Siluzan Agent **优先**用平台重新授权工具(如 `present_reauth`);备选 `account reauth -m <媒体> --id <entityId> --i-confirm --commit "…"`(内置 delink→OAuth,见 W9 / `accounts-permissions.md`)。走 CLI 时**必须把 stdout 中的完整授权 URL 原样贴给用户**,禁止只说「链接已生成」,也**禁止**自行改写/补全该 URL。解绑后若列表已无该户,恢复用平台授权工具或 `account auth -m <媒体>`。**丝路赞登录凭据失效** → `send-login-code` + `login --phone --code`,见 `references/core/setup.md`
262
268
  - **403(拉数空结果)**:`stats`/`balance`/`accounts-digest` 在 `--verbose` 下打出异常被吞的 `HTTP 403` 且无数据时:① **优先核验 `-a` 是否为 `ma.mediaCustomerId`**(勿传 `entityId`/UUID;Yandex 形如 `porg-…`);② Google 再看套餐是否激活(`scopeActivatedSources`),未激活则说明需先激活,**禁止**非 CLI 绕过取数;③ **禁止**在 `invalidOAuthToken=false` 时把 403 当成 OAuth 过期并自行 `reauth`。
263
269
  - **500**:服务可能正在部署/升级,建议反馈 Siluzan 相关人员
264
270
 
@@ -302,7 +308,7 @@
302
308
  1. **共情/确认**:一句话接住意图。
303
309
  2. **结论先行**:可操作判断(正常 / 有问题 / 原因),勿让用户从表格里猜。
304
310
  3. **数据支撑**:引用**当次 CLI/脚本**真实数字;禁止编造、禁止示例 ID。
305
- 4. **行动建议**:1~3 条,点名对象并引用数据。
311
+ 4. **行动建议**:1~3 条,点名对象并引用数据;若建议含「打开网页 / 去充值 / 去授权」,链接须遵守 §四「禁止编造平台网页地址」,**禁止**随口给一个未经验证的 URL。
306
312
  5. **下一步**:一句邀请。客户端 suggestion chips 另展示;**不要**在 skill 各任务文档维护 chips 全文清单。
307
313
 
308
314
  长任务(>30s):中间用进度短句,勿长时间沉默后突然扔大表。
@@ -139,6 +139,7 @@ siluzan-tso config show
139
139
  ## 使用 webUrl 进行网页操作
140
140
 
141
141
  - 涉及充值、账户激活、首页看板等**必须在网页完成**的操作时,应先通过 `siluzan-tso config show` 获取 `webUrl` 值,再按各业务文档提供的相对路径拼接完整链接,引导用户在浏览器中完成后续步骤。
142
+ - **硬约束**:`webUrl` **必须**来自当轮 `config show` 输出;路径**必须**来自已 Read 的业务文档路径表。**禁止**凭记忆写出完整平台 URL,**禁止**把 `apiBaseUrl` / `googleApiUrl` 当作浏览器地址发给用户。文档未给出相对路径时不要拼接。详见 `agent-conventions.md` §四。
142
143
 
143
144
  ## 更新
144
145
 
@@ -150,7 +150,7 @@
150
150
  - **触发**:充值、钱包、账户间转账、转账记录、开票、发票抬头。
151
151
  - **必读**:`references/accounts/finance.md`。
152
152
  - **步骤**:
153
- 1. 充值/钱包:CLI 不支持,`config show` 取 `webUrl` 后按 `finance.md` 给对应媒体充值页链接(Yandex/Kwai 无充值页,引导联系客服)。
153
+ 1. 充值/钱包:CLI 不支持;**当轮** `config show` 取 `webUrl`,仅按 `finance.md` 路径表拼接后贴完整链接(**禁止**凭记忆编 URL;Yandex/Kwai 无充值页,引导联系客服)。
154
154
  2. 转账:记录 `transfer list -m <媒体>`;同媒体账户间 `transfer create -m <媒体> --out <id> --in <id> --amount <n>`(写操作先确认)。
155
155
  3. 开票:`invoice billable -m <媒体> -c <币种> --json-out ./snap` 取订单 `entityId` → 选发票抬头(`invoice-info list`,查重后再 `create`)→ `invoice apply --bill-ids … --invoice-type <PI|VATI|VATSI> …`。CNY 订单仅增值税票、外币仅 PI。
156
156
  - **产物**:开票分步引导(先选订单、再选抬头、最后申请);**禁止**不展示 `invoice billable` 就让用户手写 id;保留 CLI 币种校验。
@@ -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.42-beta.4'
12
+ $PKG_VERSION = '1.1.42-beta.6'
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.42-beta.4"
12
+ readonly PKG_VERSION="1.1.42-beta.6"
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"
@@ -4,7 +4,7 @@
4
4
  "turns": [
5
5
  "我们公司要在丝路赞新开一个 Google 美国户,推广 B2C,网址 https://example.com,币种 USD。你告诉我 CLI 要怎么一步步做,我复制粘贴执行。"
6
6
  ],
7
- "judgeExpectation": "路径:用户要新开 Google 广告户时,应说明需资料(公司、网址、币种等);提交用非交互式 `open-account google ...`(Agent 环境不可用交互式 google-wizard);后续用 `account-history`、`list-accounts` 跟踪。\n输出:明确 wizard 限制;不要求真实提交开户。",
7
+ "judgeExpectation": "路径:用户要新开 Google 广告户时,应说明需资料(公司、网址、行业一级/二级、币种等);提交用非交互式 `open-account google ...`(含 industry;Agent 环境不可用交互式 google-wizard);后续用 `account-history`、`list-accounts` 跟踪。\n输出:明确 wizard 限制;不要求真实提交开户。",
8
8
  "skillMapping": "references/core/workflows.md 流程一;accounts/open-account-by-media.md",
9
9
  "judgeReferencePaths": [
10
10
  "references/core/workflows.md"
@@ -4,7 +4,7 @@
4
4
  "turns": [
5
5
  "我想开一个 Google 广告账户,公司名是「深圳星辰科技有限公司」,推广网站是 https://www.starlighttech.com ,做 B2C 电商,币种选美元。告诉我 CLI 怎么一步步提交,我复制执行。"
6
6
  ],
7
- "judgeExpectation": "路径:应给出非交互 `open-account google ...`,说明 invite-email、timezone、company 等必填;Agent 环境不可用 google-wizard;币种 USD、promotion-type b2c 与文档一致。\n输出:步骤可复制;不要求真实提交。",
7
+ "judgeExpectation": "路径:应给出非交互 `open-account google ...`,说明 invite-email、company、行业一级/二级等必填,时区可按币种默认;Agent 环境不可用 google-wizard;币种 USD、promotion-type b2c 与文档一致。\n输出:步骤可复制;不要求真实提交。",
8
8
  "skillMapping": "references/core/workflows.md;accounts/open-account-by-media.md",
9
9
  "judgeReferencePaths": [
10
10
  "references/core/workflows.md",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.42-beta.4",
3
+ "version": "1.1.42-beta.6",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",