siluzan-tso-cli 1.1.20-beta.2 → 1.1.20-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +571 -4
  3. package/dist/skill/SKILL.md +3 -1
  4. package/dist/skill/_meta.json +2 -2
  5. package/dist/skill/references/keyword-planner-workflows.md +22 -1
  6. package/dist/skill/references/rag.md +104 -0
  7. package/eval/cases/accounts-entityid-vs-mediaccustomerid.scenario.json +14 -2
  8. package/eval/cases/accounts-mcc-bind-inquiry.scenario.json +3 -1
  9. package/eval/cases/accounts-single-balance-not-bulk.scenario.json +14 -3
  10. package/eval/cases/budget-display-not-raw-micros.scenario.json +9 -2
  11. package/eval/cases/clue-meta-leads-json.scenario.json +14 -2
  12. package/eval/cases/clue-tiktok-leads-json.scenario.json +11 -2
  13. package/eval/cases/destructive-account-delink-needs-confirm.scenario.json +9 -3
  14. package/eval/cases/destructive-forewarning-delete-needs-confirm.scenario.json +9 -3
  15. package/eval/cases/destructive-invoice-apply-needs-confirm.scenario.json +9 -3
  16. package/eval/cases/finance-invoice-info-list.scenario.json +11 -3
  17. package/eval/cases/forewarning-list-google.scenario.json +14 -3
  18. package/eval/cases/google-ads-no-structural-without-confirm.scenario.json +6 -2
  19. package/eval/cases/google-analysis-keywords-route.scenario.json +14 -2
  20. package/eval/cases/human-p1-multiturn.scenario.json +5 -1
  21. package/eval/cases/meta-single-balance-not-bulk.scenario.json +17 -3
  22. package/eval/cases/open-account-bing-noninteractive.scenario.json +4 -1
  23. package/eval/cases/open-account-google-noninteractive.scenario.json +3 -1
  24. package/eval/cases/open-account-tiktok-license-file.scenario.json +3 -1
  25. package/eval/cases/optimize-list-by-account.scenario.json +11 -3
  26. package/eval/cases/p1-single-account-profile.scenario.json +11 -1
  27. package/eval/cases/p2-balance-scan-bulk.scenario.json +9 -2
  28. package/eval/cases/p3-accounts-digest.scenario.json +5 -1
  29. package/eval/cases/p4-period-report-window.scenario.json +8 -1
  30. package/eval/cases/rag-before-keyword-expand.scenario.json +24 -0
  31. package/eval/cases/rag-list-then-query.scenario.json +23 -0
  32. package/eval/cases/report-list-google.scenario.json +11 -2
  33. package/eval/cases/report-push-list-google.scenario.json +11 -2
  34. package/eval/cases/reporting-vs-account-analytics-routing.scenario.json +4 -1
  35. package/eval/cases/setup-login-or-env.scenario.json +3 -1
  36. package/eval/cases/setup-siluzan-data-permission-env.scenario.json +3 -1
  37. package/eval/cases/skill-optimize-vs-google-ads-distinction.scenario.json +4 -1
  38. package/eval/cases/tiktok-bc-bind-inquiry.scenario.json +6 -2
  39. package/eval/cases/time-range-user-delegates-default.scenario.json +8 -1
  40. package/eval/cases/tips-json-filtering.scenario.json +3 -1
  41. package/eval/cases/tips-large-json-pagination.scenario.json +3 -1
  42. package/eval/cases/uj-ad-outdoor-campgear-search-plan.scenario.json +3 -1
  43. package/eval/cases/uj-analytics-30d-pdf-campaign-device-geo.scenario.json +18 -6
  44. package/eval/cases/uj-analytics-compare-google-tiktok-last-month-roi.scenario.json +8 -1
  45. package/eval/cases/uj-analytics-google-weekly-trends-campaigns-keywords.scenario.json +11 -2
  46. package/eval/cases/uj-analytics-report-push-weekly-email.scenario.json +3 -1
  47. package/eval/cases/uj-finance-invoice-records-this-month.scenario.json +11 -2
  48. package/eval/cases/uj-life-newbie-siluzan-google-end-to-end.scenario.json +4 -1
  49. package/eval/cases/uj-ops-google-accounts-list-normal.scenario.json +14 -2
  50. package/eval/cases/uj-ops-google-yesterday-spend-conversions.scenario.json +14 -2
  51. package/eval/cases/uj-ops-open-google-b2c-usd-shenzhen.scenario.json +4 -1
  52. package/eval/cases/uj-ops-pause-worst-adgroup-confirm.scenario.json +6 -2
  53. package/eval/cases/uj-ops-tiktok-leads-last-week.scenario.json +17 -3
  54. package/eval/cases/uj-patrol-cpc-spike-adgroups-over-15.scenario.json +9 -2
  55. package/eval/cases/uj-patrol-forewarning-create-daily-cap-3000.scenario.json +3 -1
  56. package/eval/cases/uj-patrol-forewarning-trigger-records.scenario.json +17 -3
  57. package/eval/cases/uj-patrol-google-balances-low.scenario.json +11 -2
  58. package/eval/cases/uj-roi-optimize-records-then-execute-cautiously.scenario.json +14 -3
  59. package/eval/cases/uj-roi-search-terms-add-negative-keywords.scenario.json +14 -2
  60. package/eval/stub-fixtures/rag-list.json +19 -0
  61. package/eval/stub-fixtures/rag-query.json +20 -0
  62. 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.20-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.20-beta.4),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -2603,6 +2603,81 @@ function defineCommand(spec) {
2603
2603
  return cmd;
2604
2604
  };
2605
2605
  }
2606
+ function deriveMainApiOrigin(apiBase) {
2607
+ try {
2608
+ const u = new URL(apiBase);
2609
+ const host = u.hostname.toLowerCase();
2610
+ const isCi = apiBase.includes("-ci");
2611
+ if (host.endsWith("siluzan.com") || host.endsWith("siluzan.cn")) {
2612
+ if (host.startsWith("tso-api")) {
2613
+ const suffix = host.endsWith("siluzan.cn") ? "siluzan.cn" : "siluzan.com";
2614
+ const apiHost = isCi ? `api-ci.${suffix}` : `api.${suffix}`;
2615
+ return `${u.protocol}//${apiHost}${u.port ? `:${u.port}` : ""}`;
2616
+ }
2617
+ if (host === "api.siluzan.com" || host === "api-ci.siluzan.com" || host === "api.siluzan.cn" || host === "api-ci.siluzan.cn") {
2618
+ return `${u.protocol}//${host}${u.port ? `:${u.port}` : ""}`;
2619
+ }
2620
+ return isCi ? "https://api-ci.siluzan.com" : "https://api.siluzan.com";
2621
+ }
2622
+ return isCi ? "https://api-ci.siluzan.com" : "https://api.siluzan.com";
2623
+ } catch {
2624
+ return apiBase.includes("-ci") ? "https://api-ci.siluzan.com" : "https://api.siluzan.com";
2625
+ }
2626
+ }
2627
+ function pickStr(obj, key) {
2628
+ const v = obj[key];
2629
+ return typeof v === "string" && v.trim() ? v.trim() : void 0;
2630
+ }
2631
+ function pickCompanyId(data) {
2632
+ const direct = pickStr(data, "companyId") ?? pickStr(data, "companyID") ?? pickStr(data, "CompanyId");
2633
+ if (direct) return direct;
2634
+ const ci = data["companyInfo"];
2635
+ if (ci && typeof ci === "object") {
2636
+ const o = ci;
2637
+ return pickStr(o, "id") ?? pickStr(o, "companyId") ?? pickStr(o, "companyID");
2638
+ }
2639
+ return void 0;
2640
+ }
2641
+ function parseMeResponse(text) {
2642
+ try {
2643
+ const json = JSON.parse(text);
2644
+ const data = json?.data && typeof json.data === "object" ? json.data : json;
2645
+ const id = pickStr(data, "entityId") ?? pickStr(data, "id") ?? pickStr(data, "userId") ?? pickStr(data, "accountId");
2646
+ const email = pickStr(data, "email");
2647
+ const username = pickStr(data, "userName") ?? pickStr(data, "username") ?? pickStr(data, "name") ?? pickStr(data, "phone");
2648
+ const companyId = pickCompanyId(data);
2649
+ if (!id && !email && !username && !companyId) return null;
2650
+ return { id, email, username, companyId };
2651
+ } catch {
2652
+ return null;
2653
+ }
2654
+ }
2655
+ async function fetchSiluzanCurrentUser(apiBase, config) {
2656
+ const mainOrigin = deriveMainApiOrigin(apiBase);
2657
+ const meUrl = `${mainOrigin.replace(/\/$/, "")}/query/account/me`;
2658
+ const authHeaders = config.apiKey ? { "x-api-key": config.apiKey } : { Authorization: `Bearer ${config.authToken}` };
2659
+ try {
2660
+ const res = await rawRequest(meUrl, {
2661
+ method: "GET",
2662
+ headers: {
2663
+ "Content-Type": "application/json",
2664
+ "Accept-Language": "zh-CN",
2665
+ ...authHeaders
2666
+ }
2667
+ });
2668
+ if (res.status < 200 || res.status >= 300) return null;
2669
+ const parsed = parseMeResponse(res.text);
2670
+ if (!parsed) return null;
2671
+ return {
2672
+ entityId: parsed.id,
2673
+ email: parsed.email,
2674
+ username: parsed.username,
2675
+ companyId: parsed.companyId
2676
+ };
2677
+ } catch {
2678
+ return null;
2679
+ }
2680
+ }
2606
2681
  function truncate(s, max) {
2607
2682
  if (s.length <= max) return s;
2608
2683
  return `${s.slice(0, max)}\u2026[truncated ${s.length - max} chars]`;
@@ -3679,6 +3754,13 @@ function loadConfig(tokenArg) {
3679
3754
  \u274C googleApiUrl \u4E0D\u5408\u6CD5\uFF1A${googleApiErr}`);
3680
3755
  process.exit(1);
3681
3756
  }
3757
+ const csoBaseUrl = process.env.SILUZAN_CSO_BASE ?? deriveCsoApiBaseUrl(apiBaseUrl);
3758
+ const csoErr = validateBaseUrl(csoBaseUrl);
3759
+ if (csoErr) {
3760
+ console.error(`
3761
+ \u274C csoBaseUrl \u4E0D\u5408\u6CD5\uFF1A${csoErr}`);
3762
+ process.exit(1);
3763
+ }
3682
3764
  return {
3683
3765
  apiBaseUrl,
3684
3766
  authToken,
@@ -3688,6 +3770,7 @@ function loadConfig(tokenArg) {
3688
3770
  tiktokApiUrl: deriveTiktokApiUrl(apiBaseUrl),
3689
3771
  facebookApiUrl: deriveFacebookApiUrl(apiBaseUrl),
3690
3772
  chatGptApiUrl: deriveChatGptApiUrl(apiBaseUrl),
3773
+ csoBaseUrl,
3691
3774
  dataPermission: process.env.SILUZAN_DATA_PERMISSION ?? shared.dataPermission
3692
3775
  };
3693
3776
  }
@@ -112442,6 +112525,489 @@ var register21 = defineCommand({
112442
112525
  }
112443
112526
  });
112444
112527
 
112528
+ // src/commands/rag.ts
112529
+ init_auth();
112530
+ function splitCsv2(s) {
112531
+ if (!s) return [];
112532
+ return s.split(",").map((x) => x.trim()).filter(Boolean);
112533
+ }
112534
+ function splitQueryWhitespaceToKeywords(query) {
112535
+ return String(query ?? "").trim().split(/\s+/).filter(Boolean);
112536
+ }
112537
+ function ragItemDedupeKey(item) {
112538
+ const id = String(item.id ?? "").trim();
112539
+ if (id) return `id:${id}`;
112540
+ const name = String(item.fields?.name ?? "").trim();
112541
+ const content = String(item.fields?.content ?? "").slice(0, 160);
112542
+ return `fb:${name}\0${content}`;
112543
+ }
112544
+ function mergeRagResultsByBestScore(batches) {
112545
+ const best = /* @__PURE__ */ new Map();
112546
+ for (const batch of batches) {
112547
+ for (const item of batch) {
112548
+ const key = ragItemDedupeKey(item);
112549
+ const prev = best.get(key);
112550
+ const s = item.score ?? 0;
112551
+ if (!prev || s > (prev.score ?? 0)) {
112552
+ best.set(key, item);
112553
+ }
112554
+ }
112555
+ }
112556
+ return [...best.values()].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
112557
+ }
112558
+ function normalizeRagScores(rawOutput) {
112559
+ return rawOutput.map((item) => {
112560
+ const r = item.score;
112561
+ if (typeof r !== "number" || Number.isNaN(r)) return item;
112562
+ const s = Math.min(1, Math.max(0, 1 - r));
112563
+ return { ...item, score: s };
112564
+ });
112565
+ }
112566
+ function unwrapQueryKnowledges(raw) {
112567
+ if (!raw || typeof raw !== "object") return { output: [] };
112568
+ if ("output" in raw && Array.isArray(raw.output)) {
112569
+ return raw;
112570
+ }
112571
+ const w = raw;
112572
+ if (w.data != null && typeof w.data === "object") {
112573
+ const d = w.data;
112574
+ return Array.isArray(d.output) ? d : { output: [] };
112575
+ }
112576
+ return { output: [] };
112577
+ }
112578
+ function similarityPercent(score) {
112579
+ if (score === void 0 || Number.isNaN(score)) return "N/A";
112580
+ const s = Math.min(1, Math.max(0, score));
112581
+ return `${Math.round(s * 100)}%`;
112582
+ }
112583
+ function blockquoteMarkdown(text) {
112584
+ return text.split("\n").map((line) => `> ${line}`).join("\n");
112585
+ }
112586
+ function formatTime(iso) {
112587
+ if (!iso) return "";
112588
+ const d = new Date(iso);
112589
+ if (Number.isNaN(d.getTime())) return iso;
112590
+ return d.toLocaleString("zh-CN", {
112591
+ year: "numeric",
112592
+ month: "2-digit",
112593
+ day: "2-digit",
112594
+ hour: "2-digit",
112595
+ minute: "2-digit"
112596
+ });
112597
+ }
112598
+ function flatTagArray(tags) {
112599
+ if (!Array.isArray(tags) || tags.length === 0) return [];
112600
+ return tags.flat().filter((t) => typeof t === "string" && t.trim().length > 0);
112601
+ }
112602
+ function flatCategoryArray(category) {
112603
+ if (!Array.isArray(category) || category.length === 0) return [];
112604
+ return category.flat().filter((c) => typeof c === "string" && c.trim().length > 0);
112605
+ }
112606
+ function formatRagResultsMarkdown(query, items, subQueries) {
112607
+ if (!items.length) {
112608
+ return "\u672A\u627E\u5230\u76F8\u5173\u7ED3\u679C\u3002\n";
112609
+ }
112610
+ const parts = [];
112611
+ if (subQueries && subQueries.length > 1) {
112612
+ parts.push(`## \u67E5\u8BE2\uFF1A${query}
112613
+
112614
+ `);
112615
+ parts.push(
112616
+ `\u5DF2\u6309\u7A7A\u767D\u62C6\u5206\u4E3A **${subQueries.length}** \u4E2A\u5173\u952E\u8BCD\u5206\u522B\u68C0\u7D22\uFF0C\u7ED3\u679C\u6309\u7247\u6BB5\u53BB\u91CD\uFF08\u4FDD\u7559\u6700\u9AD8\u76F8\u4F3C\u5EA6\uFF09\u540E\u6392\u5E8F\u3002
112617
+
112618
+ `
112619
+ );
112620
+ parts.push(`${subQueries.map((q) => `- \`${q}\``).join("\n")}
112621
+
112622
+ `);
112623
+ } else {
112624
+ parts.push(`## \u67E5\u8BE2\uFF1A${query}
112625
+ `);
112626
+ }
112627
+ items.forEach((item, itemIndex) => {
112628
+ const fields = item.fields;
112629
+ if (!fields) return;
112630
+ const name = fields.name ?? "\u672A\u77E5\u6587\u4EF6";
112631
+ const content = fields.content ?? "";
112632
+ const sourceUrl = fields.source_url ?? "";
112633
+ const createTime = fields.create_time ?? "";
112634
+ const usageCount = fields.usage_count ?? 0;
112635
+ parts.push(`### ${itemIndex + 1}. ${name}
112636
+ `);
112637
+ if (item.id) {
112638
+ parts.push(`**\u7247\u6BB5 ID\uFF1A** \`${item.id}\`
112639
+ `);
112640
+ }
112641
+ parts.push(`**\u76F8\u4F3C\u5EA6\u5206\u6570\uFF1A** ${similarityPercent(item.score)}
112642
+ `);
112643
+ if (createTime) {
112644
+ parts.push(`**\u521B\u5EFA\u65F6\u95F4\uFF1A** ${formatTime(createTime)}
112645
+ `);
112646
+ }
112647
+ if (usageCount > 0) {
112648
+ parts.push(`**\u4F7F\u7528\u6B21\u6570\uFF1A** ${usageCount}
112649
+ `);
112650
+ }
112651
+ if (sourceUrl) {
112652
+ parts.push(`**\u9884\u89C8\u94FE\u63A5\uFF1A** ${sourceUrl}
112653
+ `);
112654
+ }
112655
+ if (content) {
112656
+ parts.push(`
112657
+ **\u5185\u5BB9\uFF1A**
112658
+
112659
+ `);
112660
+ parts.push(`${blockquoteMarkdown(content.trim())}
112661
+
112662
+ `);
112663
+ }
112664
+ const tagFlat = flatTagArray(fields.tags);
112665
+ if (tagFlat.length > 0) {
112666
+ parts.push(`**\u6807\u7B7E\uFF1A** ${tagFlat.join(", ")}
112667
+ `);
112668
+ }
112669
+ const catFlat = flatCategoryArray(fields.category);
112670
+ if (catFlat.length > 0) {
112671
+ parts.push(`**\u5206\u7C7B\uFF1A** ${catFlat.join(", ")}
112672
+ `);
112673
+ }
112674
+ parts.push(`
112675
+ ---
112676
+
112677
+ `);
112678
+ });
112679
+ return parts.join("");
112680
+ }
112681
+ function normalizeQueryPartition(p) {
112682
+ return p === "wiki" ? "wiki" : "default";
112683
+ }
112684
+ function requireCsoBaseUrl(config) {
112685
+ const url = config.csoBaseUrl?.trim();
112686
+ if (!url) {
112687
+ console.error("\n\u274C \u672A\u627E\u5230 CSO/RAG \u7F51\u5173\u5730\u5740\uFF08csoBaseUrl\uFF09\u3002");
112688
+ process.exit(1);
112689
+ }
112690
+ return url;
112691
+ }
112692
+ function buildQueryKnowledgesSearchParams(options) {
112693
+ const p = new URLSearchParams();
112694
+ p.append("queryTxt", options.queryTxt);
112695
+ p.append("belongTo", String(options.belongTo));
112696
+ p.append("belongToId", options.belongToId);
112697
+ p.append("topK", String(options.topK));
112698
+ for (const id of options.sourceIds) {
112699
+ p.append("sourceid", id);
112700
+ }
112701
+ for (const id of options.folderIds) {
112702
+ p.append("comid", id);
112703
+ }
112704
+ for (const t of options.tags) {
112705
+ p.append("tags", t);
112706
+ }
112707
+ for (const c of options.categories) {
112708
+ p.append("category", c);
112709
+ }
112710
+ p.append("partition", options.partition);
112711
+ return p.toString();
112712
+ }
112713
+ async function runRagQuery(options) {
112714
+ const sourceIds = splitCsv2(options.sourceId);
112715
+ const folderIds = splitCsv2(options.folderId);
112716
+ let topK = options.topK ?? 7;
112717
+ if (!Number.isFinite(topK) || !Number.isInteger(topK)) {
112718
+ topK = 7;
112719
+ }
112720
+ const clamped = Math.min(30, Math.max(3, topK));
112721
+ let belongTo = options.belongTo ?? 0;
112722
+ if (!Number.isFinite(belongTo)) {
112723
+ belongTo = 0;
112724
+ }
112725
+ const queryTxt = String(options.query ?? "").trim();
112726
+ if (!queryTxt) {
112727
+ console.error("\n\u274C --query \u4E0D\u80FD\u4E3A\u7A7A\u3002");
112728
+ process.exit(1);
112729
+ }
112730
+ const subQueries = splitQueryWhitespaceToKeywords(queryTxt);
112731
+ const multiKeyword = subQueries.length > 1;
112732
+ const mergedCap = Math.min(30, clamped * subQueries.length);
112733
+ const config = loadConfig(options.token);
112734
+ const csoBaseUrl = requireCsoBaseUrl(config);
112735
+ let belongToId = String(options.belongToId ?? "").trim();
112736
+ if (!belongToId) {
112737
+ const me = await fetchSiluzanCurrentUser(config.apiBaseUrl, config);
112738
+ belongToId = (me?.companyId ?? "").trim();
112739
+ }
112740
+ if (!belongToId) {
112741
+ console.error(
112742
+ "\n\u274C \u65E0\u6CD5\u786E\u5B9A\u4F01\u4E1A ID\uFF1A\u8BF7\u4F20\u5165 --belong-to-id\uFF0C\u6216\u786E\u4FDD GET /query/account/me \u54CD\u5E94\u4E2D\u542B companyId\u3002"
112743
+ );
112744
+ process.exit(1);
112745
+ }
112746
+ const tagList = splitCsv2(options.tags);
112747
+ const categories = splitCsv2(options.category);
112748
+ const rawPartition = options.partition?.trim();
112749
+ if (rawPartition && rawPartition !== "wiki" && rawPartition !== "default") {
112750
+ console.error("\n\u274C partition \u53EA\u80FD\u4E3A wiki \u6216 default\u3002");
112751
+ process.exit(1);
112752
+ }
112753
+ const partition = normalizeQueryPartition(rawPartition);
112754
+ const baseSearch = {
112755
+ belongTo,
112756
+ belongToId,
112757
+ topK: clamped,
112758
+ sourceIds,
112759
+ folderIds,
112760
+ tags: tagList,
112761
+ categories,
112762
+ partition
112763
+ };
112764
+ const verbose = Boolean(options.verbose);
112765
+ async function fetchKnowledgesOne(queryPart) {
112766
+ const qs = buildQueryKnowledgesSearchParams({
112767
+ ...baseSearch,
112768
+ queryTxt: queryPart
112769
+ });
112770
+ const url = `${csoBaseUrl}/cutapi/v1/material/queryknowledges?${qs}`;
112771
+ let raw;
112772
+ try {
112773
+ raw = await apiFetch2(
112774
+ url,
112775
+ config,
112776
+ {},
112777
+ verbose
112778
+ );
112779
+ } catch (e) {
112780
+ const msg = e.message;
112781
+ console.error(`
112782
+ \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${msg}`);
112783
+ if (!verbose) console.error(" \u52A0 --verbose \u53C2\u6570\u53EF\u67E5\u770B\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F");
112784
+ process.exit(1);
112785
+ }
112786
+ if (raw && typeof raw === "object" && "code" in raw) {
112787
+ const w = raw;
112788
+ if (w.code !== 1) {
112789
+ console.error(`
112790
+ \u274C \u77E5\u8BC6\u5E93\u68C0\u7D22\u5931\u8D25\uFF1A${w.message || `\u4E1A\u52A1\u7801 ${w.code}`}`);
112791
+ process.exit(1);
112792
+ }
112793
+ }
112794
+ const payload = unwrapQueryKnowledges(raw);
112795
+ const rawOutput = Array.isArray(payload.output) ? payload.output : [];
112796
+ return normalizeRagScores(rawOutput);
112797
+ }
112798
+ let output;
112799
+ if (multiKeyword) {
112800
+ const batches = await Promise.all(subQueries.map((q) => fetchKnowledgesOne(q)));
112801
+ output = mergeRagResultsByBestScore(batches).slice(0, mergedCap);
112802
+ } else {
112803
+ output = await fetchKnowledgesOne(subQueries[0]);
112804
+ }
112805
+ if (options.json) {
112806
+ console.log(
112807
+ JSON.stringify(
112808
+ {
112809
+ query: queryTxt,
112810
+ ...multiKeyword ? { subQueries, mergedCap } : {},
112811
+ belongToId,
112812
+ tags: tagList,
112813
+ topK: clamped,
112814
+ output,
112815
+ partition
112816
+ },
112817
+ null,
112818
+ 2
112819
+ )
112820
+ );
112821
+ return;
112822
+ }
112823
+ if (output.length === 0) {
112824
+ console.log("\n\u672A\u68C0\u7D22\u5230\u5339\u914D\u7247\u6BB5\uFF08\u53EF\u5C1D\u8BD5\u653E\u5BBD --top-k \u6216\u8C03\u6574\u5173\u952E\u8BCD/\u8303\u56F4\uFF09\u3002\n");
112825
+ return;
112826
+ }
112827
+ const modeHint = multiKeyword ? ` \xB7 \u5206\u8BCD\u5408\u5E76\u2264${mergedCap}\u6761` : "";
112828
+ const header = `> \u77E5\u8BC6\u5E93\u68C0\u7D22 \xB7 topK=${clamped}${modeHint} \xB7 \u7528\u4E8E\u5E7F\u544A\u6295\u653E/\u8D26\u6237\u5206\u6790\u4E1A\u52A1\u53C2\u8003
112829
+ >
112830
+ `;
112831
+ const body = formatRagResultsMarkdown(queryTxt, output, multiKeyword ? subQueries : void 0);
112832
+ console.log(`
112833
+ ${header}${body}`);
112834
+ }
112835
+ var RAG_ROOT_FOLDER_ID = "88888888-8888-8888-0000-888888888888";
112836
+ var RAG_BUILTIN_PARENT_FOLDER_ID = "88888888-8888-0000-0000-888888888888";
112837
+ function buildMaterialQueryListParams(belongToId, folderId) {
112838
+ return new URLSearchParams({
112839
+ belongTo: "0",
112840
+ belongToId,
112841
+ showCut: "true",
112842
+ isGlobal: "true",
112843
+ orderBy: "createTime",
112844
+ isDesc: "true",
112845
+ pageIndex: "1",
112846
+ pageSize: "999",
112847
+ folderId
112848
+ });
112849
+ }
112850
+ function mergeFoldersByIdOrder(primary, secondary) {
112851
+ const seen = new Set(primary.map((f) => f.id));
112852
+ const out = [...primary];
112853
+ for (const f of secondary) {
112854
+ if (!seen.has(f.id)) {
112855
+ seen.add(f.id);
112856
+ out.push(f);
112857
+ }
112858
+ }
112859
+ return out;
112860
+ }
112861
+ async function safeQueryMaterialFolderList(csoBaseUrl, config, belongToId, folderId, verbose) {
112862
+ const p = buildMaterialQueryListParams(belongToId, folderId);
112863
+ const url = `${csoBaseUrl}/cutapi/v1/material/querylist?${p.toString()}`;
112864
+ try {
112865
+ const raw = await apiFetch2(url, config, {}, verbose);
112866
+ if (raw.code !== 1) {
112867
+ return { ok: false, reason: raw.message || `\u4E1A\u52A1\u7801 ${raw.code}` };
112868
+ }
112869
+ return { ok: true, folders: raw.data?.folders ?? [] };
112870
+ } catch (e) {
112871
+ return { ok: false, reason: e.message };
112872
+ }
112873
+ }
112874
+ async function runRagList(options) {
112875
+ const config = loadConfig(options.token);
112876
+ const csoBaseUrl = requireCsoBaseUrl(config);
112877
+ const verbose = Boolean(options.verbose);
112878
+ let belongToId = String(options.belongToId ?? "").trim();
112879
+ if (!belongToId) {
112880
+ const me = await fetchSiluzanCurrentUser(config.apiBaseUrl, config);
112881
+ belongToId = (me?.companyId ?? "").trim();
112882
+ }
112883
+ if (!belongToId) {
112884
+ console.error(
112885
+ "\n\u274C \u65E0\u6CD5\u786E\u5B9A\u4F01\u4E1A ID\uFF1A\u8BF7\u4F20\u5165 --belong-to-id\uFF0C\u6216\u786E\u4FDD GET /query/account/me \u54CD\u5E94\u4E2D\u542B companyId\u3002"
112886
+ );
112887
+ process.exit(1);
112888
+ }
112889
+ const [rootRes, builtinRes] = await Promise.all([
112890
+ safeQueryMaterialFolderList(csoBaseUrl, config, belongToId, RAG_ROOT_FOLDER_ID, verbose),
112891
+ safeQueryMaterialFolderList(
112892
+ csoBaseUrl,
112893
+ config,
112894
+ belongToId,
112895
+ RAG_BUILTIN_PARENT_FOLDER_ID,
112896
+ verbose
112897
+ )
112898
+ ]);
112899
+ if (!rootRes.ok) {
112900
+ console.error(`
112901
+ \u274C \u83B7\u53D6\u77E5\u8BC6\u5E93\u5217\u8868\u5931\u8D25\uFF08\u6839\u8282\u70B9\uFF09\uFF1A${rootRes.reason}`);
112902
+ if (!verbose) console.error(" \u52A0 --verbose \u53C2\u6570\u53EF\u67E5\u770B\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F");
112903
+ process.exit(1);
112904
+ }
112905
+ let folders = rootRes.folders;
112906
+ if (builtinRes.ok) {
112907
+ folders = mergeFoldersByIdOrder(rootRes.folders, builtinRes.folders);
112908
+ } else if (verbose) {
112909
+ console.error(
112910
+ `
112911
+ \u26A0\uFE0F \u83B7\u53D6\u77E5\u8BC6\u5E93\u5217\u8868\u5931\u8D25\uFF08\u5185\u7F6E\u7236\u7EA7\uFF09\uFF1A${builtinRes.reason}\uFF08\u5DF2\u4EC5\u7528\u6839\u8282\u70B9\u6570\u636E\u5408\u5E76\u5C55\u793A\uFF09`
112912
+ );
112913
+ }
112914
+ if (options.ragOnly) {
112915
+ folders = folders.filter((f) => f.ragStatus === true);
112916
+ }
112917
+ if (options.json) {
112918
+ console.log(
112919
+ JSON.stringify(
112920
+ {
112921
+ belongToId,
112922
+ total: folders.length,
112923
+ folders: folders.map((f) => ({
112924
+ id: f.id,
112925
+ name: f.name,
112926
+ ragStatus: f.ragStatus,
112927
+ createTime: f.createTime
112928
+ }))
112929
+ },
112930
+ null,
112931
+ 2
112932
+ )
112933
+ );
112934
+ return;
112935
+ }
112936
+ if (folders.length === 0) {
112937
+ console.log(options.ragOnly ? "\n\u6682\u65E0\u5DF2\u5EFA RAG \u7D22\u5F15\u7684\u77E5\u8BC6\u5E93\u3002\n" : "\n\u6682\u65E0\u77E5\u8BC6\u5E93\u6587\u4EF6\u5939\u3002\n");
112938
+ return;
112939
+ }
112940
+ const ragTag = (s) => s === true ? "\u2705 \u5DF2\u7D22\u5F15" : "\u2014";
112941
+ const rows = folders.map((f) => ({
112942
+ id: f.id,
112943
+ name: f.name,
112944
+ ragStatus: ragTag(f.ragStatus),
112945
+ createTime: formatTime(f.createTime)
112946
+ }));
112947
+ const colWidths = {
112948
+ id: Math.max(36, ...rows.map((r) => r.id.length)),
112949
+ name: Math.max(4, ...rows.map((r) => [...r.name].length)),
112950
+ ragStatus: Math.max(6, ...rows.map((r) => [...r.ragStatus].length)),
112951
+ createTime: Math.max(10, ...rows.map((r) => r.createTime.length))
112952
+ };
112953
+ const pad = (s, w) => s + " ".repeat(Math.max(0, w - [...s].length));
112954
+ const sep2 = `+-${"-".repeat(colWidths.id)}-+-${"-".repeat(colWidths.name)}-+-${"-".repeat(colWidths.ragStatus)}-+-${"-".repeat(colWidths.createTime)}-+`;
112955
+ const header = `| ${pad("ID", colWidths.id)} | ${pad("\u540D\u79F0", colWidths.name)} | ${pad("RAG", colWidths.ragStatus)} | ${pad("\u521B\u5EFA\u65F6\u95F4", colWidths.createTime)} |`;
112956
+ const lines = [
112957
+ `
112958
+ \u77E5\u8BC6\u5E93\u5217\u8868\uFF08belongToId=${belongToId}\uFF0C\u5171 ${folders.length} \u6761\uFF09
112959
+ `,
112960
+ sep2,
112961
+ header,
112962
+ sep2,
112963
+ ...rows.map(
112964
+ (r) => `| ${pad(r.id, colWidths.id)} | ${pad(r.name, colWidths.name)} | ${pad(r.ragStatus, colWidths.ragStatus)} | ${pad(r.createTime, colWidths.createTime)} |`
112965
+ ),
112966
+ sep2,
112967
+ "",
112968
+ options.ragOnly ? "\u{1F4A1} \u4F7F\u7528 --folder-id <id> \u6307\u5B9A\u4E0A\u65B9\u77E5\u8BC6\u5E93\uFF0C\u914D\u5408 rag query \u68C0\u7D22\u5BA2\u6237/\u4EA7\u54C1\u8D44\u6599\u3002\n" : "\u{1F4A1} \u4EC5\u663E\u793A RAG \u5DF2\u7D22\u5F15\u7684\u77E5\u8BC6\u5E93\uFF1A\u52A0 --rag-only \u53C2\u6570\u3002\n \u7528 --folder-id <id> \u5728 rag query \u4E2D\u6307\u5B9A\u68C0\u7D22\u8303\u56F4\u3002\n"
112969
+ ];
112970
+ console.log(lines.join("\n"));
112971
+ }
112972
+ function register22(program2) {
112973
+ const ragCmd = program2.command("rag").description(
112974
+ "RAG \u77E5\u8BC6\u5E93\u68C0\u7D22\uFF08\u534F\u52A9\u5E7F\u544A\u6295\u653E\u3001\u62D3\u8BCD\u3001\u8D26\u6237\u8BCA\u65AD\u4E0E\u62A5\u544A\uFF1B\u9ED8\u8BA4 belongToId=account/me \u7684 companyId\uFF09"
112975
+ );
112976
+ ragCmd.command("list").description("\u5217\u51FA\u5F53\u524D\u4F01\u4E1A\u4E0B\u6240\u6709\u77E5\u8BC6\u5E93\u6587\u4EF6\u5939\uFF0C\u4F9B\u9009\u62E9\u5BA2\u6237/\u54C1\u724C\u68C0\u7D22\u8303\u56F4").option("--belong-to-id <id>", "\u4F01\u4E1A ID\uFF1B\u4E0D\u4F20\u5219\u4ECE GET /query/account/me \u53D6 companyId").option("--rag-only", "\u4EC5\u663E\u793A\u5DF2\u5EFA RAG \u5411\u91CF\u7D22\u5F15\uFF08ragStatus=true\uFF09\u7684\u77E5\u8BC6\u5E93", false).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option("--json", "\u8F93\u51FA\u5B8C\u6574 JSON", false).option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
112977
+ async (opts) => {
112978
+ await runRagList({
112979
+ belongToId: opts.belongToId,
112980
+ ragOnly: opts.ragOnly,
112981
+ token: opts.token,
112982
+ json: opts.json,
112983
+ verbose: opts.verbose
112984
+ });
112985
+ }
112986
+ );
112987
+ ragCmd.command("query").description("\u6309\u5173\u952E\u8BCD\u68C0\u7D22\u5DF2\u7EB3\u5165\u5411\u91CF\u7684\u77E5\u8BC6\u7247\u6BB5\uFF08GET cutapi/v1/material/queryknowledges\uFF09").requiredOption("-q, --query <text>", "\u68C0\u7D22\u5173\u952E\u8BCD\u6216\u95EE\u53E5").option("--belong-to-id <id>", "\u4F01\u4E1A ID\uFF1B\u4E0D\u4F20\u5219\u4ECE GET /query/account/me \u53D6 companyId").option("--source-id <ids>", "\u7D20\u6750\u6587\u4EF6 ID\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u53EF\u9009\uFF0Cqueryknowledges \u7684 sourceid\uFF09").option("--folder-id <ids>", "\u6587\u4EF6\u5939 ID\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u53EF\u9009\uFF0Ccomid\uFF09").option("--top-k <n>", "\u8FD4\u56DE\u6761\u6570\uFF0C3\u201330\uFF08\u9ED8\u8BA4 7\uFF09", "7").option("--belong-to <n>", "\u5F52\u5C5E\u7C7B\u578B\uFF08\u9ED8\u8BA4 0\uFF0C\u4F01\u4E1A\uFF09", "0").option("--tags <tags>", "\u6807\u7B7E\u8FC7\u6EE4\uFF0C\u9017\u53F7\u5206\u9694\uFF1B\u4E0D\u4F20\u5219\u4E0D\u8FC7\u6EE4\u6807\u7B7E\uFF08\u5168\u91CF\u68C0\u7D22\uFF09").option("--category <categories>", "\u5206\u7C7B\u8FC7\u6EE4\uFF0C\u9017\u53F7\u5206\u9694").option(
112988
+ "--partition <wiki|default>",
112989
+ "\u68C0\u7D22\u5206\u533A\uFF08queryknowledges \u7684 partition\uFF0C\u9ED8\u8BA4 default\uFF09",
112990
+ "default"
112991
+ ).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option("--json", "\u8F93\u51FA\u5B8C\u6574 JSON", false).option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
112992
+ async (opts) => {
112993
+ await runRagQuery({
112994
+ query: opts.query,
112995
+ belongToId: opts.belongToId,
112996
+ sourceId: opts.sourceId,
112997
+ folderId: opts.folderId,
112998
+ topK: opts.topK ? parseInt(opts.topK, 10) : void 0,
112999
+ belongTo: opts.belongTo ? parseInt(opts.belongTo, 10) : void 0,
113000
+ tags: opts.tags,
113001
+ category: opts.category,
113002
+ partition: opts.partition,
113003
+ token: opts.token,
113004
+ json: opts.json,
113005
+ verbose: opts.verbose
113006
+ });
113007
+ }
113008
+ );
113009
+ }
113010
+
112445
113011
  // src/commands/account-manage/share.ts
112446
113012
  init_auth();
112447
113013
  init_cli_json_snapshot();
@@ -113567,7 +114133,7 @@ async function runAccountBmBind(opts) {
113567
114133
  }
113568
114134
 
113569
114135
  // src/commands/account-manage-register.ts
113570
- function register22(program2) {
114136
+ function register23(program2) {
113571
114137
  const accountCmd = program2.command("account").description("\u5E7F\u544A\u8D26\u6237\u7BA1\u7406\uFF1AOAuth \u6DFB\u52A0\u6388\u6743\u3001\u89E3\u9664\u5173\u8054\uFF08\u89E3\u7ED1\uFF09\u3001Google MCC \u7ED1\u5B9A/\u89E3\u7ED1\u3001\u8D26\u53F7\u5206\u4EAB");
113572
114138
  accountCmd.command("delink").description(
113573
114139
  "\u89E3\u9664\u6388\u6743\uFF1A\u65AD\u5F00\u5E7F\u544A\u8D26\u6237\u4E0E\u5F53\u524D\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u7684\u5173\u8054\uFF08\u7F51\u9875 manageAccounts\u300C\u89E3\u9664\u6388\u6743\u300D\uFF1B--id \u6216 --ids\uFF09"
@@ -115003,7 +115569,7 @@ TikTok \u6CE8\u518C\u5730\u5217\u8868\uFF08\u7B2C 1 \u9875\uFF0C\u672C\u9875 ${r
115003
115569
  }
115004
115570
 
115005
115571
  // src/commands/open-account-register.ts
115006
- function register23(program2) {
115572
+ function register24(program2) {
115007
115573
  const openAccountCmd = program2.command("open-account").description("\u5F00\u6237\u7533\u8BF7\uFF08Google / TikTok / Yandex / Bing / Kwai\uFF09");
115008
115574
  openAccountCmd.command("list-groups").description("\u67E5\u8BE2\u5E7F\u544A\u4E3B\u7EC4\u5217\u8868\uFF08\u83B7\u53D6\u5F00\u6237\u6240\u9700\u7684 --advertiser-id\uFF09").option("-t, --token <token>", "Auth Token").option("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).option(
115009
115575
  "--json-out <path>",
@@ -115215,7 +115781,7 @@ function register23(program2) {
115215
115781
  }
115216
115782
 
115217
115783
  // src/commands/diagnostic.ts
115218
- function register24(program2) {
115784
+ function register25(program2) {
115219
115785
  program2.command("diagnostic").description("Google \u5E7F\u544A\u8BCA\u65AD\uFF1A\u8F93\u51FA\u4E1D\u8DEF\u8D5E\u5E7F\u544A\u8BCA\u65AD\u62A5\u544A\u9875\u9762\u5730\u5740").action(() => {
115220
115786
  console.log("\nGoogle \u5E7F\u544A\u8BCA\u65AD\n");
115221
115787
  console.log(
@@ -115279,7 +115845,8 @@ var REGISTRARS = [
115279
115845
  register21,
115280
115846
  register22,
115281
115847
  register23,
115282
- register24
115848
+ register24,
115849
+ register25
115283
115850
  ];
115284
115851
  for (const reg of REGISTRARS) reg(program);
115285
115852
  program.parseAsync();
@@ -2,7 +2,7 @@
2
2
  name: siluzan-tso
3
3
  description: >-
4
4
  Siluzan TSO 广告 skill(siluzan-tso-cli):Google/Bing/Yandex/TikTok/Kwai 账户开户、授权与分享、数据与消耗、Google 广告管理、发票与转账、优化报告与智能预警、TikTok/Meta 线索。
5
- 各能力执行细节按本文「功能以及对应文档」表路由到 references/;周期报告、OKKI 周报、Google 账户询盘分析等固定触发模板见 report-templates/ 与 REPORT-WORKFLOW.md
5
+ 各能力执行细节按本文「功能以及对应文档」表路由到 references/;周期报告、OKKI 周报、Google 账户询盘分析等固定触发模板见 report-templates/ 与 REPORT-WORKFLOW.md,有提供rag(知识库查询)
6
6
  license: MIT
7
7
  metadata:
8
8
  requires: nodejs,siluzan-tso-cli
@@ -43,6 +43,7 @@ Windows 注意:部分 Agent 客户端通过 PowerShell / cmd 代执行命令
43
43
  | `references/open-account-by-media.md` | 各媒体开户、参数与资料要求 |
44
44
  | `references/google-ads.md` | Google Ads 创建、修改、优化与管理;含 `ad list` 等读命令的 JSON 拒审字段。**搜索广告方案/计划**见本文开篇必读表与 `google-ads-rules/`(launch 模板、按需落地页推断专篇) |
45
45
  | `references/keyword-planner-workflows.md` | Google 关键字推荐与拓词:`keyword` / `google-analysis` / `ad` 多命令编排;关键词规划师、执行步骤 |
46
+ | `references/rag.md` | **知识库检索**:为拓词、广告方案、诊断/询盘报告等提供客户产品/行业事实依据(`rag list` + `rag query`) |
46
47
  | `references/reporting.md` | Siluzan TSO 优化报告(Google/TikTok)的生成、推送与查看 |
47
48
  | `references/account-analytics.md` | 广告平台账户分析数据拉取与分析/诊断报告模板 |
48
49
  | `references/google-analysis-batch.md` | **多账户 × 多维度** Google 数据批处理引擎(`run/resume/status`);产物目录、退出码、stdout 协议、错误分类、resume 语义 |
@@ -80,6 +81,7 @@ Windows 注意:部分 Agent 客户端通过 PowerShell / cmd 代执行命令
80
81
 
81
82
  - **报告生成(推荐)**:`google-analysis … --json-out <目录>`(通过 `--sections` 选取维度),**须编写并执行代码**(Node/Python)从目录读取 JSON 完成计算再写出最终文件。详见 `references/account-analytics.md`。**禁止**用 `Read` 扫 JSON 后在对话里手填数,**禁止**在报告脚本中以字面量写死应从 JSON 得到的业务数据。
82
83
  - **广告账户**:开户→`references/open-account-by-media.md`;管理→`references/accounts.md`;分析→`references/account-analytics.md`;Google 广告→`references/google-ads.md`
84
+ - **需要客户/产品背景**(拓词、方案、报告背景段):先 `references/rag.md` → `rag list` + `rag query`,再衔接 `keyword` / `ad` / `google-analysis`
83
85
  - **仅调用接口、无需你输出**:优化记录(`references/optimize.md`)、线索表单(`references/clue.md`)、预警(`references/forewarning.md`)、财务(`references/finance.md`)
84
86
 
85
87
  **写报告前必读**:`stats` / `balance` / `list-accounts` 里的 `status` 只表示**广告账户**是否可用,**不能**当作**广告系列**是否启用;系列状态须用 `ad campaigns`。
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.20-beta.2",
4
- "publishedAt": 1778828241807
3
+ "version": "1.1.20-beta.4",
4
+ "publishedAt": 1778829586058
5
5
  }