fix(batch-install-plugins): support CRLF community plugin metadata

- support CRLF docstrings and folded YAML metadata blocks
- detect community repo plugins such as iChristGit/OpenWebui-Tools correctly
- align README, mirrored docs, and announcement wording with actual behavior

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
fujie
2026-03-15 18:14:23 +08:00
parent cea31fed38
commit c818a2ac8d
8 changed files with 725 additions and 178 deletions

View File

@@ -0,0 +1,132 @@
# 🎉 Introducing Batch Install Plugins v1.0.0
## Headline
**One-Click Batch Installation of OpenWebUI Plugins - Solving the Plugin Setup Headache**
## Introduction
Installing plugins in OpenWebUI used to be tedious: searching for plugins, downloading them one by one, and hoping everything works in your environment. Today, we're excited to announce **Batch Install Plugins from GitHub** v1.0.0 — a powerful new tool that transforms plugin installation from a chore into a single command.
## Key Highlights
### 🚀 One-Click Bulk Installation
- Install multiple plugins from any public GitHub repository with a single command
- Automatically discovers plugins and validates them
- Updates previously installed plugins seamlessly
### ✅ Smart Safety Features
- Shows a confirmation dialog with the plugin list before installation
- Users can review and approve before proceeding
- Automatically excludes the tool itself from installation
### 🌍 Multi-Repository Support
Install plugins from **any public GitHub repository**, including your own community collections:
- Use one request per repository, then call the tool again to combine multiple sources
- **Default**: Fu-Jie/openwebui-extensions (my personal collection)
- Works with public repositories in `owner/repo` format
- Mix and match plugins: install from my collection first, then add community collections in subsequent calls
### 🔧 Container-Friendly
- Automatically handles port mapping issues in containerized deployments
- Smart fallback: retries with localhost:8080 if the primary connection fails
- Rich debugging logs for troubleshooting
### 🌐 Global Support
- Complete i18n support for 11 languages
- All error messages localized and user-friendly
- Works seamlessly across different deployment scenarios
## How It Works: Interactive Installation Workflow
Each request handles one repository. To combine multiple repositories, send another request after the previous installation completes.
1. **Start with My Collection**
```
"Install all plugins from Fu-Jie/openwebui-extensions"
```
Review the confirmation dialog, approve, and the plugins are installed.
2. **Add a Community Collection**
```
"Install all plugins from iChristGit/OpenWebui-Tools"
```
Add more plugins from a different repository. Already installed plugins are updated seamlessly.
3. **Install a Specific Type**
```
"Install only pipe plugins from Haervwe/open-webui-tools"
```
Pick specific plugin types from another repository, or exclude certain keywords.
4. **Use Your Own Public Repository**
```
"Install all plugins from your-username/your-collection"
```
Works with any public GitHub repository in `owner/repo` format.
## Popular Community Collections
Ready-to-install from these community favorites:
#### **iChristGit/OpenWebui-Tools**
Comprehensive tools and plugins for various use cases.
#### **Haervwe/open-webui-tools**
Specialized utilities for extending OpenWebUI functionality.
#### **Classic298/open-webui-plugins**
Diverse plugin implementations for different scenarios.
#### **suurt8ll/open_webui_functions**
Function-based plugins for custom integrations.
#### **rbb-dev/Open-WebUI-OpenRouter-pipe**
OpenRouter API pipe integration for advanced model access.
## Usage Examples
Each line below is a separate request:
```
# Start with my collection
"Install all plugins"
# Add community plugins in a new request
"Install all plugins from iChristGit/OpenWebui-Tools"
# Add only one plugin type from another repository
"Install only tool plugins from Haervwe/open-webui-tools"
# Continue building your setup
"Install only action plugins from Classic298/open-webui-plugins"
# Filter out unwanted plugins
"Install all plugins from Haervwe/open-webui-tools, exclude_keywords=test,deprecated"
# Install from your own public repository
"Install all plugins from your-username/my-plugin-collection"
```
## Technical Excellence
- **Async Architecture**: Non-blocking I/O for better performance
- **httpx Integration**: Modern async HTTP client with timeout protection
- **Comprehensive Tests**: 8 regression tests with 100% pass rate
- **Full Event Support**: Proper OpenWebUI event injection with fallback handling
## Installation
1. Open OpenWebUI → Workspace > Tools
2. Install **Batch Install Plugins from GitHub** from the marketplace
3. Enable it for your model/chat
4. Start using it with commands like "Install all plugins"
## Links
- **GitHub Repository**: https://github.com/Fu-Jie/openwebui-extensions/tree/main/plugins/tools/batch-install-plugins
- **Release Notes**: https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/tools/batch-install-plugins/v1.0.0.md
## Community Love
If this tool has been helpful to you, please give us a ⭐ on [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) — it truly motivates us to keep improving!
**Thank you for supporting the OpenWebUI community! 🙏**

View File

@@ -0,0 +1,132 @@
# 🎉 Batch Install Plugins 首发 v1.0.0
## 标题
**一键批量安装 OpenWebUI 插件 - 解决装机烦恼**
## 前言
在 OpenWebUI 中安装插件曾经很麻烦:逐个搜索、逐个下载、祈祷一切顺利。今天,我们欣然宣布 **Batch Install Plugins from GitHub** v1.0.0 的问世 — 一款强大的新工具,让插件安装从苦差事变成一条简单命令。
## 核心特性
### 🚀 一键批量安装
- 从任意公开 GitHub 仓库用一条命令安装多个插件
- 自动发现插件并进行验证
- 无缝更新已安装的插件
### ✅ 智能安全保障
- 安装前显示插件列表确认对话框
- 用户可在安装前查看和审批
- 自动排除工具自身,避免重复安装
### 🌍 多仓库支持
支持从**任意公开 GitHub 仓库**安装插件,包括你自己的社区合集:
- 每次请求处理一个仓库,需要时可再次调用工具来组合多个来源
- **默认**Fu-Jie/openwebui-extensions我的个人合集
- 支持公开仓库,格式为 `owner/repo`
- 混合搭配:先从我的合集安装,再通过后续调用添加社区合集
### 🔧 容器友好
- 自动处理容器部署中的端口映射问题
- 智能降级:主连接失败时自动重试 localhost:8080
- 丰富的调试日志便于故障排除
### 🌐 全球化支持
- 完整支持 11 种语言
- 所有错误提示本地化且用户友好
- 跨不同部署场景无缝运行
## 工作流程:交互式安装
每次请求处理一个仓库。如需组合多个仓库,请在上一次安装完成后再发起下一次请求。
1. **先从我的合集开始**
```
"安装 Fu-Jie/openwebui-extensions 中的所有插件"
```
查看确认对话框,批准后开始安装。
2. **再添加社区合集**
```
"从 iChristGit/OpenWebui-Tools 安装所有插件"
```
从不同仓库添加更多插件。已安装的插件会无缝更新。
3. **按类型继续安装**
```
"从 Haervwe/open-webui-tools 仅安装 pipe 插件"
```
从另一个仓库选择特定类型的插件,或排除某些关键词。
4. **使用你自己的公开仓库**
```
"从 your-username/your-collection 安装所有插件"
```
支持任何公开 GitHub 仓库,格式为 `owner/repo`。
## 热门社区合集
这些社区精选都已准备好安装:
#### **iChristGit/OpenWebui-Tools**
包含各种工具和插件的综合集合。
#### **Haervwe/open-webui-tools**
专业工具和实用程序,扩展 OpenWebUI 功能。
#### **Classic298/open-webui-plugins**
多样化的插件实现,满足不同场景。
#### **suurt8ll/open_webui_functions**
基于函数的插件,用于自定义集成。
#### **rbb-dev/Open-WebUI-OpenRouter-pipe**
OpenRouter API pipe 集成,提供高级模型访问。
## 使用示例
下面每一行都是一次独立请求:
```
# 先从我的合集开始
"安装所有插件"
# 在下一次请求中加入社区插件
"从 iChristGit/OpenWebui-Tools 安装所有插件"
# 从其他仓库只安装某一种类型
"从 Haervwe/open-webui-tools 仅安装 tool 插件"
# 继续补充你的插件组合
"从 Classic298/open-webui-plugins 安装仅 action 插件"
# 过滤不想安装的插件
"从 Haervwe/open-webui-tools 安装所有插件exclude_keywords=test,deprecated"
# 从你自己的公开仓库安装
"从 your-username/my-plugin-collection 安装所有插件"
```
## 技术亮点
- **异步架构**:非阻塞 I/O性能更优
- **httpx 集成**:现代化异步 HTTP 客户端,包含超时保护
- **完整测试**8 个回归测试100% 通过率
- **完整事件支持**:正确处理 OpenWebUI 事件注入,提供回退机制
## 安装方法
1. 打开 OpenWebUI → 工作区 > 工具
2. 从市场安装 **Batch Install Plugins from GitHub**
3. 为你的模型/对话启用此工具
4. 开始使用,比如说"安装所有插件"
## 相关链接
- **GitHub 仓库**https://github.com/Fu-Jie/openwebui-extensions/tree/main/plugins/tools/batch-install-plugins
- **发布说明**https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/tools/batch-install-plugins/v1.0.0_CN.md
## 社区支持
如果这个工具对你有帮助,欢迎到 [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) 点个 ⭐ — 这将是我们持续改进的动力!
**感谢你支持 OpenWebUI 社区!🙏**

View File

@@ -8,7 +8,7 @@ One-click batch install plugins from GitHub repositories to your OpenWebUI insta
- **One-Click Install**: Install all plugins with a single command
- **Auto-Update**: Automatically updates previously installed plugins
- **GitHub Support**: Install plugins from any GitHub repository
- **Public GitHub Support**: Install plugins from any public GitHub repository
- **Multi-Type Support**: Supports Pipe, Action, Filter, and Tool plugins
- **Confirmation**: Shows plugin list before installing, allows selective installation
- **i18n**: Supports 11 languages
@@ -51,68 +51,91 @@ User Input
## How to Use
1. Open OpenWebUI and go to **Workspace > Tools**
2. Install **Batch Install Plugins from GitHub** from the official marketplace
2. Install **Batch Install Plugins from GitHub** from the marketplace
3. Enable this tool for your model/chat
4. Ask the model to install plugins
## Interactive Installation Workflow
Each request handles one repository. To mix repositories, send another request after the previous installation completes.
### Example Installation Sequence
1. **Start with My Collection**
```
"Install all plugins from Fu-Jie/openwebui-extensions"
```
Review the confirmation dialog, approve, and the plugins are installed.
2. **Add a Community Collection**
```
"Install all plugins from iChristGit/OpenWebui-Tools"
```
Add more plugins from a different repository. Already installed plugins are updated seamlessly.
3. **Install a Specific Type**
```
"Install only pipe plugins from Haervwe/open-webui-tools"
```
Pick specific plugin types from another repository, or exclude certain keywords.
4. **Use Your Own Repository**
```
"Install all plugins from your-username/your-collection"
```
Works with any public GitHub repository in `owner/repo` format.
## Usage Examples
Each line below is a separate request:
```
# Install from my default collection
"Install all plugins"
"Install all plugins from github.com/username/repo"
"Install only pipe plugins"
"Install action and filter plugins"
"Install all plugins, exclude_keywords=copilot"
```
## Popular Plugin Repositories
Here are some popular repositories with many plugins you can install:
### Community Collections
```
# Install all plugins from iChristGit's collection
# Add another repository in a new request
"Install all plugins from iChristGit/OpenWebui-Tools"
# Install all tools from Haervwe's tools collection
"Install all plugins from Haervwe/open-webui-tools"
# Add only tools from a different repository
"Install only tool plugins from Haervwe/open-webui-tools"
# Install all plugins from Classic298's repository
"Install all plugins from Classic298/open-webui-plugins"
# Continue building your setup with another request
"Install only action plugins from Classic298/open-webui-plugins"
# Install all functions from suurt8ll's collection
"Install all plugins from suurt8ll/open_webui_functions"
# Install only specific types (e.g., only tools)
"Install only tool plugins from iChristGit/OpenWebui-Tools"
# Exclude certain keywords while installing
# Filter out unwanted plugins
"Install all plugins from Haervwe/open-webui-tools, exclude_keywords=test,deprecated"
# Install from your own public repository
"Install all plugins from your-username/my-plugin-collection"
```
### Supported Repositories
## Popular Public Repositories
- `Fu-Jie/openwebui-extensions` - Default, official plugin collection
- `iChristGit/OpenWebui-Tools` - Comprehensive tool and plugin collection
- `Haervwe/open-webui-tools` - Specialized tools and utilities
- `Classic298/open-webui-plugins` - Various plugin implementations
The tool works with any public GitHub repository in `owner/repo` format. Popular starting points include:
- `Fu-Jie/openwebui-extensions` - My personal collection and the default source
- `iChristGit/OpenWebui-Tools` - Comprehensive tools and plugins
- `Haervwe/open-webui-tools` - Utility-focused extensions
- `Classic298/open-webui-plugins` - Mixed community plugins
- `suurt8ll/open_webui_functions` - Function-based plugins
- `rbb-dev/Open-WebUI-OpenRouter-pipe` - OpenRouter pipe integration
To combine repositories, run the tool again with a different `repo` after the previous installation completes.
## Default Repository
When no repository is specified, defaults to `Fu-Jie/openwebui-extensions`.
When no repository is specified, the tool uses `Fu-Jie/openwebui-extensions` (my personal collection).
## Plugin Detection Rules
### Fu-Jie/openwebui-extensions (Strict)
For the default repository, plugins must have:
For the default repository, the tool applies stricter filtering:
1. A `.py` file containing `class Tools:`, `class Filter:`, `class Pipe:`, or `class Action:`
2. A docstring with `title:`, `description:`, and **`openwebui_id:`** fields
2. A docstring with `title:`, `description:`, and **`openwebui_id:`** metadata
3. Filename must not end with `_cn`
### Other GitHub Repositories
### Other Public GitHub Repositories
For other repositories:
1. A `.py` file containing `class Tools:`, `class Filter:`, `class Pipe:`, or `class Action:`

View File

@@ -8,7 +8,7 @@
- 一键安装:单个命令安装所有插件
- 自动更新:自动更新之前安装过的插件
- GitHub 支持:从任意 GitHub 仓库安装插件
- 公开 GitHub 支持:支持从任何公开 GitHub 仓库安装插件
- 多类型支持:支持 Pipe、Action、Filter 和 Tool 插件
- 安装确认:安装前显示插件列表,支持选择性安装
- 国际化:支持 11 种语言
@@ -51,68 +51,91 @@
## 使用方法
1. 打开 OpenWebUI进入 **Workspace > Tools**
2.官方市场安装 **Batch Install Plugins from GitHub**
2. 从市场安装 **Batch Install Plugins from GitHub**
3. 为你的模型/对话启用此工具
4. 让模型调用工具方法
4. 让模型调用工具来安装插件
## 交互式安装工作流
每次请求处理一个仓库。如需混合多个来源,请在上一次安装完成后再发起下一次请求。
### 安装序列示例
1. **先从我的合集开始**
```
"安装 Fu-Jie/openwebui-extensions 中的所有插件"
```
查看确认对话框,批准后插件开始安装。
2. **再添加社区合集**
```
"从 iChristGit/OpenWebui-Tools 安装所有插件"
```
从不同仓库添加更多插件。已安装的插件会无缝更新。
3. **按类型继续安装**
```
"从 Haervwe/open-webui-tools 仅安装 pipe 插件"
```
从另一个仓库选择特定类型的插件,或排除某些关键词。
4. **使用你自己的仓库**
```
"从 your-username/your-collection 安装所有插件"
```
支持任何公开的 GitHub 仓库,格式为 `owner/repo`。
## 使用示例
下面每一行都是一次独立请求:
```
# 从默认合集安装
"安装所有插件"
"从 github.com/username/repo 安装所有插件"
"只安装 pipe 插件"
"安装 action 和 filter 插件"
"安装所有插件, exclude_keywords=copilot"
```
## 热门插件仓库
这些是包含大量插件的热门仓库,你可以从中安装插件:
### 社区合集
```
# 从 iChristGit 的集合安装所有插件
# 在下一次请求中加入其他仓库
"从 iChristGit/OpenWebui-Tools 安装所有插件"
# 从 Haervwe 的工具集合只安装工具
"从 Haervwe/open-webui-tools 安装所有插件"
# 从其他仓库只安装工具
"从 Haervwe/open-webui-tools 安装 tool 插件"
# 从 Classic298 的仓库安装所有插件
"从 Classic298/open-webui-plugins 安装所有插件"
# 再继续补充另一类插件
"从 Classic298/open-webui-plugins 安装仅 action 插件"
# 从 suurt8ll 的集合安装所有函数
"从 suurt8ll/open_webui_functions 安装所有插件"
# 只安装特定类型的插件(比如只安装工具)
"从 iChristGit/OpenWebui-Tools 只安装 tool 插件"
# 安装时排除特定关键词
# 过滤不想安装的插件
"从 Haervwe/open-webui-tools 安装所有插件, exclude_keywords=test,deprecated"
# 从你自己的公开仓库安装
"从 your-username/my-plugin-collection 安装所有插件"
```
### 支持的仓库
## 热门公开仓库
- `Fu-Jie/openwebui-extensions` - 默认的官方插件集合
该工具支持任何公开 GitHub 仓库,格式为 `owner/repo`。这些都是不错的起点:
- `Fu-Jie/openwebui-extensions` - 我的个人合集,也是默认来源
- `iChristGit/OpenWebui-Tools` - 全面的工具和插件集合
- `Haervwe/open-webui-tools` - 专业的工具和实用程序
- `Classic298/open-webui-plugins` - 各种插件实现
- `suurt8ll/open_webui_functions` - 基于函数的插件
- `Haervwe/open-webui-tools` - 偏工具型的扩展集合
- `Classic298/open-webui-plugins` - 混合型社区插件集合
- `suurt8ll/open_webui_functions` - 基于函数的插件集合
- `rbb-dev/Open-WebUI-OpenRouter-pipe` - OpenRouter pipe 集成
如需混合多个来源,请在上一次安装完成后,换一个 `repo` 再调用一次工具。
## 默认仓库
未指定仓库时,默认为 `Fu-Jie/openwebui-extensions`
未指定仓库时,工具会使用 `Fu-Jie/openwebui-extensions`(我的个人合集)
## 插件检测规则
### Fu-Jie/openwebui-extensions严格模式
默认仓库的插件必须满足
对于默认仓库,工具会采用更严格的筛选规则
1. 包含 `class Tools:`、`class Filter:`、`class Pipe:` 或 `class Action:` 的 `.py` 文件
2. Docstring 中包含 `title:``description:`**`openwebui_id:`** 字段
2. Docstring 中包含 `title:`、`description:` 和 **`openwebui_id:`** 元数据
3. 文件名不能以 `_cn` 结尾
### 其他 GitHub 仓库
### 其他公开 GitHub 仓库
其他仓库的插件必须满足:
1. 包含 `class Tools:`、`class Filter:`、`class Pipe:` 或 `class Action:` 的 `.py` 文件

View File

@@ -7,11 +7,13 @@ version: 1.0.0
description: One-click batch install plugins from GitHub repositories to your OpenWebUI instance.
"""
import ast
import asyncio
import json
import logging
import os
import re
import textwrap
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
@@ -33,9 +35,10 @@ SELF_EXCLUDE_TERMS = (
SELF_EXCLUDE_HINT,
"batch install plugins from github",
)
DOCSTRING_PATTERN = re.compile(r'^\s*"""\n(.*?)\n"""', re.DOTALL)
DOCSTRING_PATTERN = re.compile(r'^\s*(?P<quote>"""|\'\'\')\s*(.*?)\s*(?P=quote)', re.DOTALL)
CLASS_PATTERN = re.compile(r'^class (Tools|Filter|Pipe|Action)\s*[\(:]', re.MULTILINE)
EMOJI_PATTERN = re.compile(r'[\U00010000-\U0010ffff]', re.UNICODE)
METADATA_KEY_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_-]*$")
TRANSLATIONS = {
"en-US": {
@@ -476,19 +479,109 @@ class PluginCandidate:
def extract_metadata(content: str) -> Dict[str, str]:
match = DOCSTRING_PATTERN.search(content)
if not match:
docstring = _extract_module_docstring(content)
if not docstring:
return {}
metadata: Dict[str, str] = {}
for raw_line in match.group(1).splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or ":" not in line:
lines = docstring.splitlines()
index = 0
while index < len(lines):
raw_line = lines[index]
stripped = raw_line.strip()
if not stripped or stripped.startswith("#"):
index += 1
continue
key, value = line.split(":", 1)
metadata[key.strip().lower()] = value.strip()
if raw_line[:1].isspace() or ":" not in raw_line:
index += 1
continue
key, value = raw_line.split(":", 1)
key = key.strip().lower()
if not METADATA_KEY_PATTERN.match(key):
index += 1
continue
value = value.strip()
if value and value[0] in {">", "|"}:
block_lines, index = _consume_indented_block(lines, index + 1)
metadata[key] = (
_fold_yaml_block(block_lines)
if value[0] == ">"
else _preserve_yaml_block(block_lines)
)
continue
metadata[key] = value
index += 1
return metadata
def _extract_module_docstring(content: str) -> str:
normalized = content.lstrip("\ufeff")
try:
module = ast.parse(normalized)
except SyntaxError:
module = None
if module is not None:
docstring = ast.get_docstring(module, clean=False)
if isinstance(docstring, str):
return docstring
fallback = normalized.replace("\r\n", "\n").replace("\r", "\n")
match = DOCSTRING_PATTERN.search(fallback)
return match.group(2) if match else ""
def _consume_indented_block(lines: List[str], start_index: int) -> Tuple[List[str], int]:
block: List[str] = []
index = start_index
while index < len(lines):
line = lines[index]
if not line.strip():
block.append("")
index += 1
continue
if line[:1].isspace():
block.append(line)
index += 1
continue
break
dedented = textwrap.dedent("\n".join(block)).splitlines()
return dedented, index
def _fold_yaml_block(lines: List[str]) -> str:
paragraphs: List[str] = []
current: List[str] = []
for line in lines:
stripped = line.strip()
if not stripped:
if current:
paragraphs.append(" ".join(current))
current = []
continue
current.append(stripped)
if current:
paragraphs.append(" ".join(current))
return "\n\n".join(paragraphs).strip()
def _preserve_yaml_block(lines: List[str]) -> str:
return "\n".join(line.rstrip() for line in lines).strip()
def detect_plugin_type(content: str) -> Optional[str]:
if "\nclass Tools:" in content or "\nclass Tools (" in content:
return "tool"
@@ -767,7 +860,7 @@ async def discover_plugins(
skipped.append((item_path, "missing title/description"))
continue
if has_emoji(metadata.get("title", "")):
if is_default_repo and has_emoji(metadata.get("title", "")):
skipped.append((item_path, "title contains emoji"))
continue