@bgicli/bgicli 2.2.0 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -74
- package/dist/bgi.js +119 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# BGI CLI
|
|
2
2
|
|
|
3
|
-
**BGI CLI**
|
|
3
|
+
**BGI CLI** 是面向中国生物学研究者的 AI 终端工具,开箱即用,无需额外配置。
|
|
4
4
|
|
|
5
|
-
- ✅
|
|
6
|
-
- ✅
|
|
7
|
-
- ✅
|
|
8
|
-
- ✅
|
|
9
|
-
- ✅
|
|
5
|
+
- ✅ **开箱即用** — `npm install -g @bgicli/bgicli` 即可
|
|
6
|
+
- ✅ **内置 889 个技能** — 21 个生信工作流 + 868 个 OpenClaw 医学技能,自动安装
|
|
7
|
+
- ✅ **智能技能路由** — 描述任务自动激活对应技能,无需手动搜索
|
|
8
|
+
- ✅ **中国 AI 服务商** — 百炼(DashScope)聚合:Qwen3.5、DeepSeek、Kimi、MiniMax 等 20+ 模型
|
|
9
|
+
- ✅ **真实工具调用** — 执行 bash、读写文件、运行 R/Python 脚本
|
|
10
|
+
- ✅ **内网支持** — 可接入公司私有化部署的大模型
|
|
10
11
|
|
|
11
12
|
---
|
|
12
13
|
|
|
@@ -14,96 +15,178 @@
|
|
|
14
15
|
|
|
15
16
|
```bash
|
|
16
17
|
# 需要 Node.js 18+
|
|
17
|
-
npm install -g @bgicli/bgicli
|
|
18
|
+
npm install -g @bgicli/bgicli --registry https://registry.npmjs.org
|
|
19
|
+
```
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
npm run build
|
|
24
|
-
npm link
|
|
21
|
+
首次运行自动初始化工作流和技能库(约 16MB),无需额外操作。
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bgi
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 卸载
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# 卸载 npm 包
|
|
33
|
+
npm uninstall -g @bgicli/bgicli
|
|
34
|
+
|
|
35
|
+
# 删除本地数据(配置、工作流、技能库)
|
|
36
|
+
# Linux / macOS
|
|
37
|
+
rm -rf ~/.bgicli
|
|
38
|
+
|
|
39
|
+
# Windows PowerShell
|
|
40
|
+
Remove-Item -Recurse -Force "$env:USERPROFILE\.bgicli"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
27
45
|
## 快速开始
|
|
28
46
|
|
|
29
47
|
```bash
|
|
30
|
-
bgi
|
|
48
|
+
bgi # 启动
|
|
49
|
+
/connect # 首次配置 API Key
|
|
50
|
+
/cat # 浏览技能分类目录
|
|
51
|
+
/sk deseq2 # 搜索并激活 DESeq2 工作流
|
|
52
|
+
/help # 查看全部命令
|
|
31
53
|
```
|
|
32
54
|
|
|
33
|
-
|
|
55
|
+
首次运行提示配置百炼 (DashScope) API Key:
|
|
56
|
+
- 获取地址:[bailian.console.aliyun.com](https://bailian.console.aliyun.com/) → API Key 管理
|
|
34
57
|
|
|
35
58
|
---
|
|
36
59
|
|
|
37
60
|
## 支持的 AI 服务商
|
|
38
61
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
|
43
|
-
|
|
44
|
-
|
|
|
62
|
+
### 百炼 · 阿里云 (DashScope) — 默认
|
|
63
|
+
通过百炼统一接入多个国内主流模型:
|
|
64
|
+
|
|
65
|
+
| 模型 | 命令 |
|
|
66
|
+
|------|------|
|
|
67
|
+
| Qwen3.5-plus(默认) | `/model qwen3.5-plus` |
|
|
68
|
+
| Qwen3-235B | `/model qwen3-235b-a22b` |
|
|
69
|
+
| DeepSeek-R1 | `/model deepseek-r1` |
|
|
70
|
+
| DeepSeek-V3 | `/model deepseek-v3` |
|
|
71
|
+
| Kimi-K2.5 | `/model kimi-k2.5` |
|
|
72
|
+
| MiniMax-M2.5 | `/model MiniMax-M2.5` |
|
|
73
|
+
| QwQ-Plus(推理) | `/model qwq-plus` |
|
|
74
|
+
|
|
75
|
+
获取 API Key:[bailian.console.aliyun.com](https://bailian.console.aliyun.com/)
|
|
76
|
+
|
|
77
|
+
### 内网私有化部署
|
|
78
|
+
```bash
|
|
79
|
+
/provider intranet # 切换到内网 Qwen3-235B(无需 Key)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 自定义 OpenAI 兼容服务
|
|
83
|
+
```bash
|
|
84
|
+
/connect custom # 配置任意 vLLM / Ollama / FastChat 地址
|
|
85
|
+
```
|
|
45
86
|
|
|
46
87
|
---
|
|
47
88
|
|
|
48
89
|
## 命令参考
|
|
49
90
|
|
|
91
|
+
### 服务商 / 模型
|
|
50
92
|
| 命令 | 说明 |
|
|
51
93
|
|------|------|
|
|
52
|
-
| `/provider <name>` |
|
|
94
|
+
| `/provider <name>` | 切换服务商 (`bailian` / `intranet` / `custom`) |
|
|
53
95
|
| `/model <name>` | 切换模型 |
|
|
54
|
-
| `/models` |
|
|
96
|
+
| `/models` | 列出当前服务商所有可用模型 |
|
|
55
97
|
| `/providers` | 列出所有服务商 |
|
|
56
98
|
| `/connect [provider]` | 配置 API Key |
|
|
57
99
|
| `/status` | 显示当前配置 |
|
|
58
|
-
| `/clear` | 清空对话历史 |
|
|
59
|
-
| `/help` | 显示帮助 |
|
|
60
|
-
| `exit` / `quit` | 退出 |
|
|
61
100
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
101
|
+
### 技能与工作流
|
|
102
|
+
| 命令 | 说明 |
|
|
103
|
+
|------|------|
|
|
104
|
+
| `/cat` | 按领域浏览技能分类目录(11个领域) |
|
|
105
|
+
| `/sk` | 列出全部技能(工作流 + OpenClaw Medical) |
|
|
106
|
+
| `/sk <关键词>` | 搜索并激活技能(如 `/sk deseq2`、`/sk alphafold`) |
|
|
107
|
+
| `/wf` | 同 `/sk`,别名 |
|
|
67
108
|
|
|
68
|
-
|
|
69
|
-
- `bulk-rnaseq-counts-to-de-deseq2` — DESeq2 差异表达分析
|
|
70
|
-
- `bulk-omics-clustering` — 样本/特征聚类
|
|
71
|
-
- `scrnaseq-scanpy-core-analysis` — 单细胞分析 (Scanpy/Python)
|
|
72
|
-
- `scrnaseq-seurat-core-analysis` — 单细胞分析 (Seurat/R)
|
|
73
|
-
- `spatial-transcriptomics` — 空间转录组
|
|
74
|
-
- `coexpression-network` — 共表达网络 (WGCNA)
|
|
75
|
-
- `functional-enrichment-from-degs` — 功能富集 (GO/KEGG/GSEA)
|
|
76
|
-
- `grn-pyscenic` — 基因调控网络 (pySCENIC)
|
|
109
|
+
> **智能路由**:直接描述任务,BGI CLI 自动识别并激活对应技能。
|
|
77
110
|
|
|
78
|
-
###
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
111
|
+
### 对话管理
|
|
112
|
+
| 命令 | 说明 |
|
|
113
|
+
|------|------|
|
|
114
|
+
| `/clear` | 清空对话历史(重置激活的技能) |
|
|
115
|
+
| `/history` | 查看对话统计(轮次 / Token 估算) |
|
|
116
|
+
| `/save [文件名]` | 保存对话为 Markdown 文件 |
|
|
117
|
+
| `/think [on\|off]` | 切换思考模式(Qwen3 `/think` 前缀) |
|
|
84
118
|
|
|
85
|
-
###
|
|
86
|
-
|
|
119
|
+
### 文件与目录
|
|
120
|
+
| 命令 | 说明 |
|
|
121
|
+
|------|------|
|
|
122
|
+
| `/cd <路径>` | 更改工作目录 |
|
|
123
|
+
| `/cwd` | 显示当前工作目录 |
|
|
124
|
+
| `/tools` | 列出 AI 可调用的工具 |
|
|
125
|
+
| `@路径` | 消息中内嵌文件内容(如 `@data.csv 里有什么?`) |
|
|
87
126
|
|
|
88
|
-
###
|
|
89
|
-
|
|
90
|
-
|
|
127
|
+
### 其他
|
|
128
|
+
| 命令 | 说明 |
|
|
129
|
+
|------|------|
|
|
130
|
+
| `/help` | 显示帮助 |
|
|
131
|
+
| `exit` / `quit` / `q` | 退出 |
|
|
91
132
|
|
|
92
133
|
---
|
|
93
134
|
|
|
94
|
-
##
|
|
135
|
+
## 内置技能库
|
|
136
|
+
|
|
137
|
+
### 生物信息学工作流(21个)
|
|
138
|
+
|
|
139
|
+
#### 转录组学
|
|
140
|
+
| ID | 说明 |
|
|
141
|
+
|----|------|
|
|
142
|
+
| `bulk-rnaseq-counts-to-de-deseq2` | DESeq2 差异表达分析 |
|
|
143
|
+
| `bulk-omics-clustering` | 样本/特征聚类(K-Means / HDBSCAN) |
|
|
144
|
+
| `scrnaseq-scanpy-core-analysis` | 单细胞 RNA-seq(Scanpy/Python) |
|
|
145
|
+
| `scrnaseq-seurat-core-analysis` | 单细胞 RNA-seq(Seurat/R) |
|
|
146
|
+
| `spatial-transcriptomics` | 空间转录组(Visium) |
|
|
147
|
+
| `coexpression-network` | 共表达网络(WGCNA) |
|
|
148
|
+
| `functional-enrichment-from-degs` | 功能富集(GO / KEGG / GSEA) |
|
|
149
|
+
| `grn-pyscenic` | 基因调控网络(pySCENIC) |
|
|
150
|
+
|
|
151
|
+
#### 基因组学
|
|
152
|
+
| ID | 说明 |
|
|
153
|
+
|----|------|
|
|
154
|
+
| `genetic-variant-annotation` | VCF 变异注释(VEP / ANNOVAR) |
|
|
155
|
+
| `gwas-to-function-twas` | GWAS → TWAS 因果基因 |
|
|
156
|
+
| `mendelian-randomization-twosamplemr` | 孟德尔随机化 |
|
|
157
|
+
| `polygenic-risk-score-prs-catalog` | 多基因风险评分(PRS) |
|
|
158
|
+
| `pooled-crispr-screens` | CRISPR 文库筛选(MAGeCK / BAGEL2) |
|
|
159
|
+
|
|
160
|
+
#### 表观基因组
|
|
161
|
+
| ID | 说明 |
|
|
162
|
+
|----|------|
|
|
163
|
+
| `chip-atlas-peak-enrichment` | ChIP-seq 峰值富集 |
|
|
164
|
+
| `chip-atlas-diff-analysis` | 差异结合分析 |
|
|
165
|
+
| `chip-atlas-target-genes` | 转录因子靶基因鉴定 |
|
|
166
|
+
|
|
167
|
+
#### 临床与流行病学
|
|
168
|
+
| ID | 说明 |
|
|
169
|
+
|----|------|
|
|
170
|
+
| `clinicaltrials-landscape` | 临床试验格局分析 |
|
|
171
|
+
| `literature-preclinical` | 临床前文献系统提取 |
|
|
172
|
+
| `experimental-design-statistics` | 实验设计与统计检验 |
|
|
173
|
+
| `lasso-biomarker-panel` | LASSO 生物标志物筛选 |
|
|
174
|
+
| `pcr-primer-design` | PCR/qPCR 引物设计 |
|
|
175
|
+
|
|
176
|
+
### OpenClaw Medical Skills(868个)
|
|
177
|
+
|
|
178
|
+
覆盖结构生物学、单细胞、药物发现、抗体设计、文献检索等领域,使用 `/cat` 浏览分类目录。
|
|
95
179
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
# 从 bgicli-opencode 目录复制(如果已克隆旧仓库)
|
|
100
|
-
cp -r /path/to/old/workflows ~/.bgicli/workflows/
|
|
101
|
-
```
|
|
180
|
+
---
|
|
102
181
|
|
|
103
|
-
|
|
182
|
+
## 从源码安装
|
|
104
183
|
|
|
105
184
|
```bash
|
|
106
|
-
|
|
185
|
+
git clone https://github.com/zja2004/BGI-CLI.git
|
|
186
|
+
cd BGI-CLI
|
|
187
|
+
npm install
|
|
188
|
+
npm run build
|
|
189
|
+
npm link
|
|
107
190
|
```
|
|
108
191
|
|
|
109
192
|
---
|
|
@@ -111,22 +194,17 @@ bash install.sh
|
|
|
111
194
|
## 架构
|
|
112
195
|
|
|
113
196
|
```
|
|
114
|
-
bgi
|
|
115
|
-
├── src/index.ts
|
|
116
|
-
├── src/chat.ts
|
|
117
|
-
├── src/tools.ts
|
|
118
|
-
├── src/
|
|
119
|
-
├── src/
|
|
120
|
-
|
|
197
|
+
bgi
|
|
198
|
+
├── src/index.ts — CLI 主入口、命令处理、智能路由
|
|
199
|
+
├── src/chat.ts — 流式对话引擎(工具调用循环)
|
|
200
|
+
├── src/tools.ts — 工具实现(bash / read_file / write_file 等)
|
|
201
|
+
├── src/skillRouter.ts — 关键词路由表(35个核心技能自动匹配)
|
|
202
|
+
├── src/prompt.ts — 生物信息学系统提示
|
|
203
|
+
├── src/providers.ts — 中国 AI 服务商配置
|
|
204
|
+
├── src/config.ts — 配置管理(~/.bgicli/config.json)
|
|
205
|
+
└── data/ — 内置数据(工作流 + Skills + Python 工具)
|
|
121
206
|
```
|
|
122
207
|
|
|
123
|
-
**工具调用流程**:
|
|
124
|
-
1. 用户提问 → 发给 LLM(带工具定义)
|
|
125
|
-
2. LLM 决定调用工具(bash/read_file 等)
|
|
126
|
-
3. BGI CLI 执行工具,将结果返回给 LLM
|
|
127
|
-
4. LLM 基于执行结果继续回答
|
|
128
|
-
5. 循环直到 LLM 完成回答
|
|
129
|
-
|
|
130
208
|
---
|
|
131
209
|
|
|
132
210
|
## License
|
package/dist/bgi.js
CHANGED
|
@@ -13930,6 +13930,33 @@ async function streamOnce(client, messages, model) {
|
|
|
13930
13930
|
finishReason
|
|
13931
13931
|
};
|
|
13932
13932
|
}
|
|
13933
|
+
async function compactMessages(messages, config) {
|
|
13934
|
+
const prov = PROVIDERS[config.provider];
|
|
13935
|
+
if (!prov) throw new Error(`Unknown provider: ${config.provider}`);
|
|
13936
|
+
const baseURL = config.provider === "custom" ? config.customUrl : prov.baseURL;
|
|
13937
|
+
const apiKey = getApiKey(config);
|
|
13938
|
+
const client = new openai_default({ apiKey: apiKey || "none", baseURL });
|
|
13939
|
+
const transcript = messages.filter((m2) => m2.role === "user" || m2.role === "assistant").map((m2) => `[${m2.role === "user" ? "\u7528\u6237" : "AI"}]: ${String(m2.content ?? "").slice(0, 2e3)}`).join("\n\n");
|
|
13940
|
+
const resp = await client.chat.completions.create({
|
|
13941
|
+
model: config.model,
|
|
13942
|
+
messages: [
|
|
13943
|
+
{
|
|
13944
|
+
role: "system",
|
|
13945
|
+
content: "\u4F60\u662F\u4E00\u4E2A\u5BF9\u8BDD\u6458\u8981\u52A9\u624B\u3002\u8BF7\u5C06\u4EE5\u4E0B\u5BF9\u8BDD\u5386\u53F2\u538B\u7F29\u4E3A\u7B80\u6D01\u7684\u4E2D\u6587\u6458\u8981\uFF0C\u4FDD\u7559\u6240\u6709\u5173\u952E\u6280\u672F\u4FE1\u606F\uFF1A\u6587\u4EF6\u8DEF\u5F84\u3001\u547D\u4EE4\u3001\u5206\u6790\u7ED3\u679C\u3001\u7528\u6237\u51B3\u7B56\u3001\u5DF2\u6FC0\u6D3B\u7684\u5DE5\u4F5C\u6D41/\u6280\u80FD\u3002\u6458\u8981\u5E94\u8BA9\u5BF9\u8BDD\u80FD\u591F\u65E0\u7F1D\u7EE7\u7EED\u3002"
|
|
13946
|
+
},
|
|
13947
|
+
{
|
|
13948
|
+
role: "user",
|
|
13949
|
+
content: `\u8BF7\u538B\u7F29\u4EE5\u4E0B\u5BF9\u8BDD\u5386\u53F2\uFF1A
|
|
13950
|
+
|
|
13951
|
+
${transcript}
|
|
13952
|
+
|
|
13953
|
+
\u8F93\u51FA\u683C\u5F0F\uFF1A\u76F4\u63A5\u8F93\u51FA\u6458\u8981\u6587\u672C\uFF0C\u4E0D\u9700\u8981\u4EFB\u4F55\u524D\u7F00\u3002`
|
|
13954
|
+
}
|
|
13955
|
+
],
|
|
13956
|
+
stream: false
|
|
13957
|
+
});
|
|
13958
|
+
return resp.choices[0]?.message?.content ?? "\uFF08\u5BF9\u8BDD\u5386\u53F2\u5DF2\u538B\u7F29\uFF09";
|
|
13959
|
+
}
|
|
13933
13960
|
function getApiKey(cfg) {
|
|
13934
13961
|
const prov = PROVIDERS[cfg.provider];
|
|
13935
13962
|
if (prov?.envKey && process.env[prov.envKey]) return process.env[prov.envKey];
|
|
@@ -14663,8 +14690,8 @@ function installBundledData() {
|
|
|
14663
14690
|
if (!(0, import_fs4.existsSync)(bundledData)) return;
|
|
14664
14691
|
ensureDirs();
|
|
14665
14692
|
const targets = [
|
|
14666
|
-
{ src: (0, import_path4.join)(bundledData, "workflows"), dest: WORKFLOWS_DIR, name: "\u5DE5\u4F5C\u6D41" },
|
|
14667
|
-
{ src: (0, import_path4.join)(bundledData, "skills"), dest: SKILLS_DIR, name: "
|
|
14693
|
+
{ src: (0, import_path4.join)(bundledData, "workflows"), dest: WORKFLOWS_DIR, name: "Skills (\u751F\u4FE1\u5DE5\u4F5C\u6D41)" },
|
|
14694
|
+
{ src: (0, import_path4.join)(bundledData, "skills"), dest: SKILLS_DIR, name: "Skills (\u533B\u5B66\u4E13\u79D1)" },
|
|
14668
14695
|
{ src: (0, import_path4.join)(bundledData, "tools"), dest: TOOLS_DIR, name: "\u5DE5\u5177" }
|
|
14669
14696
|
];
|
|
14670
14697
|
let installed = false;
|
|
@@ -14707,12 +14734,13 @@ function printHelp() {
|
|
|
14707
14734
|
console.log(source_default.bold.cyan("\u2500\u2500\u2500 \u5BF9\u8BDD\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\u2500\u2500\u2500"));
|
|
14708
14735
|
console.log(` ${source_default.cyan("/clear")} \u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2`);
|
|
14709
14736
|
console.log(` ${source_default.cyan("/history")} \u67E5\u770B\u5BF9\u8BDD\u7EDF\u8BA1\uFF08\u8F6E\u6B21 / Token \u4F30\u7B97\uFF09`);
|
|
14737
|
+
console.log(` ${source_default.cyan("/compact")} \u7ACB\u5373\u538B\u7F29\u5BF9\u8BDD\u5386\u53F2\uFF08\u8D85 60k token \u81EA\u52A8\u89E6\u53D1\uFF09`);
|
|
14710
14738
|
console.log(` ${source_default.cyan("/save")} [\u6587\u4EF6\u540D] \u4FDD\u5B58\u5BF9\u8BDD\u4E3A Markdown \u6587\u4EF6`);
|
|
14711
14739
|
console.log(` ${source_default.cyan("/think")} [on|off] \u5207\u6362\u601D\u8003\u6A21\u5F0F (Qwen3 /think \u524D\u7F00)`);
|
|
14712
14740
|
console.log();
|
|
14713
|
-
console.log(source_default.bold.cyan("\u2500\u2500\u2500 Skills
|
|
14714
|
-
console.log(` ${source_default.cyan("/cat")} \u6309\u9886\u57DF\u6D4F\u89C8\
|
|
14715
|
-
console.log(` ${source_default.cyan("/sk")} \u5217\u51FA\u5168\u90E8 Skills
|
|
14741
|
+
console.log(source_default.bold.cyan("\u2500\u2500\u2500 Skills \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\u2500\u2500\u2500"));
|
|
14742
|
+
console.log(` ${source_default.cyan("/cat")} \u6309\u9886\u57DF\u6D4F\u89C8 Skills \u5206\u7C7B\u76EE\u5F55`);
|
|
14743
|
+
console.log(` ${source_default.cyan("/sk")} \u5217\u51FA\u5168\u90E8 Skills`);
|
|
14716
14744
|
console.log(` ${source_default.cyan("/sk")} <\u5173\u952E\u8BCD> \u6A21\u7CCA\u641C\u7D22\uFF0C\u5339\u914D\u5219\u6CE8\u5165\uFF0C\u5426\u5219\u5217\u51FA\u5019\u9009`);
|
|
14717
14745
|
console.log(` ${source_default.cyan("/wf")} \u540C /sk\uFF0C\u522B\u540D`);
|
|
14718
14746
|
console.log(source_default.dim(" \u793A\u4F8B: /cat /sk deseq2 /sk pubmed /sk alphafold /sk crispr"));
|
|
@@ -14823,13 +14851,13 @@ function collectAllSkills() {
|
|
|
14823
14851
|
});
|
|
14824
14852
|
};
|
|
14825
14853
|
addFrom(SKILLS_DIR, "skill");
|
|
14826
|
-
addFrom(WORKFLOWS_DIR, "
|
|
14854
|
+
addFrom(WORKFLOWS_DIR, "skill");
|
|
14827
14855
|
return entries;
|
|
14828
14856
|
}
|
|
14829
14857
|
function listSkills(keyword) {
|
|
14830
14858
|
const all = collectAllSkills();
|
|
14831
14859
|
if (all.length === 0) {
|
|
14832
|
-
console.log(source_default.yellow("\u6682\u65E0\u5DF2\u5B89\u88C5\u7684 Skills
|
|
14860
|
+
console.log(source_default.yellow("\u6682\u65E0\u5DF2\u5B89\u88C5\u7684 Skills"));
|
|
14833
14861
|
return;
|
|
14834
14862
|
}
|
|
14835
14863
|
const matched = keyword ? all.filter((e2) => e2.id.toLowerCase().includes(keyword.toLowerCase())) : all;
|
|
@@ -14838,20 +14866,11 @@ function listSkills(keyword) {
|
|
|
14838
14866
|
console.log(source_default.dim("\u63D0\u793A: \u4F7F\u7528\u66F4\u7B80\u77ED\u7684\u5173\u952E\u8BCD\uFF0C\u5982 alphafold\u3001pubmed\u3001rnaseq\u3001crispr\u3001deseq2"));
|
|
14839
14867
|
return;
|
|
14840
14868
|
}
|
|
14841
|
-
const title = keyword ? `\u641C\u7D22\u7ED3\u679C "${keyword}" (${matched.length} \u4E2A) \u2014 \u4F7F\u7528 /sk <id> \u52A0\u8F7D:` : `\u5168\u90E8 Skills
|
|
14869
|
+
const title = keyword ? `\u641C\u7D22\u7ED3\u679C "${keyword}" (${matched.length} \u4E2A) \u2014 \u4F7F\u7528 /sk <id> \u52A0\u8F7D:` : `\u5168\u90E8 Skills (${matched.length} \u4E2A) \u2014 \u4F7F\u7528 /sk <id> \u52A0\u8F7D:`;
|
|
14842
14870
|
console.log(source_default.bold(title));
|
|
14843
|
-
const
|
|
14844
|
-
|
|
14845
|
-
if (
|
|
14846
|
-
console.log(source_default.dim(" \u2500\u2500 \u751F\u7269\u4FE1\u606F\u5B66\u5DE5\u4F5C\u6D41 \u2500\u2500"));
|
|
14847
|
-
workflows.forEach((e2) => console.log(` ${source_default.green(e2.id)}`));
|
|
14848
|
-
}
|
|
14849
|
-
if (skills.length > 0) {
|
|
14850
|
-
console.log(source_default.dim(" \u2500\u2500 OpenClaw Medical Skills \u2500\u2500"));
|
|
14851
|
-
const show = skills.slice(0, 35);
|
|
14852
|
-
show.forEach((e2) => console.log(` ${source_default.cyan(e2.id)}`));
|
|
14853
|
-
if (skills.length > 35) console.log(source_default.dim(` ... \u8FD8\u6709 ${skills.length - 35} \u4E2A\uFF0C\u8BF7\u7528\u5173\u952E\u8BCD\u7B5B\u9009`));
|
|
14854
|
-
}
|
|
14871
|
+
const show = matched.slice(0, 50);
|
|
14872
|
+
show.forEach((e2) => console.log(` ${source_default.cyan(e2.id)}`));
|
|
14873
|
+
if (matched.length > 50) console.log(source_default.dim(` ... \u8FD8\u6709 ${matched.length - 50} \u4E2A\uFF0C\u8BF7\u7528\u5173\u952E\u8BCD\u7B5B\u9009`));
|
|
14855
14874
|
console.log();
|
|
14856
14875
|
}
|
|
14857
14876
|
function injectSkill(id, history) {
|
|
@@ -14868,10 +14887,9 @@ function injectSkill(id, history) {
|
|
|
14868
14887
|
return false;
|
|
14869
14888
|
}
|
|
14870
14889
|
const content = (0, import_fs4.readFileSync)(skillPath, "utf8");
|
|
14871
|
-
const label = match.tag === "workflow" ? "\u751F\u7269\u4FE1\u606F\u5B66\u5DE5\u4F5C\u6D41" : "OpenClaw Skill";
|
|
14872
14890
|
history.push({
|
|
14873
14891
|
role: "user",
|
|
14874
|
-
content: `[
|
|
14892
|
+
content: `[Skill \u5DF2\u52A0\u8F7D: ${match.id}]
|
|
14875
14893
|
|
|
14876
14894
|
\u4EE5\u4E0B\u662F\u8BE5\u6280\u80FD\u7684\u64CD\u4F5C\u6307\u5357\uFF0C\u8BF7\u4E25\u683C\u6309\u7167\u8BF4\u660E\u6267\u884C\uFF1A
|
|
14877
14895
|
|
|
@@ -14879,9 +14897,9 @@ ${content}`
|
|
|
14879
14897
|
});
|
|
14880
14898
|
history.push({
|
|
14881
14899
|
role: "assistant",
|
|
14882
|
-
content: `\u2713
|
|
14900
|
+
content: `\u2713 Skill **${match.id}** \u5DF2\u52A0\u8F7D\u3002\u6211\u5DF2\u9605\u8BFB\u6307\u5357\uFF0C\u968F\u65F6\u53EF\u4EE5\u5F00\u59CB\u3002\u8BF7\u544A\u8BC9\u6211\u60A8\u7684\u5177\u4F53\u6570\u636E\u548C\u9700\u6C42\u3002`
|
|
14883
14901
|
});
|
|
14884
|
-
console.log(source_default.green(`\u2713
|
|
14902
|
+
console.log(source_default.green(`\u2713 Skill ${match.id} \u5DF2\u6CE8\u5165\u5230\u5F53\u524D\u5BF9\u8BDD\u4E0A\u4E0B\u6587`));
|
|
14885
14903
|
return true;
|
|
14886
14904
|
}
|
|
14887
14905
|
function expandFileRefs(input) {
|
|
@@ -14934,6 +14952,45 @@ ${msg.content}
|
|
|
14934
14952
|
(0, import_fs4.writeFileSync)(outPath, lines.join("\n"), "utf8");
|
|
14935
14953
|
console.log(source_default.green(`\u2713 \u5BF9\u8BDD\u5DF2\u4FDD\u5B58: ${outPath}`));
|
|
14936
14954
|
}
|
|
14955
|
+
var COMPACT_TOKEN_THRESHOLD = 6e4;
|
|
14956
|
+
var COMPACT_KEEP_RECENT = 8;
|
|
14957
|
+
function estimateTokens(messages) {
|
|
14958
|
+
const chars = messages.reduce((n2, m2) => n2 + String(m2.content ?? "").length, 0);
|
|
14959
|
+
return Math.round(chars / 3.5);
|
|
14960
|
+
}
|
|
14961
|
+
async function maybeCompact(history, cfg) {
|
|
14962
|
+
const tokens = estimateTokens(history);
|
|
14963
|
+
if (tokens < COMPACT_TOKEN_THRESHOLD) return history;
|
|
14964
|
+
const recent = history.slice(-COMPACT_KEEP_RECENT);
|
|
14965
|
+
const old = history.slice(0, -COMPACT_KEEP_RECENT);
|
|
14966
|
+
if (old.length === 0) return history;
|
|
14967
|
+
process.stdout.write(source_default.dim(`
|
|
14968
|
+
[\u4E0A\u4E0B\u6587\u5DF2\u8FBE ~${Math.round(tokens / 1e3)}k tokens\uFF0C\u6B63\u5728\u81EA\u52A8\u538B\u7F29...]
|
|
14969
|
+
`));
|
|
14970
|
+
try {
|
|
14971
|
+
const summary = await compactMessages(old, cfg);
|
|
14972
|
+
const compacted = [
|
|
14973
|
+
{
|
|
14974
|
+
role: "user",
|
|
14975
|
+
content: `[\u5BF9\u8BDD\u5386\u53F2\u6458\u8981 \u2014 \u8BF7\u5728\u6B64\u57FA\u7840\u4E0A\u7EE7\u7EED]
|
|
14976
|
+
|
|
14977
|
+
${summary}`
|
|
14978
|
+
},
|
|
14979
|
+
{
|
|
14980
|
+
role: "assistant",
|
|
14981
|
+
content: "\u2713 \u5DF2\u7406\u89E3\u4E4B\u524D\u7684\u5BF9\u8BDD\u6458\u8981\uFF0C\u8BF7\u7EE7\u7EED\u3002"
|
|
14982
|
+
},
|
|
14983
|
+
...recent
|
|
14984
|
+
];
|
|
14985
|
+
const saved = estimateTokens(history) - estimateTokens(compacted);
|
|
14986
|
+
process.stdout.write(source_default.dim(`[\u538B\u7F29\u5B8C\u6210\uFF0C\u91CA\u653E\u7EA6 ~${Math.round(saved / 1e3)}k tokens]
|
|
14987
|
+
|
|
14988
|
+
`));
|
|
14989
|
+
return compacted;
|
|
14990
|
+
} catch {
|
|
14991
|
+
return history.slice(-COMPACT_KEEP_RECENT * 2);
|
|
14992
|
+
}
|
|
14993
|
+
}
|
|
14937
14994
|
async function handleCommand(input, rl, history, thinkMode) {
|
|
14938
14995
|
const [cmd, ...rest] = input.slice(1).trim().split(/\s+/);
|
|
14939
14996
|
const arg = rest.join(" ");
|
|
@@ -15037,6 +15094,37 @@ async function handleCommand(input, rl, history, thinkMode) {
|
|
|
15037
15094
|
saveConversation(history, arg || void 0);
|
|
15038
15095
|
break;
|
|
15039
15096
|
}
|
|
15097
|
+
case "compact": {
|
|
15098
|
+
const tokens = estimateTokens(history);
|
|
15099
|
+
if (history.length < 4) {
|
|
15100
|
+
console.log(source_default.dim("\u5BF9\u8BDD\u592A\u77ED\uFF0C\u65E0\u9700\u538B\u7F29"));
|
|
15101
|
+
break;
|
|
15102
|
+
}
|
|
15103
|
+
console.log(source_default.dim(`\u5F53\u524D\u5BF9\u8BDD\u7EA6 ~${Math.round(tokens / 1e3)}k tokens\uFF0C\u6B63\u5728\u538B\u7F29...`));
|
|
15104
|
+
try {
|
|
15105
|
+
const currentCfg = loadConfig();
|
|
15106
|
+
const recent = history.slice(-COMPACT_KEEP_RECENT);
|
|
15107
|
+
const old = history.slice(0, -COMPACT_KEEP_RECENT);
|
|
15108
|
+
if (old.length === 0) {
|
|
15109
|
+
console.log(source_default.dim("\u8FD1\u671F\u6D88\u606F\u4E0D\u8DB3\uFF0C\u65E0\u9700\u538B\u7F29"));
|
|
15110
|
+
break;
|
|
15111
|
+
}
|
|
15112
|
+
const summary = await compactMessages(old, currentCfg);
|
|
15113
|
+
const newHistory = [
|
|
15114
|
+
{ role: "user", content: `[\u5BF9\u8BDD\u5386\u53F2\u6458\u8981 \u2014 \u8BF7\u5728\u6B64\u57FA\u7840\u4E0A\u7EE7\u7EED]
|
|
15115
|
+
|
|
15116
|
+
${summary}` },
|
|
15117
|
+
{ role: "assistant", content: "\u2713 \u5DF2\u7406\u89E3\u4E4B\u524D\u7684\u5BF9\u8BDD\u6458\u8981\uFF0C\u8BF7\u7EE7\u7EED\u3002" },
|
|
15118
|
+
...recent
|
|
15119
|
+
];
|
|
15120
|
+
const after = estimateTokens(newHistory);
|
|
15121
|
+
console.log(source_default.green(`\u2713 \u538B\u7F29\u5B8C\u6210: ${history.length} \u6761\u6D88\u606F \u2192 ${newHistory.length} \u6761\uFF0C~${Math.round(after / 1e3)}k tokens`));
|
|
15122
|
+
return { injectHistory: newHistory };
|
|
15123
|
+
} catch (err) {
|
|
15124
|
+
console.error(source_default.red(`\u538B\u7F29\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`));
|
|
15125
|
+
}
|
|
15126
|
+
break;
|
|
15127
|
+
}
|
|
15040
15128
|
case "think": {
|
|
15041
15129
|
const val = arg.toLowerCase();
|
|
15042
15130
|
if (val === "on" || val === "1" || val === "true") {
|
|
@@ -15080,8 +15168,7 @@ async function handleCommand(input, rl, history, thinkMode) {
|
|
|
15080
15168
|
console.log(` ${meta.icon} ${source_default.bold(meta.label)}`);
|
|
15081
15169
|
for (const item of items) {
|
|
15082
15170
|
const route = SKILL_ROUTES.find((r2) => r2.id === item.id);
|
|
15083
|
-
|
|
15084
|
-
console.log(` ${tag} ${source_default.white(item.id)} ${source_default.dim("\u2014 " + route.name)}`);
|
|
15171
|
+
console.log(` ${source_default.cyan(item.id)} ${source_default.dim("\u2014 " + route.name)}`);
|
|
15085
15172
|
}
|
|
15086
15173
|
console.log();
|
|
15087
15174
|
}
|
|
@@ -15089,8 +15176,8 @@ async function handleCommand(input, rl, history, thinkMode) {
|
|
|
15089
15176
|
const allInstalled = collectAllSkills();
|
|
15090
15177
|
const unrouted = allInstalled.filter((e2) => !routedIds.has(e2.id));
|
|
15091
15178
|
if (unrouted.length > 0) {
|
|
15092
|
-
console.log(` \u{1F4E6} ${source_default.bold("\u66F4\u591A
|
|
15093
|
-
console.log(source_default.dim("
|
|
15179
|
+
console.log(` \u{1F4E6} ${source_default.bold("\u66F4\u591A Skills")} ${source_default.dim(`(${unrouted.length} \u4E2A\uFF0C\u4F7F\u7528\u5173\u952E\u8BCD\u641C\u7D22)`)}`);
|
|
15180
|
+
console.log(source_default.dim(" /sk <\u5173\u952E\u8BCD>\uFF0C\u4F8B: /sk ehr /sk clinical /sk imaging"));
|
|
15094
15181
|
console.log();
|
|
15095
15182
|
}
|
|
15096
15183
|
break;
|
|
@@ -15151,7 +15238,7 @@ async function main() {
|
|
|
15151
15238
|
console.log(source_default.bold.cyan("\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
15152
15239
|
console.log(` ${source_default.bold("\u670D\u52A1\u5546:")} ${prov?.name ?? cfg.provider}`);
|
|
15153
15240
|
console.log(` ${source_default.bold("\u6A21\u578B:")} ${source_default.green(cfg.model)}`);
|
|
15154
|
-
console.log(` ${source_default.bold("Skills:")} ${totalSkills > 0 ? source_default.green(`${totalSkills} \u4E2A`) : source_default.yellow("\u672A\u5B89\u88C5")} ${source_default.dim("(
|
|
15241
|
+
console.log(` ${source_default.bold("Skills:")} ${totalSkills > 0 ? source_default.green(`${totalSkills} \u4E2A`) : source_default.yellow("\u672A\u5B89\u88C5")} ${source_default.dim("(/sk \u641C\u7D22 /cat \u5206\u7C7B\u76EE\u5F55)")}`);
|
|
15155
15242
|
console.log(` ${source_default.bold("\u5DE5\u5177:")} bash \xB7 read_file \xB7 write_file \xB7 list_dir \xB7 search_files`);
|
|
15156
15243
|
console.log(source_default.bold.cyan("\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
15157
15244
|
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"));
|
|
@@ -15182,6 +15269,7 @@ async function main() {
|
|
|
15182
15269
|
history = [];
|
|
15183
15270
|
injectedSkills.clear();
|
|
15184
15271
|
}
|
|
15272
|
+
if (result.injectHistory) history = result.injectHistory;
|
|
15185
15273
|
if (result.thinkMode !== void 0) thinkMode = result.thinkMode;
|
|
15186
15274
|
continue;
|
|
15187
15275
|
}
|
|
@@ -15197,8 +15285,7 @@ async function main() {
|
|
|
15197
15285
|
} else if (newRoutes.length >= 2 && topScore >= 4) {
|
|
15198
15286
|
console.log(source_default.dim("\n\u{1F4A1} \u68C0\u6D4B\u5230\u76F8\u5173\u6280\u80FD\uFF0C\u8F93\u5165 /sk <id> \u6FC0\u6D3B:"));
|
|
15199
15287
|
newRoutes.slice(0, 3).forEach((r2) => {
|
|
15200
|
-
|
|
15201
|
-
console.log(source_default.dim(` /sk ${r2.id} ${tag} ${r2.name}`));
|
|
15288
|
+
console.log(source_default.dim(` /sk ${r2.id} \u2014 ${r2.name}`));
|
|
15202
15289
|
});
|
|
15203
15290
|
console.log();
|
|
15204
15291
|
}
|
|
@@ -15210,7 +15297,7 @@ ${expanded}` : expanded;
|
|
|
15210
15297
|
const currentCfg = loadConfig();
|
|
15211
15298
|
const reply = await chat(history, currentCfg, systemPrompt);
|
|
15212
15299
|
history.push({ role: "assistant", content: reply });
|
|
15213
|
-
|
|
15300
|
+
history = await maybeCompact(history, currentCfg);
|
|
15214
15301
|
} catch (err) {
|
|
15215
15302
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15216
15303
|
console.error(source_default.red(`
|