release: v5.4.2 - terminal模拟器 + Operit桥接

新增插件:
- terminal: 持久交互式终端,纯标准库,全平台,13个测试
- operit_bridge: Operit AI工具格式桥接,47个脚本288个工具,12个测试

其他:
- plugins/operit/: 47个Operit官方脚本
- 更新plugin_registry.json/plugins_config.py
- 更新about.txt/CHANGELOG.md至v5.4.2
This commit is contained in:
xiaoli-cli bot committed 2026-06-23 08:50:15 +00:00
1 parent b8faaae0c6
commit dfe21298b9
56 files changed
+32586 -1

No files matched your search

+607
View File
@@ -0,0 +1,607 @@
/* METADATA
{
"name": "crossref",
"display_name": {
"zh": "Crossref 学术文献查询",
"en": "Crossref Academic Literature Search"
},
"description": {
"zh": "Crossref 学术文献查询工具,提供 DOI 查询、关键词搜索、作者搜索等功能,帮助用户查找和获取学术文章元数据。",
"en": "Crossref scholarly literature search tools: query by DOI, keyword, author, title, ISSN, and retrieve publication metadata."
},
"category": "Search",
"enabledByDefault": true,
"tools": [
{
"name": "search_by_doi",
"description": { "zh": "通过 DOI (Digital Object Identifier) 查询文章的详细信息", "en": "Query article details by DOI (Digital Object Identifier)." },
"parameters": [
{
"name": "doi",
"description": { "zh": "文章的 DOI 标识符,例如 '10.1038/nature12373'", "en": "DOI identifier, e.g. '10.1038/nature12373'" },
"type": "string",
"required": true
}
]
},
{
"name": "search_by_keyword",
"description": { "zh": "通过关键词搜索学术文章", "en": "Search scholarly articles by keyword." },
"parameters": [
{
"name": "query",
"description": { "zh": "搜索关键词", "en": "Search query keyword(s)" },
"type": "string",
"required": true
},
{
"name": "rows",
"description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" },
"type": "number",
"required": false
},
{
"name": "sort",
"description": { "zh": "排序方式,可选值:'relevance'(相关性), 'score'(评分), 'updated'(更新时间), 'deposited'(提交时间), 'indexed'(索引时间), 'published'(发布时间)", "en": "Sort mode. Options: 'relevance', 'score', 'updated', 'deposited', 'indexed', 'published'." },
"type": "string",
"required": false
},
{
"name": "order",
"description": { "zh": "排序顺序,可选值:'asc'(升序), 'desc'(降序),默认 'desc'", "en": "Sort order: 'asc' or 'desc' (default: 'desc')." },
"type": "string",
"required": false
}
]
},
{
"name": "search_by_author",
"description": { "zh": "通过作者名字搜索文章", "en": "Search articles by author name." },
"parameters": [
{
"name": "author",
"description": { "zh": "作者名字", "en": "Author name" },
"type": "string",
"required": true
},
{
"name": "rows",
"description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" },
"type": "number",
"required": false
}
]
},
{
"name": "search_by_title",
"description": { "zh": "通过文章标题搜索", "en": "Search articles by title." },
"parameters": [
{
"name": "title",
"description": { "zh": "文章标题或标题关键词", "en": "Article title or title keyword(s)" },
"type": "string",
"required": true
},
{
"name": "rows",
"description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" },
"type": "number",
"required": false
}
]
},
{
"name": "search_by_issn",
"description": { "zh": "通过期刊 ISSN 查询该期刊发表的文章", "en": "Search articles published in a journal by ISSN." },
"parameters": [
{
"name": "issn",
"description": { "zh": "期刊的 ISSN 标识符,例如 '1476-4687'", "en": "Journal ISSN identifier, e.g. '1476-4687'" },
"type": "string",
"required": true
},
{
"name": "rows",
"description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" },
"type": "number",
"required": false
}
]
}
]
}*/
const CrossrefSearch = (function () {
const BASE_URL = "https://api.crossref.org";
const DEFAULT_ROWS = 10;
const MAX_ROWS = 100;
function buildQueryString(params: Record<string, string>): string {
return Object.entries(params)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join("&");
}
type SearchByDoiParams = {
doi: string;
};
type SearchByKeywordParams = {
query: string;
rows?: number;
sort?: string;
order?: string;
};
type SearchByAuthorParams = {
author: string;
rows?: number;
};
type SearchByTitleParams = {
title: string;
rows?: number;
};
type SearchByISSNParams = {
issn: string;
rows?: number;
};
/**
* 格式化作者信息
*/
function formatAuthors(authors: any[]): string {
if (!authors || authors.length === 0) return "N/A";
return authors
.slice(0, 5) // 只显示前5个作者
.map((author: any) => {
const given = author.given || "";
const family = author.family || "";
return `${given} ${family}`.trim();
})
.filter(name => name.length > 0)
.join(", ");
}
/**
* 格式化日期
*/
function formatDate(dateParts: number[][]): string {
if (!dateParts || dateParts.length === 0 || !dateParts[0]) return "N/A";
const parts = dateParts[0];
if (parts.length === 1) return `${parts[0]}`;
if (parts.length === 2) return `${parts[0]}-${String(parts[1]).padStart(2, '0')}`;
if (parts.length === 3) return `${parts[0]}-${String(parts[1]).padStart(2, '0')}-${String(parts[2]).padStart(2, '0')}`;
return "N/A";
}
/**
* 格式化单篇文章信息
*/
function formatArticle(item: any, index?: number): string {
const lines: string[] = [];
if (index !== undefined) {
lines.push(`\n=== Article ${index + 1} ===`);
}
// 标题
const title = item.title && item.title.length > 0 ? item.title[0] : "No Title";
lines.push(`Title: ${title}`);
// DOI
if (item.DOI) {
lines.push(`DOI: ${item.DOI}`);
lines.push(`URL: https://doi.org/${item.DOI}`);
}
// 作者
const authors = formatAuthors(item.author);
lines.push(`Authors: ${authors}`);
// 发表日期
const publishedDate = formatDate(item.published?.['date-parts'] || item['published-print']?.['date-parts'] || item['published-online']?.['date-parts']);
lines.push(`Published: ${publishedDate}`);
// 期刊/会议
if (item['container-title'] && item['container-title'].length > 0) {
lines.push(`Journal/Conference: ${item['container-title'][0]}`);
}
// ISSN
if (item.ISSN && item.ISSN.length > 0) {
lines.push(`ISSN: ${item.ISSN.join(', ')}`);
}
// 出版商
if (item.publisher) {
lines.push(`Publisher: ${item.publisher}`);
}
// 类型
if (item.type) {
lines.push(`Type: ${item.type}`);
}
// 引用次数
if (item['is-referenced-by-count'] !== undefined) {
lines.push(`Citations: ${item['is-referenced-by-count']}`);
}
// 摘要(如果有)
if (item.abstract) {
// 移除 HTML 标签
const abstractText = item.abstract.replace(/<[^>]*>/g, '');
lines.push(`Abstract: ${abstractText.substring(0, 500)}${abstractText.length > 500 ? '...' : ''}`);
}
return lines.join('\n');
}
/**
* 通过 DOI 查询文章
*/
async function searchByDoi(params: SearchByDoiParams): Promise<any> {
const { doi } = params;
if (!doi || doi.trim() === "") {
return {
success: false,
message: "请提供有效的 DOI"
};
}
try {
const url = `${BASE_URL}/works/${encodeURIComponent(doi)}`;
const client = OkHttp.newClient();
const response = await client.get(url, {
'User-Agent': 'Operit/1.0 (mailto:support@example.com)'
});
if (!response.isSuccessful()) {
return {
success: false,
message: `查询失败: HTTP ${response.statusCode} - ${response.statusMessage}`
};
}
const data = response.json();
if (data.status === "ok" && data.message) {
const article = formatArticle(data.message);
return {
success: true,
message: "查询成功",
data: article
};
} else {
return {
success: false,
message: "未找到该 DOI 对应的文章"
};
}
} catch (error: any) {
return {
success: false,
message: `查询失败: ${error.message}`
};
}
}
/**
* 通过关键词搜索文章
*/
async function searchByKeyword(params: SearchByKeywordParams): Promise<any> {
const { query, rows = DEFAULT_ROWS, sort = "relevance", order = "desc" } = params;
if (!query || query.trim() === "") {
return {
success: false,
message: "请提供有效的搜索关键词"
};
}
const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS);
try {
const queryString = buildQueryString({
query: query,
rows: String(actualRows),
sort: sort,
order: order
});
const url = `${BASE_URL}/works?${queryString}`;
const client = OkHttp.newClient();
const response = await client.get(url, {
'User-Agent': 'Operit/1.0 (mailto:support@example.com)'
});
if (!response.isSuccessful()) {
return {
success: false,
message: `搜索失败: HTTP ${response.statusCode} - ${response.statusMessage}`
};
}
const data = response.json();
if (data.status === "ok" && data.message && data.message.items) {
const items = data.message.items;
const totalResults = data.message['total-results'];
const results = items.map((item: any, index: number) => formatArticle(item, index));
const summary = `Found ${totalResults} results (showing ${items.length}):\n${results.join('\n\n')}`;
return {
success: true,
message: "搜索成功",
data: summary,
total: totalResults,
count: items.length
};
} else {
return {
success: false,
message: "未找到相关文章"
};
}
} catch (error: any) {
return {
success: false,
message: `搜索失败: ${error.message}`
};
}
}
/**
* 通过作者搜索文章
*/
async function searchByAuthor(params: SearchByAuthorParams): Promise<any> {
const { author, rows = DEFAULT_ROWS } = params;
if (!author || author.trim() === "") {
return {
success: false,
message: "请提供有效的作者名字"
};
}
const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS);
try {
const queryString = buildQueryString({
'query.author': author,
rows: String(actualRows)
});
const url = `${BASE_URL}/works?${queryString}`;
const client = OkHttp.newClient();
const response = await client.get(url, {
'User-Agent': 'Operit/1.0 (mailto:support@example.com)'
});
if (!response.isSuccessful()) {
return {
success: false,
message: `搜索失败: HTTP ${response.statusCode} - ${response.statusMessage}`
};
}
const data = response.json();
if (data.status === "ok" && data.message && data.message.items) {
const items = data.message.items;
const totalResults = data.message['total-results'];
const results = items.map((item: any, index: number) => formatArticle(item, index));
const summary = `Found ${totalResults} results for author "${author}" (showing ${items.length}):\n${results.join('\n\n')}`;
return {
success: true,
message: "搜索成功",
data: summary,
total: totalResults,
count: items.length
};
} else {
return {
success: false,
message: `未找到该作者 "${author}" 的文章`
};
}
} catch (error: any) {
return {
success: false,
message: `搜索失败: ${error.message}`
};
}
}
/**
* 通过标题搜索文章
*/
async function searchByTitle(params: SearchByTitleParams): Promise<any> {
const { title, rows = DEFAULT_ROWS } = params;
if (!title || title.trim() === "") {
return {
success: false,
message: "请提供有效的文章标题"
};
}
const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS);
try {
const queryString = buildQueryString({
'query.title': title,
rows: String(actualRows)
});
const url = `${BASE_URL}/works?${queryString}`;
const client = OkHttp.newClient();
const response = await client.get(url, {
'User-Agent': 'Operit/1.0 (mailto:support@example.com)'
});
const data = response.json();
if (data.status === "ok" && data.message && data.message.items) {
const items = data.message.items;
const totalResults = data.message['total-results'];
const results = items.map((item: any, index: number) => formatArticle(item, index));
const summary = `Found ${totalResults} results matching title "${title}" (showing ${items.length}):\n${results.join('\n\n')}`;
return {
success: true,
message: "搜索成功",
data: summary,
total: totalResults,
count: items.length
};
} else {
return {
success: false,
message: `未找到标题包含 "${title}" 的文章`
};
}
} catch (error: any) {
return {
success: false,
message: `搜索失败: ${error.message}`
};
}
}
/**
* 通过 ISSN 查询期刊文章
*/
async function searchByISSN(params: SearchByISSNParams): Promise<any> {
const { issn, rows = DEFAULT_ROWS } = params;
if (!issn || issn.trim() === "") {
return {
success: false,
message: "请提供有效的 ISSN"
};
}
const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS);
try {
const url = `${BASE_URL}/journals/${encodeURIComponent(issn)}/works?rows=${actualRows}`;
const client = OkHttp.newClient();
const response = await client.get(url, {
'User-Agent': 'Operit/1.0 (mailto:support@example.com)'
});
if (!response.isSuccessful()) {
return {
success: false,
message: `查询失败: HTTP ${response.statusCode} - ${response.statusMessage}`
};
}
const data = response.json();
if (data.status === "ok" && data.message && data.message.items) {
const items = data.message.items;
const totalResults = data.message['total-results'];
const results = items.map((item: any, index: number) => formatArticle(item, index));
const summary = `Found ${totalResults} articles from journal ISSN ${issn} (showing ${items.length}):\n${results.join('\n\n')}`;
return {
success: true,
message: "查询成功",
data: summary,
total: totalResults,
count: items.length
};
} else {
return {
success: false,
message: `未找到 ISSN "${issn}" 对应期刊的文章`
};
}
} catch (error: any) {
return {
success: false,
message: `查询失败: ${error.message}`
};
}
}
/**
* 包装函数,统一处理错误
*/
async function wrapToolExecution(func: (params: any) => Promise<any>, params: any) {
try {
const result = await func(params);
complete(result);
} catch (error: any) {
console.error(`工具执行失败`, error);
complete({
success: false,
message: `工具执行时发生意外错误: ${error.message}`,
});
}
}
/**
* 测试函数
*/
async function main() {
console.log("=== Crossref API 测试 ===\n");
// 测试 1: 通过 DOI 查询
console.log("1. 测试通过 DOI 查询...");
const doiResult = await searchByDoi({ doi: "10.1038/nature12373" });
console.log(JSON.stringify(doiResult, null, 2));
console.log("\n");
// 测试 2: 通过关键词搜索
console.log("2. 测试通过关键词搜索...");
const keywordResult = await searchByKeyword({ query: "machine learning", rows: 3 });
console.log(JSON.stringify(keywordResult, null, 2));
console.log("\n");
// 测试 3: 通过作者搜索
console.log("3. 测试通过作者搜索...");
const authorResult = await searchByAuthor({ author: "John Smith", rows: 3 });
console.log(JSON.stringify(authorResult, null, 2));
console.log("\n");
// 测试 4: 通过标题搜索
console.log("4. 测试通过标题搜索...");
const titleResult = await searchByTitle({ title: "neural networks", rows: 3 });
console.log(JSON.stringify(titleResult, null, 2));
console.log("\n");
console.log("=== 测试完成 ===");
}
return {
search_by_doi: (params: any) => wrapToolExecution(searchByDoi, params),
search_by_keyword: (params: any) => wrapToolExecution(searchByKeyword, params),
search_by_author: (params: any) => wrapToolExecution(searchByAuthor, params),
search_by_title: (params: any) => wrapToolExecution(searchByTitle, params),
search_by_issn: (params: any) => wrapToolExecution(searchByISSN, params),
main,
};
})();
// 导出工具函数
exports.search_by_doi = CrossrefSearch.search_by_doi;
exports.search_by_keyword = CrossrefSearch.search_by_keyword;
exports.search_by_author = CrossrefSearch.search_by_author;
exports.search_by_title = CrossrefSearch.search_by_title;
exports.search_by_issn = CrossrefSearch.search_by_issn;
exports.main = CrossrefSearch.main;