Nicholai c1ea14975e feat(skills): add nukepedia-tools skill
Search Nukepedia's 2300+ free Nuke tools catalog. Includes:
- scrape.py: build catalog from nukepedia.com (rate-limited)
- search.py: query by name, category, rating, author
- Pre-scraped catalog with 2341 tools

Categories: gizmos, python, plugins, toolsets, blink, hiero, etc.

Support Nukepedia: https://nukepedia.com/donate
2026-01-24 23:05:06 -07:00

172 lines
5.5 KiB
Python

#!/usr/bin/env python3
"""
Search the Nukepedia tool catalog.
Usage:
python search.py <query> [options]
Examples:
python search.py keyer
python search.py edge --category gizmos
python search.py "" --category python --min-rating 4
python search.py tracker --verbose
Support Nukepedia: https://nukepedia.com/donate
"""
import argparse
import json
import math
import sys
from pathlib import Path
from typing import Optional
def load_catalog(path: Path) -> dict:
with open(path) as f:
return json.load(f)
def search(catalog: dict, query: str = "", category: str = None,
subcategory: str = None, author: str = None,
min_rating: float = None, min_downloads: int = None,
limit: int = 10) -> list:
"""Search tools matching criteria."""
results = []
query_lower = query.lower() if query else ""
for tool in catalog.get("tools", []):
# text search
if query_lower:
searchable = f"{tool['name']} {tool['description']} {tool['author']} {tool['subcategory']}".lower()
if query_lower not in searchable:
continue
# category filter
if category and tool["category"].lower() != category.lower():
continue
# subcategory filter
if subcategory and subcategory.lower() not in tool["subcategory"].lower():
continue
# author filter
if author and author.lower() not in tool["author"].lower():
continue
# rating filter
if min_rating is not None:
if tool["rating"] is None or tool["rating"] < min_rating:
continue
# downloads filter
if min_downloads is not None:
if tool["downloads"] < min_downloads:
continue
results.append(tool)
# sort by relevance
def score(t):
s = 0.0
if query_lower:
if t["name"].lower() == query_lower:
s += 100
elif t["name"].lower().startswith(query_lower):
s += 50
elif query_lower in t["name"].lower():
s += 25
if query_lower in t["description"].lower():
s += 10
if t["rating"]:
s += t["rating"] * 5
if t["downloads"] > 0:
s += math.log10(t["downloads"]) * 2
return s
results.sort(key=score, reverse=True)
return results[:limit] if limit else results
def format_brief(tool: dict) -> str:
rating = f" ({tool['rating']:.1f}/5)" if tool["rating"] else ""
desc = tool["description"][:80] + "..." if len(tool["description"]) > 80 else tool["description"]
return f" {tool['name']} - {tool['category'].title()}{rating}\n {desc}\n {tool['url']}"
def format_verbose(tool: dict) -> str:
lines = [
f"\n {tool['name']}",
f" {'=' * len(tool['name'])}",
f" Category: {tool['category'].title()} / {tool['subcategory']}",
f" Author: {tool['author'] or 'Unknown'}",
]
if tool["rating"]:
lines.append(f" Rating: {tool['rating']:.1f}/5 ({tool['rating_count']} votes)")
lines.append(f" Downloads: {tool['downloads']}")
if tool["nuke_versions"]:
lines.append(f" Nuke versions: {tool['nuke_versions']}")
if tool["platforms"]:
lines.append(f" Platforms: {', '.join(p.title() for p in tool['platforms'])}")
if tool["license"]:
lines.append(f" License: {tool['license']}")
lines.append(f" URL: {tool['url']}")
if tool["description"]:
lines.append(f"\n {tool['description']}")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Search Nukepedia tool catalog")
parser.add_argument("query", nargs="?", default="", help="Search query")
parser.add_argument("--category", "-c", help="Filter by category")
parser.add_argument("--subcategory", "-s", help="Filter by subcategory")
parser.add_argument("--author", "-a", help="Filter by author")
parser.add_argument("--min-rating", type=float, help="Minimum rating (1-5)")
parser.add_argument("--min-downloads", type=int, help="Minimum downloads")
parser.add_argument("--limit", "-l", type=int, default=10, help="Max results")
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
parser.add_argument("--catalog", default=None, help="Path to catalog JSON")
parser.add_argument("--json", action="store_true", help="Output as JSON")
args = parser.parse_args()
catalog_path = Path(args.catalog) if args.catalog else Path(__file__).parent.parent / "data" / "nukepedia-catalog.json"
if not catalog_path.exists():
print(f"Catalog not found: {catalog_path}")
print("Run scrape.py first to build the catalog.")
sys.exit(1)
catalog = load_catalog(catalog_path)
results = search(
catalog,
query=args.query,
category=args.category,
subcategory=args.subcategory,
author=args.author,
min_rating=args.min_rating,
min_downloads=args.min_downloads,
limit=args.limit
)
if args.json:
print(json.dumps(results, indent=2))
return
if not results:
print("No tools found matching your query.")
return
for tool in results:
if args.verbose:
print(format_verbose(tool))
else:
print(format_brief(tool))
print()
print(f"{len(results)} tool(s) found. Support Nukepedia: https://nukepedia.com/donate")
if __name__ == "__main__":
main()