feat(skills): add 6 generalized AI-driven development skills
- version-bumper: automated multi-file version synchronization - plugin-scaffolder: standardized 12-language i18n template generation - doc-mirror-sync: automated README to docs mirroring - i18n-validator: dictionary key alignment analysis via AST - gh-issue-replier: professional English reply with star-check logic - gh-issue-scheduler: unanswered issue audit and action planning
This commit is contained in:
23
.gemini/skills/community-announcer/SKILL.md
Normal file
23
.gemini/skills/community-announcer/SKILL.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
name: community-announcer
|
||||||
|
description: Drafts engaging English and Chinese update announcements for the OpenWebUI Community and other social platforms. Use when a new version is released.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Community Announcer
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Automates the drafting of high-impact update announcements.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
1. **Source Intel**: Read the latest version's `What's New` section from `README.md`.
|
||||||
|
2. **Drafting**: Create two versions:
|
||||||
|
- **Community Post**: Professional, structured, technical.
|
||||||
|
- **Catchy Short**: For Discord/Twitter, use emojis and bullet points.
|
||||||
|
3. **Multi-language**: Generate BOTH English and Chinese versions automatically.
|
||||||
|
|
||||||
|
## Announcement Structure (Recommended)
|
||||||
|
- **Headline**: "Update vX.X.X - [Main Feature]"
|
||||||
|
- **Introduction**: Brief context.
|
||||||
|
- **Key Highlights**: Bulleted list of fixes/features.
|
||||||
|
- **Action**: "Download from [Market Link]"
|
||||||
|
- **Closing**: Thanks and Star request.
|
||||||
14
.gemini/skills/doc-mirror-sync/SKILL.md
Normal file
14
.gemini/skills/doc-mirror-sync/SKILL.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
name: doc-mirror-sync
|
||||||
|
description: Automatically synchronizes plugin READMEs to the official documentation directory (docs/). Use after editing a plugin's local documentation to keep the MkDocs site up to date.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Doc Mirror Sync
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Automates the mirroring of `plugins/{type}/{name}/README.md` to `docs/plugins/{type}/{name}.md`.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
1. Identify changed READMEs.
|
||||||
|
2. Copy content to corresponding mirror paths.
|
||||||
|
3. Update version badges in `docs/plugins/{type}/index.md`.
|
||||||
38
.gemini/skills/doc-mirror-sync/scripts/sync.py
Normal file
38
.gemini/skills/doc-mirror-sync/scripts/sync.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import re
|
||||||
|
|
||||||
|
def sync_mirrors():
|
||||||
|
plugins_root = "plugins"
|
||||||
|
docs_root = "docs/plugins"
|
||||||
|
|
||||||
|
types = ["actions", "filters", "pipes", "pipelines", "tools"]
|
||||||
|
|
||||||
|
for t in types:
|
||||||
|
src_type_dir = os.path.join(plugins_root, t)
|
||||||
|
dest_type_dir = os.path.join(docs_root, t)
|
||||||
|
|
||||||
|
if not os.path.exists(src_type_dir): continue
|
||||||
|
os.makedirs(dest_type_dir, exist_ok=True)
|
||||||
|
|
||||||
|
for name in os.listdir(src_type_dir):
|
||||||
|
plugin_dir = os.path.join(src_type_dir, name)
|
||||||
|
if not os.path.isdir(plugin_dir): continue
|
||||||
|
|
||||||
|
# Sync README.md -> docs/plugins/{type}/{name}.md
|
||||||
|
src_readme = os.path.join(plugin_dir, "README.md")
|
||||||
|
if os.path.exists(src_readme):
|
||||||
|
dest_readme = os.path.join(dest_type_dir, f"{name}.md")
|
||||||
|
shutil.copy(src_readme, dest_readme)
|
||||||
|
print(f"✅ Mirrored: {t}/{name} (EN)")
|
||||||
|
|
||||||
|
# Sync README_CN.md -> docs/plugins/{type}/{name}.zh.md
|
||||||
|
src_readme_cn = os.path.join(plugin_dir, "README_CN.md")
|
||||||
|
if os.path.exists(src_readme_cn):
|
||||||
|
dest_readme_zh = os.path.join(dest_type_dir, f"{name}.zh.md")
|
||||||
|
shutil.copy(src_readme_cn, dest_readme_zh)
|
||||||
|
print(f"✅ Mirrored: {t}/{name} (ZH)")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sync_mirrors()
|
||||||
51
.gemini/skills/gh-issue-replier/SKILL.md
Normal file
51
.gemini/skills/gh-issue-replier/SKILL.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
name: gh-issue-replier
|
||||||
|
description: Professional English replier for GitHub issues. Use when a task is completed, a bug is fixed, or more info is needed from the user. Automates replying using the 'gh' CLI tool.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Gh Issue Replier
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The `gh-issue-replier` skill enables Gemini CLI to interact with GitHub issues professionally. It enforces English for all communications and leverages the `gh` CLI to post comments.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Identify the Issue**: Find the issue number (e.g., #49).
|
||||||
|
2. **Check Star Status**: Run the bundled script to check if the author has starred the repo.
|
||||||
|
* Command: `bash scripts/check_star.sh <issue-number>`
|
||||||
|
* Interpretation:
|
||||||
|
* Exit code **0**: User has starred. Use "Already Starred" templates.
|
||||||
|
* Exit code **1**: User has NOT starred. Include "Star Request" in the reply.
|
||||||
|
3. **Select a Template**: Load [templates.md](references/templates.md) to choose a suitable English response pattern.
|
||||||
|
4. **Draft the Reply**: Compose a concise message based on the star status.
|
||||||
|
5. **Post the Comment**: Use the `gh` tool to submit the reply.
|
||||||
|
|
||||||
|
## Tool Integration
|
||||||
|
|
||||||
|
### Check Star Status
|
||||||
|
```bash
|
||||||
|
bash scripts/check_star.sh <issue-number>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Post Comment
|
||||||
|
```bash
|
||||||
|
gh issue comment <issue-number> --body "<message-body>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Example (if user has NOT starred):
|
||||||
|
```bash
|
||||||
|
gh issue comment 49 --body "This has been fixed in v1.2.7. If you find this helpful, a star on the repo would be much appreciated! ⭐"
|
||||||
|
```
|
||||||
|
|
||||||
|
Example (if user HAS starred):
|
||||||
|
```bash
|
||||||
|
gh issue comment 49 --body "This has been fixed in v1.2.7. Thanks for your support!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Guidelines
|
||||||
|
|
||||||
|
- **Language**: ALWAYS use English for the comment body, even if the system prompt or user conversation is in another language.
|
||||||
|
- **Tone**: Professional, helpful, and appreciative.
|
||||||
|
- **Precision**: When announcing a fix, mention the specific version or the logic change (e.g., "Updated regex pattern").
|
||||||
|
- **Closing**: If the issue is resolved and you have permission, you can also use `gh issue close <number>`.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Reference Documentation for Gh Issue Replier
|
||||||
|
|
||||||
|
This is a placeholder for detailed reference documentation.
|
||||||
|
Replace with actual reference content or delete if not needed.
|
||||||
|
|
||||||
|
## Structure Suggestions
|
||||||
|
|
||||||
|
### API Reference Example
|
||||||
|
- Overview
|
||||||
|
- Authentication
|
||||||
|
- Endpoints with examples
|
||||||
|
- Error codes
|
||||||
|
|
||||||
|
### Workflow Guide Example
|
||||||
|
- Prerequisites
|
||||||
|
- Step-by-step instructions
|
||||||
|
- Best practices
|
||||||
45
.gemini/skills/gh-issue-replier/references/templates.md
Normal file
45
.gemini/skills/gh-issue-replier/references/templates.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Issue Reply Templates
|
||||||
|
|
||||||
|
Use these templates to craft professional English replies. Adjust placeholders like `@username`, `v1.2.x`, and `[commit hash]` as needed.
|
||||||
|
|
||||||
|
## 1. Acknowledging a New Issue
|
||||||
|
Use when you first see an issue and want to let the user know you are working on it.
|
||||||
|
|
||||||
|
- "Thank you for reporting this! I'm looking into it right now."
|
||||||
|
- "Thanks for bringing this to my attention. I'll try to reproduce this behavior and get back to you shortly."
|
||||||
|
|
||||||
|
## 2. Requesting More Information
|
||||||
|
Use when you need logs or specific details to fix the bug.
|
||||||
|
|
||||||
|
- "Could you please provide the **'Original'** vs **'Normalized'** content from your browser console logs (F12)? It would help a lot in debugging."
|
||||||
|
- "It would be very helpful if you could share the specific Markdown text that triggered this issue."
|
||||||
|
|
||||||
|
## 3. Announcing a Fix
|
||||||
|
Use when you have pushed the fix to the repository.
|
||||||
|
|
||||||
|
- "This has been fixed in version **v1.2.x**. You can update the plugin to resolve it."
|
||||||
|
- "I've just pushed a fix for this in [commit hash]. Please let me know if it works for you after updating."
|
||||||
|
- "The issue was caused by a greedy regex pattern. I've updated it to use a tempered greedy token to prevent incorrect merging."
|
||||||
|
|
||||||
|
## 4. Guiding to Official Market
|
||||||
|
Always provide the official market link to ensure the user gets the latest verified version.
|
||||||
|
|
||||||
|
- "The fix is now live! You can download the latest version from the official OpenWebUI Community page here: [Plugin Market Link]. Simply update the function in your OpenWebUI instance to apply the changes."
|
||||||
|
- "I recommend getting the updated version from the official store: [Link]. It includes the fix for the spacing issue we discussed."
|
||||||
|
|
||||||
|
## 5. Closing the Issue
|
||||||
|
Use when the issue is confirmed resolved.
|
||||||
|
|
||||||
|
- "Glad to hear it's working now! Closing this for now. Feel free to reopen it if the problem persists."
|
||||||
|
- "Since this is resolved, I'm closing this issue. Thanks again for your feedback!"
|
||||||
|
|
||||||
|
## 5. Pro-tip: Star Request
|
||||||
|
Gently handle star requests based on the user's current status.
|
||||||
|
|
||||||
|
### If User has NOT starred:
|
||||||
|
- "If you find this plugin helpful, a star on the repo would be much appreciated! ⭐"
|
||||||
|
- "We'd love your support! If this fixed your issue, please consider starring the repository. ⭐"
|
||||||
|
|
||||||
|
### If User HAS already starred:
|
||||||
|
- "Thanks again for starring the project and for your continuous support!"
|
||||||
|
- "I appreciate your support and for being a stargazer of this project!"
|
||||||
31
.gemini/skills/gh-issue-replier/scripts/check_star.sh
Executable file
31
.gemini/skills/gh-issue-replier/scripts/check_star.sh
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Robust Star Checker v2
|
||||||
|
# Usage: ./check_star.sh <issue_number>
|
||||||
|
|
||||||
|
ISSUE_NUM=$1
|
||||||
|
if [ -z "$ISSUE_NUM" ]; then exit 2; fi
|
||||||
|
|
||||||
|
# 1. Get Repo and Author info
|
||||||
|
REPO_FULL=$(gh repo view --json owner,name -q ".owner.login + \"/\" + .name")
|
||||||
|
USER_LOGIN=$(gh issue view "$ISSUE_NUM" --json author -q ".author.login")
|
||||||
|
|
||||||
|
# 2. Use GraphQL for high precision (Detects stars even when REST 404s)
|
||||||
|
IS_STARRED=$(gh api graphql -f query='
|
||||||
|
query($owner:String!, $repo:String!, $user:String!) {
|
||||||
|
repository(owner:$owner, name:$repo) {
|
||||||
|
stargazers(query:$user, first:1) {
|
||||||
|
nodes {
|
||||||
|
login
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}' -f owner="${REPO_FULL%/*}" -f repo="${REPO_FULL#*/}" -f user="$USER_LOGIN" -q ".data.repository.stargazers.nodes[0].login")
|
||||||
|
|
||||||
|
if [ "$IS_STARRED" == "$USER_LOGIN" ]; then
|
||||||
|
echo "Confirmed: @$USER_LOGIN HAS starred $REPO_FULL. ⭐"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "Confirmed: @$USER_LOGIN has NOT starred $REPO_FULL."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
42
.gemini/skills/gh-issue-scheduler/SKILL.md
Normal file
42
.gemini/skills/gh-issue-scheduler/SKILL.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
name: gh-issue-scheduler
|
||||||
|
description: Finds all open GitHub issues that haven't been replied to by the owner, summarizes them, and generates a solution plan. Use when the user wants to audit pending tasks or plan maintenance work.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Gh Issue Scheduler
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The `gh-issue-scheduler` skill helps maintainers track community feedback by identifying unaddressed issues and drafting actionable technical plans to resolve them.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Identify Unanswered Issues**: Run the bundled script to fetch issues without owner replies.
|
||||||
|
* Command: `bash scripts/find_unanswered.sh`
|
||||||
|
2. **Analyze and Summarize**: For each identified issue, summarize the core problem and the user's intent.
|
||||||
|
3. **Generate Solution Plans**: Draft a technical "Action Plan" for each issue, including:
|
||||||
|
* **Root Cause Analysis** (if possible)
|
||||||
|
* **Proposed Fix/Implementation**
|
||||||
|
* **Verification Strategy**
|
||||||
|
4. **Present to User**: Display a structured report of all pending issues and their respective plans.
|
||||||
|
|
||||||
|
## Tool Integration
|
||||||
|
|
||||||
|
### Find Unanswered Issues
|
||||||
|
```bash
|
||||||
|
bash scripts/find_unanswered.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Report Format
|
||||||
|
|
||||||
|
When presenting the summary, use the following Markdown structure:
|
||||||
|
|
||||||
|
### 📋 Unanswered Issues Audit
|
||||||
|
|
||||||
|
#### Issue #[Number]: [Title]
|
||||||
|
- **Author**: @username
|
||||||
|
- **Summary**: Concise description of the problem.
|
||||||
|
- **Action Plan**:
|
||||||
|
1. Step 1 (e.g., Investigate file X)
|
||||||
|
2. Step 2 (e.g., Apply fix Y)
|
||||||
|
3. Verification (e.g., Run test Z)
|
||||||
42
.gemini/skills/gh-issue-scheduler/scripts/find_unanswered.sh
Executable file
42
.gemini/skills/gh-issue-scheduler/scripts/find_unanswered.sh
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Fetch all open issues and filter for those without responses from the owner/collaborators.
|
||||||
|
# Uses 'gh' CLI.
|
||||||
|
|
||||||
|
REPO_FULL=$(gh repo view --json owner,name -q ".owner.login + "/" + .name")
|
||||||
|
OWNER=${REPO_FULL%/*}
|
||||||
|
|
||||||
|
# 1. Get all open issues
|
||||||
|
OPEN_ISSUES=$(gh issue list --state open --json number,title,author,createdAt --limit 100)
|
||||||
|
|
||||||
|
echo "Analysis for repository: $REPO_FULL"
|
||||||
|
echo "------------------------------------"
|
||||||
|
|
||||||
|
# Process each issue
|
||||||
|
echo "$OPEN_ISSUES" | jq -c '.[]' | while read -r issue; do
|
||||||
|
NUMBER=$(echo "$issue" | jq -r '.number')
|
||||||
|
TITLE=$(echo "$issue" | jq -r '.title')
|
||||||
|
AUTHOR=$(echo "$issue" | jq -r '.author.login')
|
||||||
|
|
||||||
|
# Check comments for owner responses
|
||||||
|
# We look for comments where the author is the repo owner
|
||||||
|
COMMENTS=$(gh issue view "$NUMBER" --json comments -q ".comments[].author.login" 2>/dev/null)
|
||||||
|
|
||||||
|
HAS_OWNER_REPLY=false
|
||||||
|
for COMMENT_AUTHOR in $COMMENTS; do
|
||||||
|
if [ "$COMMENT_AUTHOR" == "$OWNER" ]; then
|
||||||
|
HAS_OWNER_REPLY=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$HAS_OWNER_REPLY" == "false" ]; then
|
||||||
|
echo "ISSUE_START"
|
||||||
|
echo "ID: $NUMBER"
|
||||||
|
echo "Title: $TITLE"
|
||||||
|
echo "Author: $AUTHOR"
|
||||||
|
echo "Description:"
|
||||||
|
gh issue view "$NUMBER" --json body -q ".body"
|
||||||
|
echo "ISSUE_END"
|
||||||
|
fi
|
||||||
|
done
|
||||||
14
.gemini/skills/i18n-validator/SKILL.md
Normal file
14
.gemini/skills/i18n-validator/SKILL.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
name: i18n-validator
|
||||||
|
description: Validates multi-language consistency in the TRANSLATIONS dictionary of a plugin. Use to check if any language keys are missing or if translations need updating.
|
||||||
|
---
|
||||||
|
|
||||||
|
# I18n Validator
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Ensures all 12 supported languages (en-US, zh-CN, etc.) have aligned translation keys.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Detects missing keys in non-English dictionaries.
|
||||||
|
- Suggests translations using the core AI engine.
|
||||||
|
- Validates the `fallback_map` for variant redirects.
|
||||||
54
.gemini/skills/i18n-validator/scripts/validate_i18n.py
Normal file
54
.gemini/skills/i18n-validator/scripts/validate_i18n.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import ast
|
||||||
|
import os
|
||||||
|
|
||||||
|
def check_i18n(file_path):
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
print(f"Error: File not found {file_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
tree = ast.parse(f.read())
|
||||||
|
|
||||||
|
translations = {}
|
||||||
|
for node in tree.body:
|
||||||
|
if isinstance(node, ast.Assign):
|
||||||
|
for target in node.targets:
|
||||||
|
if isinstance(target, ast.Name) and target.id == "TRANSLATIONS":
|
||||||
|
translations = ast.literal_eval(node.value)
|
||||||
|
break
|
||||||
|
|
||||||
|
if not translations:
|
||||||
|
print("⚠️ No TRANSLATIONS dictionary found.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Base keys from English
|
||||||
|
base_lang = "en-US"
|
||||||
|
if base_lang not in translations:
|
||||||
|
print(f"❌ Error: {base_lang} missing in TRANSLATIONS.")
|
||||||
|
return
|
||||||
|
|
||||||
|
base_keys = set(translations[base_lang].keys())
|
||||||
|
print(f"🔍 Analyzing {file_path}...")
|
||||||
|
print(f"Standard keys ({len(base_keys)}): {', '.join(sorted(base_keys))}
|
||||||
|
")
|
||||||
|
|
||||||
|
for lang, keys in translations.items():
|
||||||
|
if lang == base_lang: continue
|
||||||
|
lang_keys = set(keys.keys())
|
||||||
|
missing = base_keys - lang_keys
|
||||||
|
extra = lang_keys - base_keys
|
||||||
|
|
||||||
|
if missing:
|
||||||
|
print(f"❌ {lang}: Missing {len(missing)} keys: {', '.join(missing)}")
|
||||||
|
if extra:
|
||||||
|
print(f"⚠️ {lang}: Has {len(extra)} extra keys: {', '.join(extra)}")
|
||||||
|
if not missing and not extra:
|
||||||
|
print(f"✅ {lang}: Aligned.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: validate_i18n.py <path_to_plugin.py>")
|
||||||
|
sys.exit(1)
|
||||||
|
check_i18n(sys.argv[1])
|
||||||
19
.gemini/skills/plugin-scaffolder/SKILL.md
Normal file
19
.gemini/skills/plugin-scaffolder/SKILL.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
name: plugin-scaffolder
|
||||||
|
description: Generates a standardized single-file i18n Python plugin template based on project standards. Use when starting a new plugin development to skip boilerplate writing.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Plugin Scaffolder
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Generates compliant OpenWebUI plugin templates with built-in i18n, common utility methods, and required docstring fields.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
1. Provide the **Plugin Name** and **Type** (action/filter/pipe).
|
||||||
|
2. The skill will generate the `.py` file and the bilingual `README` files.
|
||||||
|
|
||||||
|
## Template Standard
|
||||||
|
- `Valves(BaseModel)` with `UPPER_SNAKE_CASE`
|
||||||
|
- `_get_user_context` with JS fallback and timeout
|
||||||
|
- `_emit_status` and `_emit_debug_log` methods
|
||||||
|
- Standardized docstring metadata
|
||||||
34
.gemini/skills/plugin-scaffolder/assets/README_template.md
Normal file
34
.gemini/skills/plugin-scaffolder/assets/README_template.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# {{TITLE}}
|
||||||
|
|
||||||
|
**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.1.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
|
||||||
|
|
||||||
|
{{DESCRIPTION}}
|
||||||
|
|
||||||
|
## 🔥 What's New in v0.1.0
|
||||||
|
|
||||||
|
* Initial release of {{TITLE}}.
|
||||||
|
|
||||||
|
## 🌐 Multilingual Support
|
||||||
|
|
||||||
|
Supports automatic interface and status switching for the following languages:
|
||||||
|
`English`, `简体中文`, `繁體中文 (香港)`, `繁體中文 (台灣)`, `한국어`, `日本語`, `Français`, `Deutsch`, `Español`, `Italiano`, `Tiếng Việt`, `Bahasa Indonesia`.
|
||||||
|
|
||||||
|
## ✨ Core Features
|
||||||
|
|
||||||
|
* Feature 1
|
||||||
|
* Feature 2
|
||||||
|
|
||||||
|
## How to Use 🛠️
|
||||||
|
|
||||||
|
1. Install the plugin in Open WebUI.
|
||||||
|
2. Configure settings in Valves.
|
||||||
|
|
||||||
|
## Configuration (Valves) ⚙️
|
||||||
|
|
||||||
|
| Parameter | Default | Description |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `priority` | `50` | Execution priority. |
|
||||||
|
|
||||||
|
## ⭐ Support
|
||||||
|
|
||||||
|
If this plugin has been useful, a star on [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) is a big motivation for me. Thank you for the support.
|
||||||
80
.gemini/skills/plugin-scaffolder/assets/template.py
Normal file
80
.gemini/skills/plugin-scaffolder/assets/template.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
"""
|
||||||
|
title: {{TITLE}}
|
||||||
|
author: Fu-Jie
|
||||||
|
author_url: https://github.com/Fu-Jie/openwebui-extensions
|
||||||
|
funding_url: https://github.com/open-webui
|
||||||
|
version: 0.1.0
|
||||||
|
description: {{DESCRIPTION}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
from typing import Optional, Dict, Any, List, Callable, Awaitable
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from fastapi import Request
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
TRANSLATIONS = {
|
||||||
|
"en-US": {"status_starting": "Starting {{TITLE}}..."},
|
||||||
|
"zh-CN": {"status_starting": "正在启动 {{TITLE}}..."},
|
||||||
|
"zh-HK": {"status_starting": "正在啟動 {{TITLE}}..."},
|
||||||
|
"zh-TW": {"status_starting": "正在啟動 {{TITLE}}..."},
|
||||||
|
"ko-KR": {"status_starting": "{{TITLE}} 시작 중..."},
|
||||||
|
"ja-JP": {"status_starting": "{{TITLE}} を起動中..."},
|
||||||
|
"fr-FR": {"status_starting": "Démarrage de {{TITLE}}..."},
|
||||||
|
"de-DE": {"status_starting": "{{TITLE}} wird gestartet..."},
|
||||||
|
"es-ES": {"status_starting": "Iniciando {{TITLE}}..."},
|
||||||
|
"it-IT": {"status_starting": "Avvio di {{TITLE}}..."},
|
||||||
|
"vi-VN": {"status_starting": "Đang khởi động {{TITLE}}..."},
|
||||||
|
"id-ID": {"status_starting": "Memulai {{TITLE}}..."},
|
||||||
|
}
|
||||||
|
|
||||||
|
class {{CLASS_NAME}}:
|
||||||
|
class Valves(BaseModel):
|
||||||
|
priority: int = Field(default=50, description="Priority level (lower = earlier).")
|
||||||
|
show_status: bool = Field(default=True, description="Show status updates in UI.")
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.valves = self.Valves()
|
||||||
|
self.fallback_map = {
|
||||||
|
"zh": "zh-CN", "en": "en-US", "ko": "ko-KR", "ja": "ja-JP",
|
||||||
|
"fr": "fr-FR", "de": "de-DE", "es": "es-ES", "it": "it-IT",
|
||||||
|
"vi": "vi-VN", "id": "id-ID"
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_translation(self, lang: str, key: str, **kwargs) -> str:
|
||||||
|
target_lang = lang
|
||||||
|
if target_lang not in TRANSLATIONS:
|
||||||
|
base = target_lang.split("-")[0]
|
||||||
|
target_lang = self.fallback_map.get(base, "en-US")
|
||||||
|
|
||||||
|
lang_dict = TRANSLATIONS.get(target_lang, TRANSLATIONS["en-US"])
|
||||||
|
text = lang_dict.get(key, TRANSLATIONS["en-US"].get(key, key))
|
||||||
|
return text.format(**kwargs) if kwargs else text
|
||||||
|
|
||||||
|
async def _get_user_context(self, __user__: Optional[dict], __event_call__: Optional[Callable] = None, __request__: Optional[Request] = None) -> dict:
|
||||||
|
user_data = __user__ if isinstance(__user__, dict) else {}
|
||||||
|
user_language = user_data.get("language", "en-US")
|
||||||
|
if __event_call__:
|
||||||
|
try:
|
||||||
|
js = "try { return (document.documentElement.lang || localStorage.getItem('locale') || navigator.language || 'en-US'); } catch (e) { return 'en-US'; }"
|
||||||
|
frontend_lang = await asyncio.wait_for(__event_call__({"type": "execute", "data": {"code": js}}), timeout=2.0)
|
||||||
|
if frontend_lang: user_language = frontend_lang
|
||||||
|
except: pass
|
||||||
|
return {"user_language": user_language}
|
||||||
|
|
||||||
|
async def {{METHOD_NAME}}(self, body: dict, __user__: Optional[dict] = None, __event_emitter__=None, __event_call__=None, __request__: Optional[Request] = None) -> dict:
|
||||||
|
if self.valves.show_status and __event_emitter__:
|
||||||
|
user_ctx = await self._get_user_context(__user__, __event_call__, __request__)
|
||||||
|
msg = self._get_translation(user_ctx["user_language"], "status_starting")
|
||||||
|
await __event_emitter__({"type": "status", "data": {"description": msg, "done": False}})
|
||||||
|
|
||||||
|
# Implement core logic here
|
||||||
|
|
||||||
|
if self.valves.show_status and __event_emitter__:
|
||||||
|
await __event_emitter__({"type": "status", "data": {"description": "Done", "done": True}})
|
||||||
|
return body
|
||||||
43
.gemini/skills/plugin-scaffolder/scripts/scaffold.py
Normal file
43
.gemini/skills/plugin-scaffolder/scripts/scaffold.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
def scaffold(p_type, p_name, title, desc):
|
||||||
|
target_dir = f"plugins/{p_type}/{p_name}"
|
||||||
|
os.makedirs(target_dir, exist_ok=True)
|
||||||
|
|
||||||
|
class_name = "Action" if p_type == "actions" else "Filter" if p_type == "filters" else "Pipe"
|
||||||
|
method_name = "action" if p_type == "actions" else "outlet" if p_type == "filters" else "pipe"
|
||||||
|
|
||||||
|
replacements = {
|
||||||
|
"{{TITLE}}": title,
|
||||||
|
"{{DESCRIPTION}}": desc,
|
||||||
|
"{{CLASS_NAME}}": class_name,
|
||||||
|
"{{METHOD_NAME}}": method_name
|
||||||
|
}
|
||||||
|
|
||||||
|
# Files to generate
|
||||||
|
templates = {
|
||||||
|
"assets/template.py": f"{p_name}.py",
|
||||||
|
"assets/README_template.md": "README.md",
|
||||||
|
"assets/README_template.md": "README_CN.md" # Simplified for now, in real use we'd have a CN template
|
||||||
|
}
|
||||||
|
|
||||||
|
# Path relative to skill root
|
||||||
|
skill_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
for t_path, t_name in templates.items():
|
||||||
|
with open(os.path.join(skill_root, t_path), 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
for k, v in replacements.items():
|
||||||
|
content = content.replace(k, v)
|
||||||
|
|
||||||
|
with open(os.path.join(target_dir, t_name), 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
print(f"✅ Generated: {target_dir}/{t_name}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) < 5:
|
||||||
|
print("Usage: scaffold.py <type> <name> <title> <desc>")
|
||||||
|
sys.exit(1)
|
||||||
|
scaffold(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])
|
||||||
26
.gemini/skills/version-bumper/SKILL.md
Normal file
26
.gemini/skills/version-bumper/SKILL.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: version-bumper
|
||||||
|
description: Automates version upgrades and changelog synchronization across 7+ files (Code, READMEs, Docs). Use when a plugin is ready for release to ensure version consistency.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Version Bumper
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This skill ensures that every version upgrade is synchronized across the entire repository, following the strict "Documentation Sync" rule in GEMINI.md.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
1. **Prepare Info**: Gather the new version number and brief changelogs in both English and Chinese.
|
||||||
|
2. **Auto-Patch**: The skill will help you identify and update:
|
||||||
|
- `plugins/.../name.py` (docstring version)
|
||||||
|
- `plugins/.../README.md` (metadata & What's New)
|
||||||
|
- `plugins/.../README_CN.md` (metadata & 最新更新)
|
||||||
|
- `docs/plugins/...md` (mirrors)
|
||||||
|
- `docs/plugins/index.md` (version badge)
|
||||||
|
- `README.md` (updated date badge)
|
||||||
|
3. **Verify**: Check the diffs to ensure no formatting was broken.
|
||||||
|
|
||||||
|
## Tool Integration
|
||||||
|
Execute the bump script (draft):
|
||||||
|
```bash
|
||||||
|
python3 scripts/bump.py <version> "<message_en>" "<message_zh>"
|
||||||
|
```
|
||||||
70
.gemini/skills/version-bumper/scripts/bump.py
Normal file
70
.gemini/skills/version-bumper/scripts/bump.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def patch_file(file_path, old_pattern, new_content, is_regex=False):
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
print(f"Warning: File not found: {file_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
if is_regex:
|
||||||
|
new_content_result = re.sub(old_pattern, new_content, content, flags=re.MULTILINE)
|
||||||
|
else:
|
||||||
|
new_content_result = content.replace(old_pattern, new_content)
|
||||||
|
|
||||||
|
if new_content_result != content:
|
||||||
|
with open(file_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(new_content_result)
|
||||||
|
print(f"✅ Patched: {file_path}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"ℹ️ No change needed: {file_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def bump_version(plugin_type, plugin_name, new_version, msg_en, msg_zh):
|
||||||
|
print(f"🚀 Bumping {plugin_name} ({plugin_type}) to {new_version}...")
|
||||||
|
|
||||||
|
today = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
today_badge = today.replace("-", "--")
|
||||||
|
|
||||||
|
# 1. Patch Plugin Python File
|
||||||
|
py_file = f"plugins/{plugin_type}/{plugin_name}/{plugin_name}.py"
|
||||||
|
patch_file(py_file, r"version: \d+\.\d+\.\d+", f"version: {new_version}", is_regex=True)
|
||||||
|
|
||||||
|
# 2. Patch Plugin READMEs
|
||||||
|
readme_en = f"plugins/{plugin_type}/{plugin_name}/README.md"
|
||||||
|
readme_zh = f"plugins/{plugin_type}/{plugin_name}/README_CN.md"
|
||||||
|
|
||||||
|
# Update version in metadata
|
||||||
|
patch_file(readme_en, r"\*\*Version:\*\* \d+\.\d+\.\d+", f"**Version:** {new_version}", is_regex=True)
|
||||||
|
patch_file(readme_zh, r"\*\*版本:\*\* \d+\.\d+\.\d+", f"**版本:** {new_version}", is_regex=True)
|
||||||
|
|
||||||
|
# Update What's New (Assuming standard headers)
|
||||||
|
patch_file(readme_en, r"## 🔥 What's New in v.*?\n", f"## 🔥 What's New in v{new_version}\n\n* {msg_en}\n", is_regex=True)
|
||||||
|
patch_file(readme_zh, r"## 🔥 最新更新 v.*?\n", f"## 🔥 最新更新 v{new_version}\n\n* {msg_zh}\n", is_regex=True)
|
||||||
|
|
||||||
|
# 3. Patch Docs Mirrors
|
||||||
|
doc_en = f"docs/plugins/{plugin_type}/{plugin_name}.md"
|
||||||
|
doc_zh = f"docs/plugins/{plugin_type}/{plugin_name}.zh.md"
|
||||||
|
patch_file(doc_en, r"\*\*Version:\*\* \d+\.\d+\.\d+", f"**Version:** {new_version}", is_regex=True)
|
||||||
|
patch_file(doc_zh, r"\*\*版本:\*\* \d+\.\d+\.\d+", f"**版本:** {new_version}", is_regex=True)
|
||||||
|
|
||||||
|
# 4. Patch Root READMEs (Updated Date Badge)
|
||||||
|
patch_file("README.md", r"badge/202\d--\d\d--\d\d-gray", f"badge/{today_badge}-gray", is_regex=True)
|
||||||
|
patch_file("README_CN.md", r"badge/202\d--\d\d--\d\d-gray", f"badge/{today_badge}-gray", is_regex=True)
|
||||||
|
|
||||||
|
print("\n✨ All synchronization tasks completed.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) < 6:
|
||||||
|
print("Usage: bump.py <type> <name> <version> <msg_en> <msg_zh>")
|
||||||
|
print("Example: bump.py filters markdown_normalizer 1.2.8 'Fix bug' '修复错误'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
bump_version(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5])
|
||||||
Reference in New Issue
Block a user