@wentorai/research-plugins 1.3.2 → 1.4.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.
Files changed (266) hide show
  1. package/README.md +32 -56
  2. package/curated/analysis/README.md +1 -13
  3. package/curated/domains/README.md +1 -5
  4. package/curated/literature/README.md +3 -12
  5. package/curated/research/README.md +1 -18
  6. package/curated/tools/README.md +1 -12
  7. package/curated/writing/README.md +2 -6
  8. package/index.ts +88 -5
  9. package/openclaw.plugin.json +3 -12
  10. package/package.json +3 -5
  11. package/skills/analysis/statistics/SKILL.md +1 -1
  12. package/skills/analysis/statistics/meta-analysis-guide/SKILL.md +1 -1
  13. package/skills/domains/ai-ml/SKILL.md +3 -2
  14. package/skills/domains/ai-ml/generative-ai-guide/SKILL.md +1 -0
  15. package/skills/domains/ai-ml/huggingface-api/SKILL.md +251 -0
  16. package/skills/domains/biomedical/SKILL.md +9 -2
  17. package/skills/domains/biomedical/alphafold-api/SKILL.md +227 -0
  18. package/skills/domains/biomedical/biothings-api/SKILL.md +296 -0
  19. package/skills/domains/biomedical/clinicaltrials-api-v2/SKILL.md +216 -0
  20. package/skills/domains/biomedical/enrichr-api/SKILL.md +264 -0
  21. package/skills/domains/biomedical/ensembl-rest-api/SKILL.md +204 -0
  22. package/skills/domains/biomedical/medical-data-api/SKILL.md +197 -0
  23. package/skills/domains/biomedical/pdb-structure-api/SKILL.md +219 -0
  24. package/skills/domains/business/SKILL.md +2 -3
  25. package/skills/domains/chemistry/SKILL.md +3 -2
  26. package/skills/domains/chemistry/catalysis-hub-api/SKILL.md +171 -0
  27. package/skills/domains/education/SKILL.md +2 -3
  28. package/skills/domains/law/SKILL.md +3 -2
  29. package/skills/domains/law/uk-legislation-api/SKILL.md +179 -0
  30. package/skills/literature/discovery/SKILL.md +1 -1
  31. package/skills/literature/discovery/citation-alert-guide/SKILL.md +2 -2
  32. package/skills/literature/discovery/conference-proceedings-guide/SKILL.md +2 -2
  33. package/skills/literature/discovery/literature-mapping-guide/SKILL.md +1 -1
  34. package/skills/literature/discovery/paper-recommendation-guide/SKILL.md +8 -14
  35. package/skills/literature/discovery/rss-paper-feeds/SKILL.md +20 -14
  36. package/skills/literature/discovery/semantic-paper-radar/SKILL.md +8 -8
  37. package/skills/literature/discovery/semantic-scholar-recs-guide/SKILL.md +103 -86
  38. package/skills/literature/fulltext/SKILL.md +3 -2
  39. package/skills/literature/fulltext/arxiv-latex-source/SKILL.md +195 -0
  40. package/skills/literature/fulltext/open-access-guide/SKILL.md +1 -1
  41. package/skills/literature/fulltext/open-access-mining-guide/SKILL.md +5 -5
  42. package/skills/literature/metadata/citation-network-guide/SKILL.md +3 -3
  43. package/skills/literature/metadata/h-index-guide/SKILL.md +0 -27
  44. package/skills/literature/search/SKILL.md +3 -4
  45. package/skills/literature/search/citation-chaining-guide/SKILL.md +42 -32
  46. package/skills/literature/search/database-comparison-guide/SKILL.md +1 -1
  47. package/skills/literature/search/semantic-scholar-api/SKILL.md +56 -53
  48. package/skills/research/automation/SKILL.md +2 -3
  49. package/skills/research/automation/datagen-research-guide/SKILL.md +1 -0
  50. package/skills/research/automation/mle-agent-guide/SKILL.md +1 -0
  51. package/skills/research/automation/paper-to-agent-guide/SKILL.md +2 -1
  52. package/skills/research/deep-research/auto-deep-research-guide/SKILL.md +1 -0
  53. package/skills/research/deep-research/in-depth-research-guide/SKILL.md +1 -1
  54. package/skills/research/deep-research/kosmos-scientist-guide/SKILL.md +3 -3
  55. package/skills/research/deep-research/llm-scientific-discovery-guide/SKILL.md +1 -1
  56. package/skills/research/deep-research/local-deep-research-guide/SKILL.md +6 -6
  57. package/skills/research/deep-research/open-researcher-guide/SKILL.md +3 -3
  58. package/skills/research/deep-research/tongyi-deep-research-guide/SKILL.md +4 -4
  59. package/skills/research/methodology/SKILL.md +1 -1
  60. package/skills/research/methodology/claude-scientific-guide/SKILL.md +1 -0
  61. package/skills/research/methodology/grad-school-guide/SKILL.md +1 -1
  62. package/skills/research/methodology/qualitative-research-guide/SKILL.md +1 -1
  63. package/skills/research/paper-review/SKILL.md +1 -1
  64. package/skills/research/paper-review/automated-review-guide/SKILL.md +1 -1
  65. package/skills/research/paper-review/peer-review-guide/SKILL.md +1 -1
  66. package/skills/tools/diagram/excalidraw-diagram-guide/SKILL.md +1 -1
  67. package/skills/tools/diagram/mermaid-architect-guide/SKILL.md +1 -1
  68. package/skills/tools/diagram/plantuml-guide/SKILL.md +1 -1
  69. package/skills/tools/document/grobid-pdf-parsing/SKILL.md +1 -1
  70. package/skills/tools/document/paper-parse-guide/SKILL.md +2 -2
  71. package/skills/tools/knowledge-graph/SKILL.md +2 -3
  72. package/skills/tools/knowledge-graph/citation-network-builder/SKILL.md +5 -5
  73. package/skills/tools/knowledge-graph/knowledge-graph-construction/SKILL.md +1 -1
  74. package/skills/tools/ocr-translate/zotero-pdf2zh-guide/SKILL.md +1 -0
  75. package/skills/tools/scraping/academic-web-scraping/SKILL.md +1 -2
  76. package/skills/tools/scraping/google-scholar-scraper/SKILL.md +7 -7
  77. package/skills/writing/citation/SKILL.md +1 -1
  78. package/skills/writing/citation/academic-citation-manager/SKILL.md +20 -17
  79. package/skills/writing/citation/citation-assistant-skill/SKILL.md +72 -58
  80. package/skills/writing/citation/obsidian-citation-guide/SKILL.md +1 -0
  81. package/skills/writing/citation/obsidian-zotero-guide/SKILL.md +1 -0
  82. package/skills/writing/citation/onecite-reference-guide/SKILL.md +1 -1
  83. package/skills/writing/citation/papersgpt-zotero-guide/SKILL.md +1 -0
  84. package/skills/writing/citation/zotero-mdnotes-guide/SKILL.md +1 -0
  85. package/skills/writing/citation/zotero-reference-guide/SKILL.md +2 -1
  86. package/skills/writing/citation/zotero-scholar-guide/SKILL.md +1 -1
  87. package/skills/writing/composition/scientific-writing-resources/SKILL.md +1 -0
  88. package/skills/writing/latex/latex-drawing-collection/SKILL.md +1 -0
  89. package/skills/writing/latex/latex-templates-collection/SKILL.md +1 -0
  90. package/skills/writing/templates/novathesis-guide/SKILL.md +1 -0
  91. package/src/tools/arxiv.ts +81 -30
  92. package/src/tools/biorxiv.ts +158 -0
  93. package/src/tools/crossref.ts +63 -22
  94. package/src/tools/datacite.ts +191 -0
  95. package/src/tools/dblp.ts +125 -0
  96. package/src/tools/doaj.ts +82 -0
  97. package/src/tools/europe-pmc.ts +159 -0
  98. package/src/tools/hal.ts +118 -0
  99. package/src/tools/inspire-hep.ts +165 -0
  100. package/src/tools/openaire.ts +158 -0
  101. package/src/tools/openalex.ts +26 -15
  102. package/src/tools/opencitations.ts +112 -0
  103. package/src/tools/orcid.ts +139 -0
  104. package/src/tools/osf-preprints.ts +104 -0
  105. package/src/tools/pubmed.ts +22 -13
  106. package/src/tools/ror.ts +118 -0
  107. package/src/tools/unpaywall.ts +15 -6
  108. package/src/tools/util.ts +141 -0
  109. package/src/tools/zenodo.ts +157 -0
  110. package/mcp-configs/academic-db/ChatSpatial.json +0 -17
  111. package/mcp-configs/academic-db/academia-mcp.json +0 -17
  112. package/mcp-configs/academic-db/academic-paper-explorer.json +0 -17
  113. package/mcp-configs/academic-db/academic-search-mcp-server.json +0 -17
  114. package/mcp-configs/academic-db/agentinterviews-mcp.json +0 -17
  115. package/mcp-configs/academic-db/all-in-mcp.json +0 -17
  116. package/mcp-configs/academic-db/alphafold-mcp.json +0 -20
  117. package/mcp-configs/academic-db/apple-health-mcp.json +0 -17
  118. package/mcp-configs/academic-db/arxiv-latex-mcp.json +0 -17
  119. package/mcp-configs/academic-db/arxiv-mcp-server.json +0 -17
  120. package/mcp-configs/academic-db/bgpt-mcp.json +0 -17
  121. package/mcp-configs/academic-db/biomcp.json +0 -17
  122. package/mcp-configs/academic-db/biothings-mcp.json +0 -17
  123. package/mcp-configs/academic-db/brightspace-mcp.json +0 -21
  124. package/mcp-configs/academic-db/catalysishub-mcp-server.json +0 -17
  125. package/mcp-configs/academic-db/climatiq-mcp.json +0 -20
  126. package/mcp-configs/academic-db/clinicaltrialsgov-mcp-server.json +0 -17
  127. package/mcp-configs/academic-db/deep-research-mcp.json +0 -17
  128. package/mcp-configs/academic-db/dicom-mcp.json +0 -17
  129. package/mcp-configs/academic-db/enrichr-mcp-server.json +0 -17
  130. package/mcp-configs/academic-db/fec-mcp-server.json +0 -17
  131. package/mcp-configs/academic-db/fhir-mcp-server-themomentum.json +0 -17
  132. package/mcp-configs/academic-db/fhir-mcp.json +0 -19
  133. package/mcp-configs/academic-db/gget-mcp.json +0 -17
  134. package/mcp-configs/academic-db/gibs-mcp.json +0 -20
  135. package/mcp-configs/academic-db/gis-mcp-server.json +0 -22
  136. package/mcp-configs/academic-db/google-earth-engine-mcp.json +0 -21
  137. package/mcp-configs/academic-db/google-researcher-mcp.json +0 -17
  138. package/mcp-configs/academic-db/idea-reality-mcp.json +0 -17
  139. package/mcp-configs/academic-db/legiscan-mcp.json +0 -19
  140. package/mcp-configs/academic-db/lex.json +0 -17
  141. package/mcp-configs/academic-db/m4-clinical-mcp.json +0 -21
  142. package/mcp-configs/academic-db/medical-mcp.json +0 -21
  143. package/mcp-configs/academic-db/nexonco-mcp.json +0 -20
  144. package/mcp-configs/academic-db/omop-mcp.json +0 -20
  145. package/mcp-configs/academic-db/onekgpd-mcp.json +0 -20
  146. package/mcp-configs/academic-db/openedu-mcp.json +0 -20
  147. package/mcp-configs/academic-db/opengenes-mcp.json +0 -20
  148. package/mcp-configs/academic-db/openstax-mcp.json +0 -21
  149. package/mcp-configs/academic-db/openstreetmap-mcp.json +0 -21
  150. package/mcp-configs/academic-db/opentargets-mcp.json +0 -21
  151. package/mcp-configs/academic-db/pdb-mcp.json +0 -21
  152. package/mcp-configs/academic-db/smithsonian-mcp.json +0 -20
  153. package/mcp-configs/ai-platform/Adaptive-Graph-of-Thoughts-MCP-server.json +0 -17
  154. package/mcp-configs/ai-platform/ai-counsel.json +0 -17
  155. package/mcp-configs/ai-platform/atlas-mcp-server.json +0 -17
  156. package/mcp-configs/ai-platform/counsel-mcp.json +0 -17
  157. package/mcp-configs/ai-platform/cross-llm-mcp.json +0 -17
  158. package/mcp-configs/ai-platform/gptr-mcp.json +0 -17
  159. package/mcp-configs/ai-platform/magi-researchers.json +0 -21
  160. package/mcp-configs/ai-platform/mcp-academic-researcher.json +0 -22
  161. package/mcp-configs/ai-platform/open-paper-machine.json +0 -21
  162. package/mcp-configs/ai-platform/paper-intelligence.json +0 -21
  163. package/mcp-configs/ai-platform/paper-reader.json +0 -21
  164. package/mcp-configs/ai-platform/paperdebugger.json +0 -21
  165. package/mcp-configs/browser/decipher-research-agent.json +0 -17
  166. package/mcp-configs/browser/deep-research.json +0 -17
  167. package/mcp-configs/browser/everything-claude-code.json +0 -17
  168. package/mcp-configs/browser/exa-mcp.json +0 -20
  169. package/mcp-configs/browser/gpt-researcher.json +0 -17
  170. package/mcp-configs/browser/heurist-agent-framework.json +0 -17
  171. package/mcp-configs/browser/mcp-searxng.json +0 -21
  172. package/mcp-configs/browser/mcp-webresearch.json +0 -20
  173. package/mcp-configs/cloud-docs/confluence-mcp.json +0 -37
  174. package/mcp-configs/cloud-docs/google-drive-mcp.json +0 -35
  175. package/mcp-configs/cloud-docs/notion-mcp.json +0 -29
  176. package/mcp-configs/communication/discord-mcp.json +0 -29
  177. package/mcp-configs/communication/discourse-mcp.json +0 -21
  178. package/mcp-configs/communication/slack-mcp.json +0 -29
  179. package/mcp-configs/communication/telegram-mcp.json +0 -28
  180. package/mcp-configs/data-platform/4everland-hosting-mcp.json +0 -17
  181. package/mcp-configs/data-platform/automl-stat-mcp.json +0 -21
  182. package/mcp-configs/data-platform/context-keeper.json +0 -17
  183. package/mcp-configs/data-platform/context7.json +0 -19
  184. package/mcp-configs/data-platform/contextstream-mcp.json +0 -17
  185. package/mcp-configs/data-platform/email-mcp.json +0 -17
  186. package/mcp-configs/data-platform/jefferson-stats-mcp.json +0 -22
  187. package/mcp-configs/data-platform/mcp-excel-server.json +0 -21
  188. package/mcp-configs/data-platform/mcp-stata.json +0 -21
  189. package/mcp-configs/data-platform/mcpstack-jupyter.json +0 -21
  190. package/mcp-configs/data-platform/ml-mcp.json +0 -21
  191. package/mcp-configs/data-platform/nasdaq-data-link-mcp.json +0 -20
  192. package/mcp-configs/data-platform/numpy-mcp.json +0 -21
  193. package/mcp-configs/database/neo4j-mcp.json +0 -37
  194. package/mcp-configs/database/postgres-mcp.json +0 -28
  195. package/mcp-configs/database/sqlite-mcp.json +0 -29
  196. package/mcp-configs/dev-platform/geogebra-mcp.json +0 -21
  197. package/mcp-configs/dev-platform/github-mcp.json +0 -31
  198. package/mcp-configs/dev-platform/gitlab-mcp.json +0 -34
  199. package/mcp-configs/dev-platform/latex-mcp-server.json +0 -21
  200. package/mcp-configs/dev-platform/manim-mcp.json +0 -20
  201. package/mcp-configs/dev-platform/mcp-echarts.json +0 -20
  202. package/mcp-configs/dev-platform/panel-viz-mcp.json +0 -20
  203. package/mcp-configs/dev-platform/paperbanana.json +0 -20
  204. package/mcp-configs/dev-platform/texflow-mcp.json +0 -20
  205. package/mcp-configs/dev-platform/texmcp.json +0 -20
  206. package/mcp-configs/dev-platform/typst-mcp.json +0 -21
  207. package/mcp-configs/dev-platform/vizro-mcp.json +0 -20
  208. package/mcp-configs/email/email-mcp.json +0 -40
  209. package/mcp-configs/email/gmail-mcp.json +0 -37
  210. package/mcp-configs/note-knowledge/ApeRAG.json +0 -17
  211. package/mcp-configs/note-knowledge/In-Memoria.json +0 -17
  212. package/mcp-configs/note-knowledge/agent-memory.json +0 -17
  213. package/mcp-configs/note-knowledge/aimemo.json +0 -17
  214. package/mcp-configs/note-knowledge/biel-mcp.json +0 -19
  215. package/mcp-configs/note-knowledge/cognee.json +0 -17
  216. package/mcp-configs/note-knowledge/context-awesome.json +0 -17
  217. package/mcp-configs/note-knowledge/context-mcp.json +0 -17
  218. package/mcp-configs/note-knowledge/conversation-handoff-mcp.json +0 -17
  219. package/mcp-configs/note-knowledge/cortex.json +0 -17
  220. package/mcp-configs/note-knowledge/devrag.json +0 -17
  221. package/mcp-configs/note-knowledge/easy-obsidian-mcp.json +0 -17
  222. package/mcp-configs/note-knowledge/engram.json +0 -17
  223. package/mcp-configs/note-knowledge/gnosis-mcp.json +0 -17
  224. package/mcp-configs/note-knowledge/graphlit-mcp-server.json +0 -19
  225. package/mcp-configs/note-knowledge/local-faiss-mcp.json +0 -21
  226. package/mcp-configs/note-knowledge/mcp-memory-service.json +0 -21
  227. package/mcp-configs/note-knowledge/mcp-obsidian.json +0 -23
  228. package/mcp-configs/note-knowledge/mcp-ragdocs.json +0 -20
  229. package/mcp-configs/note-knowledge/mcp-summarizer.json +0 -21
  230. package/mcp-configs/note-knowledge/mediawiki-mcp.json +0 -21
  231. package/mcp-configs/note-knowledge/openzim-mcp.json +0 -20
  232. package/mcp-configs/note-knowledge/zettelkasten-mcp.json +0 -21
  233. package/mcp-configs/reference-mgr/academic-paper-mcp-http.json +0 -20
  234. package/mcp-configs/reference-mgr/academix.json +0 -20
  235. package/mcp-configs/reference-mgr/arxiv-cli.json +0 -17
  236. package/mcp-configs/reference-mgr/arxiv-research-mcp.json +0 -21
  237. package/mcp-configs/reference-mgr/arxiv-search-mcp.json +0 -17
  238. package/mcp-configs/reference-mgr/chiken.json +0 -17
  239. package/mcp-configs/reference-mgr/claude-scholar.json +0 -17
  240. package/mcp-configs/reference-mgr/devonthink-mcp.json +0 -17
  241. package/mcp-configs/reference-mgr/google-scholar-abstract-mcp.json +0 -19
  242. package/mcp-configs/reference-mgr/google-scholar-mcp.json +0 -20
  243. package/mcp-configs/reference-mgr/mcp-paperswithcode.json +0 -21
  244. package/mcp-configs/reference-mgr/mcp-scholarly.json +0 -20
  245. package/mcp-configs/reference-mgr/mcp-simple-arxiv.json +0 -20
  246. package/mcp-configs/reference-mgr/mcp-simple-pubmed.json +0 -20
  247. package/mcp-configs/reference-mgr/mcp-zotero.json +0 -21
  248. package/mcp-configs/reference-mgr/mendeley-mcp.json +0 -20
  249. package/mcp-configs/reference-mgr/ncbi-mcp-server.json +0 -22
  250. package/mcp-configs/reference-mgr/onecite.json +0 -21
  251. package/mcp-configs/reference-mgr/paper-search-mcp.json +0 -21
  252. package/mcp-configs/reference-mgr/pubmed-search-mcp.json +0 -21
  253. package/mcp-configs/reference-mgr/scholar-mcp.json +0 -21
  254. package/mcp-configs/reference-mgr/scholar-multi-mcp.json +0 -21
  255. package/mcp-configs/reference-mgr/seerai.json +0 -21
  256. package/mcp-configs/reference-mgr/semantic-scholar-fastmcp.json +0 -21
  257. package/mcp-configs/reference-mgr/sourcelibrary.json +0 -20
  258. package/mcp-configs/registry.json +0 -476
  259. package/mcp-configs/repository/dataverse-mcp.json +0 -33
  260. package/mcp-configs/repository/huggingface-mcp.json +0 -29
  261. package/skills/domains/business/xpert-bi-guide/SKILL.md +0 -84
  262. package/skills/domains/education/edumcp-guide/SKILL.md +0 -74
  263. package/skills/literature/search/paper-search-mcp-guide/SKILL.md +0 -107
  264. package/skills/research/automation/mcp-server-guide/SKILL.md +0 -211
  265. package/skills/tools/knowledge-graph/paperpile-notion-guide/SKILL.md +0 -84
  266. package/src/tools/semantic-scholar.ts +0 -66
@@ -0,0 +1,158 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
+ import { toolResult, trackedFetch, isTrackedError } from "./util.js";
4
+
5
+ const BASE = "https://api.openaire.eu";
6
+
7
+ /**
8
+ * Parse OpenAIRE XML response wrapped in JSON.
9
+ * OpenAIRE returns XML by default; with format=json it wraps XML nodes in JSON objects.
10
+ */
11
+ function parseOpenAireResult(result: Record<string, unknown>): Record<string, unknown> {
12
+ const metadata = result.metadata as Record<string, unknown> | undefined;
13
+ const oafEntity = metadata?.["oaf:entity"] as Record<string, unknown> | undefined;
14
+ const oafResult = oafEntity?.["oaf:result"] as Record<string, unknown> | undefined;
15
+
16
+ if (!oafResult) {
17
+ // Flat JSON format (newer API versions)
18
+ return {
19
+ title: result.title,
20
+ authors: result.authors,
21
+ doi: result.doi,
22
+ url: result.url,
23
+ };
24
+ }
25
+
26
+ // Extract title
27
+ const titleRaw = oafResult.title;
28
+ let title = "";
29
+ if (Array.isArray(titleRaw)) {
30
+ const main = titleRaw.find((t: Record<string, string>) => t["@classid"] === "main title");
31
+ title = (main ?? titleRaw[0])?.["$"] ?? "";
32
+ } else if (typeof titleRaw === "object" && titleRaw !== null) {
33
+ title = (titleRaw as Record<string, string>)["$"] ?? "";
34
+ }
35
+
36
+ // Extract authors
37
+ const creatorsRaw = oafResult.creator;
38
+ const authors: string[] = [];
39
+ if (Array.isArray(creatorsRaw)) {
40
+ for (const c of creatorsRaw) {
41
+ const name = typeof c === "string" ? c : (c as Record<string, string>)["$"];
42
+ if (name) authors.push(name);
43
+ }
44
+ } else if (creatorsRaw) {
45
+ const name = typeof creatorsRaw === "string" ? creatorsRaw : (creatorsRaw as Record<string, string>)["$"];
46
+ if (name) authors.push(name);
47
+ }
48
+
49
+ // Extract DOI and other identifiers
50
+ const pidRaw = oafResult.pid;
51
+ let doi: string | undefined;
52
+ if (Array.isArray(pidRaw)) {
53
+ const doiPid = pidRaw.find((p: Record<string, string>) => p["@classid"] === "doi");
54
+ doi = (doiPid as Record<string, string> | undefined)?.["$"];
55
+ } else if (typeof pidRaw === "object" && pidRaw !== null) {
56
+ if ((pidRaw as Record<string, string>)["@classid"] === "doi") {
57
+ doi = (pidRaw as Record<string, string>)["$"];
58
+ }
59
+ }
60
+
61
+ // Extract dates
62
+ const dateRaw = oafResult.dateofacceptance;
63
+ const date = typeof dateRaw === "string" ? dateRaw : (dateRaw as Record<string, string> | undefined)?.["$"];
64
+
65
+ // Extract best access right
66
+ const accessRaw = oafResult.bestaccessright;
67
+ const isOa = typeof accessRaw === "object" && accessRaw !== null
68
+ ? (accessRaw as Record<string, string>)["@classid"] === "OPEN"
69
+ : false;
70
+
71
+ return {
72
+ title,
73
+ authors,
74
+ doi,
75
+ date,
76
+ is_oa: isOa,
77
+ url: doi ? `https://doi.org/${doi}` : undefined,
78
+ };
79
+ }
80
+
81
+ export function createOpenAireTools(
82
+ _ctx: OpenClawPluginToolContext,
83
+ _api: OpenClawPluginApi,
84
+ ) {
85
+ return [
86
+ {
87
+ name: "search_openaire",
88
+ label: "Search Publications (OpenAIRE)",
89
+ description:
90
+ "Search OpenAIRE for publications (170M+ records). Uniquely supports filtering by EU funding source (EC, NSF, etc.) and project ID.",
91
+ parameters: Type.Object({
92
+ keywords: Type.String({
93
+ description: "Search keywords",
94
+ }),
95
+ author: Type.Optional(Type.String({ description: "Author name" })),
96
+ doi: Type.Optional(Type.String({ description: "DOI" })),
97
+ from_date: Type.Optional(
98
+ Type.String({ description: "From date (YYYY-MM-DD)" }),
99
+ ),
100
+ to_date: Type.Optional(
101
+ Type.String({ description: "To date (YYYY-MM-DD)" }),
102
+ ),
103
+ oa_only: Type.Optional(
104
+ Type.Boolean({ description: "Only open access results" }),
105
+ ),
106
+ funder: Type.Optional(
107
+ Type.String({
108
+ description: "Funder abbreviation: 'EC' (EU), 'NSF', 'NIH', 'UKRI', 'DFG', etc.",
109
+ }),
110
+ ),
111
+ max_results: Type.Optional(
112
+ Type.Number({ description: "Max results (default 10, max 50)" }),
113
+ ),
114
+ }),
115
+ execute: async (input: {
116
+ keywords: string;
117
+ author?: string;
118
+ doi?: string;
119
+ from_date?: string;
120
+ to_date?: string;
121
+ oa_only?: boolean;
122
+ funder?: string;
123
+ max_results?: number;
124
+ }) => {
125
+ const params = new URLSearchParams({
126
+ keywords: input.keywords,
127
+ format: "json",
128
+ size: String(Math.min(input.max_results ?? 10, 50)),
129
+ });
130
+ if (input.author) params.set("author", input.author);
131
+ if (input.doi) params.set("doi", input.doi);
132
+ if (input.from_date) params.set("fromDateAccepted", input.from_date);
133
+ if (input.to_date) params.set("toDateAccepted", input.to_date);
134
+ if (input.oa_only) params.set("OA", "true");
135
+ if (input.funder) params.set("funder", input.funder);
136
+
137
+ const tracked = await trackedFetch("openaire", `${BASE}/search/publications?${params}`, undefined, 15_000);
138
+ if (isTrackedError(tracked)) return tracked;
139
+ const data = await tracked.res.json();
140
+
141
+ const response = data.response as Record<string, unknown> | undefined;
142
+ const header = response?.header as Record<string, unknown> | undefined;
143
+ const total = header?.total as Record<string, string> | undefined;
144
+ const results = response?.results as Record<string, unknown> | undefined;
145
+ const resultList = results?.result;
146
+
147
+ const papers = (Array.isArray(resultList) ? resultList : []).map(
148
+ (r: Record<string, unknown>) => parseOpenAireResult(r),
149
+ );
150
+
151
+ return toolResult({
152
+ total_results: total?.["$"] ? parseInt(total["$"], 10) : papers.length,
153
+ papers,
154
+ });
155
+ },
156
+ },
157
+ ];
158
+ }
@@ -1,6 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
- import { toolResult } from "./util.js";
3
+ import { toolResult, trackedFetch, isTrackedError } from "./util.js";
4
4
 
5
5
  const BASE = "https://api.openalex.org";
6
6
 
@@ -58,9 +58,9 @@ export function createOpenAlexTools(
58
58
  if (filters.length > 0) params.set("filter", filters.join(","));
59
59
  if (input.sort_by) params.set("sort", input.sort_by);
60
60
 
61
- const res = await fetch(`${BASE}/works?${params}`, { headers });
62
- if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
63
- const data = await res.json();
61
+ const tracked = await trackedFetch("openalex", `${BASE}/works?${params}`, { headers });
62
+ if (isTrackedError(tracked)) return tracked;
63
+ const data = await tracked.res.json();
64
64
  return toolResult({
65
65
  total_count: data.meta?.count,
66
66
  results: data.results?.map((w: Record<string, unknown>) => ({
@@ -78,6 +78,7 @@ export function createOpenAlexTools(
78
78
  )
79
79
  : [],
80
80
  })),
81
+ _source_health: { source: "openalex", latency_ms: tracked.latency_ms },
81
82
  });
82
83
  },
83
84
  },
@@ -93,14 +94,15 @@ export function createOpenAlexTools(
93
94
  }),
94
95
  }),
95
96
  execute: async (input: { work_id: string }) => {
97
+ if (!input?.work_id) {
98
+ return toolResult({ error: 'work_id parameter is required (e.g., "W2741809807" or a DOI like "10.1234/example")' });
99
+ }
96
100
  const id = input.work_id.startsWith("10.")
97
101
  ? `https://doi.org/${input.work_id}`
98
102
  : input.work_id;
99
- const res = await fetch(`${BASE}/works/${encodeURIComponent(id)}`, {
100
- headers,
101
- });
102
- if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
103
- const w = await res.json();
103
+ const tracked = await trackedFetch("openalex", `${BASE}/works/${encodeURIComponent(id)}`, { headers });
104
+ if (isTrackedError(tracked)) return tracked;
105
+ const w = await tracked.res.json();
104
106
  return toolResult({
105
107
  id: w.id,
106
108
  doi: w.doi,
@@ -120,6 +122,7 @@ export function createOpenAlexTools(
120
122
  ?.slice(0, 10)
121
123
  .map((c: Record<string, unknown>) => c.display_name),
122
124
  referenced_works_count: w.referenced_works?.length,
125
+ _source_health: { source: "openalex", latency_ms: tracked.latency_ms },
123
126
  });
124
127
  },
125
128
  },
@@ -135,6 +138,9 @@ export function createOpenAlexTools(
135
138
  }),
136
139
  }),
137
140
  execute: async (input: { author_id: string }) => {
141
+ if (!input?.author_id) {
142
+ return toolResult({ error: 'author_id parameter is required (OpenAlex ID e.g. "A5023888391", ORCID, or author name)' });
143
+ }
138
144
  let url: string;
139
145
  if (
140
146
  input.author_id.startsWith("A") ||
@@ -148,14 +154,18 @@ export function createOpenAlexTools(
148
154
  search: input.author_id,
149
155
  per_page: "5",
150
156
  });
151
- const res = await fetch(`${BASE}/authors?${params}`, { headers });
152
- if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
153
- return toolResult(await res.json());
157
+ const tracked = await trackedFetch("openalex", `${BASE}/authors?${params}`, { headers });
158
+ if (isTrackedError(tracked)) return tracked;
159
+ const data = await tracked.res.json();
160
+ return toolResult({
161
+ ...data,
162
+ _source_health: { source: "openalex", latency_ms: tracked.latency_ms },
163
+ });
154
164
  }
155
165
 
156
- const res = await fetch(url, { headers });
157
- if (!res.ok) return toolResult({ error: `API error: ${res.status} ${res.statusText}` });
158
- const a = await res.json();
166
+ const tracked = await trackedFetch("openalex", url, { headers });
167
+ if (isTrackedError(tracked)) return tracked;
168
+ const a = await tracked.res.json();
159
169
  return toolResult({
160
170
  id: a.id,
161
171
  display_name: a.display_name,
@@ -171,6 +181,7 @@ export function createOpenAlexTools(
171
181
  top_concepts: a.x_concepts
172
182
  ?.slice(0, 5)
173
183
  .map((c: Record<string, unknown>) => c.display_name),
184
+ _source_health: { source: "openalex", latency_ms: tracked.latency_ms },
174
185
  });
175
186
  },
176
187
  },
@@ -0,0 +1,112 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
+ import { toolResult, trackedFetch, isTrackedError } from "./util.js";
4
+
5
+ const BASE = "https://api.opencitations.net";
6
+
7
+ /**
8
+ * Extract DOI from OpenCitations multi-identifier string.
9
+ * Format: "omid:br/... doi:10.1038/nature12373 openalex:W..."
10
+ */
11
+ function extractDoi(multiId: string): string | undefined {
12
+ const match = multiId.match(/doi:(10\.\S+)/);
13
+ return match ? match[1] : undefined;
14
+ }
15
+
16
+ export function createOpenCitationsTools(
17
+ _ctx: OpenClawPluginToolContext,
18
+ _api: OpenClawPluginApi,
19
+ ) {
20
+ return [
21
+ {
22
+ name: "get_citations_open",
23
+ label: "Get Citations (OpenCitations)",
24
+ description:
25
+ "Get all papers citing a given DOI using OpenCitations (2B+ open citation links). Works across all disciplines.",
26
+ parameters: Type.Object({
27
+ doi: Type.String({
28
+ description: "DOI of the paper, e.g. '10.1038/nature12373'",
29
+ }),
30
+ }),
31
+ execute: async (input: { doi: string }) => {
32
+ if (!input?.doi) {
33
+ return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
34
+ }
35
+ const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
36
+ const tracked = await trackedFetch("opencitations", `${BASE}/index/v2/citations/doi:${encodeURIComponent(doi)}`);
37
+ if (isTrackedError(tracked)) return tracked;
38
+ const data = await tracked.res.json();
39
+
40
+ if (!Array.isArray(data)) return toolResult({ error: "Unexpected response format" });
41
+
42
+ return toolResult({
43
+ total_citations: data.length,
44
+ citations: data.slice(0, 100).map((c: Record<string, string>) => ({
45
+ citing_doi: extractDoi(c.citing ?? ""),
46
+ cited_doi: extractDoi(c.cited ?? ""),
47
+ creation_date: c.creation,
48
+ })),
49
+ _source_health: { source: "opencitations", latency_ms: tracked.latency_ms },
50
+ });
51
+ },
52
+ },
53
+ {
54
+ name: "get_references_open",
55
+ label: "Get References (OpenCitations)",
56
+ description:
57
+ "Get all references of a paper by DOI. Shows what a paper cites.",
58
+ parameters: Type.Object({
59
+ doi: Type.String({
60
+ description: "DOI of the paper, e.g. '10.1038/nature12373'",
61
+ }),
62
+ }),
63
+ execute: async (input: { doi: string }) => {
64
+ if (!input?.doi) {
65
+ return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
66
+ }
67
+ const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
68
+ const tracked = await trackedFetch("opencitations", `${BASE}/index/v2/references/doi:${encodeURIComponent(doi)}`);
69
+ if (isTrackedError(tracked)) return tracked;
70
+ const data = await tracked.res.json();
71
+
72
+ if (!Array.isArray(data)) return toolResult({ error: "Unexpected response format" });
73
+
74
+ return toolResult({
75
+ total_references: data.length,
76
+ references: data.slice(0, 100).map((r: Record<string, string>) => ({
77
+ cited_doi: extractDoi(r.cited ?? ""),
78
+ citing_doi: extractDoi(r.citing ?? ""),
79
+ creation_date: r.creation,
80
+ })),
81
+ _source_health: { source: "opencitations", latency_ms: tracked.latency_ms },
82
+ });
83
+ },
84
+ },
85
+ {
86
+ name: "get_citation_count",
87
+ label: "Get Citation Count (OpenCitations)",
88
+ description:
89
+ "Get the total citation count for a DOI from OpenCitations open data.",
90
+ parameters: Type.Object({
91
+ doi: Type.String({
92
+ description: "DOI of the paper, e.g. '10.1038/nature12373'",
93
+ }),
94
+ }),
95
+ execute: async (input: { doi: string }) => {
96
+ if (!input?.doi) {
97
+ return toolResult({ error: 'doi parameter is required (e.g., "10.1038/nature12373")' });
98
+ }
99
+ const doi = input.doi.replace(/^https?:\/\/doi\.org\//, "");
100
+ const tracked = await trackedFetch("opencitations", `${BASE}/index/v2/citation-count/doi:${encodeURIComponent(doi)}`);
101
+ if (isTrackedError(tracked)) return tracked;
102
+ const data = await tracked.res.json();
103
+
104
+ const count = Array.isArray(data) && data[0]?.count
105
+ ? parseInt(data[0].count, 10)
106
+ : 0;
107
+
108
+ return toolResult({ doi, citation_count: count, _source_health: { source: "opencitations", latency_ms: tracked.latency_ms } });
109
+ },
110
+ },
111
+ ];
112
+ }
@@ -0,0 +1,139 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
+ import { toolResult, trackedFetch, isTrackedError } from "./util.js";
4
+
5
+ const BASE = "https://pub.orcid.org/v3.0";
6
+
7
+ const HEADERS: Record<string, string> = {
8
+ Accept: "application/json",
9
+ };
10
+
11
+ export function createOrcidTools(
12
+ _ctx: OpenClawPluginToolContext,
13
+ _api: OpenClawPluginApi,
14
+ ) {
15
+ return [
16
+ {
17
+ name: "search_orcid",
18
+ label: "Search Researchers (ORCID)",
19
+ description:
20
+ "Search the ORCID registry for researchers by name, affiliation, or keyword. Returns ORCID iDs that can be used with get_orcid_works.",
21
+ parameters: Type.Object({
22
+ query: Type.String({
23
+ description:
24
+ "Lucene-style query. Supports fields: family-name, given-names, affiliation-org-name, keyword, orcid, ringgold-org-id. E.g. 'family-name:Smith AND affiliation-org-name:MIT'",
25
+ }),
26
+ limit: Type.Optional(
27
+ Type.Number({ description: "Max results (default 10, max 100)" }),
28
+ ),
29
+ }),
30
+ execute: async (input: { query: string; limit?: number }) => {
31
+ const params = new URLSearchParams({
32
+ q: input.query,
33
+ rows: String(Math.min(input.limit ?? 10, 100)),
34
+ });
35
+
36
+ const result = await trackedFetch(
37
+ "orcid",
38
+ `${BASE}/search/?${params}`,
39
+ { headers: HEADERS },
40
+ 10_000,
41
+ );
42
+ if (isTrackedError(result)) return result;
43
+ const data = await result.res.json();
44
+
45
+ const researchers = (data.result ?? []).map(
46
+ (r: Record<string, unknown>) => {
47
+ const oi = r["orcid-identifier"] as Record<string, string> | undefined;
48
+ return {
49
+ orcid: oi?.path,
50
+ uri: oi?.uri,
51
+ source: "orcid",
52
+ };
53
+ },
54
+ );
55
+
56
+ return toolResult({
57
+ total_results: data["num-found"],
58
+ source: "orcid",
59
+ researchers,
60
+ _latency_ms: result.latency_ms,
61
+ });
62
+ },
63
+ },
64
+ {
65
+ name: "get_orcid_works",
66
+ label: "Get Works by ORCID (ORCID)",
67
+ description:
68
+ "Get the publication list for a researcher by their ORCID iD. Returns titles, DOIs, publication years, and work types.",
69
+ parameters: Type.Object({
70
+ orcid: Type.String({
71
+ description: "ORCID iD, e.g. '0000-0002-1825-0097'",
72
+ }),
73
+ }),
74
+ execute: async (input: { orcid: string }) => {
75
+ if (!input?.orcid) {
76
+ return toolResult({ error: 'orcid parameter is required (e.g., "0000-0002-1825-0097")' });
77
+ }
78
+ const orcid = input.orcid.replace(/^https?:\/\/orcid\.org\//, "");
79
+
80
+ const result = await trackedFetch(
81
+ "orcid",
82
+ `${BASE}/${encodeURIComponent(orcid)}/works`,
83
+ { headers: HEADERS },
84
+ 10_000,
85
+ );
86
+ if (isTrackedError(result)) return result;
87
+ const data = await result.res.json();
88
+
89
+ const groups = data.group as Array<Record<string, unknown>> | undefined;
90
+
91
+ const works = (groups ?? []).map((g) => {
92
+ const summaries = g["work-summary"] as Array<Record<string, unknown>> | undefined;
93
+ const first = summaries?.[0];
94
+ if (!first) return null;
95
+
96
+ // Extract title
97
+ const titleObj = first.title as Record<string, unknown> | undefined;
98
+ const title = (titleObj?.title as Record<string, string> | undefined)?.value;
99
+
100
+ // Extract publication date
101
+ const pubDate = first["publication-date"] as Record<string, unknown> | undefined;
102
+ const year = (pubDate?.year as Record<string, string> | undefined)?.value;
103
+ const month = (pubDate?.month as Record<string, string> | undefined)?.value;
104
+
105
+ // Extract DOI from external-ids
106
+ const extIds = first["external-ids"] as Record<string, unknown> | undefined;
107
+ const extIdList = extIds?.["external-id"] as Array<Record<string, unknown>> | undefined;
108
+ const doiEntry = extIdList?.find(
109
+ (e) => e["external-id-type"] === "doi" && e["external-id-relationship"] === "self",
110
+ );
111
+ const doi = doiEntry?.["external-id-value"] as string | undefined;
112
+
113
+ // Extract journal title
114
+ const journalTitle = first["journal-title"] as Record<string, string> | undefined;
115
+
116
+ return {
117
+ title,
118
+ doi,
119
+ year,
120
+ month,
121
+ type: first.type,
122
+ journal: journalTitle?.value,
123
+ url: doi ? `https://doi.org/${doi}` : undefined,
124
+ authors: undefined as string[] | undefined, // ORCID works endpoint does not include author lists
125
+ source: "orcid",
126
+ };
127
+ }).filter(Boolean);
128
+
129
+ return toolResult({
130
+ orcid,
131
+ total_works: works.length,
132
+ source: "orcid",
133
+ works,
134
+ _latency_ms: result.latency_ms,
135
+ });
136
+ },
137
+ },
138
+ ];
139
+ }
@@ -0,0 +1,104 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
3
+ import { toolResult, trackedFetch, isTrackedError } from "./util.js";
4
+
5
+ const BASE = "https://api.osf.io/v2";
6
+
7
+ export function createOsfPreprintsTools(
8
+ _ctx: OpenClawPluginToolContext,
9
+ _api: OpenClawPluginApi,
10
+ ) {
11
+ return [
12
+ {
13
+ name: "search_osf_preprints",
14
+ label: "Search Preprints (OSF)",
15
+ description:
16
+ "Search OSF Preprints, an aggregator covering 180K+ preprints across multiple providers including SocArXiv, PsyArXiv, EarthArXiv, EngrXiv, and NutriXiv. Covers social sciences, psychology, earth sciences, engineering, and more.",
17
+ parameters: Type.Object({
18
+ provider: Type.Optional(
19
+ Type.String({
20
+ description:
21
+ "Preprint provider filter: 'osf', 'socarxiv', 'psyarxiv', 'engrxiv', 'eartharxiv', 'nutrixiv'. Omit for all providers.",
22
+ }),
23
+ ),
24
+ size: Type.Optional(
25
+ Type.Number({ description: "Max results (default 10, max 100)" }),
26
+ ),
27
+ page: Type.Optional(
28
+ Type.Number({ description: "Page number (default 1)" }),
29
+ ),
30
+ }),
31
+ execute: async (input: {
32
+ provider?: string;
33
+ size?: number;
34
+ page?: number;
35
+ }) => {
36
+ const params = new URLSearchParams({
37
+ "page[size]": String(Math.min(input.size ?? 10, 100)),
38
+ });
39
+ if (input.provider) params.set("filter[provider]", input.provider);
40
+ if (input.page) params.set("page", String(input.page));
41
+
42
+ const result = await trackedFetch(
43
+ "osf_preprints",
44
+ `${BASE}/preprints/?${params}`,
45
+ {
46
+ headers: {
47
+ Accept: "application/vnd.api+json",
48
+ },
49
+ },
50
+ 20_000,
51
+ );
52
+ if (isTrackedError(result)) return result;
53
+ const data = await result.res.json();
54
+
55
+ const items = data.data as Array<Record<string, unknown>> | undefined;
56
+ const linksMeta = (data.links as Record<string, unknown>)?.meta as Record<string, unknown> | undefined;
57
+
58
+ return toolResult({
59
+ total_results: linksMeta?.total,
60
+ page: input.page ?? 1,
61
+ source: "osf_preprints",
62
+ preprints: (items ?? []).map((item) => {
63
+ const attrs = item.attributes as Record<string, unknown> | undefined;
64
+ const relationships = item.relationships as Record<string, unknown> | undefined;
65
+ const providerData = (
66
+ relationships?.provider as Record<string, unknown> | undefined
67
+ )?.data as Record<string, string> | undefined;
68
+ const subjects = attrs?.subjects as Array<Array<{ id: string; text: string }>> | undefined;
69
+
70
+ // Flatten nested subject arrays
71
+ const subjectNames = subjects
72
+ ?.flat()
73
+ .map((s) => s.text)
74
+ .filter(Boolean);
75
+
76
+ return {
77
+ id: item.id,
78
+ title: attrs?.title,
79
+ description: attrs?.description
80
+ ? String(attrs.description).slice(0, 500)
81
+ : undefined,
82
+ doi: attrs?.doi ?? undefined,
83
+ date_created: attrs?.date_created,
84
+ date_published: attrs?.date_published,
85
+ year: attrs?.date_published
86
+ ? String(attrs.date_published).slice(0, 4)
87
+ : attrs?.date_created
88
+ ? String(attrs.date_created).slice(0, 4)
89
+ : undefined,
90
+ provider: providerData?.id,
91
+ tags: attrs?.tags,
92
+ subjects: subjectNames,
93
+ is_published: attrs?.is_published,
94
+ authors: undefined as string[] | undefined, // OSF preprint list does not embed contributor names
95
+ url: `https://osf.io/preprints/${providerData?.id ?? "osf"}/${String(item.id).replace(/_v\d+$/, "")}`,
96
+ source: "osf_preprints",
97
+ };
98
+ }),
99
+ _latency_ms: result.latency_ms,
100
+ });
101
+ },
102
+ },
103
+ ];
104
+ }