From fbd68ad0420e881610575efca7cabe001bd83332 Mon Sep 17 00:00:00 2001 From: fujie Date: Wed, 25 Mar 2026 19:26:44 +0800 Subject: [PATCH] chore(openwebui-skills-manager): release v0.3.1 - support multi-line SKILL.md frontmatter descriptions during install - sync README, docs, and release notes for v0.3.1 - add regression coverage for YAML block scalars and CRLF inputs --- docs/plugins/tools/index.md | 2 +- docs/plugins/tools/index.zh.md | 2 +- .../tools/openwebui-skills-manager-tool.md | 6 +- .../tools/openwebui-skills-manager-tool.zh.md | 6 +- .../plugins/tools/openwebui-skills-manager.md | 9 +- .../tools/openwebui-skills-manager.zh.md | 9 +- .../tools/openwebui-skills-manager/README.md | 9 +- .../openwebui-skills-manager/README_CN.md | 9 +- .../openwebui_skills_manager.py | 140 ++++++++++++++++-- .../tools/openwebui-skills-manager/v0.3.1.md | 15 ++ .../openwebui-skills-manager/v0.3.1_CN.md | 15 ++ .../tools/test_openwebui_skills_manager.py | 63 ++++++++ 12 files changed, 243 insertions(+), 42 deletions(-) create mode 100644 plugins/tools/openwebui-skills-manager/v0.3.1.md create mode 100644 plugins/tools/openwebui-skills-manager/v0.3.1_CN.md create mode 100644 tests/plugins/tools/test_openwebui_skills_manager.py diff --git a/docs/plugins/tools/index.md b/docs/plugins/tools/index.md index 4b6af77..8ed68c7 100644 --- a/docs/plugins/tools/index.md +++ b/docs/plugins/tools/index.md @@ -5,5 +5,5 @@ OpenWebUI native Tool plugins that can be used across models. ## Available Tool Plugins - [Batch Install Plugins from GitHub](batch-install-plugins-tool.md) (v1.1.0) - One-click batch install plugins from GitHub repositories with an interactive selection dialog and multi-language support. -- [OpenWebUI Skills Manager Tool](openwebui-skills-manager-tool.md) (v0.3.0) - Simple native skill management (`list/show/install/create/update/delete`). +- [OpenWebUI Skills Manager Tool](openwebui-skills-manager-tool.md) (v0.3.1) - Native skill management with multi-line `SKILL.md` frontmatter description support. - [Smart Mind Map Tool](smart-mind-map-tool.md) (v1.0.0) - Intelligently analyzes text content and proactively generates interactive mind maps to help users structure and visualize knowledge. diff --git a/docs/plugins/tools/index.zh.md b/docs/plugins/tools/index.zh.md index a30613e..fb6fec3 100644 --- a/docs/plugins/tools/index.zh.md +++ b/docs/plugins/tools/index.zh.md @@ -5,5 +5,5 @@ ## 可用 Tool 插件 - [Batch Install Plugins from GitHub](batch-install-plugins-tool.zh.md) (v1.1.0) - 一键从 GitHub 仓库批量安装插件,支持交互式选择对话框和多语言。 -- [OpenWebUI Skills 管理工具](openwebui-skills-manager-tool.zh.md) (v0.3.0) - 简化技能管理(`list/show/install/create/update/delete`)。 +- [OpenWebUI Skills 管理工具](openwebui-skills-manager-tool.zh.md) (v0.3.1) - 支持多行 `SKILL.md` frontmatter 描述的原生技能管理工具。 - [智能思维导图工具 (Smart Mind Map Tool)](smart-mind-map-tool.zh.md) (v1.0.0) - 智能分析文本内容并主动生成交互式思维导图,帮助用户结构化与可视化知识。 diff --git a/docs/plugins/tools/openwebui-skills-manager-tool.md b/docs/plugins/tools/openwebui-skills-manager-tool.md index 434d49d..2ddc055 100644 --- a/docs/plugins/tools/openwebui-skills-manager-tool.md +++ b/docs/plugins/tools/openwebui-skills-manager-tool.md @@ -1,13 +1,13 @@ # OpenWebUI Skills Manager Tool -**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.3.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) +**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.3.1 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) A standalone OpenWebUI Tool plugin for managing native Workspace Skills across models. ## What's New -- Added GitHub skills-directory auto-discovery for `install_skill` (e.g., `.../tree/main/skills`) to install all child skills in one request. -- Fixed language detection with robust frontend-first fallback (`__event_call__` + timeout), request header fallback, and profile fallback. +- `install_skill` now supports multi-line `description: >` / `description: |` frontmatter blocks when importing remote `SKILL.md` files. +- Added metadata fallback to use `title` when `name` is missing, plus regression tests for CRLF and YAML block scalars. ## Key Features diff --git a/docs/plugins/tools/openwebui-skills-manager-tool.zh.md b/docs/plugins/tools/openwebui-skills-manager-tool.zh.md index 3cf0cbd..65632c1 100644 --- a/docs/plugins/tools/openwebui-skills-manager-tool.zh.md +++ b/docs/plugins/tools/openwebui-skills-manager-tool.zh.md @@ -1,13 +1,13 @@ # OpenWebUI Skills 管理工具 -**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.3.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) +**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.3.1 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) 一个可跨模型使用的 OpenWebUI 原生 Tool 插件,用于管理 Workspace Skills。 ## 最新更新 -- `install_skill` 新增 GitHub 技能目录自动发现(例如 `.../tree/main/skills`),可一键安装目录下所有子技能。 -- 修复语言获取逻辑:前端优先(`__event_call__` + 超时保护),并回退到请求头与用户资料。 +- `install_skill` 现已支持远程 `SKILL.md` 中的多行 `description: >` / `description: |` frontmatter 描述。 +- 新增 `title` 作为 `name` 缺失时的元数据回退,并补齐 CRLF 与 YAML 块标量回归测试。 ## 核心特性 diff --git a/docs/plugins/tools/openwebui-skills-manager.md b/docs/plugins/tools/openwebui-skills-manager.md index 811b4fc..45aa385 100644 --- a/docs/plugins/tools/openwebui-skills-manager.md +++ b/docs/plugins/tools/openwebui-skills-manager.md @@ -1,6 +1,6 @@ # 🧰 OpenWebUI Skills Manager Tool -| By [Fu-Jie](https://github.com/Fu-Jie) · v0.3.0 | [⭐ Star this repo](https://github.com/Fu-Jie/openwebui-extensions) | +| By [Fu-Jie](https://github.com/Fu-Jie) · v0.3.1 | [⭐ Star this repo](https://github.com/Fu-Jie/openwebui-extensions) | | :--- | ---: | | ![followers](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_followers.json&label=%F0%9F%91%A5&style=flat) | ![points](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_points.json&label=%E2%AD%90&style=flat) | ![top](https://img.shields.io/badge/%F0%9F%8F%86-Top%20%3C1%25-10b981?style=flat) | ![contributions](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_contributions.json&label=%F0%9F%93%A6&style=flat) | ![downloads](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_downloads.json&label=%E2%AC%87%EF%B8%8F&style=flat) | ![saves](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_saves.json&label=%F0%9F%92%BE&style=flat) | ![views](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_views.json&label=%F0%9F%91%81%EF%B8%8F&style=flat) | @@ -23,10 +23,9 @@ When the selection dialog opens, search for this plugin, check it, and continue. ## What's New -- **🤖 Automatic Repo Root Discovery**: Install any GitHub repo by providing just the root URL (e.g., `https://github.com/owner/repo`). System auto-converts to discovery mode and installs all skills. -- **🔄 Batch Deduplication**: Automatically removes duplicate URLs from batch installations and detects duplicate skill names. -- Added GitHub skills-directory auto-discovery for `install_skill` (e.g., `.../tree/main/skills`) to install all child skills in one request. -- Fixed language detection with robust frontend-first fallback (`__event_call__` + timeout), request header fallback, and profile fallback. +- **📝 Multi-line Frontmatter Descriptions**: `install_skill` now correctly parses `description: >` and `description: |` blocks in remote `SKILL.md` files, so imported skill descriptions no longer truncate to a single line. +- **↩️ Better Metadata Fallbacks**: If a skill frontmatter provides `title` without `name`, the installer now uses that title before falling back to directory-based names. +- **🧪 Regression Coverage**: Added focused tests for folded/literal YAML blocks and CRLF line endings to keep external skill imports stable. > [!TIP] > **💡 Looking to batch install global plugins (Actions, Filters, Pipes, Tools)?** diff --git a/docs/plugins/tools/openwebui-skills-manager.zh.md b/docs/plugins/tools/openwebui-skills-manager.zh.md index 4aea37e..366266a 100644 --- a/docs/plugins/tools/openwebui-skills-manager.zh.md +++ b/docs/plugins/tools/openwebui-skills-manager.zh.md @@ -1,6 +1,6 @@ # 🧰 OpenWebUI Skills 管理工具 -| 作者:[Fu-Jie](https://github.com/Fu-Jie) · v0.3.0 | [⭐ 点个 Star 支持项目](https://github.com/Fu-Jie/openwebui-extensions) | +| 作者:[Fu-Jie](https://github.com/Fu-Jie) · v0.3.1 | [⭐ 点个 Star 支持项目](https://github.com/Fu-Jie/openwebui-extensions) | | :--- | ---: | | ![followers](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_followers.json&label=%F0%9F%91%A5&style=flat) | ![points](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_points.json&label=%E2%AD%90&style=flat) | ![top](https://img.shields.io/badge/%F0%9F%8F%86-Top%20%3C1%25-10b981?style=flat) | ![contributions](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_contributions.json&label=%F0%9F%93%A6&style=flat) | ![downloads](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_downloads.json&label=%E2%AC%87%EF%B8%8F&style=flat) | ![saves](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_saves.json&label=%F0%9F%92%BE&style=flat) | ![views](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_views.json&label=%F0%9F%91%81%EF%B8%8F&style=flat) | @@ -23,10 +23,9 @@ ## 最新更新 -- **🤖 自动发现仓库根目录**:现在可以直接提供 GitHub 仓库根 URL(如 `https://github.com/owner/repo`),系统会自动转换为发现模式并安装所有 skill。 -- **🔄 批量去重**:自动清除重复 URL,检测重复的 skill 名称。 -- `install_skill` 新增 GitHub 技能目录自动发现(例如 `.../tree/main/skills`),可一键安装目录下所有子技能。 -- 修复语言获取逻辑:前端优先(`__event_call__` + 超时保护),并回退到请求头与用户资料。 +- **📝 支持多行 Frontmatter 描述**:`install_skill` 现在可以正确解析远程 `SKILL.md` 里的 `description: >` 和 `description: |`,导入后的技能描述不再被截断成单行。 +- **↩️ 更稳的元数据回退**:当 frontmatter 只有 `title` 没有 `name` 时,安装器会优先使用 `title`,避免退回到通用目录名。 +- **🧪 回归测试补齐**:新增 folded/literal YAML 块和 CRLF 换行场景测试,保证外部技能导入行为稳定。 > [!TIP] > **💡 想要批量安装/管理全局插件 (Actions, Filters, Pipes, Tools)?** diff --git a/plugins/tools/openwebui-skills-manager/README.md b/plugins/tools/openwebui-skills-manager/README.md index 811b4fc..45aa385 100644 --- a/plugins/tools/openwebui-skills-manager/README.md +++ b/plugins/tools/openwebui-skills-manager/README.md @@ -1,6 +1,6 @@ # 🧰 OpenWebUI Skills Manager Tool -| By [Fu-Jie](https://github.com/Fu-Jie) · v0.3.0 | [⭐ Star this repo](https://github.com/Fu-Jie/openwebui-extensions) | +| By [Fu-Jie](https://github.com/Fu-Jie) · v0.3.1 | [⭐ Star this repo](https://github.com/Fu-Jie/openwebui-extensions) | | :--- | ---: | | ![followers](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_followers.json&label=%F0%9F%91%A5&style=flat) | ![points](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_points.json&label=%E2%AD%90&style=flat) | ![top](https://img.shields.io/badge/%F0%9F%8F%86-Top%20%3C1%25-10b981?style=flat) | ![contributions](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_contributions.json&label=%F0%9F%93%A6&style=flat) | ![downloads](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_downloads.json&label=%E2%AC%87%EF%B8%8F&style=flat) | ![saves](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_saves.json&label=%F0%9F%92%BE&style=flat) | ![views](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_views.json&label=%F0%9F%91%81%EF%B8%8F&style=flat) | @@ -23,10 +23,9 @@ When the selection dialog opens, search for this plugin, check it, and continue. ## What's New -- **🤖 Automatic Repo Root Discovery**: Install any GitHub repo by providing just the root URL (e.g., `https://github.com/owner/repo`). System auto-converts to discovery mode and installs all skills. -- **🔄 Batch Deduplication**: Automatically removes duplicate URLs from batch installations and detects duplicate skill names. -- Added GitHub skills-directory auto-discovery for `install_skill` (e.g., `.../tree/main/skills`) to install all child skills in one request. -- Fixed language detection with robust frontend-first fallback (`__event_call__` + timeout), request header fallback, and profile fallback. +- **📝 Multi-line Frontmatter Descriptions**: `install_skill` now correctly parses `description: >` and `description: |` blocks in remote `SKILL.md` files, so imported skill descriptions no longer truncate to a single line. +- **↩️ Better Metadata Fallbacks**: If a skill frontmatter provides `title` without `name`, the installer now uses that title before falling back to directory-based names. +- **🧪 Regression Coverage**: Added focused tests for folded/literal YAML blocks and CRLF line endings to keep external skill imports stable. > [!TIP] > **💡 Looking to batch install global plugins (Actions, Filters, Pipes, Tools)?** diff --git a/plugins/tools/openwebui-skills-manager/README_CN.md b/plugins/tools/openwebui-skills-manager/README_CN.md index 4aea37e..366266a 100644 --- a/plugins/tools/openwebui-skills-manager/README_CN.md +++ b/plugins/tools/openwebui-skills-manager/README_CN.md @@ -1,6 +1,6 @@ # 🧰 OpenWebUI Skills 管理工具 -| 作者:[Fu-Jie](https://github.com/Fu-Jie) · v0.3.0 | [⭐ 点个 Star 支持项目](https://github.com/Fu-Jie/openwebui-extensions) | +| 作者:[Fu-Jie](https://github.com/Fu-Jie) · v0.3.1 | [⭐ 点个 Star 支持项目](https://github.com/Fu-Jie/openwebui-extensions) | | :--- | ---: | | ![followers](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_followers.json&label=%F0%9F%91%A5&style=flat) | ![points](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_points.json&label=%E2%AD%90&style=flat) | ![top](https://img.shields.io/badge/%F0%9F%8F%86-Top%20%3C1%25-10b981?style=flat) | ![contributions](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_contributions.json&label=%F0%9F%93%A6&style=flat) | ![downloads](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_downloads.json&label=%E2%AC%87%EF%B8%8F&style=flat) | ![saves](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_saves.json&label=%F0%9F%92%BE&style=flat) | ![views](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_views.json&label=%F0%9F%91%81%EF%B8%8F&style=flat) | @@ -23,10 +23,9 @@ ## 最新更新 -- **🤖 自动发现仓库根目录**:现在可以直接提供 GitHub 仓库根 URL(如 `https://github.com/owner/repo`),系统会自动转换为发现模式并安装所有 skill。 -- **🔄 批量去重**:自动清除重复 URL,检测重复的 skill 名称。 -- `install_skill` 新增 GitHub 技能目录自动发现(例如 `.../tree/main/skills`),可一键安装目录下所有子技能。 -- 修复语言获取逻辑:前端优先(`__event_call__` + 超时保护),并回退到请求头与用户资料。 +- **📝 支持多行 Frontmatter 描述**:`install_skill` 现在可以正确解析远程 `SKILL.md` 里的 `description: >` 和 `description: |`,导入后的技能描述不再被截断成单行。 +- **↩️ 更稳的元数据回退**:当 frontmatter 只有 `title` 没有 `name` 时,安装器会优先使用 `title`,避免退回到通用目录名。 +- **🧪 回归测试补齐**:新增 folded/literal YAML 块和 CRLF 换行场景测试,保证外部技能导入行为稳定。 > [!TIP] > **💡 想要批量安装/管理全局插件 (Actions, Filters, Pipes, Tools)?** diff --git a/plugins/tools/openwebui-skills-manager/openwebui_skills_manager.py b/plugins/tools/openwebui-skills-manager/openwebui_skills_manager.py index 40caa16..5a65587 100644 --- a/plugins/tools/openwebui-skills-manager/openwebui_skills_manager.py +++ b/plugins/tools/openwebui-skills-manager/openwebui_skills_manager.py @@ -3,7 +3,7 @@ title: OpenWebUI Skills Manager Tool author: Fu-Jie author_url: https://github.com/Fu-Jie/openwebui-extensions funding_url: https://github.com/open-webui -version: 0.3.0 +version: 0.3.1 openwebui_id: b4bce8e4-08e7-4f90-bea7-dc31d463a0bb requirements: description: Standalone OpenWebUI tool for managing native Workspace Skills (list/show/install/create/update/delete) for any model. @@ -846,26 +846,138 @@ async def _fetch_bytes(valves, url: str) -> bytes: ) +_FRONTMATTER_KEY_RE = re.compile(r"^([A-Za-z0-9_-]+):(.*)$") + + +def _normalize_newlines(text: str) -> str: + """Normalize CRLF/CR text to LF for stable frontmatter parsing.""" + return text.replace("\r\n", "\n").replace("\r", "\n") + + +def _strip_matching_quotes(value: str) -> str: + """Remove matching wrapping quotes from a scalar value.""" + value = (value or "").strip() + if len(value) >= 2 and value[0] == value[-1] and value[0] in {"'", '"'}: + return value[1:-1] + return value + + +def _collect_frontmatter_block(lines: List[str], start_index: int) -> Tuple[List[str], int]: + """Collect indented block lines until the next top-level frontmatter key.""" + block_lines: List[str] = [] + index = start_index + + while index < len(lines): + line = lines[index] + if not line.strip(): + block_lines.append("") + index += 1 + continue + + indent = len(line) - len(line.lstrip(" ")) + if indent == 0 and _FRONTMATTER_KEY_RE.match(line): + break + if indent == 0: + break + + block_lines.append(line) + index += 1 + + non_empty_indents = [ + len(line) - len(line.lstrip(" ")) for line in block_lines if line.strip() + ] + min_indent = min(non_empty_indents) if non_empty_indents else 0 + dedented = [line[min_indent:] if line else "" for line in block_lines] + return dedented, index + + +def _fold_yaml_block(lines: List[str]) -> str: + """Fold YAML `>` block lines into paragraphs separated by blank lines.""" + parts: List[str] = [] + paragraph: List[str] = [] + + for line in lines: + if line == "": + if paragraph: + parts.append(" ".join(segment.strip() for segment in paragraph)) + paragraph = [] + if parts and parts[-1] != "": + parts.append("") + continue + paragraph.append(line.strip()) + + if paragraph: + parts.append(" ".join(segment.strip() for segment in paragraph)) + + return "\n".join(parts).strip() + + +def _parse_frontmatter_scalars(frontmatter_text: str) -> Dict[str, str]: + """Parse simple top-level YAML frontmatter scalars, including block scalars.""" + lines = _normalize_newlines(frontmatter_text).split("\n") + metadata: Dict[str, str] = {} + index = 0 + + while index < len(lines): + line = lines[index] + stripped = line.strip() + + if not stripped or stripped.startswith("#") or line.startswith(" "): + index += 1 + continue + + match = _FRONTMATTER_KEY_RE.match(line) + if not match: + index += 1 + continue + + key = match.group(1) + raw_value = match.group(2).lstrip() + + if raw_value[:1] in {"|", ">"}: + block_lines, index = _collect_frontmatter_block(lines, index + 1) + metadata[key] = ( + "\n".join(block_lines).strip() + if raw_value.startswith("|") + else _fold_yaml_block(block_lines) + ) + continue + + block_lines, next_index = _collect_frontmatter_block(lines, index + 1) + if block_lines: + segments = [_strip_matching_quotes(raw_value)] + [ + segment.strip() for segment in block_lines + ] + metadata[key] = _fold_yaml_block(segments) + index = next_index + continue + + metadata[key] = _strip_matching_quotes(raw_value) + index += 1 + + return metadata + + def _parse_skill_md_meta(content: str, fallback_name: str) -> Tuple[str, str, str]: """Parse markdown skill content into (name, description, body).""" - fm_match = re.match(r"^---\s*\n(.*?)\n---\s*\n", content, re.DOTALL) + normalized_content = _normalize_newlines(content) + fm_match = re.match(r"^---\s*\n(.*?)\n---\s*(?:\n|$)", normalized_content, re.DOTALL) if fm_match: fm_text = fm_match.group(1) - body = content[fm_match.end() :].strip() - name = fallback_name - description = "" - for line in fm_text.split("\n"): - m_name = re.match(r"^name:\s*(.+)$", line) - if m_name: - name = m_name.group(1).strip().strip("\"'") - m_desc = re.match(r"^description:\s*(.+)$", line) - if m_desc: - description = m_desc.group(1).strip().strip("\"'") + body = normalized_content[fm_match.end() :].strip() + metadata = _parse_frontmatter_scalars(fm_text) + name = ( + metadata.get("name") + or metadata.get("title") + or fallback_name + ).strip() + description = (metadata.get("description") or "").strip() return name, description, body - h1_match = re.search(r"^#\s+(.+)$", content.strip(), re.MULTILINE) + stripped_content = normalized_content.strip() + h1_match = re.search(r"^#\s+(.+)$", stripped_content, re.MULTILINE) name = h1_match.group(1).strip() if h1_match else fallback_name - return name, "", content.strip() + return name, "", stripped_content def _append_source_url_to_content(content: str, url: str, lang: str = "en-US") -> str: diff --git a/plugins/tools/openwebui-skills-manager/v0.3.1.md b/plugins/tools/openwebui-skills-manager/v0.3.1.md new file mode 100644 index 0000000..25daf18 --- /dev/null +++ b/plugins/tools/openwebui-skills-manager/v0.3.1.md @@ -0,0 +1,15 @@ +# OpenWebUI Skills Manager v0.3.1 Release Notes + +This patch release improves compatibility when importing external `SKILL.md` files by correctly handling multi-line YAML frontmatter descriptions and safer metadata fallbacks. + +### New Features +- Support folded (`description: >`) and literal (`description: |`) multi-line descriptions during `install_skill`. +- Normalize CRLF/CR line endings before parsing skill frontmatter metadata. + +### Bug Fixes +- Fixed imported skill descriptions being truncated or reduced to a single line when source frontmatter used YAML block scalars. +- Added `title` as a name fallback when `name` is absent, reducing generic directory-name fallbacks for third-party skills. + +### Enhancements +- Added focused regression tests for folded/literal YAML blocks and CRLF inputs. +- Synced README and docs mirrors so the latest release surface documents the new metadata compatibility. diff --git a/plugins/tools/openwebui-skills-manager/v0.3.1_CN.md b/plugins/tools/openwebui-skills-manager/v0.3.1_CN.md new file mode 100644 index 0000000..ce13c97 --- /dev/null +++ b/plugins/tools/openwebui-skills-manager/v0.3.1_CN.md @@ -0,0 +1,15 @@ +# OpenWebUI Skills Manager v0.3.1 版本发布说明 + +这个补丁版本重点增强了外部 `SKILL.md` 导入兼容性,能够正确处理多行 YAML frontmatter 描述,并提供更稳健的元数据回退逻辑。 + +### 新功能 +- `install_skill` 现已支持 folded(`description: >`)和 literal(`description: |`)两种多行描述格式。 +- 在解析技能 frontmatter 之前,统一规范化 CRLF/CR 换行,提升跨平台兼容性。 + +### 问题修复 +- 修复第三方 `SKILL.md` 使用 YAML 块标量时,导入后的技能描述被截断或压缩成单行的问题。 +- 当 frontmatter 缺少 `name` 但提供了 `title` 时,新增 `title` 回退逻辑,避免退回到通用目录名。 + +### 优化提升 +- 新增 folded/literal YAML 块与 CRLF 输入的定向回归测试。 +- 同步更新 README 与 docs 镜像页,确保最新发布文案与元数据兼容行为保持一致。 diff --git a/tests/plugins/tools/test_openwebui_skills_manager.py b/tests/plugins/tools/test_openwebui_skills_manager.py new file mode 100644 index 0000000..822700c --- /dev/null +++ b/tests/plugins/tools/test_openwebui_skills_manager.py @@ -0,0 +1,63 @@ +import importlib.util +import sys +from pathlib import Path + + +MODULE_PATH = ( + Path(__file__).resolve().parents[3] + / "plugins" + / "tools" + / "openwebui-skills-manager" + / "openwebui_skills_manager.py" +) +SPEC = importlib.util.spec_from_file_location("openwebui_skills_manager", MODULE_PATH) +openwebui_skills_manager = importlib.util.module_from_spec(SPEC) +assert SPEC.loader is not None +sys.modules[SPEC.name] = openwebui_skills_manager +SPEC.loader.exec_module(openwebui_skills_manager) + + +def test_parse_skill_md_meta_supports_folded_multiline_description(): + content = ( + "---\r\n" + "name: persona-selector\r\n" + "description: >\r\n" + " Two-step persona picker. Step 1: numbered category list.\r\n" + " Step 2: numbered persona list. 160 personas + Custom.\r\n" + "---\r\n\r\n" + "# Persona Selector\r\n\r\n" + "Body content.\r\n" + ) + + name, description, body = openwebui_skills_manager._parse_skill_md_meta( + content, "fallback-skill" + ) + + assert name == "persona-selector" + assert description == ( + "Two-step persona picker. Step 1: numbered category list. " + "Step 2: numbered persona list. 160 personas + Custom." + ) + assert body == "# Persona Selector\n\nBody content." + + +def test_parse_skill_md_meta_supports_literal_multiline_description_and_title_fallback(): + content = ( + "---\n" + 'title: "Data Storyteller"\n' + "description: |\n" + " First line.\n" + " Second line.\n" + "\n" + " Third paragraph.\n" + "---\n\n" + "Explain how to turn analysis into a narrative.\n" + ) + + name, description, body = openwebui_skills_manager._parse_skill_md_meta( + content, "fallback-skill" + ) + + assert name == "Data Storyteller" + assert description == "First line.\nSecond line.\n\nThird paragraph." + assert body == "Explain how to turn analysis into a narrative."