@wentorai/research-plugins 1.4.2 → 1.4.3
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.en.md +143 -0
- package/README.md +98 -131
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/tools/arxiv.ts +10 -3
- package/src/tools/biorxiv.ts +2 -2
- package/src/tools/crossref.ts +10 -6
- package/src/tools/datacite.ts +4 -3
- package/src/tools/doaj.ts +3 -2
- package/src/tools/europe-pmc.ts +4 -3
- package/src/tools/hal.ts +6 -4
- package/src/tools/inspire-hep.ts +3 -2
- package/src/tools/openaire.ts +11 -6
- package/src/tools/openalex.ts +11 -2
- package/src/tools/osf-preprints.ts +3 -2
- package/src/tools/pubmed.ts +9 -5
- package/src/tools/util.ts +33 -0
- package/src/tools/zenodo.ts +7 -4
package/README.en.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="assets/logo.png" width="160" alt="Research-Claw · 科研龙虾" />
|
|
4
|
+
|
|
5
|
+
# The most comprehensive library of AI skills&plugins for academic research
|
|
6
|
+
|
|
7
|
+
**The brain of Research-Claw — 438 academic skills + 34 API tools**
|
|
8
|
+
|
|
9
|
+
Plug-and-play research capabilities for [Research-Claw](https://github.com/wentorai/Research-Claw) and 40+ AI agent frameworks
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/@wentorai/research-plugins)
|
|
12
|
+
[](LICENSE)
|
|
13
|
+
[](https://www.npmjs.com/package/@wentorai/research-plugins)
|
|
14
|
+
[](#agent-tools-34)
|
|
15
|
+
|
|
16
|
+
[🌐 wentor.ai](https://wentor.ai) · [🇨🇳 中文](README.md) · [🦞 Research-Claw](https://github.com/wentorai/Research-Claw) · [🪲 Issues](https://github.com/wentorai/research-plugins/issues)
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### Install (Research-Claw / OpenClaw)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
openclaw plugins install @wentorai/research-plugins
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Installs to `~/.openclaw/extensions/`. Restart the gateway to auto-load all skills + tools.
|
|
31
|
+
|
|
32
|
+
### Install (Claude Code / Cursor / Windsurf / OpenCode)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx skills add wentorai/research-plugins
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
> This installs SKILL.md files only (no API tools). Compatible with 41 agent frameworks supporting the skills protocol.
|
|
39
|
+
|
|
40
|
+
### Uninstall
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# OpenClaw / Research-Claw — manually remove plugin directory
|
|
44
|
+
rm -rf ~/.openclaw/extensions/research-plugins
|
|
45
|
+
|
|
46
|
+
# npx skills — manually remove skills directory
|
|
47
|
+
rm -rf .skills/wentorai/research-plugins
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
> **pnpm projects:** Do not add this as a pnpm dependency and load from `node_modules` via `plugins.load.paths`. pnpm hardlinks are rejected by OpenClaw's security validator. Always use `openclaw plugins install`.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## What's Inside
|
|
55
|
+
|
|
56
|
+
### Academic Skills (438)
|
|
57
|
+
|
|
58
|
+
Structured SKILL.md guides covering the full research lifecycle, loaded on demand:
|
|
59
|
+
|
|
60
|
+
| Category | Count | Coverage |
|
|
61
|
+
|:--|:--|:--|
|
|
62
|
+
| **Literature** | 87 | Multi-database search · Citation tracking · Full-text · Open access |
|
|
63
|
+
| **Methodology** | 79 | DID · RDD · IV · Meta-analysis · Systematic review · Grant writing |
|
|
64
|
+
| **Data Analysis** | 68 | Python · R · STATA · Visualization · Panel data · Econometrics |
|
|
65
|
+
| **Writing** | 74 | Paper sections · LaTeX · References · Rebuttal generation |
|
|
66
|
+
| **Domains** | 93 | 16 disciplines: CS · AI/ML · Biomedical · Economics · Law · Physics etc. |
|
|
67
|
+
| **Tools** | 51 | Diagrams · PDF parsing · Knowledge graphs · OCR · Scraping |
|
|
68
|
+
|
|
69
|
+
Skills use **progressive disclosure**: 6 category entries → 40 subcategory indexes → 438 concrete skills. Agents load on demand, never injecting everything at once.
|
|
70
|
+
|
|
71
|
+
### Agent Tools (34)
|
|
72
|
+
|
|
73
|
+
TypeScript wrappers for 18 free academic database APIs, auto-registered as OpenClaw plugin tools:
|
|
74
|
+
|
|
75
|
+
| Module | Tools | Source |
|
|
76
|
+
|:--|:--|:--|
|
|
77
|
+
| `openalex` | `search_openalex` · `get_work` · `get_author_openalex` | OpenAlex (250M+ works) |
|
|
78
|
+
| `crossref` | `resolve_doi` · `search_crossref` | CrossRef (150M+ DOIs) |
|
|
79
|
+
| `arxiv` | `search_arxiv` · `get_arxiv_paper` | arXiv |
|
|
80
|
+
| `pubmed` | `search_pubmed` · `get_article` | PubMed / NCBI |
|
|
81
|
+
| `unpaywall` | `find_oa_version` | Unpaywall (open access) |
|
|
82
|
+
| `europe-pmc` | `search_europe_pmc` · `get_epmc_citations` · `get_epmc_references` | Europe PMC (33M+) |
|
|
83
|
+
| `opencitations` | `get_citations_open` · `get_references_open` · `get_citation_count` | OpenCitations (2B+ links) |
|
|
84
|
+
| `dblp` | `search_dblp` · `search_dblp_author` | DBLP (7M+ CS records) |
|
|
85
|
+
| `doaj` | `search_doaj` | DOAJ (9M+ OA articles) |
|
|
86
|
+
| `biorxiv` | `search_biorxiv` · `search_medrxiv` · `get_preprint_by_doi` | bioRxiv / medRxiv |
|
|
87
|
+
| `openaire` | `search_openaire` | OpenAIRE (170M+, EU funder filter) |
|
|
88
|
+
| `zenodo` | `search_zenodo` · `get_zenodo_record` | Zenodo |
|
|
89
|
+
| `orcid` | `search_orcid` · `get_orcid_works` | ORCID |
|
|
90
|
+
| `inspire-hep` | `search_inspire` · `get_inspire_paper` | INSPIRE-HEP (physics) |
|
|
91
|
+
| `hal` | `search_hal` | HAL (French open archive) |
|
|
92
|
+
| `osf-preprints` | `search_osf_preprints` | OSF Preprints |
|
|
93
|
+
| `datacite` | `search_datacite` · `resolve_datacite_doi` | DataCite (dataset DOIs) |
|
|
94
|
+
| `ror` | `search_ror` | ROR (research organizations) |
|
|
95
|
+
|
|
96
|
+
### Curated Resources (6)
|
|
97
|
+
|
|
98
|
+
Hand-picked resource collections for each skill category. See `curated/` directory.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Architecture
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
@wentorai/research-plugins
|
|
106
|
+
├── skills/ ← 438 SKILL.md (6 categories × 40 subcategories)
|
|
107
|
+
│ ├── literature/ ← Search, discovery, full-text, metadata
|
|
108
|
+
│ ├── writing/ ← Composition, citation, LaTeX, polish
|
|
109
|
+
│ ├── analysis/ ← Statistics, econometrics, dataviz
|
|
110
|
+
│ ├── research/ ← Methodology, reviews, grants
|
|
111
|
+
│ ├── domains/ ← 16 academic disciplines
|
|
112
|
+
│ └── tools/ ← Diagrams, documents, scraping, OCR
|
|
113
|
+
├── src/tools/ ← 34 API tools (18 modules)
|
|
114
|
+
├── curated/ ← 6 curated resource lists
|
|
115
|
+
├── catalog.json ← Full index (462 entries)
|
|
116
|
+
├── index.ts ← Plugin entry (OpenClaw Plugin SDK)
|
|
117
|
+
└── openclaw.plugin.json ← Plugin manifest
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Loading modes:**
|
|
121
|
+
- **Research-Claw / OpenClaw**: Full plugin (skills + API tools) via `openclaw plugins install`
|
|
122
|
+
- **Other agent frameworks**: SKILL.md files only (no API tools) via `npx skills add`
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Community Attribution
|
|
127
|
+
|
|
128
|
+
This project curates, organizes, and enhances publicly available academic resources.
|
|
129
|
+
|
|
130
|
+
- **Skills** are authored guides based on established research methodologies, public API docs, and widely-used workflows. Where content derives from specific projects, the `source` field in each SKILL.md frontmatter links to the original.
|
|
131
|
+
- **Curated lists** aggregate community resource links for discovery purposes.
|
|
132
|
+
|
|
133
|
+
All original content is released under the [MIT License](LICENSE). Referenced third-party projects retain their own licenses.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Contributing
|
|
138
|
+
|
|
139
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
[MIT](LICENSE) — Copyright (c) 2026 Wentor AI
|
package/README.md
CHANGED
|
@@ -1,176 +1,143 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="assets/logo.png" width="160" alt="科研龙虾 · Research-Claw" />
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
<a id="english"></a>
|
|
8
|
-
|
|
9
|
-
## Research Plugins for Research-Claw
|
|
10
|
-
|
|
11
|
-
An open-source collection of **438 academic research skills**, **34 agent tools** (18 API modules), and **6 curated resource lists** for [Research-Claw](https://wentor.ai) and other AI coding agents.
|
|
12
|
-
|
|
13
|
-
Built by [Wentor AI](https://wentor.ai) for the global research community.
|
|
5
|
+
# 最全的学术科研领域技能插件库
|
|
14
6
|
|
|
15
|
-
|
|
7
|
+
**科研龙虾的大脑 — 438 个学术技能 + 34 个 API 工具**
|
|
16
8
|
|
|
17
|
-
|
|
18
|
-
|-----------|-------|-------------|
|
|
19
|
-
| **Skills** | 438 | Practical SKILL.md guides covering literature search, academic writing, data analysis, research methods, 16 domain specialties, and productivity tools |
|
|
20
|
-
| **Agent Tools** | 34 | TypeScript API wrappers for 18 academic databases: OpenAlex, CrossRef, arXiv, PubMed, Unpaywall, Europe PMC, OpenCitations, DOAJ, DBLP, bioRxiv/medRxiv, OpenAIRE, Zenodo, ORCID, INSPIRE-HEP, HAL, OSF, DataCite, ROR |
|
|
21
|
-
| **Curated Lists** | 6 | Hand-picked resource collections for each skill category |
|
|
9
|
+
为 [Research-Claw (科研龙虾)](https://github.com/wentorai/Research-Claw) 及 40+ AI Agent 框架提供即插即用的科研能力
|
|
22
10
|
|
|
23
|
-
|
|
11
|
+
[](https://www.npmjs.com/package/@wentorai/research-plugins)
|
|
12
|
+
[](LICENSE)
|
|
13
|
+
[](https://www.npmjs.com/package/@wentorai/research-plugins)
|
|
14
|
+
[](#智能体工具34-个)
|
|
24
15
|
|
|
25
|
-
|
|
16
|
+
[🌐 wentor.ai](https://wentor.ai) · [🇬🇧 English](README.en.md) · [🦞 Research-Claw](https://github.com/wentorai/Research-Claw) · [🪲 Issues](https://github.com/wentorai/research-plugins/issues)
|
|
26
17
|
|
|
27
|
-
|
|
18
|
+
</div>
|
|
28
19
|
|
|
29
|
-
|
|
30
|
-
|----------|---------------|--------|-------------|
|
|
31
|
-
| **literature** | search, discovery, fulltext, metadata | 80 | Paper search, citation tracking, open access |
|
|
32
|
-
| **writing** | composition, polish, latex, templates, citation | 64 | Academic writing, LaTeX, reference management |
|
|
33
|
-
| **analysis** | statistics, econometrics, wrangling, dataviz | 46 | Statistical methods, data cleaning, visualization |
|
|
34
|
-
| **research** | methodology, deep-research, paper-review, automation, funding | 53 | Research design, systematic reviews, grant writing |
|
|
35
|
-
| **domains** | 16 disciplines (CS, AI/ML, biomedical, chemistry, economics, finance, law, physics, math, ecology, etc.) | 147 | Domain-specific research methods and tools |
|
|
36
|
-
| **tools** | diagram, document, code-exec, scraping, knowledge-graph, ocr-translate | 48 | Diagrams, PDF parsing, reproducible code, OCR |
|
|
20
|
+
---
|
|
37
21
|
|
|
38
|
-
|
|
22
|
+
## 快速开始
|
|
39
23
|
|
|
40
|
-
|
|
24
|
+
### 安装(Research-Claw / OpenClaw 用户)
|
|
41
25
|
|
|
42
26
|
```bash
|
|
43
27
|
openclaw plugins install @wentorai/research-plugins
|
|
44
28
|
```
|
|
45
29
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
> **Note for satellite/pnpm projects:** Do not add this package as a `pnpm` dependency and load it from `node_modules` via `plugins.load.paths`. pnpm's content-addressable store uses hardlinks, which OpenClaw's security validator rejects (`unsafe plugin manifest path`). Always use `openclaw plugins install` instead.
|
|
30
|
+
插件安装到 `~/.openclaw/extensions/`,重启 gateway 后自动加载所有技能 + 工具。
|
|
49
31
|
|
|
50
|
-
|
|
32
|
+
### 安装(Claude Code / Cursor / Windsurf / OpenCode 用户)
|
|
51
33
|
|
|
52
34
|
```bash
|
|
53
35
|
npx skills add wentorai/research-plugins
|
|
54
36
|
```
|
|
55
37
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
npm install @wentorai/research-plugins
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### Agent Tools
|
|
63
|
-
|
|
64
|
-
34 tool functions across 18 academic API modules, registered automatically when used as an OpenClaw plugin. All APIs are free and open:
|
|
65
|
-
|
|
66
|
-
| Module | Tools | API |
|
|
67
|
-
|--------|-------|-----|
|
|
68
|
-
| `openalex` | `search_openalex`, `get_work`, `get_author_openalex` | OpenAlex (250M+ works) |
|
|
69
|
-
| `crossref` | `resolve_doi`, `search_crossref` | CrossRef (150M+ DOIs) |
|
|
70
|
-
| `arxiv` | `search_arxiv`, `get_arxiv_paper` | arXiv |
|
|
71
|
-
| `pubmed` | `search_pubmed`, `get_article` | PubMed / NCBI |
|
|
72
|
-
| `unpaywall` | `find_oa_version` | Unpaywall |
|
|
73
|
-
| `europe-pmc` | `search_europe_pmc`, `get_epmc_citations`, `get_epmc_references` | Europe PMC (33M+) |
|
|
74
|
-
| `opencitations` | `get_citations_open`, `get_references_open`, `get_citation_count` | OpenCitations (2B+ links) |
|
|
75
|
-
| `doaj` | `search_doaj` | DOAJ (9M+ OA articles) |
|
|
76
|
-
| `dblp` | `search_dblp`, `search_dblp_author` | DBLP (7M+ CS records) |
|
|
77
|
-
| `biorxiv` | `search_biorxiv`, `search_medrxiv`, `get_preprint_by_doi` | bioRxiv / medRxiv |
|
|
78
|
-
| `openaire` | `search_openaire` | OpenAIRE (170M+, EU funder filter) |
|
|
79
|
-
| `zenodo` | `search_zenodo`, `get_zenodo_record` | Zenodo |
|
|
80
|
-
| `orcid` | `search_orcid`, `get_orcid_works` | ORCID |
|
|
81
|
-
| `inspire-hep` | `search_inspire`, `get_inspire_paper` | INSPIRE-HEP (physics) |
|
|
82
|
-
| `hal` | `search_hal` | HAL (French open archive) |
|
|
83
|
-
| `osf-preprints` | `search_osf_preprints` | OSF Preprints |
|
|
84
|
-
| `datacite` | `search_datacite`, `resolve_datacite_doi` | DataCite (dataset DOIs) |
|
|
85
|
-
| `ror` | `search_ror` | ROR (research organizations) |
|
|
86
|
-
|
|
87
|
-
### Community Attribution
|
|
88
|
-
|
|
89
|
-
This project curates, organizes, and enhances publicly available academic resources from across the open-source ecosystem. We are grateful to the researchers, developers, and open-source communities whose work makes this collection possible.
|
|
90
|
-
|
|
91
|
-
- **Skills** are authored guides based on established research methodologies, public API documentation, and widely-used academic workflows. Where content is derived from specific open-source projects, the `source` field in each SKILL.md frontmatter links to the original.
|
|
92
|
-
- **Curated Lists** aggregate links to community resources and are provided for discovery purposes.
|
|
38
|
+
> 此方式仅安装 SKILL.md 技能文件(无 API 工具),适用于 41 个支持 skills 协议的 Agent 框架。
|
|
93
39
|
|
|
94
|
-
|
|
40
|
+
### 卸载
|
|
95
41
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
42
|
+
```bash
|
|
43
|
+
# OpenClaw / Research-Claw — 手动删除插件目录
|
|
44
|
+
rm -rf ~/.openclaw/extensions/research-plugins
|
|
99
45
|
|
|
100
|
-
|
|
46
|
+
# npx skills — 手动删除技能目录
|
|
47
|
+
rm -rf .skills/wentorai/research-plugins
|
|
48
|
+
```
|
|
101
49
|
|
|
102
|
-
|
|
50
|
+
> **pnpm 项目注意**:请勿将本包作为 `pnpm` 依赖并通过 `plugins.load.paths` 从 `node_modules` 加载。pnpm 的硬链接机制会被 OpenClaw 安全校验拒绝 (`unsafe plugin manifest path`)。请始终使用 `openclaw plugins install`。
|
|
103
51
|
|
|
104
52
|
---
|
|
105
53
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
|
117
|
-
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
54
|
+
## 包含什么
|
|
55
|
+
|
|
56
|
+
### 学术技能(438 个)
|
|
57
|
+
|
|
58
|
+
覆盖科研全流程的结构化 SKILL.md 指南,按需自动加载:
|
|
59
|
+
|
|
60
|
+
| 类别 | 技能数 | 覆盖范围 |
|
|
61
|
+
|:--|:--|:--|
|
|
62
|
+
| **文献** (literature) | 87 | 多库联搜 · 引文追踪 · 全文获取 · 开放获取 |
|
|
63
|
+
| **研究方法** (research) | 79 | DID · RDD · IV · 元分析 · 系统综述 · 基金申请 |
|
|
64
|
+
| **数据分析** (analysis) | 68 | Python · R · STATA · 可视化 · 面板数据 · 计量经济 |
|
|
65
|
+
| **学术写作** (writing) | 74 | 论文各章节 · LaTeX · 参考文献 · 审稿意见回复 |
|
|
66
|
+
| **学科领域** (domains) | 93 | 16 个学科:CS · AI/ML · 生物医学 · 经济 · 法学 · 物理 等 |
|
|
67
|
+
| **效率工具** (tools) | 51 | 流程图 · PDF 解析 · 知识图谱 · OCR · 爬虫 |
|
|
68
|
+
|
|
69
|
+
技能通过 **渐进式加载** 机制工作:6 个类别入口 → 40 个子分类索引 → 438 个具体技能。Agent 按需加载,不会一次性注入全部内容。
|
|
70
|
+
|
|
71
|
+
### 智能体工具(34 个)
|
|
72
|
+
|
|
73
|
+
直连 18 个免费学术数据库 API 的 TypeScript 工具,作为 OpenClaw 插件自动注册:
|
|
74
|
+
|
|
75
|
+
| 模块 | 工具 | 数据源 |
|
|
76
|
+
|:--|:--|:--|
|
|
77
|
+
| `openalex` | `search_openalex` · `get_work` · `get_author_openalex` | OpenAlex (250M+ 作品) |
|
|
78
|
+
| `crossref` | `resolve_doi` · `search_crossref` | CrossRef (150M+ DOI) |
|
|
79
|
+
| `arxiv` | `search_arxiv` · `get_arxiv_paper` | arXiv |
|
|
80
|
+
| `pubmed` | `search_pubmed` · `get_article` | PubMed / NCBI |
|
|
81
|
+
| `unpaywall` | `find_oa_version` | Unpaywall (开放获取) |
|
|
82
|
+
| `europe-pmc` | `search_europe_pmc` · `get_epmc_citations` · `get_epmc_references` | Europe PMC (33M+) |
|
|
83
|
+
| `opencitations` | `get_citations_open` · `get_references_open` · `get_citation_count` | OpenCitations (2B+ 引用) |
|
|
84
|
+
| `dblp` | `search_dblp` · `search_dblp_author` | DBLP (7M+ CS 文献) |
|
|
85
|
+
| `doaj` | `search_doaj` | DOAJ (9M+ OA 文章) |
|
|
86
|
+
| `biorxiv` | `search_biorxiv` · `search_medrxiv` · `get_preprint_by_doi` | bioRxiv / medRxiv |
|
|
87
|
+
| `openaire` | `search_openaire` | OpenAIRE (170M+, EU 资助筛选) |
|
|
88
|
+
| `zenodo` | `search_zenodo` · `get_zenodo_record` | Zenodo |
|
|
89
|
+
| `orcid` | `search_orcid` · `get_orcid_works` | ORCID |
|
|
90
|
+
| `inspire-hep` | `search_inspire` · `get_inspire_paper` | INSPIRE-HEP (高能物理) |
|
|
91
|
+
| `hal` | `search_hal` | HAL (法国开放档案) |
|
|
92
|
+
| `osf-preprints` | `search_osf_preprints` | OSF Preprints |
|
|
93
|
+
| `datacite` | `search_datacite` · `resolve_datacite_doi` | DataCite (数据集 DOI) |
|
|
94
|
+
| `ror` | `search_ror` | ROR (研究机构) |
|
|
125
95
|
|
|
126
|
-
|
|
96
|
+
### 精选资源(6 套)
|
|
127
97
|
|
|
128
|
-
|
|
129
|
-
|------|--------|--------|---------|
|
|
130
|
-
| **文献 (literature)** | 检索、发现、全文获取、元数据 | 80 | 论文搜索、引文追踪、开放获取 |
|
|
131
|
-
| **写作 (writing)** | 写作、润色、LaTeX、模板、引用 | 64 | 学术写作、LaTeX 排版、参考文献管理 |
|
|
132
|
-
| **分析 (analysis)** | 统计、计量经济、数据处理、可视化 | 46 | 统计方法、数据清洗、图表制作 |
|
|
133
|
-
| **研究 (research)** | 方法论、深度研究、论文评审、自动化、基金 | 53 | 研究设计、系统综述、基金申请 |
|
|
134
|
-
| **学科 (domains)** | 16 个学科方向 | 147 | 各学科专属研究方法与工具 |
|
|
135
|
-
| **工具 (tools)** | 图表、文档、代码执行、爬虫、知识图谱、OCR | 48 | 流程图、PDF 解析、可复现代码、OCR 翻译 |
|
|
98
|
+
每个技能类别附带一套手工精选的优质资源列表,见 `curated/` 目录。
|
|
136
99
|
|
|
137
|
-
|
|
100
|
+
---
|
|
138
101
|
|
|
139
|
-
|
|
102
|
+
## 架构
|
|
140
103
|
|
|
141
|
-
```bash
|
|
142
|
-
openclaw plugins install @wentorai/research-plugins
|
|
143
104
|
```
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
105
|
+
@wentorai/research-plugins
|
|
106
|
+
├── skills/ ← 438 SKILL.md (6 类别 × 40 子分类)
|
|
107
|
+
│ ├── literature/ ← 文献检索/追踪/获取
|
|
108
|
+
│ ├── writing/ ← 学术写作/引用/LaTeX
|
|
109
|
+
│ ├── analysis/ ← 数据分析/统计/可视化
|
|
110
|
+
│ ├── research/ ← 研究方法/综述/基金
|
|
111
|
+
│ ├── domains/ ← 16 学科领域
|
|
112
|
+
│ └── tools/ ← 效率工具/图表/OCR
|
|
113
|
+
├── src/tools/ ← 34 API 工具 (18 模块)
|
|
114
|
+
├── curated/ ← 6 套精选资源列表
|
|
115
|
+
├── catalog.json ← 全量索引 (462 entries)
|
|
116
|
+
├── index.ts ← 插件入口 (OpenClaw Plugin SDK)
|
|
117
|
+
└── openclaw.plugin.json ← 插件清单
|
|
153
118
|
```
|
|
154
119
|
|
|
155
|
-
|
|
120
|
+
**加载方式**:
|
|
121
|
+
- **Research-Claw / OpenClaw**:作为完整插件加载(技能 + API 工具),通过 `openclaw plugins install`
|
|
122
|
+
- **其他 Agent 框架**:仅加载 SKILL.md 文件(无 API 工具),通过 `npx skills add`
|
|
156
123
|
|
|
157
|
-
|
|
158
|
-
npm install @wentorai/research-plugins
|
|
159
|
-
```
|
|
124
|
+
---
|
|
160
125
|
|
|
161
|
-
|
|
126
|
+
## 社区致谢
|
|
162
127
|
|
|
163
|
-
|
|
128
|
+
本项目对互联网上公开可用的学术资源进行了遴选、整理和增强。
|
|
164
129
|
|
|
165
|
-
- **技能**
|
|
166
|
-
- **精选列表**
|
|
130
|
+
- **技能** 基于成熟研究方法、公开 API 文档和广泛使用的学术工作流编写。凡内容源自特定开源项目的,SKILL.md frontmatter 中 `source` 字段标注了原始链接。
|
|
131
|
+
- **精选列表** 汇集社区资源链接,仅供发现和参考。
|
|
167
132
|
|
|
168
|
-
|
|
133
|
+
所有原创内容以 [MIT 许可证](LICENSE) 发布。引用的第三方项目保留各自许可证。
|
|
134
|
+
|
|
135
|
+
---
|
|
169
136
|
|
|
170
|
-
|
|
137
|
+
## 贡献
|
|
171
138
|
|
|
172
|
-
请查阅 [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
139
|
+
请查阅 [CONTRIBUTING.md](CONTRIBUTING.md) 了解添加技能和资源的指南。
|
|
173
140
|
|
|
174
|
-
|
|
141
|
+
## 许可证
|
|
175
142
|
|
|
176
|
-
[MIT](LICENSE)
|
|
143
|
+
[MIT](LICENSE) — Copyright (c) 2026 Wentor AI
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/tools/arxiv.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
-
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError, validEnum } from "./util.js";
|
|
4
4
|
|
|
5
5
|
const BASE = "https://export.arxiv.org/api/query";
|
|
6
6
|
|
|
@@ -109,11 +109,18 @@ export function createArxivTools(
|
|
|
109
109
|
sort_by?: string;
|
|
110
110
|
sort_order?: string;
|
|
111
111
|
}) => {
|
|
112
|
+
if (!input?.query || input.query.trim() === "" || input.query === "undefined") {
|
|
113
|
+
return toolResult({ error: "query parameter is required and must not be empty" });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const SORT_BY = ["relevance", "lastUpdatedDate", "submittedDate"] as const;
|
|
117
|
+
const SORT_ORDER = ["ascending", "descending"] as const;
|
|
118
|
+
|
|
112
119
|
const params = new URLSearchParams({
|
|
113
120
|
search_query: input.query,
|
|
114
121
|
max_results: String(Math.min(input.max_results ?? 10, 50)),
|
|
115
|
-
sortBy: input.sort_by
|
|
116
|
-
sortOrder: input.sort_order
|
|
122
|
+
sortBy: validEnum(input.sort_by, SORT_BY, "relevance"),
|
|
123
|
+
sortOrder: validEnum(input.sort_order, SORT_ORDER, "descending"),
|
|
117
124
|
});
|
|
118
125
|
|
|
119
126
|
const tracked = await trackedFetch("arxiv", `${BASE}?${params}`, undefined, 15_000);
|
package/src/tools/biorxiv.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
-
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError, validEnum } from "./util.js";
|
|
4
4
|
|
|
5
5
|
const BASE = "https://api.biorxiv.org";
|
|
6
6
|
|
|
@@ -128,7 +128,7 @@ export function createBiorxivTools(
|
|
|
128
128
|
if (!input?.doi) {
|
|
129
129
|
return toolResult({ error: 'doi parameter is required (e.g., "10.1101/2024.01.15.575123")' });
|
|
130
130
|
}
|
|
131
|
-
const server = input.server
|
|
131
|
+
const server = validEnum(input.server, ["biorxiv", "medrxiv"] as const, "biorxiv");
|
|
132
132
|
const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
|
|
133
133
|
const tracked = await trackedFetch(server, `${BASE}/details/${server}/${doi}/na/json`, undefined, 15_000);
|
|
134
134
|
if (isTrackedError(tracked)) return tracked;
|
package/src/tools/crossref.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
-
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError, validParam } from "./util.js";
|
|
4
4
|
|
|
5
5
|
const BASE = "https://api.crossref.org";
|
|
6
6
|
|
|
@@ -114,16 +114,20 @@ export function createCrossRefTools(
|
|
|
114
114
|
const filters: string[] = [];
|
|
115
115
|
if (input.from_year) filters.push(`from-pub-date:${input.from_year}`);
|
|
116
116
|
if (input.until_year) filters.push(`until-pub-date:${input.until_year}`);
|
|
117
|
-
|
|
117
|
+
const type = validParam(input.type);
|
|
118
|
+
if (type) filters.push(`type:${type}`);
|
|
118
119
|
if (input.has_abstract) filters.push("has-abstract:true");
|
|
119
|
-
|
|
120
|
+
const issn = validParam(input.issn);
|
|
121
|
+
if (issn) filters.push(`issn:${issn}`);
|
|
120
122
|
if (filters.length > 0) params.set("filter", filters.join(","));
|
|
121
123
|
|
|
122
124
|
// Journal name as query.container-title (separate from filter)
|
|
123
|
-
|
|
125
|
+
const journal = validParam(input.journal);
|
|
126
|
+
if (journal) params.set("query.container-title", journal);
|
|
124
127
|
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
const sort = validParam(input.sort);
|
|
129
|
+
if (sort) {
|
|
130
|
+
params.set("sort", sort);
|
|
127
131
|
params.set("order", "desc");
|
|
128
132
|
}
|
|
129
133
|
|
package/src/tools/datacite.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
-
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError, validParam } from "./util.js";
|
|
4
4
|
|
|
5
5
|
const BASE = "https://api.datacite.org";
|
|
6
6
|
|
|
@@ -42,8 +42,9 @@ export function createDataCiteTools(
|
|
|
42
42
|
query: input.query,
|
|
43
43
|
"page[size]": String(pageSize),
|
|
44
44
|
});
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
const resourceType = validParam(input.resource_type);
|
|
46
|
+
if (resourceType) {
|
|
47
|
+
params.set("resource-type-id", resourceType.toLowerCase());
|
|
47
48
|
}
|
|
48
49
|
if (input.from_year) {
|
|
49
50
|
params.set("query", `${input.query} AND publicationYear:[${input.from_year} TO *]`);
|
package/src/tools/doaj.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
-
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError, validParam } from "./util.js";
|
|
4
4
|
|
|
5
5
|
const BASE = "https://doaj.org/api";
|
|
6
6
|
|
|
@@ -39,7 +39,8 @@ export function createDoajTools(
|
|
|
39
39
|
const page = input.page ?? 1;
|
|
40
40
|
|
|
41
41
|
let url = `${BASE}/search/articles/${encodeURIComponent(input.query)}?page=${page}&pageSize=${pageSize}`;
|
|
42
|
-
|
|
42
|
+
const sort = validParam(input.sort);
|
|
43
|
+
if (sort) url += `&sort=${encodeURIComponent(sort)}`;
|
|
43
44
|
|
|
44
45
|
const tracked = await trackedFetch("doaj", url);
|
|
45
46
|
if (isTrackedError(tracked)) return tracked;
|
package/src/tools/europe-pmc.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
-
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError, validParam, validEnum } from "./util.js";
|
|
4
4
|
|
|
5
5
|
const BASE = "https://www.ebi.ac.uk/europepmc/webservices/rest";
|
|
6
6
|
|
|
@@ -43,8 +43,9 @@ export function createEuropePmcTools(
|
|
|
43
43
|
pageSize: String(Math.min(input.max_results ?? 10, 1000)),
|
|
44
44
|
resultType: "core",
|
|
45
45
|
});
|
|
46
|
-
|
|
47
|
-
params.set("
|
|
46
|
+
const sort = validParam(input.sort);
|
|
47
|
+
if (sort) params.set("sort", sort);
|
|
48
|
+
params.set("cursorMark", validParam(input.cursor) ?? "*");
|
|
48
49
|
|
|
49
50
|
const tracked = await trackedFetch("europe_pmc", `${BASE}/search?${params}`);
|
|
50
51
|
if (isTrackedError(tracked)) return tracked;
|
package/src/tools/hal.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
-
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError, validParam, validEnum } from "./util.js";
|
|
4
4
|
|
|
5
5
|
const BASE = "https://api.archives-ouvertes.fr";
|
|
6
6
|
|
|
@@ -56,8 +56,9 @@ export function createHalTools(
|
|
|
56
56
|
doc_type?: string;
|
|
57
57
|
}) => {
|
|
58
58
|
let q = input.query;
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
const docType = validParam(input.doc_type);
|
|
60
|
+
if (docType) {
|
|
61
|
+
q = `(${q}) AND docType_s:${docType}`;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
const params = new URLSearchParams({
|
|
@@ -66,7 +67,8 @@ export function createHalTools(
|
|
|
66
67
|
rows: String(Math.min(input.rows ?? 10, 100)),
|
|
67
68
|
wt: "json",
|
|
68
69
|
});
|
|
69
|
-
|
|
70
|
+
const sort = validParam(input.sort);
|
|
71
|
+
if (sort) params.set("sort", sort);
|
|
70
72
|
|
|
71
73
|
const result = await trackedFetch(
|
|
72
74
|
"hal",
|
package/src/tools/inspire-hep.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
-
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError, validEnum } from "./util.js";
|
|
4
4
|
|
|
5
5
|
const BASE = "https://inspirehep.net/api";
|
|
6
6
|
|
|
@@ -99,10 +99,11 @@ export function createInspireHepTools(
|
|
|
99
99
|
),
|
|
100
100
|
}),
|
|
101
101
|
execute: async (input: { query: string; size?: number; sort?: string }) => {
|
|
102
|
+
const sort = validEnum(input.sort, ["mostrecent", "mostcited", "bestmatch"] as const, "bestmatch");
|
|
102
103
|
const params = new URLSearchParams({
|
|
103
104
|
q: input.query,
|
|
104
105
|
size: String(Math.min(input.size ?? 10, 100)),
|
|
105
|
-
sort
|
|
106
|
+
sort,
|
|
106
107
|
});
|
|
107
108
|
|
|
108
109
|
const result = await trackedFetch(
|
package/src/tools/openaire.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
-
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError, validParam } from "./util.js";
|
|
4
4
|
|
|
5
5
|
const BASE = "https://api.openaire.eu";
|
|
6
6
|
|
|
@@ -127,12 +127,17 @@ export function createOpenAireTools(
|
|
|
127
127
|
format: "json",
|
|
128
128
|
size: String(Math.min(input.max_results ?? 10, 50)),
|
|
129
129
|
});
|
|
130
|
-
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
if (
|
|
130
|
+
const author = validParam(input.author);
|
|
131
|
+
if (author) params.set("author", author);
|
|
132
|
+
const doi = validParam(input.doi);
|
|
133
|
+
if (doi) params.set("doi", doi);
|
|
134
|
+
const fromDate = validParam(input.from_date);
|
|
135
|
+
if (fromDate) params.set("fromDateAccepted", fromDate);
|
|
136
|
+
const toDate = validParam(input.to_date);
|
|
137
|
+
if (toDate) params.set("toDateAccepted", toDate);
|
|
134
138
|
if (input.oa_only) params.set("OA", "true");
|
|
135
|
-
|
|
139
|
+
const funder = validParam(input.funder);
|
|
140
|
+
if (funder) params.set("funder", funder);
|
|
136
141
|
|
|
137
142
|
const tracked = await trackedFetch("openaire", `${BASE}/search/publications?${params}`, undefined, 15_000);
|
|
138
143
|
if (isTrackedError(tracked)) return tracked;
|
package/src/tools/openalex.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
-
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError, validParam, validEnum } from "./util.js";
|
|
4
4
|
|
|
5
5
|
const BASE = "https://api.openalex.org";
|
|
6
6
|
|
|
@@ -46,6 +46,12 @@ export function createOpenAlexTools(
|
|
|
46
46
|
open_access?: boolean;
|
|
47
47
|
sort_by?: string;
|
|
48
48
|
}) => {
|
|
49
|
+
if (!input?.query || input.query.trim() === "" || input.query === "undefined") {
|
|
50
|
+
return toolResult({ error: "query parameter is required and must not be empty" });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const SORT_BY = ["cited_by_count", "publication_date", "relevance_score"] as const;
|
|
54
|
+
|
|
49
55
|
const filters: string[] = [];
|
|
50
56
|
if (input.from_year) filters.push(`from_publication_date:${input.from_year}-01-01`);
|
|
51
57
|
if (input.to_year) filters.push(`to_publication_date:${input.to_year}-12-31`);
|
|
@@ -56,7 +62,10 @@ export function createOpenAlexTools(
|
|
|
56
62
|
per_page: String(Math.min(input.limit ?? 10, 200)),
|
|
57
63
|
});
|
|
58
64
|
if (filters.length > 0) params.set("filter", filters.join(","));
|
|
59
|
-
|
|
65
|
+
const sortBy = validParam(input.sort_by);
|
|
66
|
+
if (sortBy && (SORT_BY as readonly string[]).includes(sortBy)) {
|
|
67
|
+
params.set("sort", sortBy);
|
|
68
|
+
}
|
|
60
69
|
|
|
61
70
|
const tracked = await trackedFetch("openalex", `${BASE}/works?${params}`, { headers });
|
|
62
71
|
if (isTrackedError(tracked)) return tracked;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
-
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError, validParam } from "./util.js";
|
|
4
4
|
|
|
5
5
|
const BASE = "https://api.osf.io/v2";
|
|
6
6
|
|
|
@@ -36,7 +36,8 @@ export function createOsfPreprintsTools(
|
|
|
36
36
|
const params = new URLSearchParams({
|
|
37
37
|
"page[size]": String(Math.min(input.size ?? 10, 100)),
|
|
38
38
|
});
|
|
39
|
-
|
|
39
|
+
const provider = validParam(input.provider);
|
|
40
|
+
if (provider) params.set("filter[provider]", provider);
|
|
40
41
|
if (input.page) params.set("page", String(input.page));
|
|
41
42
|
|
|
42
43
|
const result = await trackedFetch(
|
package/src/tools/pubmed.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
-
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError, validParam, validEnum } from "./util.js";
|
|
4
4
|
|
|
5
5
|
const EUTILS = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils";
|
|
6
6
|
|
|
@@ -41,17 +41,21 @@ export function createPubMedTools(
|
|
|
41
41
|
min_date?: string;
|
|
42
42
|
max_date?: string;
|
|
43
43
|
}) => {
|
|
44
|
+
const sort = validEnum(input.sort, ["relevance", "pub_date"] as const, "relevance");
|
|
45
|
+
const minDate = validParam(input.min_date);
|
|
46
|
+
const maxDate = validParam(input.max_date);
|
|
47
|
+
|
|
44
48
|
const searchParams = new URLSearchParams({
|
|
45
49
|
db: "pubmed",
|
|
46
50
|
term: input.query,
|
|
47
51
|
retmax: String(Math.min(input.max_results ?? 10, 100)),
|
|
48
52
|
retmode: "json",
|
|
49
|
-
sort
|
|
53
|
+
sort,
|
|
50
54
|
usehistory: "y",
|
|
51
55
|
});
|
|
52
|
-
if (
|
|
53
|
-
if (
|
|
54
|
-
if (
|
|
56
|
+
if (minDate) searchParams.set("mindate", minDate);
|
|
57
|
+
if (maxDate) searchParams.set("maxdate", maxDate);
|
|
58
|
+
if (minDate || maxDate) searchParams.set("datetype", "pdat");
|
|
55
59
|
|
|
56
60
|
const searchTracked = await trackedFetch("pubmed", `${EUTILS}/esearch.fcgi?${searchParams}`);
|
|
57
61
|
if (isTrackedError(searchTracked)) return searchTracked;
|
package/src/tools/util.ts
CHANGED
|
@@ -150,3 +150,36 @@ export function isTrackedError(
|
|
|
150
150
|
): result is ReturnType<typeof toolResult> {
|
|
151
151
|
return "content" in result && "details" in result;
|
|
152
152
|
}
|
|
153
|
+
|
|
154
|
+
// ── LLM Input Sanitization ──────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
const INVALID_PARAM_VALUES = new Set(["undefined", "null", "none", "None", ""]);
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Sanitize an optional string parameter from LLM tool calls.
|
|
160
|
+
*
|
|
161
|
+
* LLMs (especially weaker models) sometimes pass the literal string "undefined"
|
|
162
|
+
* or "null" instead of omitting the parameter. The nullish coalescing operator
|
|
163
|
+
* (`??`) does NOT catch these because they are truthy strings.
|
|
164
|
+
*
|
|
165
|
+
* Returns the value if valid, or `undefined` if it's a known non-value.
|
|
166
|
+
*/
|
|
167
|
+
export function validParam(value: string | undefined | null): string | undefined {
|
|
168
|
+
if (value == null) return undefined;
|
|
169
|
+
if (INVALID_PARAM_VALUES.has(value.trim())) return undefined;
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Validate a string parameter against a whitelist of allowed values.
|
|
175
|
+
* Returns the value if it matches, or the fallback otherwise.
|
|
176
|
+
*/
|
|
177
|
+
export function validEnum<T extends string>(
|
|
178
|
+
value: string | undefined | null,
|
|
179
|
+
allowed: readonly T[],
|
|
180
|
+
fallback: T,
|
|
181
|
+
): T {
|
|
182
|
+
const clean = validParam(value);
|
|
183
|
+
if (clean && (allowed as readonly string[]).includes(clean)) return clean as T;
|
|
184
|
+
return fallback;
|
|
185
|
+
}
|
package/src/tools/zenodo.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
|
|
3
|
-
import { toolResult, trackedFetch, isTrackedError } from "./util.js";
|
|
3
|
+
import { toolResult, trackedFetch, isTrackedError, validParam } from "./util.js";
|
|
4
4
|
|
|
5
5
|
const BASE = "https://zenodo.org/api";
|
|
6
6
|
|
|
@@ -48,9 +48,12 @@ export function createZenodoTools(
|
|
|
48
48
|
q: input.query,
|
|
49
49
|
size: String(Math.min(input.size ?? 10, 100)),
|
|
50
50
|
});
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
|
|
51
|
+
const type = validParam(input.type);
|
|
52
|
+
if (type) params.set("type", type);
|
|
53
|
+
const sort = validParam(input.sort);
|
|
54
|
+
if (sort) params.set("sort", sort);
|
|
55
|
+
const accessRight = validParam(input.access_right);
|
|
56
|
+
if (accessRight) params.set("access_right", accessRight);
|
|
54
57
|
|
|
55
58
|
const result = await trackedFetch("zenodo", `${BASE}/records?${params}`, undefined, 10_000);
|
|
56
59
|
if (isTrackedError(result)) return result;
|