
Skills Updater
- 452 installs
- 173 repo stars
- Updated January 22, 2026
- yizhiyanhua-ai/skills-updater
skills-updater is a Claude agent skill from yizhiyanhua-ai/skills-updater that helps developers manage and refresh AI agent skill packages during AI and agent building workflows.
About
skills-updater is a lightweight agent skill published under yizhiyanhua-ai/skills-updater with the catalog description of helping with AI and agent building tasks. The skill targets developers who maintain Claude Code or similar agent skill libraries and need assistance keeping skill definitions current. With no bundled readme excerpt in the catalog record, specifics are inferred from the skill name: updating, syncing, or revising skill metadata and instructions. Developers reach for skills-updater when agent skill maintenance is part of an active build session rather than a one-off prompt.
- skills-updater
- AI & Agent Building
- AI-coding skill
Skills Updater by the numbers
- 452 all-time installs (skills.sh)
- +4 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #1,887 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/yizhiyanhua-ai/skills-updater --skill skills-updaterAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 452 |
|---|---|
| repo stars | ★ 173 |
| Last updated | January 22, 2026 |
| Repository | yizhiyanhua-ai/skills-updater ↗ |
How do you update Claude agent skill packages?
Helps with ai & agent building tasks.
Who is it for?
Developers maintaining yizhiyanhua-ai agent skill libraries who need guided skill refresh during build sessions.
Skip if: Teams needing a documented, versioned skill sync CLI when the catalog provides no readme or manifest details.
When should I use this skill?
An agent build session requires updating, syncing, or revising skill package definitions.
What you get
Revised agent skill definitions and updated skill metadata ready for agent sessions.
Files
Skills Updater
Manage, update, and discover Claude Code skills across multiple installation sources.
Internationalization (i18n)
All scripts automatically detect user locale from environment variables and display output in the appropriate language.
Supported Languages:
- English (en) - Default
- Chinese (zh) - 中文
Auto-detection order: 1. LANG environment variable 2. LC_ALL environment variable 3. LANGUAGE environment variable 4. System locale
Manual override:
python scripts/check_updates.py --lang zh # Force Chinese
python scripts/check_updates.py --lang en # Force EnglishSupported Sources
Claude Code Plugins (~/.claude/plugins/):
installed_plugins.json- Tracks installed skills with versionsknown_marketplaces.json- Registered marketplace sourcescache/- Installed skill files
npx skills (~/.skills/ if present):
- Skills installed via
npx skills add <owner/repo> - Managed by skills.sh infrastructure
Update Check Workflow
Step 1: Scan Installed Skills
python scripts/check_updates.pyOutput format:
📦 Installed Skills Status
━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Up-to-date (12):
• skill-creator@daymade-skills (1.2.2)
• github-ops@daymade-skills (1.0.0)
...
⬆️ Updates Available (3):
• planning-with-files@planning-with-files
Local: 2.5.0 → Remote: 2.6.1
• superpowers@superpowers-marketplace
Local: 4.0.3 → Remote: 4.1.0
...
⚠️ Unknown Version (2):
• document-skills@anthropic-agent-skills (unknown)
...Step 2: Confirm Update Strategy
Present options to user: 1. Update All - Update all skills with available updates 2. Select Individual - Let user choose specific skills to update 3. Skip - Cancel the update process
Step 3: Handle Local Modifications
Before updating, check for local modifications:
# Check if local skill has uncommitted changes
cd ~/.claude/plugins/cache/<marketplace>/<skill>/<version>
git status --porcelainIf local changes detected: 1. Create backup of modified files 2. Pull remote updates 3. Attempt 3-way merge 4. If conflicts:
- Show conflict files to user
- Offer manual resolution or keep local version
Step 4: Execute Update
For Claude Code plugins:
# Trigger marketplace refresh and skill reinstall
# This uses Claude Code's built-in update mechanism
claude /install <skill-name>@<marketplace>For npx skills:
npx skills add <owner/repo> --forceAuto-Install After Marketplace Update
The update_marketplace.py script can automatically reinstall affected skills after updating a marketplace repository.
Usage
# Update marketplace only (show affected skills)
python scripts/update_marketplace.py anthropic-agent-skills
# Update marketplace AND auto-reinstall affected skills
python scripts/update_marketplace.py anthropic-agent-skills --auto-install
# Output as JSON
python scripts/update_marketplace.py anthropic-agent-skills --json
# Force language
python scripts/update_marketplace.py anthropic-agent-skills --lang zhOutput (Chinese locale)
📡 正在获取远程更新...
当前提交: e5c60158df67
远程提交: 69c0b1a06741
状态: 落后 6 个提交
📝 更新内容:
• 69c0b1a Add link to Agent Skills specification website
• be229a5 Fix links in agent skills specification
...
📦 受影响的技能: document-skills
📥 正在更新市场: anthropic-agent-skills
✅ 市场更新成功
🔄 正在重新安装受影响的技能...
正在安装: document-skills
✅ 已安装: document-skills
✅ 所有受影响的技能已更新Workflow
1. Fetch remote - Git fetch to check for updates 2. Compare commits - Show how many commits behind 3. List affected skills - Find installed skills from this marketplace 4. Pull updates - Git pull to update local marketplace 5. Auto-reinstall - (with --auto-install) Reinstall each affected skill
Skill Recommendations
Fetch Trending Skills
python scripts/recommend_skills.py --source allSources:
- skills.sh - Leaderboard ranked by installs
- skillsmp.com - Curated marketplace (if accessible)
Output Format
🔥 Trending Skills
━━━━━━━━━━━━━━━━━━
From skills.sh:
1. vercel-react-best-practices (25.5K installs)
npx skills add vercel/react-best-practices
2. web-design-guidelines (19.2K installs)
npx skills add webdesign/guidelines
3. remotion-best-practices (2.2K installs)
npx skills add remotion/best-practices
💡 Personalized Recommendations:
Based on your installed skills (developer-tools, productivity):
- playwright-skill - Browser automation testing
- github-ops - GitHub CLI operationsInstall Recommended Skill
After showing recommendations, offer to install:
Would you like to install any of these skills?
1. Install by number (e.g., "1" or "1,3,5")
2. Install by name
3. SkipVersion Detection Methods
Primary: marketplace.json
Read version from remote marketplace.json:
curl -s "https://raw.githubusercontent.com/<owner>/<repo>/main/.claude-plugin/marketplace.json" | jq '.plugins[] | select(.name == "<skill>") | .version'Fallback: GitHub API
If marketplace.json unavailable or version not specified:
# Get latest release tag
curl -s "https://api.github.com/repos/<owner>/<repo>/releases/latest" | jq -r '.tag_name'
# Or latest commit on main
curl -s "https://api.github.com/repos/<owner>/<repo>/commits/main" | jq -r '.sha[:7]'Commit SHA Comparison
For skills tracking by commit (e.g., e30768372b41):
# Compare local gitCommitSha with remote HEAD
local_sha=$(jq -r '.plugins["<key>"][0].gitCommitSha' ~/.claude/plugins/installed_plugins.json)
remote_sha=$(curl -s "https://api.github.com/repos/<owner>/<repo>/commits/main" | jq -r '.sha')
if [ "$local_sha" != "$remote_sha" ]; then
echo "Update available"
fiSmart Merge Strategy
When local modifications exist:
1. Identify modified files:
git diff --name-only HEAD2. Categorize changes:
- SKILL.md customizations → Preserve user sections
- scripts/ modifications → Keep local, note for review
- references/ additions → Merge both
- assets/ → Keep both versions if different
3. Merge approach:
# Pseudo-code for smart merge
for file in modified_files:
if file == 'SKILL.md':
merge_skill_md(local, remote) # Preserve user customizations
elif file.startswith('scripts/'):
backup_and_warn(local) # User scripts need review
else:
three_way_merge(base, local, remote)User Interaction Patterns
Check for Updates
User says: "检查 skills 更新" / "check skill updates" / "update my skills"
→ Run scripts/check_updates.py and display results
Update Specific Skill
User says: "更新 skill-creator" / "update skill-creator"
→ Check and update only the specified skill
Discover New Skills
User says: "推荐一些好用的 skills" / "recommend skills" / "popular skills"
→ Run scripts/recommend_skills.py and show curated list
Full Update Workflow
User says: "更新所有 skills" / "update all skills"
→ Scan → Confirm → Handle merges → Update → Report results
Error Handling
Network errors: Retry with exponential backoff, cache last known state
Permission errors: Suggest running with appropriate permissions
Merge conflicts: Show conflict markers, offer resolution options:
- Accept local (keep your changes)
- Accept remote (use upstream)
- Manual merge (show diff)
Missing marketplace: Inform user if source is no longer available
Resources
scripts/
check_updates.py- Scan and compare installed vs remote versionsrecommend_skills.py- Fetch trending skills from marketplacesupdate_marketplace.py- Update marketplace repos and auto-reinstall skillsi18n.py- Internationalization module (locale detection, translations)
references/
marketplaces.md- Supported marketplace documentation
Adding New Languages
To add a new language, edit scripts/i18n.py:
1. Add translations to TRANSLATIONS dict:
TRANSLATIONS["ja"] = {
"checking_updates": "スキルの更新を確認中...",
# ... other translations
}2. Update detect_locale() to recognize the new locale:
if lang_lower.startswith('ja'):
return 'ja'__pycache__/
*.pyc
*.pyo
.DS_Store
Skills Updater
Manage, update, and discover Claude Code skills across multiple installation sources.
中文
Features
- Update Checker - Scan installed skills and check for available updates
- Auto-Install - Automatically reinstall affected skills after marketplace updates
- Skill Recommendations - Discover trending skills from skills.sh
- i18n Support - Auto-detect locale (English/Chinese)
Installation
In Claude Code, simply ask it to clone and install:
Clone https://github.com/yizhiyanhua-ai/skills-updater to ~/.claude/skills/Or just say:
Install the skills-updater skillUsage
After installation, trigger with natural language or commands in Claude Code:
Check for Updates
Check for skill updates/skills-updaterUpdate a Marketplace
Update the anthropic-agent-skills marketplaceDiscover New Skills
Recommend some useful skillsWhat are some popular skills?Examples
Example 1: Check for Updates
You: Check for skill updates
Claude: Checking for skill updates...
📦 Installed Skills Status
━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Up-to-date (15):
• skill-creator@daymade-skills (1.2.2)
...
⬆️ Updates Available (1):
• document-skills@anthropic-agent-skills
Local: e5c60158 → Remote: 69c0b1a0Example 2: Update and Auto-Install
You: Update the anthropic-agent-skills marketplace
Claude:
📡 Fetching remote updates...
Current commit: e5c60158df67
Remote commit: 69c0b1a06741
Status: Behind by 6 commits
📦 Affected skills: document-skills
Confirm update? Enter 'yes' to proceed.
You: yes
Claude:
📥 Updating marketplace: anthropic-agent-skills
✅ Marketplace updated successfully
✅ Reinstalled: document-skillsExample 3: Discover New Skills
You: Recommend some useful skills
Claude:
🔥 Trending Skills
━━━━━━━━━━━━━━━━━━
From skills.sh (Top 10):
1. vercel-react-best-practices (25.5K installs)
2. web-design-guidelines (19.2K installs)
...
💡 Personalized Recommendations
Based on your installed skills:
• playwright-skill - Browser automation testing
• github-ops - GitHub CLI operationsTrigger Summary
| Feature | Natural Language | Command |
|---|---|---|
| Check updates | "Check for skill updates" | /skills-updater |
| Update marketplace | "Update xxx marketplace" | - |
| Skill recommendations | "Recommend some skills" | - |
| Update all | "Update all skills" | - |
Language Support
Automatically displays in English or Chinese based on your system language. Claude Code will auto-detect your locale.
Documentation
- SKILL.md - Complete skill documentation
- references/marketplaces.md - Supported marketplaces list
License
MIT
Skills Updater
管理、更新和发现 Claude Code 技能,支持多种安装来源。
English
功能特性
- 更新检查 - 扫描已安装技能,检查可用更新
- 自动安装 - 市场更新后自动重新安装受影响的技能
- 技能推荐 - 从 skills.sh 发现热门技能
- 国际化支持 - 自动检测语言环境(中文/英文)
安装
在 Claude Code 中输入以下命令,自动克隆并安装:
帮我把 https://github.com/yizhiyanhua-ai/skills-updater 克隆到 ~/.claude/skills/ 目录或者直接说:
安装 skills-updater 技能使用方法
安装完成后,在 Claude Code 中用自然语言或命令触发:
检查技能更新
检查 skills 更新/skills-updater更新指定市场
更新 anthropic-agent-skills 市场发现新技能
推荐一些好用的 skills有什么热门技能推荐?使用示例
示例 1:检查更新
你:检查 skills 更新
Claude:正在检查技能更新...
📦 已安装技能状态
━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 已是最新 (15):
• skill-creator@daymade-skills (1.2.2)
...
⬆️ 有可用更新 (1):
• document-skills@anthropic-agent-skills
本地: e5c60158 → 远程: 69c0b1a0示例 2:更新并自动安装
你:更新 anthropic-agent-skills 市场
Claude:
📡 正在获取远程更新...
当前提交: e5c60158df67
远程提交: 69c0b1a06741
状态: 落后 6 个提交
📦 受影响的技能: document-skills
是否要更新?输入「是」确认。
你:是
Claude:
📥 正在更新市场: anthropic-agent-skills
✅ 市场更新成功
✅ 已重新安装: document-skills示例 3:发现新技能
你:推荐一些好用的 skills
Claude:
🔥 热门技能
━━━━━━━━━━━━━━━━━━
来自 skills.sh (前 10 名):
1. vercel-react-best-practices (25.5K 次安装)
2. web-design-guidelines (19.2K 次安装)
...
💡 个性化推荐
基于您已安装的技能:
• playwright-skill - 浏览器自动化测试
• github-ops - GitHub CLI 操作触发方式汇总
| 功能 | 自然语言 | 命令 |
|---|---|---|
| 检查更新 | "检查 skills 更新" | /skills-updater |
| 更新市场 | "更新 xxx 市场" | - |
| 技能推荐 | "推荐一些 skills" | - |
| 更新全部 | "更新所有 skills" | - |
语言支持
自动根据系统语言显示中文或英文。Claude Code 会自动检测您的语言环境。
详细文档
- SKILL.md - 完整技能文档
- references/marketplaces.md - 支持的市场列表
许可证
MIT
Supported Skill Marketplaces
This document lists the skill marketplaces supported by the skills-updater.
Claude Code Plugins (Official)
anthropics/skills
- URL: https://github.com/anthropics/skills
- Type: Official Anthropic example skills
- Install:
claude /install <skill-name>@anthropic-agent-skills - Notable Skills: document-skills (xlsx, docx, pptx, pdf), frontend-design, canvas-design
anthropics/claude-plugins-official
- URL: https://github.com/anthropics/claude-plugins-official
- Type: Official Claude plugins collection
- Install:
claude /install <plugin-name>@claude-plugins-official - Notable Plugins: hookify, github, playwright, code-review, commit-commands
Community Marketplaces
daymade/claude-code-skills
- URL: https://github.com/daymade/claude-code-skills
- Type: Community skills collection
- Install:
claude /install <skill-name>@daymade-skills - Notable Skills: skill-creator, github-ops, youtube-downloader, macos-cleaner, fact-checker
obra/superpowers-marketplace
- URL: https://github.com/obra/superpowers-marketplace
- Type: Extended capabilities marketplace
- Install:
claude /install <skill-name>@superpowers-marketplace - Notable Skills: superpowers, double-shot-latte
kepano/obsidian-skills
- URL: https://github.com/kepano/obsidian-skills
- Type: Obsidian integration skills
- Install:
claude /install obsidian@obsidian-skills
lackeyjb/playwright-skill
- URL: https://github.com/lackeyjb/playwright-skill
- Type: Browser automation skill
- Install:
claude /install playwright-skill@playwright-skill
OthmanAdi/planning-with-files
- URL: https://github.com/OthmanAdi/planning-with-files
- Type: File-based planning workflow
- Install:
claude /install planning-with-files@planning-with-files
npx skills Marketplaces
skills.sh
- URL: https://skills.sh/
- Type: Community skills leaderboard
- Install:
npx skills add <owner/repo> - Features:
- Install count rankings
- Category browsing
- One-command installation
skillsmp.com
- URL: https://skillsmp.com/
- Type: Curated skills marketplace
- Install:
npx skills add <owner/repo> - Note: May require authentication or have access restrictions
Version Tracking Mechanisms
Semantic Versioning (marketplace.json)
Most marketplaces use semantic versioning in their marketplace.json:
{
"plugins": [
{
"name": "skill-name",
"version": "1.2.3",
...
}
]
}Commit SHA Tracking
Some skills track by git commit SHA instead of semantic version:
{
"installPath": "...",
"version": "e30768372b41",
"gitCommitSha": "e30768372b41c97d13054211657275029ca8b6d"
}Auto-Update Configuration
Some marketplaces support auto-update flags in known_marketplaces.json:
{
"planning-with-files": {
"autoUpdate": true
}
}Adding New Marketplaces
To register a new marketplace:
# Via GitHub repository
claude /marketplace add github:<owner>/<repo>
# Via git URL
claude /marketplace add git:https://github.com/<owner>/<repo>.gitData Locations
| File | Purpose |
|---|---|
~/.claude/plugins/installed_plugins.json | Tracks installed skills with versions |
~/.claude/plugins/known_marketplaces.json | Registered marketplace sources |
~/.claude/plugins/cache/ | Downloaded skill files |
~/.claude/plugins/marketplaces/ | Cloned marketplace repositories |
~/.skills/ | npx skills installation directory |
#!/usr/bin/env python3
"""
Skill Update Checker - Scans installed skills and checks for available updates.
Usage:
python check_updates.py [--skill <name>] [--json]
Examples:
python check_updates.py # Check all installed skills
python check_updates.py --skill skill-creator # Check specific skill
python check_updates.py --json # Output as JSON
"""
import json
import sys
import argparse
import io
from pathlib import Path
# Fix Windows console encoding
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
from enum import Enum
import urllib.request
import urllib.error
# Import i18n module
script_dir = Path(__file__).parent
sys.path.insert(0, str(script_dir))
from i18n import get_i18n, t # noqa: E402
class UpdateStatus(Enum):
UP_TO_DATE = "up_to_date"
UPDATE_AVAILABLE = "update_available"
UNKNOWN_VERSION = "unknown_version"
ERROR = "error"
@dataclass
class SkillInfo:
name: str
marketplace: str
local_version: str
remote_version: Optional[str]
status: UpdateStatus
install_path: str
git_commit_sha: Optional[str] = None
remote_commit_sha: Optional[str] = None
error_message: Optional[str] = None
def get_plugins_dir() -> Path:
"""Get the Claude Code plugins directory."""
return Path.home() / ".claude" / "plugins"
def load_installed_plugins() -> Dict:
"""Load the installed_plugins.json file."""
plugins_file = get_plugins_dir() / "installed_plugins.json"
if not plugins_file.exists():
return {"version": 2, "plugins": {}}
with open(plugins_file, encoding='utf-8') as f:
return json.load(f)
def load_known_marketplaces() -> Dict:
"""Load the known_marketplaces.json file."""
marketplaces_file = get_plugins_dir() / "known_marketplaces.json"
if not marketplaces_file.exists():
return {}
with open(marketplaces_file, encoding='utf-8') as f:
return json.load(f)
def parse_plugin_key(key: str) -> Tuple[str, str]:
"""Parse plugin key into (skill_name, marketplace)."""
parts = key.rsplit("@", 1)
if len(parts) == 2:
return parts[0], parts[1]
return key, "unknown"
def get_github_repo_from_marketplace(marketplace_name: str, marketplaces: Dict) -> Optional[str]:
"""Get the GitHub repo from marketplace info."""
marketplace_info = marketplaces.get(marketplace_name, {})
source = marketplace_info.get("source", {})
if source.get("source") == "github":
return source.get("repo")
elif source.get("source") == "git":
url = source.get("url", "")
# Parse git URL to get owner/repo
if "github.com" in url:
# Handle formats: https://github.com/owner/repo.git or git@github.com:owner/repo.git
url = url.replace(".git", "")
if "github.com/" in url:
return url.split("github.com/")[-1]
elif "github.com:" in url:
return url.split("github.com:")[-1]
return None
def fetch_remote_marketplace_json(repo: str) -> Optional[Dict]:
"""Fetch marketplace.json from GitHub repo."""
url = f"https://raw.githubusercontent.com/{repo}/main/.claude-plugin/marketplace.json"
try:
req = urllib.request.Request(url, headers={"User-Agent": "skills-updater/1.0"})
with urllib.request.urlopen(req, timeout=10) as response:
return json.loads(response.read().decode())
except urllib.error.HTTPError as e:
if e.code == 404:
# Try HEAD branch instead of main
url_head = url.replace("/main/", "/HEAD/")
try:
req = urllib.request.Request(url_head, headers={"User-Agent": "skills-updater/1.0"})
with urllib.request.urlopen(req, timeout=10) as response:
return json.loads(response.read().decode())
except Exception:
pass
return None
except Exception:
return None
def fetch_remote_commit_sha(repo: str) -> Optional[str]:
"""Fetch the latest commit SHA from GitHub."""
url = f"https://api.github.com/repos/{repo}/commits/main"
try:
req = urllib.request.Request(url, headers={
"User-Agent": "skills-updater/1.0",
"Accept": "application/vnd.github.v3+json"
})
with urllib.request.urlopen(req, timeout=10) as response:
data = json.loads(response.read().decode())
return data.get("sha")
except Exception:
# Try HEAD branch
url_head = url.replace("/main", "/HEAD")
try:
req = urllib.request.Request(url_head, headers={
"User-Agent": "skills-updater/1.0",
"Accept": "application/vnd.github.v3+json"
})
with urllib.request.urlopen(req, timeout=10) as response:
data = json.loads(response.read().decode())
return data.get("sha")
except Exception:
return None
def get_skill_version_from_marketplace_json(marketplace_json: Dict, skill_name: str) -> Optional[str]:
"""Extract skill version from marketplace.json."""
plugins = marketplace_json.get("plugins", [])
for plugin in plugins:
if plugin.get("name") == skill_name:
return plugin.get("version")
return None
def compare_versions(local: str, remote: str) -> bool:
"""Compare versions. Returns True if remote is newer."""
if local == remote:
return False
# Handle unknown versions
if local in ["unknown", "", None]:
return True
# Try semantic version comparison
try:
local_parts = [int(x) for x in local.split(".")]
remote_parts = [int(x) for x in remote.split(".")]
# Pad shorter version with zeros
max_len = max(len(local_parts), len(remote_parts))
local_parts.extend([0] * (max_len - len(local_parts)))
remote_parts.extend([0] * (max_len - len(remote_parts)))
return remote_parts > local_parts
except Exception:
# Fall back to string comparison
return local != remote
def compare_commit_sha(local_sha: Optional[str], remote_sha: Optional[str]) -> bool:
"""Compare commit SHAs. Returns True if different."""
if not local_sha or not remote_sha:
return False
# Handle short SHA comparison
min_len = min(len(local_sha), len(remote_sha))
return local_sha[:min_len] != remote_sha[:min_len]
def check_skill_update(skill_name: str, marketplace: str, plugin_info: Dict, marketplaces: Dict) -> SkillInfo:
"""Check if a skill has an available update."""
local_version = plugin_info.get("version", "unknown")
install_path = plugin_info.get("installPath", "")
git_commit_sha = plugin_info.get("gitCommitSha")
# Get GitHub repo
repo = get_github_repo_from_marketplace(marketplace, marketplaces)
if not repo:
return SkillInfo(
name=skill_name,
marketplace=marketplace,
local_version=local_version,
remote_version=None,
status=UpdateStatus.ERROR,
install_path=install_path,
git_commit_sha=git_commit_sha,
error_message="Could not determine GitHub repo"
)
# Try to get remote version from marketplace.json
remote_marketplace = fetch_remote_marketplace_json(repo)
remote_version = None
if remote_marketplace:
remote_version = get_skill_version_from_marketplace_json(remote_marketplace, skill_name)
# Fetch remote commit SHA as fallback
remote_commit = fetch_remote_commit_sha(repo)
# Determine update status
if local_version in ["unknown", "", None]:
# Unknown local version - check by commit
if git_commit_sha and remote_commit:
if compare_commit_sha(git_commit_sha, remote_commit):
status = UpdateStatus.UPDATE_AVAILABLE
else:
status = UpdateStatus.UP_TO_DATE
else:
status = UpdateStatus.UNKNOWN_VERSION
elif remote_version:
# Have both versions - compare them
if compare_versions(local_version, remote_version):
status = UpdateStatus.UPDATE_AVAILABLE
else:
status = UpdateStatus.UP_TO_DATE
elif remote_commit and git_commit_sha:
# No version but have commits - compare commits
if compare_commit_sha(git_commit_sha, remote_commit):
status = UpdateStatus.UPDATE_AVAILABLE
else:
status = UpdateStatus.UP_TO_DATE
else:
status = UpdateStatus.UNKNOWN_VERSION
return SkillInfo(
name=skill_name,
marketplace=marketplace,
local_version=local_version,
remote_version=remote_version,
status=status,
install_path=install_path,
git_commit_sha=git_commit_sha,
remote_commit_sha=remote_commit[:12] if remote_commit else None
)
def check_all_updates(filter_skill: Optional[str] = None) -> List[SkillInfo]:
"""Check updates for all installed skills."""
installed = load_installed_plugins()
marketplaces = load_known_marketplaces()
results = []
for key, plugin_list in installed.get("plugins", {}).items():
if not plugin_list:
continue
skill_name, marketplace = parse_plugin_key(key)
if filter_skill and skill_name != filter_skill:
continue
# Use the first (usually only) plugin entry
plugin_info = plugin_list[0]
skill_info = check_skill_update(skill_name, marketplace, plugin_info, marketplaces)
results.append(skill_info)
return results
def print_results(results: List[SkillInfo], as_json: bool = False):
"""Print the update check results."""
if as_json:
output = []
for r in results:
output.append({
"name": r.name,
"marketplace": r.marketplace,
"local_version": r.local_version,
"remote_version": r.remote_version,
"status": r.status.value,
"install_path": r.install_path,
"git_commit_sha": r.git_commit_sha,
"remote_commit_sha": r.remote_commit_sha,
"error_message": r.error_message
})
print(json.dumps(output, indent=2))
return
# Group by status
up_to_date = [r for r in results if r.status == UpdateStatus.UP_TO_DATE]
updates_available = [r for r in results if r.status == UpdateStatus.UPDATE_AVAILABLE]
unknown = [r for r in results if r.status == UpdateStatus.UNKNOWN_VERSION]
errors = [r for r in results if r.status == UpdateStatus.ERROR]
print(f"📦 {t('installed_skills_status')}")
print("━" * 26)
print()
if up_to_date:
print(f"✅ {t('up_to_date')} ({len(up_to_date)}):")
for r in up_to_date:
version_str = r.local_version
if r.git_commit_sha and r.local_version in ["unknown", ""]:
version_str = r.git_commit_sha[:12]
print(f" • {r.name}@{r.marketplace} ({version_str})")
print()
if updates_available:
print(f"⬆️ {t('updates_available')} ({len(updates_available)}):")
for r in updates_available:
local_str = r.local_version
remote_str = r.remote_version or r.remote_commit_sha or "newer"
if r.local_version in ["unknown", ""]:
local_str = r.git_commit_sha[:12] if r.git_commit_sha else "unknown"
print(f" • {r.name}@{r.marketplace}")
print(f" {t('local')}: {local_str} → {t('remote')}: {remote_str}")
print()
if unknown:
print(f"⚠️ {t('unknown_version')} ({len(unknown)}):")
for r in unknown:
print(f" • {r.name}@{r.marketplace} ({r.local_version})")
print()
if errors:
print(f"❌ {t('errors')} ({len(errors)}):")
for r in errors:
print(f" • {r.name}@{r.marketplace}: {r.error_message}")
print()
# Summary
print("━" * 26)
print(f"{t('total')}: {len(results)} {t('skills')} | "
f"{len(updates_available)} {t('updates_available_count')}")
def main():
parser = argparse.ArgumentParser(description="Check for skill updates")
parser.add_argument("--skill", help="Check specific skill only")
parser.add_argument("--json", action="store_true", help="Output as JSON")
parser.add_argument("--lang", choices=["en", "zh"],
help="Language for output (auto-detected if not specified)")
args = parser.parse_args()
# Initialize i18n
if args.lang:
get_i18n(args.lang)
if not args.json:
print(f"🔍 {t('checking_updates')}\n")
results = check_all_updates(filter_skill=args.skill)
if not results:
if args.skill:
print(t('skill_not_found', skill=args.skill))
else:
print(t('no_installed_skills'))
sys.exit(1)
print_results(results, as_json=args.json)
# Exit with code 1 if updates available (useful for CI/CD)
updates_available = any(r.status == UpdateStatus.UPDATE_AVAILABLE for r in results)
sys.exit(0 if not updates_available else 1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Internationalization (i18n) module for skills-updater.
Detects user locale from environment and provides translated strings.
Supports: English (en), Chinese (zh)
"""
import os
import locale
from typing import Dict, Optional
# Translation dictionaries
TRANSLATIONS: Dict[str, Dict[str, str]] = {
"en": {
# Check updates
"checking_updates": "Checking for skill updates...",
"installed_skills_status": "Installed Skills Status",
"up_to_date": "Up-to-date",
"updates_available": "Updates Available",
"unknown_version": "Unknown Version",
"errors": "Errors",
"total": "Total",
"skills": "skills",
"updates_available_count": "updates available",
"skill_not_found": "Skill '{skill}' not found.",
"no_installed_skills": "No installed skills found.",
"local": "Local",
"remote": "Remote",
# Update marketplace
"updating_marketplace": "Updating marketplace: {marketplace}",
"marketplace_up_to_date": "Marketplace is up to date",
"marketplace_updated": "Marketplace updated successfully",
"commits_behind": "Behind by {count} commit(s)",
"update_content": "Update content",
"affected_skills": "Affected skills",
"reinstalling_skills": "Reinstalling affected skills...",
"reinstalling_skill": "Reinstalling: {skill}",
"skill_reinstalled": "Reinstalled: {skill}",
"skill_reinstall_failed": "Failed to reinstall: {skill}",
"all_skills_updated": "All affected skills have been updated",
"no_affected_skills": "No installed skills affected by this update",
"confirm_update": "Confirm update? Enter 'yes' to proceed",
"update_cancelled": "Update cancelled",
"fetching_remote": "Fetching remote updates...",
"current_commit": "Current commit",
"remote_commit": "Remote commit",
"status": "Status",
# Recommendations
"fetching_recommendations": "Fetching skill recommendations...",
"trending_skills": "Trending Skills",
"from_skills_sh": "From skills.sh",
"top_n": "Top {n}",
"installs": "installs",
"personalized_recommendations": "Personalized Recommendations",
"based_on_installed": "Based on your installed skills:",
"install_hint": "Install: claude /install <skill-name>@<marketplace>",
"install_hint_npx": " or: npx skills add <owner/repo>",
"could_not_fetch": "Could not fetch trending skills.",
# Common
"yes": "yes",
"no": "no",
"error": "Error",
"warning": "Warning",
"success": "Success",
},
"zh": {
# Check updates
"checking_updates": "正在检查技能更新...",
"installed_skills_status": "已安装技能状态",
"up_to_date": "已是最新",
"updates_available": "有可用更新",
"unknown_version": "版本未知",
"errors": "错误",
"total": "总计",
"skills": "个技能",
"updates_available_count": "个可更新",
"skill_not_found": "未找到技能 '{skill}'",
"no_installed_skills": "未找到已安装的技能",
"local": "本地",
"remote": "远程",
# Update marketplace
"updating_marketplace": "正在更新市场: {marketplace}",
"marketplace_up_to_date": "市场已是最新",
"marketplace_updated": "市场更新成功",
"commits_behind": "落后 {count} 个提交",
"update_content": "更新内容",
"affected_skills": "受影响的技能",
"reinstalling_skills": "正在重新安装受影响的技能...",
"reinstalling_skill": "正在安装: {skill}",
"skill_reinstalled": "已安装: {skill}",
"skill_reinstall_failed": "安装失败: {skill}",
"all_skills_updated": "所有受影响的技能已更新",
"no_affected_skills": "此更新不影响已安装的技能",
"confirm_update": "确认更新?输入 '是' 继续",
"update_cancelled": "更新已取消",
"fetching_remote": "正在获取远程更新...",
"current_commit": "当前提交",
"remote_commit": "远程提交",
"status": "状态",
# Recommendations
"fetching_recommendations": "正在获取技能推荐...",
"trending_skills": "热门技能",
"from_skills_sh": "来自 skills.sh",
"top_n": "前 {n} 名",
"installs": "次安装",
"personalized_recommendations": "个性化推荐",
"based_on_installed": "基于您已安装的技能:",
"install_hint": "安装命令: claude /install <技能名>@<市场>",
"install_hint_npx": " 或: npx skills add <owner/repo>",
"could_not_fetch": "无法获取热门技能",
# Common
"yes": "是",
"no": "否",
"error": "错误",
"warning": "警告",
"success": "成功",
}
}
def detect_locale() -> str:
"""
Detect user's preferred language from environment.
Checks in order:
1. LANG environment variable
2. LC_ALL environment variable
3. LANGUAGE environment variable
4. System locale
Returns: 'zh' for Chinese, 'en' for others
"""
# Check environment variables
for env_var in ['LANG', 'LC_ALL', 'LANGUAGE', 'LC_MESSAGES']:
lang = os.environ.get(env_var, '')
if lang:
lang_lower = lang.lower()
if lang_lower.startswith('zh') or 'chinese' in lang_lower:
return 'zh'
elif lang_lower.startswith('en'):
return 'en'
# Try system locale
try:
system_locale = locale.getlocale()[0]
if system_locale:
if system_locale.lower().startswith('zh'):
return 'zh'
except Exception:
pass
# Default to English
return 'en'
class I18n:
"""Internationalization helper class."""
def __init__(self, lang: Optional[str] = None):
"""
Initialize with specified language or auto-detect.
Args:
lang: Language code ('en', 'zh') or None for auto-detect
"""
self.lang = lang or detect_locale()
self.translations = TRANSLATIONS.get(self.lang, TRANSLATIONS['en'])
def t(self, key: str, **kwargs) -> str:
"""
Get translated string.
Args:
key: Translation key
**kwargs: Format arguments
Returns:
Translated and formatted string
"""
text = self.translations.get(key, TRANSLATIONS['en'].get(key, key))
if kwargs:
try:
return text.format(**kwargs)
except KeyError:
return text
return text
def is_chinese(self) -> bool:
"""Check if current language is Chinese."""
return self.lang == 'zh'
# Global instance for convenience
_i18n: Optional[I18n] = None
def get_i18n(lang: Optional[str] = None) -> I18n:
"""Get or create global I18n instance."""
global _i18n
if _i18n is None or (lang and _i18n.lang != lang):
_i18n = I18n(lang)
return _i18n
def t(key: str, **kwargs) -> str:
"""Convenience function for translation."""
return get_i18n().t(key, **kwargs)
if __name__ == "__main__":
import io
import sys
# Fix Windows console encoding for test
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
# Test locale detection
print(f"Detected locale: {detect_locale()}")
i18n = get_i18n()
print(f"Language: {i18n.lang}")
print(f"Test translation: {t('checking_updates')}")
print(f"With params: {t('updating_marketplace', marketplace='test-market')}")
#!/usr/bin/env python3
"""
Skill Recommender - Fetches trending and recommended skills from marketplaces.
Usage:
python recommend_skills.py [--source <source>] [--limit <n>] [--json]
Sources:
- skills.sh: Community skills leaderboard
- skillsmp.com: Curated marketplace (if accessible)
- all: All sources (default)
Examples:
python recommend_skills.py # Show trending from all sources
python recommend_skills.py --source skills.sh # Only skills.sh
python recommend_skills.py --limit 10 # Show top 10
python recommend_skills.py --json # Output as JSON
"""
import json
import sys
import argparse
import re
import io
from pathlib import Path
# Fix Windows console encoding
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
from typing import Dict, List, Optional, Set
from dataclasses import dataclass
import urllib.request
from html.parser import HTMLParser
# Import i18n module
script_dir = Path(__file__).parent
sys.path.insert(0, str(script_dir))
from i18n import get_i18n, t # noqa: E402
@dataclass
class RecommendedSkill:
name: str
installs: Optional[int]
source: str
repo: Optional[str]
description: Optional[str]
install_command: str
category: Optional[str] = None
class SkillsShParser(HTMLParser):
"""Parse skills.sh leaderboard page."""
def __init__(self):
super().__init__()
self.skills = []
self.current_skill = {}
self.in_skill_item = False
self.capture_text = False
self.current_tag = None
def handle_starttag(self, tag, attrs):
attrs_dict = dict(attrs)
# Look for skill entries in the leaderboard
if tag == "div" and "class" in attrs_dict:
classes = attrs_dict.get("class", "")
if classes and ("skill" in classes.lower() or "item" in classes.lower()):
self.in_skill_item = True
self.current_skill = {}
if self.in_skill_item:
if tag == "a" and "href" in attrs_dict:
href = attrs_dict.get("href", "")
if href and ("github.com" in href or "/" in href):
self.current_skill["repo"] = href
if tag in ["span", "p", "div", "h3", "h4"]:
self.capture_text = True
self.current_tag = tag
def handle_endtag(self, tag):
if tag == "div" and self.in_skill_item:
if self.current_skill.get("name"):
self.skills.append(self.current_skill)
self.in_skill_item = False
self.current_skill = {}
self.capture_text = False
self.current_tag = None
def handle_data(self, data):
if self.capture_text and self.in_skill_item:
text = data.strip()
if not text:
return
# Try to extract install count
install_match = re.search(r"([\d,\.]+)\s*[kKmM]?\s*install", text, re.IGNORECASE)
if install_match:
count_str = install_match.group(1).replace(",", "")
try:
count = float(count_str)
if "k" in text.lower():
count *= 1000
elif "m" in text.lower():
count *= 1000000
self.current_skill["installs"] = int(count)
except Exception:
pass
# Capture name (usually in h3/h4 or first significant text)
if self.current_tag in ["h3", "h4"] or "name" not in self.current_skill:
if len(text) > 2 and len(text) < 100 and not text.startswith("http"):
if "install" not in text.lower() and not re.match(r"^[\d,\.]+$", text):
self.current_skill["name"] = text
def fetch_skills_sh(limit: int = 20) -> List[RecommendedSkill]:
"""Fetch trending skills from skills.sh."""
url = "https://skills.sh/"
try:
req = urllib.request.Request(url, headers={
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) skills-updater/1.0"
})
with urllib.request.urlopen(req, timeout=15) as response:
html = response.read().decode("utf-8")
except Exception as e:
print(f"Warning: Could not fetch skills.sh: {e}", file=sys.stderr)
return get_hardcoded_skills_sh_top(limit)
# Try to parse the page
parser = SkillsShParser()
try:
parser.feed(html)
except Exception:
pass
skills = []
if parser.skills:
for item in parser.skills[:limit]:
if "name" in item:
repo = item.get("repo", "")
if repo.startswith("/"):
repo = repo.lstrip("/")
skills.append(RecommendedSkill(
name=item["name"],
installs=item.get("installs"),
source="skills.sh",
repo=repo if repo else None,
description=None,
install_command=f"npx skills add {repo}" if repo else f"npx skills add <owner>/{item['name']}"
))
else:
# Fallback to hardcoded top skills if parsing fails
skills = get_hardcoded_skills_sh_top(limit)
return skills
def load_recommendations_config() -> Dict:
"""Load recommendations from external config file."""
config_file = script_dir / "recommendations.json"
if config_file.exists():
try:
with open(config_file, encoding='utf-8') as f:
return json.load(f)
except Exception:
pass
return {}
def get_hardcoded_skills_sh_top(limit: int = 20) -> List[RecommendedSkill]:
"""Return top skills from config file as fallback."""
config = load_recommendations_config()
fallback_trending = config.get("fallback_trending", [])
skills = []
for item in fallback_trending[:limit]:
skills.append(RecommendedSkill(
name=item.get("name", ""),
installs=item.get("installs"),
source="skills.sh",
repo=item.get("repo"),
description=None,
install_command=f"npx skills add {item.get('repo', '')}"
))
return skills
def get_installed_categories() -> Set[str]:
"""Get categories of installed skills for personalized recommendations."""
plugins_file = Path.home() / ".claude" / "plugins" / "installed_plugins.json"
if not plugins_file.exists():
return set()
try:
with open(plugins_file, encoding='utf-8') as f:
data = json.load(f)
except Exception:
return set()
# Extract keywords from skill names
categories = set()
for key in data.get("plugins", {}).keys():
skill_name = key.split("@")[0]
# Common category keywords
if any(kw in skill_name.lower() for kw in ["github", "git", "code"]):
categories.add("developer-tools")
if any(kw in skill_name.lower() for kw in ["doc", "pdf", "ppt", "excel", "word"]):
categories.add("document-tools")
if any(kw in skill_name.lower() for kw in ["test", "qa", "playwright"]):
categories.add("testing")
if any(kw in skill_name.lower() for kw in ["front", "ui", "design", "css"]):
categories.add("frontend")
if any(kw in skill_name.lower() for kw in ["security", "safe"]):
categories.add("security")
if any(kw in skill_name.lower() for kw in ["learn", "study", "explain"]):
categories.add("learning")
return categories
def get_personalized_recommendations(installed_categories: Set[str], limit: int = 5) -> List[RecommendedSkill]:
"""Get personalized skill recommendations based on installed categories."""
config = load_recommendations_config()
recommendations_by_category = config.get("category_recommendations", {})
default_recommendations = config.get("default_recommendations", [])
recommendations = []
seen_names = set()
# Add category-specific recommendations
for category in installed_categories:
if category in recommendations_by_category:
for item in recommendations_by_category[category]:
name = item.get("name", "")
if name and name not in seen_names:
recommendations.append(RecommendedSkill(
name=name,
installs=None,
source="personalized",
repo=item.get("repo"),
description=item.get("description"),
install_command=f"claude /install {name}",
category=category
))
seen_names.add(name)
# Fill with defaults if needed
for item in default_recommendations:
if len(recommendations) >= limit:
break
name = item.get("name", "")
if name and name not in seen_names:
recommendations.append(RecommendedSkill(
name=name,
installs=None,
source="personalized",
repo=item.get("repo"),
description=item.get("description"),
install_command=f"claude /install {name}"
))
seen_names.add(name)
return recommendations[:limit]
def format_installs(count: Optional[int]) -> str:
"""Format install count for display."""
if count is None:
return ""
if count >= 1000000:
return f"{count/1000000:.1f}M"
elif count >= 1000:
return f"{count/1000:.1f}K"
else:
return str(count)
def print_recommendations(trending: List[RecommendedSkill],
personalized: List[RecommendedSkill],
as_json: bool = False):
"""Print skill recommendations."""
if as_json:
output = {
"trending": [],
"personalized": []
}
for skill in trending:
output["trending"].append({
"name": skill.name,
"installs": skill.installs,
"source": skill.source,
"repo": skill.repo,
"install_command": skill.install_command
})
for skill in personalized:
output["personalized"].append({
"name": skill.name,
"description": skill.description,
"category": skill.category,
"repo": skill.repo,
"install_command": skill.install_command
})
print(json.dumps(output, indent=2, ensure_ascii=False))
return
print(f"🔥 {t('trending_skills')}")
print("━" * 18)
print()
if trending:
print(f"{t('from_skills_sh')} ({t('top_n', n=len(trending))}):")
for i, skill in enumerate(trending, 1):
installs_str = format_installs(skill.installs)
if installs_str:
installs_str = f" ({installs_str} {t('installs')})"
print(f"{i:2}. {skill.name}{installs_str}")
print(f" {skill.install_command}")
print()
else:
print(t('could_not_fetch'))
print()
if personalized:
print(f"💡 {t('personalized_recommendations')}")
print("━" * 31)
print()
print(t('based_on_installed'))
for skill in personalized:
category_str = f" [{skill.category}]" if skill.category else ""
print(f"• {skill.name}{category_str}")
if skill.description:
print(f" {skill.description}")
print(f" → {skill.install_command}")
print()
print("━" * 40)
print(t('install_hint'))
print(t('install_hint_npx'))
def main():
parser = argparse.ArgumentParser(description="Discover recommended skills")
parser.add_argument("--source", choices=["skills.sh", "skillsmp.com", "all"],
default="all", help="Source for recommendations")
parser.add_argument("--limit", type=int, default=10,
help="Number of trending skills to show")
parser.add_argument("--json", action="store_true",
help="Output as JSON")
parser.add_argument("--lang", choices=["en", "zh"],
help="Language for output (auto-detected if not specified)")
args = parser.parse_args()
# Initialize i18n
if args.lang:
get_i18n(args.lang)
if not args.json:
print(f"🔍 {t('fetching_recommendations')}\n")
trending = []
personalized = []
# Fetch trending skills
if args.source in ["skills.sh", "all"]:
trending = fetch_skills_sh(limit=args.limit)
# Get personalized recommendations
installed_categories = get_installed_categories()
if installed_categories:
personalized = get_personalized_recommendations(installed_categories)
else:
# Default recommendations for new users
personalized = get_personalized_recommendations(set(), limit=5)
print_recommendations(trending, personalized, as_json=args.json)
if __name__ == "__main__":
main()
{
"_meta": {
"description": "Skill recommendations configuration file",
"last_updated": "2025-01-22",
"note": "Edit this file to update recommendations without modifying code"
},
"fallback_trending": [
{
"name": "vercel-react-best-practices",
"installs": 25500,
"repo": "vercel/react-best-practices"
},
{
"name": "web-design-guidelines",
"installs": 19200,
"repo": "webdesign/guidelines"
},
{
"name": "remotion-best-practices",
"installs": 2200,
"repo": "remotion-dev/remotion-best-practices"
},
{
"name": "nextjs-cursor-rules",
"installs": 1800,
"repo": "vercel/nextjs-cursor-rules"
},
{
"name": "ai-coding-standards",
"installs": 1500,
"repo": "anthropic/ai-coding-standards"
},
{
"name": "typescript-best-practices",
"installs": 1200,
"repo": "typescript-skills/best-practices"
},
{
"name": "react-native-guidelines",
"installs": 1100,
"repo": "react-native/guidelines"
},
{
"name": "tailwind-design-system",
"installs": 950,
"repo": "tailwindlabs/design-system"
},
{
"name": "python-clean-code",
"installs": 900,
"repo": "python-skills/clean-code"
},
{
"name": "security-best-practices",
"installs": 850,
"repo": "security-skills/best-practices"
},
{
"name": "api-design-patterns",
"installs": 800,
"repo": "api-skills/design-patterns"
},
{
"name": "testing-strategies",
"installs": 750,
"repo": "testing-skills/strategies"
},
{
"name": "devops-automation",
"installs": 700,
"repo": "devops-skills/automation"
},
{
"name": "database-optimization",
"installs": 650,
"repo": "database-skills/optimization"
},
{
"name": "frontend-performance",
"installs": 600,
"repo": "frontend-skills/performance"
},
{
"name": "backend-architecture",
"installs": 550,
"repo": "backend-skills/architecture"
},
{
"name": "mobile-development",
"installs": 500,
"repo": "mobile-skills/development"
},
{
"name": "cloud-infrastructure",
"installs": 450,
"repo": "cloud-skills/infrastructure"
},
{
"name": "data-engineering",
"installs": 400,
"repo": "data-skills/engineering"
},
{
"name": "machine-learning-ops",
"installs": 350,
"repo": "ml-skills/ops"
}
],
"category_recommendations": {
"developer-tools": [
{
"name": "github-ops",
"repo": "daymade/claude-code-skills",
"description": "GitHub CLI operations for PRs, issues, and workflows"
},
{
"name": "commit-commands",
"repo": "anthropics/claude-plugins-official",
"description": "Smart git commit message generation"
}
],
"testing": [
{
"name": "playwright-skill",
"repo": "lackeyjb/playwright-skill",
"description": "Browser automation and web testing"
},
{
"name": "qa-expert",
"repo": "daymade/claude-code-skills",
"description": "Comprehensive QA testing infrastructure"
}
],
"frontend": [
{
"name": "frontend-design",
"repo": "anthropics/skills",
"description": "Production-grade frontend interfaces"
},
{
"name": "canvas-design",
"repo": "anthropics/skills",
"description": "Visual design with canvas-based components"
}
],
"document-tools": [
{
"name": "document-skills",
"repo": "anthropics/skills",
"description": "Excel, Word, PowerPoint, PDF processing"
},
{
"name": "markdown-tools",
"repo": "daymade/claude-code-skills",
"description": "Document to markdown conversion"
}
],
"security": [
{
"name": "security-guidance",
"repo": "anthropics/claude-plugins-official",
"description": "Security best practices guidance"
},
{
"name": "repomix-safe-mixer",
"repo": "daymade/claude-code-skills",
"description": "Secure code packaging"
}
],
"learning": [
{
"name": "learning-output-style",
"repo": "anthropics/claude-plugins-official",
"description": "Educational explanations style"
},
{
"name": "explanatory-output-style",
"repo": "anthropics/claude-plugins-official",
"description": "Detailed explanatory output"
}
]
},
"default_recommendations": [
{
"name": "skill-creator",
"repo": "daymade/claude-code-skills",
"description": "Create effective Claude Code skills"
},
{
"name": "superpowers",
"repo": "obra/superpowers-marketplace",
"description": "Extended Claude capabilities"
},
{
"name": "planning-with-files",
"repo": "OthmanAdi/planning-with-files",
"description": "File-based planning workflow"
}
]
}
#!/usr/bin/env python3
"""
Marketplace Updater - Updates marketplace repos and reinstalls affected skills.
Usage:
python update_marketplace.py <marketplace_name> [--auto-install] [--json]
Examples:
python update_marketplace.py anthropic-agent-skills
python update_marketplace.py anthropic-agent-skills --auto-install
python update_marketplace.py claude-plugins-official --json
"""
import json
import sys
import argparse
import subprocess
import io
from pathlib import Path
# Fix Windows console encoding
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
# Import i18n module
script_dir = Path(__file__).parent
sys.path.insert(0, str(script_dir))
from i18n import get_i18n, t # noqa: E402
@dataclass
class UpdateResult:
marketplace: str
updated: bool
local_commit: str
remote_commit: str
commits_behind: int
commit_messages: List[str]
affected_skills: List[str]
reinstalled_skills: List[str]
failed_skills: List[str]
error: Optional[str] = None
def get_plugins_dir() -> Path:
"""Get the Claude Code plugins directory."""
return Path.home() / ".claude" / "plugins"
def get_marketplace_dir(marketplace_name: str) -> Optional[Path]:
"""Get the marketplace directory path."""
marketplace_dir = get_plugins_dir() / "marketplaces" / marketplace_name
if marketplace_dir.exists():
return marketplace_dir
return None
def load_installed_plugins() -> Dict:
"""Load the installed_plugins.json file."""
plugins_file = get_plugins_dir() / "installed_plugins.json"
if not plugins_file.exists():
return {"version": 2, "plugins": {}}
with open(plugins_file, encoding='utf-8') as f:
return json.load(f)
def load_known_marketplaces() -> Dict:
"""Load the known_marketplaces.json file."""
marketplaces_file = get_plugins_dir() / "known_marketplaces.json"
if not marketplaces_file.exists():
return {}
with open(marketplaces_file, encoding='utf-8') as f:
return json.load(f)
def get_affected_skills(marketplace_name: str) -> List[str]:
"""Get list of installed skills from the specified marketplace."""
installed = load_installed_plugins()
affected = []
for key in installed.get("plugins", {}).keys():
if key.endswith(f"@{marketplace_name}"):
skill_name = key.rsplit("@", 1)[0]
affected.append(skill_name)
return affected
def get_default_branch(repo_dir: Path) -> str:
"""
Detect the default branch of a git repository.
Tries multiple methods:
1. Check symbolic-ref of origin/HEAD
2. Check remote show origin
3. Fall back to 'main', then 'master'
Returns: branch name (e.g., 'main', 'master')
"""
# Method 1: Try symbolic-ref
result = subprocess.run(
["git", "symbolic-ref", "refs/remotes/origin/HEAD"],
cwd=repo_dir,
capture_output=True,
text=True
)
if result.returncode == 0:
# Output like: refs/remotes/origin/main
ref = result.stdout.strip()
if ref:
return ref.split("/")[-1]
# Method 2: Check if origin/main exists
result = subprocess.run(
["git", "rev-parse", "--verify", "origin/main"],
cwd=repo_dir,
capture_output=True,
text=True
)
if result.returncode == 0:
return "main"
# Method 3: Check if origin/master exists
result = subprocess.run(
["git", "rev-parse", "--verify", "origin/master"],
cwd=repo_dir,
capture_output=True,
text=True
)
if result.returncode == 0:
return "master"
# Default fallback
return "main"
def git_fetch_and_check(marketplace_dir: Path) -> Tuple[str, str, int, List[str]]:
"""
Fetch remote and check for updates.
Returns: (local_commit, remote_commit, commits_behind, commit_messages)
"""
# Detect default branch
default_branch = get_default_branch(marketplace_dir)
# Get local commit
result = subprocess.run(
["git", "rev-parse", "HEAD"],
cwd=marketplace_dir,
capture_output=True,
text=True
)
local_commit = result.stdout.strip()[:12] if result.returncode == 0 else "unknown"
# Fetch remote
subprocess.run(
["git", "fetch", "origin", default_branch, "--quiet"],
cwd=marketplace_dir,
capture_output=True
)
# Get remote commit
result = subprocess.run(
["git", "rev-parse", f"origin/{default_branch}"],
cwd=marketplace_dir,
capture_output=True,
text=True
)
remote_commit = result.stdout.strip()[:12] if result.returncode == 0 else "unknown"
# Count commits behind
result = subprocess.run(
["git", "rev-list", f"HEAD..origin/{default_branch}", "--count"],
cwd=marketplace_dir,
capture_output=True,
text=True
)
commits_behind = int(result.stdout.strip()) if result.returncode == 0 else 0
# Get commit messages
commit_messages = []
if commits_behind > 0:
result = subprocess.run(
["git", "log", f"HEAD..origin/{default_branch}", "--oneline"],
cwd=marketplace_dir,
capture_output=True,
text=True
)
if result.returncode == 0:
commit_messages = result.stdout.strip().split("\n")[:10] # Limit to 10
return local_commit, remote_commit, commits_behind, commit_messages
def git_pull(marketplace_dir: Path) -> bool:
"""Pull latest changes from remote."""
default_branch = get_default_branch(marketplace_dir)
result = subprocess.run(
["git", "pull", "origin", default_branch],
cwd=marketplace_dir,
capture_output=True,
text=True
)
return result.returncode == 0
def reinstall_skill(skill_name: str, marketplace_name: str) -> bool:
"""
Reinstall a skill using Claude Code's install mechanism.
This generates an install command that should be executed by Claude Code.
"""
# Generate the install command
install_cmd = f"/install {skill_name}@{marketplace_name}"
# Write command to a temporary file that can be read by Claude
cmd_file = get_plugins_dir() / ".pending_installs"
try:
with open(cmd_file, "a", encoding='utf-8') as f:
f.write(f"{install_cmd}\n")
return True
except Exception:
return False
def update_marketplace(
marketplace_name: str,
auto_install: bool = False,
interactive: bool = True
) -> UpdateResult:
"""
Update a marketplace and optionally reinstall affected skills.
Args:
marketplace_name: Name of the marketplace to update
auto_install: Whether to automatically reinstall affected skills
interactive: Whether to show progress output
Returns:
UpdateResult with details of the update
"""
i18n = get_i18n()
# Get marketplace directory
marketplace_dir = get_marketplace_dir(marketplace_name)
if not marketplace_dir:
return UpdateResult(
marketplace=marketplace_name,
updated=False,
local_commit="",
remote_commit="",
commits_behind=0,
commit_messages=[],
affected_skills=[],
reinstalled_skills=[],
failed_skills=[],
error=f"Marketplace '{marketplace_name}' not found"
)
if interactive:
print(f"📡 {t('fetching_remote')}")
# Check for updates
local_commit, remote_commit, commits_behind, commit_messages = git_fetch_and_check(marketplace_dir)
# Get affected skills
affected_skills = get_affected_skills(marketplace_name)
if commits_behind == 0:
return UpdateResult(
marketplace=marketplace_name,
updated=False,
local_commit=local_commit,
remote_commit=remote_commit,
commits_behind=0,
commit_messages=[],
affected_skills=affected_skills,
reinstalled_skills=[],
failed_skills=[]
)
if interactive:
print(f"\n{t('current_commit')}: {local_commit}")
print(f"{t('remote_commit')}: {remote_commit}")
print(f"{t('status')}: {t('commits_behind', count=commits_behind)}")
if commit_messages:
print(f"\n📝 {t('update_content')}:")
for msg in commit_messages[:5]:
print(f" • {msg}")
if len(commit_messages) > 5:
print(f" ... +{len(commit_messages) - 5} more")
if affected_skills:
print(f"\n📦 {t('affected_skills')}: {', '.join(affected_skills)}")
else:
print(f"\n📦 {t('no_affected_skills')}")
# Pull updates
if interactive:
print(f"\n📥 {t('updating_marketplace', marketplace=marketplace_name)}")
if not git_pull(marketplace_dir):
return UpdateResult(
marketplace=marketplace_name,
updated=False,
local_commit=local_commit,
remote_commit=remote_commit,
commits_behind=commits_behind,
commit_messages=commit_messages,
affected_skills=affected_skills,
reinstalled_skills=[],
failed_skills=[],
error="Git pull failed"
)
if interactive:
print(f"✅ {t('marketplace_updated')}")
# Reinstall affected skills if requested
reinstalled = []
failed = []
if auto_install and affected_skills:
if interactive:
print(f"\n🔄 {t('reinstalling_skills')}")
for skill in affected_skills:
if interactive:
print(f" {t('reinstalling_skill', skill=skill)}")
if reinstall_skill(skill, marketplace_name):
reinstalled.append(skill)
if interactive:
print(f" ✅ {t('skill_reinstalled', skill=skill)}")
else:
failed.append(skill)
if interactive:
print(f" ❌ {t('skill_reinstall_failed', skill=skill)}")
if interactive and reinstalled:
print(f"\n✅ {t('all_skills_updated')}")
return UpdateResult(
marketplace=marketplace_name,
updated=True,
local_commit=local_commit,
remote_commit=remote_commit,
commits_behind=commits_behind,
commit_messages=commit_messages,
affected_skills=affected_skills,
reinstalled_skills=reinstalled,
failed_skills=failed
)
def print_result_json(result: UpdateResult):
"""Print result as JSON."""
output = {
"marketplace": result.marketplace,
"updated": result.updated,
"local_commit": result.local_commit,
"remote_commit": result.remote_commit,
"commits_behind": result.commits_behind,
"commit_messages": result.commit_messages,
"affected_skills": result.affected_skills,
"reinstalled_skills": result.reinstalled_skills,
"failed_skills": result.failed_skills,
"error": result.error
}
print(json.dumps(output, indent=2, ensure_ascii=False))
def get_pending_installs() -> List[str]:
"""Get list of pending skill installs."""
cmd_file = get_plugins_dir() / ".pending_installs"
if not cmd_file.exists():
return []
with open(cmd_file, encoding='utf-8') as f:
commands = [line.strip() for line in f if line.strip()]
# Clear the file
cmd_file.unlink()
return commands
def main():
parser = argparse.ArgumentParser(description="Update marketplace and reinstall skills")
parser.add_argument("marketplace", help="Marketplace name to update")
parser.add_argument("--auto-install", action="store_true",
help="Automatically reinstall affected skills")
parser.add_argument("--json", action="store_true",
help="Output as JSON")
parser.add_argument("--lang", choices=["en", "zh"],
help="Language for output (auto-detected if not specified)")
args = parser.parse_args()
# Initialize i18n
if args.lang:
get_i18n(args.lang)
result = update_marketplace(
args.marketplace,
auto_install=args.auto_install,
interactive=not args.json
)
if args.json:
print_result_json(result)
elif result.error:
print(f"❌ {t('error')}: {result.error}")
sys.exit(1)
# Output pending installs for Claude to execute
pending = get_pending_installs()
if pending:
print("\n" + "=" * 40)
print("PENDING_SKILL_INSTALLS:")
for cmd in pending:
print(cmd)
print("=" * 40)
if __name__ == "__main__":
main()
Related skills
FAQ
What does skills-updater do?
skills-updater is a yizhiyanhua-ai agent skill described as helping with AI and agent building tasks. From its name, developers use it to update or refresh agent skill definitions during development workflows.
Which repo hosts skills-updater?
skills-updater lives in the yizhiyanhua-ai/skills-updater repository on Skillselion. The catalog lists it as a skill type candidate with an AI and agent building description.