Initial commit: MCP Hub Community Edition v3.0.0

Community edition generated from private repo via sync pipeline.
Includes 9 plugins (WordPress, WooCommerce, WP Advanced, Gitea, n8n,
Supabase, OpenPanel, Appwrite, Directus) with ~587 tools.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
airano
2026-02-17 08:34:44 +03:30
commit cf62e65c55
237 changed files with 87596 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
"""
WordPress Handlers
Modular handlers for WordPress functionality.
Each handler is responsible for a specific domain of WordPress operations.
Part of Option B clean architecture refactoring.
"""
from plugins.wordpress.handlers.comments import CommentsHandler
from plugins.wordpress.handlers.comments import get_tool_specifications as get_comments_specs
from plugins.wordpress.handlers.coupons import CouponsHandler
from plugins.wordpress.handlers.coupons import get_tool_specifications as get_coupons_specs
from plugins.wordpress.handlers.customers import CustomersHandler
from plugins.wordpress.handlers.customers import get_tool_specifications as get_customers_specs
from plugins.wordpress.handlers.media import MediaHandler
from plugins.wordpress.handlers.media import get_tool_specifications as get_media_specs
from plugins.wordpress.handlers.menus import MenusHandler
from plugins.wordpress.handlers.menus import get_tool_specifications as get_menus_specs
from plugins.wordpress.handlers.orders import OrdersHandler
from plugins.wordpress.handlers.orders import get_tool_specifications as get_orders_specs
from plugins.wordpress.handlers.posts import PostsHandler
from plugins.wordpress.handlers.posts import get_tool_specifications as get_posts_specs
from plugins.wordpress.handlers.products import ProductsHandler
from plugins.wordpress.handlers.products import get_tool_specifications as get_products_specs
from plugins.wordpress.handlers.reports import ReportsHandler
from plugins.wordpress.handlers.reports import get_tool_specifications as get_reports_specs
from plugins.wordpress.handlers.seo import SEOHandler
from plugins.wordpress.handlers.seo import get_tool_specifications as get_seo_specs
from plugins.wordpress.handlers.site import SiteHandler
from plugins.wordpress.handlers.site import get_tool_specifications as get_site_specs
from plugins.wordpress.handlers.taxonomy import TaxonomyHandler
from plugins.wordpress.handlers.taxonomy import get_tool_specifications as get_taxonomy_specs
from plugins.wordpress.handlers.users import UsersHandler
from plugins.wordpress.handlers.users import get_tool_specifications as get_users_specs
from plugins.wordpress.handlers.wp_cli import WPCLIHandler
from plugins.wordpress.handlers.wp_cli import get_tool_specifications as get_wp_cli_specs
__all__ = [
# Core Handlers
"PostsHandler",
"MediaHandler",
"TaxonomyHandler",
"CommentsHandler",
"UsersHandler",
"SiteHandler",
# WooCommerce Handlers
"ProductsHandler",
"OrdersHandler",
"CustomersHandler",
"ReportsHandler",
"CouponsHandler",
# Advanced Handlers
"SEOHandler",
"WPCLIHandler",
"MenusHandler",
# Tool specifications
"get_posts_specs",
"get_media_specs",
"get_taxonomy_specs",
"get_comments_specs",
"get_users_specs",
"get_site_specs",
"get_products_specs",
"get_orders_specs",
"get_customers_specs",
"get_reports_specs",
"get_coupons_specs",
"get_seo_specs",
"get_wp_cli_specs",
"get_menus_specs",
]

View File

@@ -0,0 +1,365 @@
"""Comments Handler - manages WordPress comments and moderation"""
import json
from typing import Any
from plugins.wordpress.client import WordPressClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
# === COMMENTS ===
{
"name": "list_comments",
"method_name": "list_comments",
"description": "List WordPress comments. Returns paginated list of comments with author, content, and moderation status.",
"schema": {
"type": "object",
"properties": {
"per_page": {
"type": "integer",
"description": "Number of comments per page (1-100)",
"default": 10,
"minimum": 1,
"maximum": 100,
},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"post_id": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Filter by post ID (optional)",
},
"status": {
"type": "string",
"description": "Filter by comment status",
"enum": ["approve", "hold", "spam", "trash", "all"],
"default": "approve",
},
},
},
"scope": "read",
},
{
"name": "get_comment",
"method_name": "get_comment",
"description": "Get a specific WordPress comment by ID. Returns complete comment data including content, author, and moderation status.",
"schema": {
"type": "object",
"properties": {
"comment_id": {
"type": "integer",
"description": "Comment ID to retrieve",
"minimum": 1,
}
},
"required": ["comment_id"],
},
"scope": "read",
},
{
"name": "create_comment",
"method_name": "create_comment",
"description": "Create a new WordPress comment on a post. Supports author information and moderation status.",
"schema": {
"type": "object",
"properties": {
"post_id": {
"type": "integer",
"description": "Post ID to attach comment to",
"minimum": 1,
},
"content": {
"type": "string",
"description": "Comment content/text",
"minLength": 1,
},
"author_name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Author name (for unauthenticated comments)",
},
"author_email": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Author email address",
},
"status": {
"type": "string",
"description": "Comment moderation status",
"enum": ["approve", "hold", "spam"],
"default": "hold",
},
},
"required": ["post_id", "content"],
},
"scope": "write",
},
{
"name": "update_comment",
"method_name": "update_comment",
"description": "Update an existing WordPress comment. Can update content, status, author information, etc.",
"schema": {
"type": "object",
"properties": {
"comment_id": {
"type": "integer",
"description": "Comment ID to update",
"minimum": 1,
},
"content": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Comment content/text",
},
"author_name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Author name",
},
"author_email": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Author email",
},
"status": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Comment moderation status",
"enum": ["approve", "hold", "spam"],
},
},
"required": ["comment_id"],
},
"scope": "write",
},
{
"name": "delete_comment",
"method_name": "delete_comment",
"description": "Delete or trash a WordPress comment. Can permanently delete or move to trash.",
"schema": {
"type": "object",
"properties": {
"comment_id": {
"type": "integer",
"description": "Comment ID to delete",
"minimum": 1,
},
"force": {
"type": "boolean",
"description": "Permanently delete (true) or move to trash (false)",
"default": False,
},
},
"required": ["comment_id"],
},
"scope": "write",
},
]
class CommentsHandler:
"""Handle comment-related operations for WordPress"""
def __init__(self, client: WordPressClient):
"""
Initialize comments handler.
Args:
client: WordPress API client instance
"""
self.client = client
async def list_comments(
self, per_page: int = 10, page: int = 1, post_id: int | None = None, status: str = "approve"
) -> str:
"""
List WordPress comments.
Args:
per_page: Number of comments per page (1-100)
page: Page number
post_id: Optional filter by post ID
status: Comment status filter (approve, hold, spam, trash, all)
Returns:
JSON string with comments list
"""
try:
params = {"per_page": per_page, "page": page, "status": status}
if post_id:
params["post"] = post_id
comments = await self.client.get("comments", params=params)
# Format response
result = {
"total": len(comments),
"page": page,
"per_page": per_page,
"comments": [
{
"id": c["id"],
"post_id": c["post"],
"author_name": c["author_name"],
"author_email": c.get("author_email", ""),
"content": c["content"]["rendered"][:200],
"status": c["status"],
"date": c["date"],
"link": c.get("link", ""),
}
for c in comments
],
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to list comments: {str(e)}"}, indent=2
)
async def get_comment(self, comment_id: int) -> str:
"""
Get a specific WordPress comment.
Args:
comment_id: Comment ID to retrieve
Returns:
JSON string with comment data
"""
try:
comment = await self.client.get(f"comments/{comment_id}")
result = {
"id": comment["id"],
"post_id": comment["post"],
"author_name": comment["author_name"],
"author_email": comment.get("author_email", ""),
"author_url": comment.get("author_url", ""),
"content": comment["content"]["rendered"],
"status": comment["status"],
"date": comment["date"],
"link": comment.get("link", ""),
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to get comment {comment_id}: {str(e)}"},
indent=2,
)
async def create_comment(
self,
post_id: int,
content: str,
author_name: str | None = None,
author_email: str | None = None,
status: str = "hold",
) -> str:
"""
Create a new WordPress comment.
Args:
post_id: Post ID to attach comment to
content: Comment content/text
author_name: Author name (for unauthenticated comments)
author_email: Author email address
status: Comment moderation status (approve, hold, spam)
Returns:
JSON string with created comment data
"""
try:
data = {"post": post_id, "content": content, "status": status}
if author_name:
data["author_name"] = author_name
if author_email:
data["author_email"] = author_email
comment = await self.client.post("comments", json_data=data)
result = {
"id": comment["id"],
"post_id": comment["post"],
"author_name": comment["author_name"],
"content": comment["content"]["rendered"],
"status": comment["status"],
"message": f"Comment created successfully with ID {comment['id']}",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to create comment: {str(e)}"}, indent=2
)
async def update_comment(
self,
comment_id: int,
content: str | None = None,
author_name: str | None = None,
author_email: str | None = None,
status: str | None = None,
) -> str:
"""
Update an existing WordPress comment.
Args:
comment_id: Comment ID to update
content: Comment content/text
author_name: Author name
author_email: Author email
status: Comment moderation status
Returns:
JSON string with updated comment data
"""
try:
# Build data dict with only provided values
data = {}
if content is not None:
data["content"] = content
if author_name is not None:
data["author_name"] = author_name
if author_email is not None:
data["author_email"] = author_email
if status is not None:
data["status"] = status
comment = await self.client.post(f"comments/{comment_id}", json_data=data)
result = {
"id": comment["id"],
"post_id": comment["post"],
"content": comment["content"]["rendered"],
"status": comment["status"],
"message": f"Comment {comment_id} updated successfully",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to update comment {comment_id}: {str(e)}"},
indent=2,
)
async def delete_comment(self, comment_id: int, force: bool = False) -> str:
"""
Delete or trash a WordPress comment.
Args:
comment_id: Comment ID to delete
force: Permanently delete (True) or move to trash (False)
Returns:
JSON string with deletion result
"""
try:
params = {"force": "true" if force else "false"}
result = await self.client.delete(f"comments/{comment_id}", params=params)
message = f"Comment {comment_id} {'permanently deleted' if force else 'moved to trash'}"
return json.dumps({"success": True, "message": message, "result": result}, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to delete comment {comment_id}: {str(e)}"},
indent=2,
)

View File

@@ -0,0 +1,495 @@
"""Coupons Handler - manages WooCommerce coupons"""
import json
from typing import Any
from plugins.wordpress.client import WordPressClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
{
"name": "list_coupons",
"method_name": "list_coupons",
"description": "List WooCommerce coupons. Returns paginated coupon list with discount details and usage restrictions.",
"schema": {
"type": "object",
"properties": {
"per_page": {
"type": "integer",
"description": "Number of coupons per page (1-100)",
"default": 10,
"minimum": 1,
"maximum": 100,
},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search term to filter coupons by code",
},
},
},
"scope": "read",
},
{
"name": "create_coupon",
"method_name": "create_coupon",
"description": "Create a new WooCommerce coupon. Supports percentage and fixed discounts with usage limits and restrictions.",
"schema": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Coupon code (e.g., 'SAVE20')",
"minLength": 1,
},
"amount": {
"type": "string",
"description": "Discount amount (e.g., '20' for 20% or $20)",
},
"discount_type": {
"type": "string",
"description": "Type of discount",
"enum": ["percent", "fixed_cart", "fixed_product"],
"default": "percent",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Coupon description (internal note)",
},
"date_expires": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Expiration date in ISO 8601 format (e.g., '2024-12-31T23:59:59')",
},
"minimum_amount": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Minimum order amount required to use coupon",
},
"maximum_amount": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Maximum order amount allowed to use coupon",
},
"individual_use": {
"type": "boolean",
"description": "If true, coupon cannot be combined with other coupons",
"default": False,
},
"product_ids": {
"anyOf": [
{"type": "array", "items": {"type": "integer"}},
{"type": "null"},
],
"description": "Array of product IDs coupon applies to",
},
"excluded_product_ids": {
"anyOf": [
{"type": "array", "items": {"type": "integer"}},
{"type": "null"},
],
"description": "Array of product IDs coupon does NOT apply to",
},
"usage_limit": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Maximum number of times coupon can be used",
},
"usage_limit_per_user": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Maximum number of times coupon can be used per user",
},
"limit_usage_to_x_items": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Maximum number of items coupon applies to",
},
"free_shipping": {
"type": "boolean",
"description": "If true, grants free shipping",
"default": False,
},
},
"required": ["code", "amount"],
},
"scope": "write",
},
{
"name": "update_coupon",
"method_name": "update_coupon",
"description": "Update an existing WooCommerce coupon. Can update discount amount, restrictions, and expiration.",
"schema": {
"type": "object",
"properties": {
"coupon_id": {
"type": "integer",
"description": "Coupon ID to update",
"minimum": 1,
},
"code": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Coupon code (e.g., 'SAVE20')",
},
"discount_type": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Type of discount",
"enum": ["percent", "fixed_cart", "fixed_product"],
},
"amount": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Discount amount",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Coupon description (internal note)",
},
"date_expires": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Expiration date in ISO 8601 format",
},
"minimum_amount": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Minimum order amount",
},
"maximum_amount": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Maximum order amount",
},
"individual_use": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "If true, coupon cannot be combined with other coupons",
},
"product_ids": {
"anyOf": [
{"type": "array", "items": {"type": "integer"}},
{"type": "null"},
],
"description": "Array of product IDs coupon applies to",
},
"excluded_product_ids": {
"anyOf": [
{"type": "array", "items": {"type": "integer"}},
{"type": "null"},
],
"description": "Array of product IDs coupon does NOT apply to",
},
"usage_limit": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Maximum number of uses",
},
"usage_limit_per_user": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Maximum uses per user",
},
"limit_usage_to_x_items": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Maximum number of items coupon applies to",
},
"free_shipping": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "If true, grants free shipping",
},
},
"required": ["coupon_id"],
},
"scope": "write",
},
{
"name": "delete_coupon",
"method_name": "delete_coupon",
"description": "Delete a WooCommerce coupon. Can force delete or move to trash.",
"schema": {
"type": "object",
"properties": {
"coupon_id": {
"type": "integer",
"description": "Coupon ID to delete",
"minimum": 1,
},
"force": {
"type": "boolean",
"description": "Force permanent delete (bypass trash)",
"default": False,
},
},
"required": ["coupon_id"],
},
"scope": "write",
},
]
class CouponsHandler:
"""Handle coupon-related operations for WooCommerce"""
def __init__(self, client: WordPressClient):
"""
Initialize coupons handler.
Args:
client: WordPress API client instance
"""
self.client = client
async def list_coupons(
self, per_page: int = 10, page: int = 1, search: str | None = None
) -> str:
"""
List WooCommerce coupons.
Args:
per_page: Number of coupons per page (1-100)
page: Page number
search: Search term to filter coupons by code
Returns:
JSON string with coupons list
"""
try:
params = {"per_page": per_page, "page": page}
if search:
params["search"] = search
coupons = await self.client.get("coupons", params=params, use_woocommerce=True)
result = {
"total": len(coupons),
"page": page,
"per_page": per_page,
"coupons": [
{
"id": coupon["id"],
"code": coupon["code"],
"discount_type": coupon["discount_type"],
"amount": coupon["amount"],
"description": coupon.get("description", ""),
"date_expires": coupon.get("date_expires"),
"usage_count": coupon.get("usage_count", 0),
"usage_limit": coupon.get("usage_limit"),
"individual_use": coupon.get("individual_use", False),
"free_shipping": coupon.get("free_shipping", False),
"minimum_amount": coupon.get("minimum_amount", "0"),
"maximum_amount": coupon.get("maximum_amount", "0"),
}
for coupon in coupons
],
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to list coupons: {str(e)}"}, indent=2
)
async def create_coupon(
self,
code: str,
amount: str,
discount_type: str = "percent",
description: str | None = None,
date_expires: str | None = None,
minimum_amount: str | None = None,
maximum_amount: str | None = None,
individual_use: bool = False,
product_ids: list[int] | None = None,
excluded_product_ids: list[int] | None = None,
usage_limit: int | None = None,
usage_limit_per_user: int | None = None,
limit_usage_to_x_items: int | None = None,
free_shipping: bool = False,
) -> str:
"""
Create a new WooCommerce coupon.
Args:
code: Coupon code (e.g., 'SAVE20')
amount: Discount amount (e.g., '20' for 20% or $20)
discount_type: Type of discount (percent, fixed_cart, fixed_product)
description: Coupon description (internal note)
date_expires: Expiration date in ISO 8601 format
minimum_amount: Minimum order amount required
maximum_amount: Maximum order amount allowed
individual_use: If true, coupon cannot be combined with others
product_ids: Product IDs coupon applies to
excluded_product_ids: Product IDs coupon does NOT apply to
usage_limit: Maximum number of times coupon can be used
usage_limit_per_user: Maximum uses per user
limit_usage_to_x_items: Maximum number of items coupon applies to
free_shipping: If true, grants free shipping
Returns:
JSON string with created coupon data
"""
try:
data = {
"code": code,
"discount_type": discount_type,
"amount": amount,
"individual_use": individual_use,
"free_shipping": free_shipping,
}
# Add optional fields
if description:
data["description"] = description
if date_expires:
data["date_expires"] = date_expires
if minimum_amount:
data["minimum_amount"] = minimum_amount
if maximum_amount:
data["maximum_amount"] = maximum_amount
if product_ids:
data["product_ids"] = product_ids
if excluded_product_ids:
data["excluded_product_ids"] = excluded_product_ids
if usage_limit:
data["usage_limit"] = usage_limit
if usage_limit_per_user:
data["usage_limit_per_user"] = usage_limit_per_user
if limit_usage_to_x_items:
data["limit_usage_to_x_items"] = limit_usage_to_x_items
coupon = await self.client.post("coupons", json_data=data, use_woocommerce=True)
result = {
"id": coupon["id"],
"code": coupon["code"],
"discount_type": coupon["discount_type"],
"amount": coupon["amount"],
"date_expires": coupon.get("date_expires"),
"message": f"Coupon '{code}' created successfully with ID {coupon['id']}",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to create coupon: {str(e)}"}, indent=2
)
async def update_coupon(
self,
coupon_id: int,
code: str | None = None,
discount_type: str | None = None,
amount: str | None = None,
description: str | None = None,
date_expires: str | None = None,
minimum_amount: str | None = None,
maximum_amount: str | None = None,
individual_use: bool | None = None,
product_ids: list[int] | None = None,
excluded_product_ids: list[int] | None = None,
usage_limit: int | None = None,
usage_limit_per_user: int | None = None,
limit_usage_to_x_items: int | None = None,
free_shipping: bool | None = None,
) -> str:
"""
Update an existing WooCommerce coupon.
Args:
coupon_id: Coupon ID to update
code: Coupon code
discount_type: Type of discount
amount: Discount amount
description: Coupon description
date_expires: Expiration date in ISO 8601 format
minimum_amount: Minimum order amount
maximum_amount: Maximum order amount
individual_use: If true, coupon cannot be combined with others
product_ids: Product IDs coupon applies to
excluded_product_ids: Product IDs coupon does NOT apply to
usage_limit: Maximum number of uses
usage_limit_per_user: Maximum uses per user
limit_usage_to_x_items: Maximum number of items coupon applies to
free_shipping: If true, grants free shipping
Returns:
JSON string with updated coupon data
"""
try:
# Build data dict with only provided values
data = {}
if code is not None:
data["code"] = code
if discount_type is not None:
data["discount_type"] = discount_type
if amount is not None:
data["amount"] = amount
if description is not None:
data["description"] = description
if date_expires is not None:
data["date_expires"] = date_expires
if minimum_amount is not None:
data["minimum_amount"] = minimum_amount
if maximum_amount is not None:
data["maximum_amount"] = maximum_amount
if individual_use is not None:
data["individual_use"] = individual_use
if product_ids is not None:
data["product_ids"] = product_ids
if excluded_product_ids is not None:
data["excluded_product_ids"] = excluded_product_ids
if usage_limit is not None:
data["usage_limit"] = usage_limit
if usage_limit_per_user is not None:
data["usage_limit_per_user"] = usage_limit_per_user
if limit_usage_to_x_items is not None:
data["limit_usage_to_x_items"] = limit_usage_to_x_items
if free_shipping is not None:
data["free_shipping"] = free_shipping
if not data:
raise Exception("No update data provided")
coupon = await self.client.put(
f"coupons/{coupon_id}", json_data=data, use_woocommerce=True
)
result = {
"id": coupon["id"],
"code": coupon["code"],
"discount_type": coupon["discount_type"],
"amount": coupon["amount"],
"message": f"Coupon ID {coupon_id} updated successfully",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to update coupon {coupon_id}: {str(e)}"},
indent=2,
)
async def delete_coupon(self, coupon_id: int, force: bool = False) -> str:
"""
Delete a WooCommerce coupon.
Args:
coupon_id: Coupon ID to delete
force: Force permanent delete (True) or move to trash (False)
Returns:
JSON string with deletion result
"""
try:
params = {"force": "true" if force else "false"}
result = await self.client.delete(
f"coupons/{coupon_id}", params=params, use_woocommerce=True
)
action = "permanently deleted" if force else "moved to trash"
return json.dumps(
{
"success": True,
"coupon_id": coupon_id,
"message": f"Coupon {action} successfully",
"result": result,
},
indent=2,
)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to delete coupon {coupon_id}: {str(e)}"},
indent=2,
)

View File

@@ -0,0 +1,373 @@
"""Customers Handler - manages WooCommerce customers"""
import json
from typing import Any
from plugins.wordpress.client import WordPressClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
{
"name": "list_customers",
"method_name": "list_customers",
"description": "List WooCommerce customers. Returns paginated customer list with email, orders count, and total spent.",
"schema": {
"type": "object",
"properties": {
"per_page": {
"type": "integer",
"description": "Number of customers per page (1-100)",
"default": 10,
"minimum": 1,
"maximum": 100,
},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"search": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Search by name or email",
},
"email": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Filter by specific email address",
},
"role": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Filter by role (customer, subscriber, etc.)",
},
},
},
"scope": "read",
},
{
"name": "get_customer",
"method_name": "get_customer",
"description": "Get detailed information about a specific WooCommerce customer. Returns customer details, billing, shipping, and order history.",
"schema": {
"type": "object",
"properties": {
"customer_id": {
"type": "integer",
"description": "Customer ID to retrieve",
"minimum": 1,
}
},
"required": ["customer_id"],
},
"scope": "read",
},
{
"name": "create_customer",
"method_name": "create_customer",
"description": "Create a new WooCommerce customer. Requires email, optionally includes name, username, password, billing, and shipping address.",
"schema": {
"type": "object",
"properties": {
"email": {
"type": "string",
"description": "Customer email address (required)",
"minLength": 1,
},
"first_name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Customer first name",
},
"last_name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Customer last name",
},
"username": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Username (generated from email if not provided)",
},
"password": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Password (auto-generated if not provided)",
},
"billing": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Billing address object",
},
"shipping": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Shipping address object",
},
},
"required": ["email"],
},
"scope": "write",
},
{
"name": "update_customer",
"method_name": "update_customer",
"description": "Update an existing WooCommerce customer. Can update name, email, billing, shipping, and other customer fields.",
"schema": {
"type": "object",
"properties": {
"customer_id": {
"type": "integer",
"description": "Customer ID to update",
"minimum": 1,
},
"first_name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Customer first name",
},
"last_name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Customer last name",
},
"email": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Customer email address",
},
"billing": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Billing address object",
},
"shipping": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Shipping address object",
},
},
"required": ["customer_id"],
},
"scope": "write",
},
]
class CustomersHandler:
"""Handle WooCommerce customer operations"""
def __init__(self, client: WordPressClient):
"""
Initialize customers handler.
Args:
client: WordPress API client instance
"""
self.client = client
async def list_customers(
self,
per_page: int = 10,
page: int = 1,
search: str | None = None,
email: str | None = None,
role: str | None = None,
) -> str:
"""
List WooCommerce customers.
Args:
per_page: Number of customers per page (1-100)
page: Page number
search: Search by name or email
email: Filter by specific email
role: Filter by role (customer, subscriber, etc.)
Returns:
JSON string with customers list
"""
try:
# Build query parameters
params = {"per_page": per_page, "page": page}
# Add optional filters
if search:
params["search"] = search
if email:
params["email"] = email
if role:
params["role"] = role
# Make request to WooCommerce API
customers = await self.client.get("customers", params=params, use_woocommerce=True)
# Format response
result = {
"total": len(customers),
"page": page,
"per_page": per_page,
"customers": [
{
"id": customer["id"],
"email": customer["email"],
"first_name": customer.get("first_name", ""),
"last_name": customer.get("last_name", ""),
"username": customer.get("username", ""),
"role": customer.get("role", ""),
"date_created": customer.get("date_created", ""),
"orders_count": customer.get("orders_count", 0),
"total_spent": customer.get("total_spent", "0"),
"avatar_url": customer.get("avatar_url", ""),
}
for customer in customers
],
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to list customers: {str(e)}"}, indent=2
)
async def get_customer(self, customer_id: int) -> str:
"""
Get detailed information about a specific customer.
Args:
customer_id: Customer ID to retrieve
Returns:
JSON string with customer data
"""
try:
customer = await self.client.get(f"customers/{customer_id}", use_woocommerce=True)
# Format detailed response
result = {
"id": customer["id"],
"email": customer["email"],
"username": customer.get("username", ""),
"first_name": customer.get("first_name", ""),
"last_name": customer.get("last_name", ""),
"role": customer.get("role", ""),
"date_created": customer.get("date_created", ""),
"date_modified": customer.get("date_modified", ""),
"orders_count": customer.get("orders_count", 0),
"total_spent": customer.get("total_spent", "0"),
"avatar_url": customer.get("avatar_url", ""),
"billing": customer.get("billing", {}),
"shipping": customer.get("shipping", {}),
"is_paying_customer": customer.get("is_paying_customer", False),
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to get customer {customer_id}: {str(e)}"},
indent=2,
)
async def create_customer(
self,
email: str,
first_name: str | None = None,
last_name: str | None = None,
username: str | None = None,
password: str | None = None,
billing: dict | None = None,
shipping: dict | None = None,
) -> str:
"""
Create a new WooCommerce customer.
Args:
email: Customer email (required)
first_name: First name
last_name: Last name
username: Username (generated from email if not provided)
password: Password (auto-generated if not provided)
billing: Billing address dictionary
shipping: Shipping address dictionary
Returns:
JSON string with created customer data
"""
try:
# Build customer data
data = {"email": email}
if first_name:
data["first_name"] = first_name
if last_name:
data["last_name"] = last_name
if username:
data["username"] = username
if password:
data["password"] = password
if billing:
data["billing"] = billing
if shipping:
data["shipping"] = shipping
# Create customer via WooCommerce API
customer = await self.client.post("customers", json_data=data, use_woocommerce=True)
result = {
"id": customer["id"],
"email": customer["email"],
"username": customer.get("username", ""),
"first_name": customer.get("first_name", ""),
"last_name": customer.get("last_name", ""),
"message": f"Customer created successfully with ID {customer['id']}",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to create customer: {str(e)}"}, indent=2
)
async def update_customer(
self,
customer_id: int,
first_name: str | None = None,
last_name: str | None = None,
email: str | None = None,
billing: dict | None = None,
shipping: dict | None = None,
) -> str:
"""
Update an existing WooCommerce customer.
Args:
customer_id: Customer ID to update
first_name: First name
last_name: Last name
email: Email address
billing: Billing address dictionary
shipping: Shipping address dictionary
Returns:
JSON string with updated customer data
"""
try:
# Build data dict with only provided values
data = {}
if first_name is not None:
data["first_name"] = first_name
if last_name is not None:
data["last_name"] = last_name
if email is not None:
data["email"] = email
if billing is not None:
data["billing"] = billing
if shipping is not None:
data["shipping"] = shipping
# Update customer via WooCommerce API
customer = await self.client.put(
f"customers/{customer_id}", json_data=data, use_woocommerce=True
)
result = {
"id": customer["id"],
"email": customer["email"],
"first_name": customer.get("first_name", ""),
"last_name": customer.get("last_name", ""),
"message": f"Customer {customer_id} updated successfully",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to update customer {customer_id}: {str(e)}"},
indent=2,
)

View File

@@ -0,0 +1,418 @@
"""Media Handler - manages WordPress media library operations"""
import json
from typing import Any
import aiohttp
from plugins.wordpress.client import WordPressClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
# === MEDIA ===
{
"name": "list_media",
"method_name": "list_media",
"description": "List media library items. Returns images, videos, documents with URLs and metadata.",
"schema": {
"type": "object",
"properties": {
"per_page": {
"type": "integer",
"description": "Number of media items per page",
"default": 20,
"minimum": 1,
"maximum": 100,
},
"page": {
"type": "integer",
"description": "Page number for pagination",
"default": 1,
"minimum": 1,
},
"media_type": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Filter by media type (image, video, audio, or application)",
"enum": ["image", "video", "audio", "application"],
},
},
},
"scope": "read",
},
{
"name": "get_media",
"method_name": "get_media",
"description": "Get detailed information about a media item. Returns full metadata including URLs, dimensions, and MIME type.",
"schema": {
"type": "object",
"properties": {
"media_id": {
"type": "integer",
"description": "Media ID to retrieve",
"minimum": 1,
}
},
"required": ["media_id"],
},
"scope": "read",
},
{
"name": "upload_media_from_url",
"method_name": "upload_media_from_url",
"description": "Upload media from URL to media library (sideload). Downloads file from public URL and uploads to WordPress.",
"schema": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "Public URL of the media file to upload (image, video, document, etc.)",
},
"title": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Media title (used in media library)",
},
"alt_text": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Alternative text for accessibility (important for images)",
},
"caption": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Media caption (displayed below image when inserted into content)",
},
},
"required": ["url"],
},
"scope": "write",
},
{
"name": "update_media",
"method_name": "update_media",
"description": "Update media metadata. Supports title, description, slug, alt text, caption, status, and associated post.",
"schema": {
"type": "object",
"properties": {
"media_id": {
"type": "integer",
"description": "Media ID to update",
"minimum": 1,
},
"title": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Media title (displayed in media library)",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Media description (full text content, displayed in attachment page)",
},
"slug": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Media URL slug (e.g., 'my-image-name')",
},
"alt_text": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Alternative text for accessibility (important for images)",
},
"caption": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Media caption (shown below image in content)",
},
"status": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Publication status of the media",
"enum": ["publish", "draft", "private"],
},
"post": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "ID of the post/page to attach this media to",
},
},
"required": ["media_id"],
},
"scope": "write",
},
{
"name": "delete_media",
"method_name": "delete_media",
"description": "Delete media from library. Can permanently delete or move to trash.",
"schema": {
"type": "object",
"properties": {
"media_id": {
"type": "integer",
"description": "Media ID to delete",
"minimum": 1,
},
"force": {
"type": "boolean",
"description": "Permanently delete (true) or move to trash (false)",
"default": False,
},
},
"required": ["media_id"],
},
"scope": "write",
},
]
class MediaHandler:
"""Handle media-related operations for WordPress"""
def __init__(self, client: WordPressClient):
"""
Initialize media handler.
Args:
client: WordPress API client instance
"""
self.client = client
# === MEDIA ===
async def list_media(
self, per_page: int = 20, page: int = 1, media_type: str | None = None
) -> str:
"""
List media library items.
Args:
per_page: Number of media items per page (1-100)
page: Page number
media_type: Filter by media type (image, video, audio, application)
Returns:
JSON string with media list
"""
try:
params = {"per_page": per_page, "page": page}
if media_type:
params["media_type"] = media_type
media = await self.client.get("media", params=params)
# Format response
result = {
"total": len(media),
"page": page,
"per_page": per_page,
"media": [
{
"id": m["id"],
"title": m["title"]["rendered"],
"mime_type": m["mime_type"],
"media_type": m.get("media_type", ""),
"url": m["source_url"],
"date": m["date"],
"alt_text": m.get("alt_text", ""),
"link": m.get("link", ""),
}
for m in media
],
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to list media: {str(e)}"}, indent=2
)
async def get_media(self, media_id: int) -> str:
"""
Get detailed information about a specific media item.
Args:
media_id: Media ID to retrieve
Returns:
JSON string with media data
"""
try:
media = await self.client.get(f"media/{media_id}")
result = {
"id": media["id"],
"title": media["title"]["rendered"],
"mime_type": media["mime_type"],
"media_type": media.get("media_type", ""),
"url": media["source_url"],
"alt_text": media.get("alt_text", ""),
"caption": media.get("caption", {}).get("rendered", ""),
"description": media.get("description", {}).get("rendered", ""),
"date": media["date"],
"modified": media.get("modified", ""),
"link": media.get("link", ""),
"media_details": media.get("media_details", {}),
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to get media {media_id}: {str(e)}"}, indent=2
)
async def upload_media_from_url(
self,
url: str,
title: str | None = None,
alt_text: str | None = None,
caption: str | None = None,
) -> str:
"""
Upload media from URL to media library (sideload).
Downloads file from a public URL and uploads it to WordPress media library.
Args:
url: Public URL of the media file to upload
title: Media title (used in media library)
alt_text: Alternative text for accessibility
caption: Media caption (displayed below image)
Returns:
JSON string with uploaded media data
"""
try:
# Download file from URL
async with aiohttp.ClientSession() as session, session.get(url) as response:
if response.status >= 400:
raise Exception(f"Failed to download from URL: HTTP {response.status}")
file_content = await response.read()
content_type = response.headers.get("Content-Type", "application/octet-stream")
# Extract filename from URL
filename = url.split("/")[-1].split("?")[0]
if not filename:
filename = "downloaded_file"
# Create FormData for upload
form = aiohttp.FormData()
form.add_field("file", file_content, filename=filename, content_type=content_type)
# Upload to WordPress using client's upload method
# Note: We need to use the client's base_url and auth directly for file upload
upload_url = f"{self.client.base_url}/media"
headers = {
"Authorization": self.client.auth_header,
"Content-Disposition": f'attachment; filename="{filename}"',
}
async with aiohttp.ClientSession() as session:
async with session.post(upload_url, data=form, headers=headers) as response:
if response.status >= 400:
error_text = await response.text()
raise Exception(f"Upload failed (HTTP {response.status}): {error_text}")
media = await response.json()
# Update metadata if provided
if title or alt_text or caption:
update_data = {}
if title:
update_data["title"] = title
if alt_text:
update_data["alt_text"] = alt_text
if caption:
update_data["caption"] = caption
await self.client.post(f"media/{media['id']}", json_data=update_data)
result = {
"id": media["id"],
"title": media["title"]["rendered"],
"url": media["source_url"],
"mime_type": media["mime_type"],
"message": f"Media uploaded from URL successfully with ID {media['id']}",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to upload media from URL: {str(e)}"}, indent=2
)
async def update_media(
self,
media_id: int,
title: str | None = None,
description: str | None = None,
slug: str | None = None,
alt_text: str | None = None,
caption: str | None = None,
status: str | None = None,
post: int | None = None,
) -> str:
"""
Update media metadata.
Args:
media_id: Media ID to update
title: Media title
description: Media description
slug: Media URL slug
alt_text: Alternative text for accessibility
caption: Media caption
status: Publication status (publish, draft, private)
post: ID of post/page to attach media to
Returns:
JSON string with updated media data
"""
try:
# Build data dict with only provided values
data = {}
if title is not None:
data["title"] = title
if description is not None:
data["description"] = description
if slug is not None:
data["slug"] = slug
if alt_text is not None:
data["alt_text"] = alt_text
if caption is not None:
data["caption"] = caption
if status is not None:
data["status"] = status
if post is not None:
data["post"] = post
media = await self.client.post(f"media/{media_id}", json_data=data)
result = {
"id": media["id"],
"title": media["title"]["rendered"],
"alt_text": media.get("alt_text", ""),
"caption": media.get("caption", {}).get("rendered", ""),
"url": media["source_url"],
"message": f"Media {media_id} updated successfully",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to update media {media_id}: {str(e)}"},
indent=2,
)
async def delete_media(self, media_id: int, force: bool = False) -> str:
"""
Delete or trash media from library.
Args:
media_id: Media ID to delete
force: Permanently delete (True) or move to trash (False)
Returns:
JSON string with deletion result
"""
try:
params = {"force": "true" if force else "false"}
result = await self.client.delete(f"media/{media_id}", params=params)
message = f"Media {media_id} {'permanently deleted' if force else 'moved to trash'}"
return json.dumps({"success": True, "message": message, "result": result}, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to delete media {media_id}: {str(e)}"},
indent=2,
)

View File

@@ -0,0 +1,401 @@
"""Menus Handler - manages WordPress navigation menus"""
import json
from typing import Any
from plugins.wordpress.client import WordPressClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
{
"name": "list_menus",
"method_name": "list_menus",
"description": "List all WordPress navigation menus. Returns list of menus with their locations and item counts.",
"schema": {"type": "object", "properties": {}},
"scope": "read",
},
{
"name": "get_menu",
"method_name": "get_menu",
"description": "Get detailed information about a specific menu including all items. Returns menu details with hierarchical structure of menu items.",
"schema": {
"type": "object",
"properties": {
"menu_id": {
"type": "integer",
"description": "Menu ID to retrieve",
"minimum": 1,
}
},
"required": ["menu_id"],
},
"scope": "read",
},
{
"name": "create_menu",
"method_name": "create_menu",
"description": "Create a new navigation menu. Can optionally assign to theme locations.",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Menu name (displayed in admin)",
"minLength": 1,
},
"slug": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Menu slug (auto-generated from name if not provided)",
},
"locations": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Theme locations to assign menu to (e.g., ['primary', 'footer'])",
},
},
"required": ["name"],
},
"scope": "write",
},
{
"name": "list_menu_items",
"method_name": "list_menu_items",
"description": "List all items in a specific menu. Returns hierarchical list of menu items with links and ordering.",
"schema": {
"type": "object",
"properties": {
"menu_id": {
"type": "integer",
"description": "Menu ID to get items from",
"minimum": 1,
}
},
"required": ["menu_id"],
},
"scope": "read",
},
{
"name": "create_menu_item",
"method_name": "create_menu_item",
"description": "Add a new item to a menu. Supports linking to posts, pages, categories, or custom URLs.",
"schema": {
"type": "object",
"properties": {
"menu_id": {
"type": "integer",
"description": "Menu ID to add item to",
"minimum": 1,
},
"title": {
"type": "string",
"description": "Item title/label displayed in menu",
"minLength": 1,
},
"type": {
"type": "string",
"description": "Item type: 'post_type' (post/page), 'taxonomy' (category/tag), or 'custom' (URL)",
"enum": ["post_type", "taxonomy", "custom"],
},
"object_id": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "ID of linked post/page/term (required for post_type/taxonomy)",
},
"url": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Custom URL (required for type=custom)",
},
"parent": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Parent item ID for creating sub-menu items",
},
},
"required": ["menu_id", "title", "type"],
},
"scope": "write",
},
{
"name": "update_menu_item",
"method_name": "update_menu_item",
"description": "Update an existing menu item. Can change title, URL, parent, or menu order.",
"schema": {
"type": "object",
"properties": {
"item_id": {
"type": "integer",
"description": "Menu item ID to update",
"minimum": 1,
},
"title": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "New item title",
},
"url": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "New URL",
},
"parent": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "New parent item ID (0 for top-level)",
},
"menu_order": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Position in menu (lower numbers appear first)",
},
},
"required": ["item_id"],
},
"scope": "write",
},
]
class MenusHandler:
"""Handle menu-related operations for WordPress"""
def __init__(self, client: WordPressClient):
"""
Initialize menus handler.
Args:
client: WordPress API client instance
"""
self.client = client
async def list_menus(self) -> str:
"""
List all WordPress navigation menus.
Returns list of all menus with their locations and item counts.
Returns:
JSON string with total count and menu list
Example response:
{
"total": 3,
"menus": [
{
"id": 2,
"name": "Primary Menu",
"slug": "primary-menu",
"locations": ["primary"],
"count": 8
}
]
}
"""
try:
# WordPress REST API for menus (requires plugin support)
# Try custom endpoint first, fallback to standard if not available
try:
menus = await self.client.get("menus", use_custom_namespace=True)
except:
# Fallback: use wp/v2/navigation endpoint (WP 5.9+)
menus = await self.client.get("navigation")
result = {"total": len(menus) if isinstance(menus, list) else 0, "menus": menus}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to list menus: {str(e)}"}, indent=2
)
async def get_menu(self, menu_id: int) -> str:
"""
Get detailed information about a specific menu including all items.
Args:
menu_id: Menu ID
Returns:
JSON string with menu details and items
"""
try:
# Get menu details
try:
menu = await self.client.get(f"menus/{menu_id}", use_custom_namespace=True)
except:
menu = await self.client.get(f"navigation/{menu_id}")
# Get menu items
menu_items = await self.list_menu_items(menu_id)
result = {
"menu": menu,
"items": json.loads(menu_items) if isinstance(menu_items, str) else menu_items,
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to get menu {menu_id}: {str(e)}"}, indent=2
)
async def create_menu(
self, name: str, slug: str | None = None, locations: list[str] | None = None
) -> str:
"""
Create a new navigation menu.
Args:
name: Menu name
slug: Menu slug (auto-generated if not provided)
locations: Theme locations to assign menu to
Returns:
JSON string with created menu details
"""
try:
data = {"name": name}
if slug:
data["slug"] = slug
if locations:
data["locations"] = locations
try:
menu = await self.client.post("menus", json_data=data, use_custom_namespace=True)
except:
# Try navigation endpoint
menu = await self.client.post("navigation", json_data=data)
result = {
"id": menu.get("id"),
"name": menu.get("name"),
"slug": menu.get("slug"),
"message": f"Menu '{name}' created successfully",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to create menu: {str(e)}"}, indent=2
)
async def list_menu_items(self, menu_id: int) -> str:
"""
List all items in a specific menu.
Args:
menu_id: Menu ID
Returns:
JSON string with menu items list
"""
try:
params = {"menus": menu_id, "per_page": 100}
items = await self.client.get("menu-items", params=params)
result = {
"total": len(items) if isinstance(items, list) else 0,
"menu_id": menu_id,
"items": items,
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{
"error": str(e),
"message": f"Failed to list menu items for menu {menu_id}: {str(e)}",
},
indent=2,
)
async def create_menu_item(
self,
menu_id: int,
title: str,
type: str,
object_id: int | None = None,
url: str | None = None,
parent: int | None = None,
) -> str:
"""
Add a new item to a menu.
Args:
menu_id: Menu ID to add item to
title: Item title/label
type: Item type (post_type, taxonomy, custom)
object_id: ID of linked post/term (required for post_type/taxonomy)
url: Custom URL (required for type=custom)
parent: Parent item ID for creating sub-menu items
Returns:
JSON string with created menu item
"""
try:
data = {"menus": menu_id, "title": title, "type": type}
if object_id:
data["object_id"] = object_id
if url:
data["url"] = url
if parent:
data["parent"] = parent
item = await self.client.post("menu-items", json_data=data)
result = {
"id": item.get("id"),
"title": item.get("title", {}).get("rendered", title),
"type": item.get("type"),
"url": item.get("url"),
"message": f"Menu item '{title}' created successfully",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to create menu item: {str(e)}"}, indent=2
)
async def update_menu_item(
self,
item_id: int,
title: str | None = None,
url: str | None = None,
parent: int | None = None,
menu_order: int | None = None,
) -> str:
"""
Update an existing menu item.
Args:
item_id: Menu item ID
title: New title
url: New URL
parent: New parent item ID
menu_order: Position in menu
Returns:
JSON string with updated menu item
"""
try:
# Build data dict with only provided values
data = {}
if title is not None:
data["title"] = title
if url is not None:
data["url"] = url
if parent is not None:
data["parent"] = parent
if menu_order is not None:
data["menu_order"] = menu_order
item = await self.client.put(f"menu-items/{item_id}", json_data=data)
result = {
"id": item.get("id"),
"title": item.get("title", {}).get("rendered", title),
"url": item.get("url"),
"menu_order": item.get("menu_order"),
"message": f"Menu item {item_id} updated successfully",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to update menu item {item_id}: {str(e)}"},
indent=2,
)

View File

@@ -0,0 +1,449 @@
"""Orders Handler - manages WooCommerce orders"""
import json
from typing import Any
from plugins.wordpress.client import WordPressClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
{
"name": "list_orders",
"method_name": "list_orders",
"description": "List WooCommerce orders. Returns paginated order list with customer details, totals, and status.",
"schema": {
"type": "object",
"properties": {
"per_page": {
"type": "integer",
"description": "Number of orders per page (1-100)",
"default": 10,
"minimum": 1,
"maximum": 100,
},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"status": {
"anyOf": [
{
"type": "string",
"enum": [
"any",
"pending",
"processing",
"on-hold",
"completed",
"cancelled",
"refunded",
"failed",
"trash",
],
},
{"type": "null"},
],
"description": "Filter by order status (optional)",
},
"customer": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Filter by customer ID",
},
"after": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Filter orders after this date (ISO 8601 format)",
},
"before": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Filter orders before this date (ISO 8601 format)",
},
},
},
"scope": "read",
},
{
"name": "get_order",
"method_name": "get_order",
"description": "Get detailed information about a specific WooCommerce order. Returns full order details including line items, totals, billing, and shipping.",
"schema": {
"type": "object",
"properties": {
"order_id": {
"type": "integer",
"description": "Order ID to retrieve",
"minimum": 1,
}
},
"required": ["order_id"],
},
"scope": "read",
},
{
"name": "update_order_status",
"method_name": "update_order_status",
"description": "Update WooCommerce order status. Change order status to pending, processing, completed, etc.",
"schema": {
"type": "object",
"properties": {
"order_id": {
"type": "integer",
"description": "Order ID to update",
"minimum": 1,
},
"status": {
"type": "string",
"description": "New order status",
"enum": [
"pending",
"processing",
"on-hold",
"completed",
"cancelled",
"refunded",
"failed",
],
},
},
"required": ["order_id", "status"],
},
"scope": "write",
},
{
"name": "create_order",
"method_name": "create_order",
"description": "Create a new WooCommerce order. Supports line items, billing, shipping, and payment method configuration.",
"schema": {
"type": "object",
"properties": {
"customer_id": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Customer ID (optional)",
},
"line_items": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"properties": {
"product_id": {
"type": "integer",
"description": "Product ID",
},
"quantity": {
"type": "integer",
"description": "Quantity",
"minimum": 1,
},
"variation_id": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Variation ID (for variable products)",
},
},
"required": ["product_id", "quantity"],
},
},
{"type": "null"},
],
"description": "Order line items with product IDs and quantities",
},
"billing": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Billing address object",
},
"shipping": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Shipping address object",
},
"payment_method": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Payment method ID (e.g., 'bacs', 'cod', 'paypal')",
},
"status": {
"type": "string",
"description": "Order status",
"enum": ["pending", "processing", "on-hold", "completed"],
"default": "pending",
},
},
},
"scope": "write",
},
{
"name": "delete_order",
"method_name": "delete_order",
"description": "Delete or trash a WooCommerce order. Can permanently delete or move to trash.",
"schema": {
"type": "object",
"properties": {
"order_id": {
"type": "integer",
"description": "Order ID to delete",
"minimum": 1,
},
"force": {
"type": "boolean",
"description": "Permanently delete (true) or move to trash (false)",
"default": False,
},
},
"required": ["order_id"],
},
"scope": "write",
},
]
class OrdersHandler:
"""Handle WooCommerce order-related operations"""
def __init__(self, client: WordPressClient):
"""
Initialize orders handler.
Args:
client: WordPress API client instance
"""
self.client = client
async def list_orders(
self,
per_page: int = 10,
page: int = 1,
status: str | None = None,
customer: int | None = None,
after: str | None = None,
before: str | None = None,
) -> str:
"""
List WooCommerce orders with filters.
Args:
per_page: Number of orders per page (1-100)
page: Page number
status: Filter by order status (any, pending, processing, on-hold, completed, cancelled, refunded, failed, trash)
customer: Filter by customer ID
after: Filter orders after this date (ISO 8601 format)
before: Filter orders before this date (ISO 8601 format)
Returns:
JSON string with orders list
"""
try:
# Build query parameters
params = {"per_page": per_page, "page": page}
# Add optional filters
if status:
params["status"] = status
if customer is not None:
params["customer"] = customer
if after:
params["after"] = after
if before:
params["before"] = before
# Make request to WooCommerce API
orders = await self.client.get("orders", params=params, use_woocommerce=True)
# Format response
result = {
"total": len(orders),
"page": page,
"per_page": per_page,
"orders": [
{
"id": order["id"],
"number": order["number"],
"status": order["status"],
"date_created": order["date_created"],
"date_modified": order.get("date_modified", ""),
"total": order["total"],
"currency": order["currency"],
"customer_id": order["customer_id"],
"billing": {
"first_name": order["billing"].get("first_name", ""),
"last_name": order["billing"].get("last_name", ""),
"email": order["billing"].get("email", ""),
},
"line_items_count": len(order.get("line_items", [])),
"payment_method_title": order.get("payment_method_title", ""),
"transaction_id": order.get("transaction_id", ""),
}
for order in orders
],
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to list orders: {str(e)}"}, indent=2
)
async def get_order(self, order_id: int) -> str:
"""
Get detailed information about a specific order.
Args:
order_id: Order ID to retrieve
Returns:
JSON string with order data
"""
try:
order = await self.client.get(f"orders/{order_id}", use_woocommerce=True)
# Format detailed response
result = {
"id": order["id"],
"number": order["number"],
"status": order["status"],
"currency": order["currency"],
"date_created": order["date_created"],
"date_modified": order.get("date_modified", ""),
"discount_total": order["discount_total"],
"shipping_total": order["shipping_total"],
"total": order["total"],
"total_tax": order["total_tax"],
"customer_id": order["customer_id"],
"customer_note": order.get("customer_note", ""),
"billing": order["billing"],
"shipping": order["shipping"],
"payment_method": order["payment_method"],
"payment_method_title": order.get("payment_method_title", ""),
"transaction_id": order.get("transaction_id", ""),
"line_items": [
{
"id": item["id"],
"name": item["name"],
"product_id": item["product_id"],
"quantity": item["quantity"],
"subtotal": item["subtotal"],
"total": item["total"],
"sku": item.get("sku", ""),
}
for item in order.get("line_items", [])
],
"shipping_lines": order.get("shipping_lines", []),
"fee_lines": order.get("fee_lines", []),
"coupon_lines": order.get("coupon_lines", []),
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to get order {order_id}: {str(e)}"}, indent=2
)
async def update_order_status(self, order_id: int, status: str) -> str:
"""
Update order status.
Args:
order_id: Order ID to update
status: New status (pending, processing, on-hold, completed, cancelled, refunded, failed)
Returns:
JSON string with updated order data
"""
try:
data = {"status": status}
order = await self.client.put(
f"orders/{order_id}", json_data=data, use_woocommerce=True
)
result = {
"id": order["id"],
"number": order["number"],
"status": order["status"],
"message": f"Order #{order['number']} status updated to '{status}'",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to update order {order_id} status: {str(e)}"},
indent=2,
)
async def create_order(
self,
customer_id: int | None = None,
line_items: list[dict] | None = None,
billing: dict | None = None,
shipping: dict | None = None,
payment_method: str | None = None,
status: str = "pending",
) -> str:
"""
Create a new order.
Args:
customer_id: Customer ID (optional)
line_items: List of line items [{"product_id": 123, "quantity": 1}]
billing: Billing address dictionary
shipping: Shipping address dictionary
payment_method: Payment method ID (e.g., 'bacs', 'cod', 'paypal')
status: Order status (default: pending)
Returns:
JSON string with created order data
"""
try:
data = {"status": status}
if customer_id is not None:
data["customer_id"] = customer_id
if line_items:
data["line_items"] = line_items
if billing:
data["billing"] = billing
if shipping:
data["shipping"] = shipping
if payment_method:
data["payment_method"] = payment_method
order = await self.client.post("orders", json_data=data, use_woocommerce=True)
result = {
"id": order["id"],
"number": order["number"],
"status": order["status"],
"total": order["total"],
"currency": order["currency"],
"message": f"Order #{order['number']} created successfully with ID {order['id']}",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to create order: {str(e)}"}, indent=2
)
async def delete_order(self, order_id: int, force: bool = False) -> str:
"""
Delete or trash an order.
Args:
order_id: Order ID to delete
force: Permanently delete (True) or move to trash (False)
Returns:
JSON string with deletion result
"""
try:
params = {"force": "true" if force else "false"}
result = await self.client.delete(
f"orders/{order_id}", params=params, use_woocommerce=True
)
message = f"Order {order_id} {'permanently deleted' if force else 'moved to trash'}"
return json.dumps({"success": True, "message": message, "result": result}, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to delete order {order_id}: {str(e)}"},
indent=2,
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,299 @@
"""Reports Handler - manages WooCommerce reporting and analytics"""
import json
from typing import Any
from plugins.wordpress.client import WordPressClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
# === WOOCOMMERCE REPORTS ===
{
"name": "get_sales_report",
"method_name": "get_sales_report",
"description": "Get WooCommerce sales report. Returns sales data with totals and date ranges. Note: WooCommerce v3 API has limited reporting capabilities.",
"schema": {
"type": "object",
"properties": {
"period": {
"type": "string",
"description": "Report period (week, month, last_month, year)",
"enum": ["week", "month", "last_month", "year"],
"default": "week",
},
"date_min": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Start date for report (ISO 8601 format, e.g., '2024-01-01')",
},
"date_max": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "End date for report (ISO 8601 format, e.g., '2024-12-31')",
},
},
},
"scope": "read",
},
{
"name": "get_top_sellers",
"method_name": "get_top_sellers",
"description": "Get top selling products report. Returns products with highest sales quantities.",
"schema": {
"type": "object",
"properties": {
"period": {
"type": "string",
"description": "Report period (week, month, last_month, year)",
"enum": ["week", "month", "last_month", "year"],
"default": "week",
},
"date_min": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Start date for report (ISO 8601 format)",
},
"date_max": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "End date for report (ISO 8601 format)",
},
},
},
"scope": "read",
},
{
"name": "get_customer_report",
"method_name": "get_customer_report",
"description": "Get customer statistics report. Returns customer count and spending data. Falls back to customer list if reports endpoint unavailable.",
"schema": {
"type": "object",
"properties": {
"period": {
"type": "string",
"description": "Report period (week, month, last_month, year)",
"enum": ["week", "month", "last_month", "year"],
"default": "week",
},
"date_min": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Start date for report (ISO 8601 format)",
},
"date_max": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "End date for report (ISO 8601 format)",
},
},
},
"scope": "read",
},
]
class ReportsHandler:
"""Handle WooCommerce reporting operations"""
def __init__(self, client: WordPressClient):
"""
Initialize reports handler.
Args:
client: WordPress API client instance
"""
self.client = client
# === WOOCOMMERCE REPORTS ===
async def get_sales_report(
self, period: str = "week", date_min: str | None = None, date_max: str | None = None
) -> str:
"""
Get WooCommerce sales report.
Args:
period: Report period (week, month, last_month, year)
date_min: Start date for report (ISO 8601 format, e.g., '2024-01-01')
date_max: End date for report (ISO 8601 format, e.g., '2024-12-31')
Returns:
JSON string with sales report data
Note:
WooCommerce v3 API has limited reporting capabilities.
For advanced analytics, use WooCommerce Analytics extension.
"""
try:
params = {"period": period}
if date_min:
params["date_min"] = date_min
if date_max:
params["date_max"] = date_max
report_data = await self.client.get(
"reports/sales", params=params, use_woocommerce=True
)
# Format the response based on what the API returns
result = {
"period": period,
"sales_data": report_data if isinstance(report_data, list) else [report_data],
"note": "WooCommerce v3 API has limited reporting. For advanced analytics, use WooCommerce Analytics extension.",
}
return json.dumps(result, indent=2)
except Exception as e:
error_msg = str(e)
# Check if reports endpoint is not available
if "404" in error_msg or "not found" in error_msg.lower():
return json.dumps(
{
"error": "Sales reports endpoint not available",
"message": "WooCommerce v3 API has limited reporting capabilities. Consider using WooCommerce Analytics or custom queries.",
"details": error_msg,
},
indent=2,
)
return json.dumps(
{"error": str(e), "message": f"Failed to get sales report: {str(e)}"}, indent=2
)
async def get_top_sellers(
self, period: str = "week", date_min: str | None = None, date_max: str | None = None
) -> str:
"""
Get top selling products report.
Args:
period: Report period (week, month, last_month, year)
date_min: Start date for report (ISO 8601 format)
date_max: End date for report (ISO 8601 format)
Returns:
JSON string with top sellers data
"""
try:
params = {"period": period}
if date_min:
params["date_min"] = date_min
if date_max:
params["date_max"] = date_max
top_sellers = await self.client.get(
"reports/top_sellers", params=params, use_woocommerce=True
)
result = {
"period": period,
"total_products": len(top_sellers) if isinstance(top_sellers, list) else 0,
"top_sellers": [
{
"product_id": item.get("product_id"),
"title": item.get("title"),
"quantity": item.get("quantity", 0),
}
for item in (top_sellers if isinstance(top_sellers, list) else [])
],
}
return json.dumps(result, indent=2)
except Exception as e:
error_msg = str(e)
if "404" in error_msg or "not found" in error_msg.lower():
return json.dumps(
{
"error": "Top sellers endpoint not available",
"message": "WooCommerce v3 API has limited reporting capabilities.",
"details": error_msg,
},
indent=2,
)
return json.dumps(
{"error": str(e), "message": f"Failed to get top sellers: {str(e)}"}, indent=2
)
async def get_customer_report(
self, period: str = "week", date_min: str | None = None, date_max: str | None = None
) -> str:
"""
Get customer statistics report.
Args:
period: Report period (week, month, last_month, year)
date_min: Start date for report (ISO 8601 format)
date_max: End date for report (ISO 8601 format)
Returns:
JSON string with customer report data
Note:
Falls back to customer list endpoint if reports endpoint is unavailable.
"""
try:
params = {"period": period}
if date_min:
params["date_min"] = date_min
if date_max:
params["date_max"] = date_max
try:
customer_data = await self.client.get(
"reports/customers", params=params, use_woocommerce=True
)
result = {
"period": period,
"customer_data": (
customer_data if isinstance(customer_data, list) else [customer_data]
),
"note": "Customer reporting may be limited in WooCommerce v3 API.",
}
return json.dumps(result, indent=2)
except Exception as report_error:
# Check if it's a 404 error - use fallback
if "404" in str(report_error) or "not found" in str(report_error).lower():
return await self._get_customer_report_fallback()
raise report_error
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to get customer report: {str(e)}"}, indent=2
)
async def _get_customer_report_fallback(self) -> str:
"""
Fallback method when customer reports endpoint is not available.
Uses the customers list endpoint to generate basic statistics.
Returns:
JSON string with basic customer statistics
"""
try:
# Use customers list to generate basic stats
customers = await self.client.get(
"customers", params={"per_page": 100}, use_woocommerce=True
)
# Calculate basic stats
total_customers = len(customers) if isinstance(customers, list) else 0
total_spent = (
sum(float(c.get("total_spent", 0)) for c in customers)
if isinstance(customers, list)
else 0
)
avg_spent = total_spent / total_customers if total_customers > 0 else 0
result = {
"total_customers": total_customers,
"total_spent": f"{total_spent:.2f}",
"average_spent_per_customer": f"{avg_spent:.2f}",
"note": "Generated from customer list (fallback method). For detailed analytics, use WooCommerce Analytics.",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{
"error": str(e),
"message": "Customer report and fallback both unavailable",
"details": str(e),
},
indent=2,
)

View File

@@ -0,0 +1,617 @@
"""SEO Handler - manages WordPress SEO plugin operations (Yoast/RankMath)"""
import json
from typing import Any
from plugins.wordpress.client import WordPressClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
# === SEO (Rank Math / Yoast) ===
{
"name": "get_post_seo",
"method_name": "get_post_seo",
"description": "Get SEO metadata for a WordPress post or page. Returns Rank Math or Yoast SEO fields including focus keyword, meta title, description, and social media settings. Requires SEO API Bridge plugin.",
"schema": {
"type": "object",
"properties": {
"post_id": {"type": "integer", "description": "Post or Page ID", "minimum": 1}
},
"required": ["post_id"],
},
"scope": "read",
},
{
"name": "get_product_seo",
"method_name": "get_product_seo",
"description": "Get SEO metadata for a WooCommerce product. Returns Rank Math or Yoast SEO fields including focus keyword, meta title, description, and social media settings. Requires SEO API Bridge plugin.",
"schema": {
"type": "object",
"properties": {
"product_id": {"type": "integer", "description": "Product ID", "minimum": 1}
},
"required": ["product_id"],
},
"scope": "read",
},
{
"name": "update_post_seo",
"method_name": "update_post_seo",
"description": "Update SEO metadata for a WordPress post or page. Supports both Rank Math and Yoast SEO fields. Automatically detects which plugin is active. Requires SEO API Bridge plugin.",
"schema": {
"type": "object",
"properties": {
"post_id": {
"type": "integer",
"description": "Post or Page ID to update",
"minimum": 1,
},
"focus_keyword": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Primary focus keyword for SEO",
},
"seo_title": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "SEO meta title (appears in search results)",
},
"meta_description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "SEO meta description (appears in search results)",
},
"additional_keywords": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Additional keywords (comma-separated)",
},
"canonical_url": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Canonical URL for this content",
},
"robots": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Robots meta directives (e.g., ['noindex', 'nofollow'])",
},
"og_title": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Open Graph title for Facebook",
},
"og_description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Open Graph description for Facebook",
},
"og_image": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Open Graph image URL for Facebook",
},
"twitter_title": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Twitter Card title",
},
"twitter_description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Twitter Card description",
},
"twitter_image": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Twitter Card image URL",
},
},
"required": ["post_id"],
},
"scope": "write",
},
{
"name": "update_product_seo",
"method_name": "update_product_seo",
"description": "Update SEO metadata for a WooCommerce product. Same as update_post_seo but specifically for products. Requires SEO API Bridge plugin.",
"schema": {
"type": "object",
"properties": {
"product_id": {
"type": "integer",
"description": "Product ID to update",
"minimum": 1,
},
"focus_keyword": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Primary focus keyword for SEO",
},
"seo_title": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "SEO meta title (appears in search results)",
},
"meta_description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "SEO meta description (appears in search results)",
},
"additional_keywords": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Additional keywords (comma-separated)",
},
"canonical_url": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Canonical URL for this product",
},
"og_title": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Open Graph title for Facebook",
},
"og_description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Open Graph description for Facebook",
},
"og_image": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Open Graph image URL for Facebook",
},
"twitter_title": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Twitter Card title",
},
"twitter_description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Twitter Card description",
},
"twitter_image": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Twitter Card image URL",
},
},
"required": ["product_id"],
},
"scope": "write",
},
]
class SEOHandler:
"""Handle SEO-related operations for WordPress (Yoast SEO and RankMath)"""
def __init__(self, client: WordPressClient):
"""
Initialize SEO handler.
Args:
client: WordPress API client instance
"""
self.client = client
async def _check_seo_plugins(self) -> dict[str, Any]:
"""
Check if Rank Math or Yoast SEO is installed and SEO API Bridge is active.
Returns:
Dict with plugin status information
"""
try:
# First, try to use the new health check endpoint (v1.1.0+)
try:
status_result = await self.client.get(
"seo-api-bridge/v1/status", use_custom_namespace=True
)
if status_result and isinstance(status_result, dict):
# Successfully got status from dedicated endpoint
rank_math_info = status_result.get("seo_plugins", {}).get("rank_math", {})
yoast_info = status_result.get("seo_plugins", {}).get("yoast", {})
return {
"rank_math": {
"active": rank_math_info.get("active", False),
"version": rank_math_info.get("version"),
},
"yoast": {
"active": yoast_info.get("active", False),
"version": yoast_info.get("version"),
},
"api_bridge_active": True,
"api_bridge_version": status_result.get("version"),
"message": status_result.get("message", "SEO API Bridge is active"),
}
except Exception:
# Health check endpoint not available, fall back to old method
pass
# Fallback: Try to check posts for SEO meta fields
result = await self.client.get("posts", params={"per_page": 1})
# If no posts, try products
if not result or (isinstance(result, list) and len(result) == 0):
result = await self.client.get(
"products", params={"per_page": 1}, use_woocommerce=True
)
if not result or (isinstance(result, list) and len(result) == 0):
return {
"rank_math": {"active": False},
"yoast": {"active": False},
"api_bridge_active": False,
"message": "No posts or products available to check SEO plugin status. Please install SEO API Bridge v1.1.0+ or create content with SEO metadata.",
}
# Check if meta fields are present (indicates SEO API Bridge is active)
first_item = result[0] if isinstance(result, list) and len(result) > 0 else {}
meta = first_item.get("meta", {})
# Check for Rank Math fields
rank_math_active = any(
key in meta
for key in [
"rank_math_focus_keyword",
"rank_math_seo_title",
"rank_math_description",
]
)
# Check for Yoast SEO fields
yoast_active = any(
key in meta
for key in ["_yoast_wpseo_focuskw", "_yoast_wpseo_title", "_yoast_wpseo_metadesc"]
)
api_bridge_active = rank_math_active or yoast_active
return {
"rank_math": {"active": rank_math_active},
"yoast": {"active": yoast_active},
"api_bridge_active": api_bridge_active,
"message": (
"SEO API Bridge required. Please install and activate the plugin, then upgrade to v1.1.0+ for better detection."
if not api_bridge_active
else "SEO fields accessible via meta (legacy detection)"
),
}
except Exception as e:
return {
"rank_math": {"active": False},
"yoast": {"active": False},
"api_bridge_active": False,
"message": f"SEO plugin check failed: {str(e)}",
}
# === SEO METHODS ===
async def get_post_seo(self, post_id: int) -> str:
"""
Get SEO metadata for a post or page.
Args:
post_id: Post or Page ID
Returns:
JSON string with SEO metadata
"""
try:
result = await self.client.get(f"posts/{post_id}")
# Extract SEO meta fields
meta = result.get("meta", {})
# Check which SEO plugin is active
has_rank_math = "rank_math_focus_keyword" in meta
has_yoast = "_yoast_wpseo_focuskw" in meta
if not has_rank_math and not has_yoast:
return json.dumps(
{
"error": "SEO API Bridge plugin not detected",
"message": "Please install and activate the SEO API Bridge WordPress plugin.",
},
indent=2,
)
# Format SEO data
seo_data = {
"post_id": post_id,
"post_title": result.get("title", {}).get("rendered", ""),
"plugin_detected": "rank_math" if has_rank_math else "yoast",
}
if has_rank_math:
seo_data.update(
{
"focus_keyword": meta.get("rank_math_focus_keyword", ""),
"seo_title": meta.get("rank_math_seo_title", ""),
"meta_description": meta.get("rank_math_description", ""),
"additional_keywords": meta.get("rank_math_additional_keywords", ""),
"canonical_url": meta.get("rank_math_canonical_url", ""),
"robots": meta.get("rank_math_robots", []),
"breadcrumb_title": meta.get("rank_math_breadcrumb_title", ""),
"open_graph": {
"title": meta.get("rank_math_facebook_title", ""),
"description": meta.get("rank_math_facebook_description", ""),
"image": meta.get("rank_math_facebook_image", ""),
"image_id": meta.get("rank_math_facebook_image_id", ""),
},
"twitter": {
"title": meta.get("rank_math_twitter_title", ""),
"description": meta.get("rank_math_twitter_description", ""),
"image": meta.get("rank_math_twitter_image", ""),
"image_id": meta.get("rank_math_twitter_image_id", ""),
"card_type": meta.get("rank_math_twitter_card_type", ""),
},
}
)
elif has_yoast:
seo_data.update(
{
"focus_keyword": meta.get("_yoast_wpseo_focuskw", ""),
"seo_title": meta.get("_yoast_wpseo_title", ""),
"meta_description": meta.get("_yoast_wpseo_metadesc", ""),
"canonical_url": meta.get("_yoast_wpseo_canonical", ""),
"noindex": meta.get("_yoast_wpseo_meta-robots-noindex", ""),
"nofollow": meta.get("_yoast_wpseo_meta-robots-nofollow", ""),
"breadcrumb_title": meta.get("_yoast_wpseo_bctitle", ""),
"open_graph": {
"title": meta.get("_yoast_wpseo_opengraph-title", ""),
"description": meta.get("_yoast_wpseo_opengraph-description", ""),
"image": meta.get("_yoast_wpseo_opengraph-image", ""),
"image_id": meta.get("_yoast_wpseo_opengraph-image-id", ""),
},
"twitter": {
"title": meta.get("_yoast_wpseo_twitter-title", ""),
"description": meta.get("_yoast_wpseo_twitter-description", ""),
"image": meta.get("_yoast_wpseo_twitter-image", ""),
"image_id": meta.get("_yoast_wpseo_twitter-image-id", ""),
},
}
)
return json.dumps(seo_data, indent=2)
except Exception as e:
return json.dumps(
{
"error": str(e),
"message": f"Failed to get SEO data for post {post_id}: {str(e)}",
},
indent=2,
)
async def get_product_seo(self, product_id: int) -> str:
"""
Get SEO metadata for a WooCommerce product.
Uses SEO API Bridge endpoint for products.
Args:
product_id: Product ID
Returns:
JSON string with SEO metadata
"""
try:
# Use SEO API Bridge endpoint for products (same as update_product_seo)
result = await self.client.get(
f"seo-api-bridge/v1/products/{product_id}/seo", use_custom_namespace=True
)
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{
"error": str(e),
"message": f"Failed to get SEO data for product {product_id}: {str(e)}",
},
indent=2,
)
async def update_post_seo(
self,
post_id: int,
focus_keyword: str | None = None,
seo_title: str | None = None,
meta_description: str | None = None,
additional_keywords: str | None = None,
canonical_url: str | None = None,
robots: list[str] | None = None,
og_title: str | None = None,
og_description: str | None = None,
og_image: str | None = None,
twitter_title: str | None = None,
twitter_description: str | None = None,
twitter_image: str | None = None,
) -> str:
"""
Update SEO metadata for a post or page.
Automatically detects whether Rank Math or Yoast is active and uses appropriate field names.
Args:
post_id: Post or Page ID
focus_keyword: Primary focus keyword
seo_title: Meta title
meta_description: Meta description
additional_keywords: Additional keywords
canonical_url: Canonical URL
robots: Robots meta directives
og_title: Open Graph title
og_description: Open Graph description
og_image: Open Graph image URL
twitter_title: Twitter Card title
twitter_description: Twitter Card description
twitter_image: Twitter Card image URL
Returns:
JSON string with update result
"""
try:
# First check which SEO plugin is active
seo_check = await self._check_seo_plugins()
if not seo_check.get("api_bridge_active"):
return json.dumps(
{
"error": "SEO API Bridge plugin not detected",
"message": "Please install and activate the SEO API Bridge WordPress plugin.",
},
indent=2,
)
# Build meta object based on active plugin
meta = {}
if seo_check.get("rank_math", {}).get("active"):
# Use Rank Math field names
if focus_keyword is not None:
meta["rank_math_focus_keyword"] = focus_keyword
if seo_title is not None:
meta["rank_math_seo_title"] = seo_title
if meta_description is not None:
meta["rank_math_description"] = meta_description
if additional_keywords is not None:
meta["rank_math_additional_keywords"] = additional_keywords
if canonical_url is not None:
meta["rank_math_canonical_url"] = canonical_url
if robots is not None:
meta["rank_math_robots"] = robots
if og_title is not None:
meta["rank_math_facebook_title"] = og_title
if og_description is not None:
meta["rank_math_facebook_description"] = og_description
if og_image is not None:
meta["rank_math_facebook_image"] = og_image
if twitter_title is not None:
meta["rank_math_twitter_title"] = twitter_title
if twitter_description is not None:
meta["rank_math_twitter_description"] = twitter_description
if twitter_image is not None:
meta["rank_math_twitter_image"] = twitter_image
elif seo_check.get("yoast", {}).get("active"):
# Use Yoast field names
if focus_keyword is not None:
meta["_yoast_wpseo_focuskw"] = focus_keyword
if seo_title is not None:
meta["_yoast_wpseo_title"] = seo_title
if meta_description is not None:
meta["_yoast_wpseo_metadesc"] = meta_description
if canonical_url is not None:
meta["_yoast_wpseo_canonical"] = canonical_url
if og_title is not None:
meta["_yoast_wpseo_opengraph-title"] = og_title
if og_description is not None:
meta["_yoast_wpseo_opengraph-description"] = og_description
if og_image is not None:
meta["_yoast_wpseo_opengraph-image"] = og_image
if twitter_title is not None:
meta["_yoast_wpseo_twitter-title"] = twitter_title
if twitter_description is not None:
meta["_yoast_wpseo_twitter-description"] = twitter_description
if twitter_image is not None:
meta["_yoast_wpseo_twitter-image"] = twitter_image
# Update post with meta fields
data = {"meta": meta}
await self.client.post(f"posts/{post_id}", json_data=data)
# Read back saved values for confirmation
saved_seo_json = await self.get_post_seo(post_id)
saved_seo = json.loads(saved_seo_json)
response = {
"post_id": post_id,
"updated_fields": list(meta.keys()),
"message": f"SEO metadata updated successfully for post {post_id}",
"current_values": saved_seo,
}
return json.dumps(response, indent=2)
except Exception as e:
return json.dumps(
{
"error": str(e),
"message": f"Failed to update SEO data for post {post_id}: {str(e)}",
},
indent=2,
)
async def update_product_seo(
self,
product_id: int,
focus_keyword: str | None = None,
seo_title: str | None = None,
meta_description: str | None = None,
additional_keywords: str | None = None,
canonical_url: str | None = None,
og_title: str | None = None,
og_description: str | None = None,
og_image: str | None = None,
twitter_title: str | None = None,
twitter_description: str | None = None,
twitter_image: str | None = None,
) -> str:
"""
Update SEO metadata for a WooCommerce product.
Uses SEO API Bridge endpoint for products.
Args:
product_id: Product ID
focus_keyword: Primary focus keyword
seo_title: Meta title
meta_description: Meta description
additional_keywords: Additional keywords
canonical_url: Canonical URL
og_title: Open Graph title
og_description: Open Graph description
og_image: Open Graph image URL
twitter_title: Twitter Card title
twitter_description: Twitter Card description
twitter_image: Twitter Card image URL
Returns:
JSON string with update result
"""
try:
# Build request data with only provided fields
data = {}
if focus_keyword is not None:
data["focus_keyword"] = focus_keyword
if seo_title is not None:
data["seo_title"] = seo_title
if meta_description is not None:
data["meta_description"] = meta_description
if additional_keywords is not None:
data["additional_keywords"] = additional_keywords
if canonical_url is not None:
data["canonical_url"] = canonical_url
if og_title is not None:
data["og_title"] = og_title
if og_description is not None:
data["og_description"] = og_description
if og_image is not None:
data["og_image"] = og_image
if twitter_title is not None:
data["twitter_title"] = twitter_title
if twitter_description is not None:
data["twitter_description"] = twitter_description
if twitter_image is not None:
data["twitter_image"] = twitter_image
# Use SEO API Bridge endpoint for products
await self.client.post(
f"seo-api-bridge/v1/products/{product_id}/seo",
json_data=data,
use_custom_namespace=True,
)
# Read back saved values for confirmation
saved_seo_json = await self.get_product_seo(product_id)
saved_seo = json.loads(saved_seo_json)
response = {
"product_id": product_id,
"updated_fields": list(data.keys()),
"message": f"SEO metadata updated successfully for product {product_id}",
"current_values": saved_seo,
}
return json.dumps(response, indent=2)
except Exception as e:
return json.dumps(
{
"error": str(e),
"message": f"Failed to update SEO data for product {product_id}: {str(e)}",
},
indent=2,
)

View File

@@ -0,0 +1,243 @@
"""Site Handler - manages WordPress site management operations"""
import json
from typing import Any
from plugins.wordpress.client import WordPressClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
# === PLUGINS ===
{
"name": "list_plugins",
"method_name": "list_plugins",
"description": "List installed WordPress plugins. Shows plugin status (active/inactive), version, and details.",
"schema": {
"type": "object",
"properties": {
"status": {
"type": "string",
"description": "Filter plugins by status",
"enum": ["active", "inactive", "all"],
"default": "all",
}
},
},
"scope": "read",
},
# === THEMES ===
{
"name": "list_themes",
"method_name": "list_themes",
"description": "List installed WordPress themes. Returns all themes with their status and metadata.",
"schema": {
"type": "object",
"properties": {
"status": {
"type": "string",
"description": "Filter themes by status",
"enum": ["active", "inactive", "all"],
"default": "all",
}
},
},
"scope": "read",
},
{
"name": "get_active_theme",
"method_name": "get_active_theme",
"description": "Get information about the currently active WordPress theme including name, version, and author.",
"schema": {"type": "object", "properties": {}},
"scope": "read",
},
# === SETTINGS ===
{
"name": "get_settings",
"method_name": "get_settings",
"description": "Get WordPress site settings. Includes site title, description, URL, email, timezone, and language.",
"schema": {"type": "object", "properties": {}},
"scope": "read",
},
# === HEALTH ===
{
"name": "get_site_health",
"method_name": "get_site_health",
"description": "Check WordPress site health and accessibility. Returns comprehensive health status including WordPress, WooCommerce, and SEO plugin availability.",
"schema": {"type": "object", "properties": {}},
"scope": "read",
},
]
class SiteHandler:
"""Handle site management operations for WordPress"""
def __init__(self, client: WordPressClient):
"""
Initialize site handler.
Args:
client: WordPress API client instance
"""
self.client = client
# === PLUGINS ===
async def list_plugins(self, status: str = "all") -> str:
"""
List installed WordPress plugins.
Args:
status: Filter by plugin status (active, inactive, all)
Returns:
JSON string with plugins list
"""
try:
# Build endpoint with status filter
params = {}
if status != "all":
params["status"] = status
plugins = await self.client.get("plugins", params=params)
result = {
"total": len(plugins),
"plugins": [
{
"plugin": p["plugin"],
"name": p["name"],
"version": p["version"],
"status": p["status"],
"description": p.get("description", {}).get("raw", "")[:100],
}
for p in plugins
],
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to list plugins: {str(e)}"}, indent=2
)
# === THEMES ===
async def list_themes(self, status: str = "all") -> str:
"""
List installed WordPress themes.
Args:
status: Filter by theme status (active, inactive, all)
Returns:
JSON string with themes list
"""
try:
# Build endpoint with status filter
params = {}
if status != "all":
params["status"] = status
themes = await self.client.get("themes", params=params)
result = {
"total": len(themes),
"themes": [
{
"stylesheet": t["stylesheet"],
"name": t["name"]["rendered"],
"version": t["version"],
"status": t["status"],
}
for t in themes
],
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to list themes: {str(e)}"}, indent=2
)
async def get_active_theme(self) -> str:
"""
Get the currently active WordPress theme.
Returns:
JSON string with active theme details
"""
try:
themes = await self.client.get("themes", params={"status": "active"})
if themes:
theme = themes[0]
result = {
"stylesheet": theme["stylesheet"],
"name": theme["name"]["rendered"],
"version": theme["version"],
"author": theme.get("author", {}).get("raw", "Unknown"),
}
else:
result = {"message": "No active theme found"}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to get active theme: {str(e)}"}, indent=2
)
# === SETTINGS ===
async def get_settings(self) -> str:
"""
Get WordPress site settings.
Returns:
JSON string with site settings including title, description, URL, email, timezone, and language
"""
try:
settings = await self.client.get("settings")
result = {
"title": settings.get("title", ""),
"description": settings.get("description", ""),
"url": settings.get("url", ""),
"email": settings.get("email", ""),
"timezone": settings.get("timezone_string", ""),
"language": settings.get("language", ""),
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to get settings: {str(e)}"}, indent=2
)
# === HEALTH ===
async def get_site_health(self) -> str:
"""
Check WordPress site health and accessibility.
Returns:
JSON string with comprehensive health status
"""
try:
health = await self.client.check_site_health()
return json.dumps(health, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to get site health: {str(e)}"}, indent=2
)
async def health_check(self) -> dict[str, Any]:
"""
Perform health check using client's check_site_health method.
This method is used internally and returns a dict instead of JSON string.
Returns:
Dict with health status information
"""
return await self.client.check_site_health()

View File

@@ -0,0 +1,689 @@
"""Taxonomy Handler - manages WordPress categories, tags, and custom taxonomies"""
import json
from typing import Any
from plugins.wordpress.client import WordPressClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
# === CATEGORIES ===
{
"name": "list_categories",
"method_name": "list_categories",
"description": "List WordPress post categories. Returns hierarchical list of categories with post counts.",
"schema": {
"type": "object",
"properties": {
"per_page": {
"type": "integer",
"description": "Number of categories per page (1-100)",
"default": 100,
"minimum": 1,
"maximum": 100,
},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"hide_empty": {
"type": "boolean",
"description": "Hide categories with no posts",
"default": False,
},
},
},
"scope": "read",
},
{
"name": "create_category",
"method_name": "create_category",
"description": "Create a new WordPress category. Supports hierarchical categories with parent relationships.",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Category name", "minLength": 1},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Category description",
},
"parent": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Parent category ID for hierarchy",
},
},
"required": ["name"],
},
"scope": "write",
},
{
"name": "update_category",
"method_name": "update_category",
"description": "Update an existing WordPress category. Can modify name, description, and parent category.",
"schema": {
"type": "object",
"properties": {
"category_id": {
"type": "integer",
"description": "Category ID to update",
"minimum": 1,
},
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "New category name",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "New category description",
},
"parent": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "New parent category ID",
},
},
"required": ["category_id"],
},
"scope": "write",
},
{
"name": "delete_category",
"method_name": "delete_category",
"description": "Delete a WordPress category. Can permanently delete or reassign posts to another category.",
"schema": {
"type": "object",
"properties": {
"category_id": {
"type": "integer",
"description": "Category ID to delete",
"minimum": 1,
},
"force": {
"type": "boolean",
"description": "Force permanent deletion",
"default": False,
},
},
"required": ["category_id"],
},
"scope": "write",
},
# === TAGS ===
{
"name": "list_tags",
"method_name": "list_tags",
"description": "List WordPress post tags. Returns all tags with usage counts and metadata.",
"schema": {
"type": "object",
"properties": {
"per_page": {
"type": "integer",
"description": "Number of tags per page (1-100)",
"default": 100,
"minimum": 1,
"maximum": 100,
},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"hide_empty": {
"type": "boolean",
"description": "Hide tags with no posts",
"default": False,
},
},
},
"scope": "read",
},
{
"name": "create_tag",
"method_name": "create_tag",
"description": "Create a new WordPress tag. Tag slug is auto-generated from name if not provided.",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Tag name", "minLength": 1},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Tag description",
},
},
"required": ["name"],
},
"scope": "write",
},
{
"name": "update_tag",
"method_name": "update_tag",
"description": "Update an existing WordPress tag. Can modify name and description.",
"schema": {
"type": "object",
"properties": {
"tag_id": {"type": "integer", "description": "Tag ID to update", "minimum": 1},
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "New tag name",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "New tag description",
},
},
"required": ["tag_id"],
},
"scope": "write",
},
{
"name": "delete_tag",
"method_name": "delete_tag",
"description": "Delete a WordPress tag. Permanently removes the tag from all posts.",
"schema": {
"type": "object",
"properties": {
"tag_id": {"type": "integer", "description": "Tag ID to delete", "minimum": 1},
"force": {
"type": "boolean",
"description": "Force permanent deletion",
"default": False,
},
},
"required": ["tag_id"],
},
"scope": "write",
},
# === CUSTOM TAXONOMIES ===
{
"name": "list_taxonomies",
"method_name": "list_taxonomies",
"description": "List all registered taxonomies including built-in (category, post_tag) and custom taxonomies. Returns configuration and post type associations.",
"schema": {"type": "object", "properties": {}},
"scope": "read",
},
{
"name": "list_taxonomy_terms",
"method_name": "list_taxonomy_terms",
"description": "List terms of a specific taxonomy. Works with any registered taxonomy including categories, tags, and custom taxonomies.",
"schema": {
"type": "object",
"properties": {
"taxonomy": {
"type": "string",
"description": "Taxonomy slug (e.g., 'category', 'post_tag', 'product_cat')",
},
"per_page": {
"type": "integer",
"description": "Number of terms per page",
"default": 100,
"minimum": 1,
"maximum": 100,
},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"hide_empty": {
"type": "boolean",
"description": "Hide terms with no posts",
"default": False,
},
"parent": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Filter by parent term ID (for hierarchical taxonomies)",
},
},
"required": ["taxonomy"],
},
"scope": "read",
},
{
"name": "create_taxonomy_term",
"method_name": "create_taxonomy_term",
"description": "Create a new term in a taxonomy. Supports hierarchical taxonomies with parent terms. Works with any registered taxonomy.",
"schema": {
"type": "object",
"properties": {
"taxonomy": {
"type": "string",
"description": "Taxonomy slug (e.g., 'category', 'product_cat')",
},
"name": {"type": "string", "description": "Term name", "minLength": 1},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Term description",
},
"parent": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "Parent term ID for hierarchical taxonomies (e.g., subcategories)",
},
},
"required": ["taxonomy", "name"],
},
"scope": "write",
},
]
class TaxonomyHandler:
"""Handle taxonomy-related operations for WordPress"""
def __init__(self, client: WordPressClient):
"""
Initialize taxonomy handler.
Args:
client: WordPress API client instance
"""
self.client = client
# === CATEGORIES ===
async def list_categories(
self, per_page: int = 100, page: int = 1, hide_empty: bool = False
) -> str:
"""
List WordPress post categories.
Args:
per_page: Number of categories per page (1-100)
page: Page number
hide_empty: Hide categories with no posts
Returns:
JSON string with categories list
"""
try:
params = {
"per_page": per_page,
"page": page,
"hide_empty": "true" if hide_empty else "false",
}
categories = await self.client.get("categories", params=params)
result = {
"total": len(categories),
"categories": [
{
"id": cat["id"],
"name": cat["name"],
"slug": cat["slug"],
"description": cat.get("description", ""),
"count": cat.get("count", 0),
"parent": cat.get("parent", 0),
}
for cat in categories
],
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to list categories: {str(e)}"}, indent=2
)
async def create_category(
self, name: str, description: str | None = None, parent: int | None = None
) -> str:
"""
Create a new WordPress category.
Args:
name: Category name
description: Category description
parent: Parent category ID for hierarchy
Returns:
JSON string with created category data
"""
try:
data = {"name": name}
if description:
data["description"] = description
if parent:
data["parent"] = parent
category = await self.client.post("categories", json_data=data)
result = {
"id": category["id"],
"name": category["name"],
"slug": category["slug"],
"message": f"Category '{name}' created successfully with ID {category['id']}",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to create category: {str(e)}"}, indent=2
)
async def update_category(
self,
category_id: int,
name: str | None = None,
description: str | None = None,
parent: int | None = None,
) -> str:
"""
Update an existing WordPress category.
Args:
category_id: Category ID to update
name: New category name
description: New category description
parent: New parent category ID
Returns:
JSON string with updated category data
"""
try:
# Build data dict with only provided values
data = {}
if name is not None:
data["name"] = name
if description is not None:
data["description"] = description
if parent is not None:
data["parent"] = parent
category = await self.client.post(f"categories/{category_id}", json_data=data)
result = {
"id": category["id"],
"name": category["name"],
"slug": category["slug"],
"message": f"Category {category_id} updated successfully",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to update category {category_id}: {str(e)}"},
indent=2,
)
async def delete_category(self, category_id: int, force: bool = False) -> str:
"""
Delete a WordPress category.
Args:
category_id: Category ID to delete
force: Force permanent deletion
Returns:
JSON string with deletion result
"""
try:
params = {"force": "true" if force else "false"}
result = await self.client.delete(f"categories/{category_id}", params=params)
message = f"Category {category_id} deleted successfully"
return json.dumps({"success": True, "message": message, "result": result}, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to delete category {category_id}: {str(e)}"},
indent=2,
)
# === TAGS ===
async def list_tags(self, per_page: int = 100, page: int = 1, hide_empty: bool = False) -> str:
"""
List WordPress post tags.
Args:
per_page: Number of tags per page (1-100)
page: Page number
hide_empty: Hide tags with no posts
Returns:
JSON string with tags list
"""
try:
params = {
"per_page": per_page,
"page": page,
"hide_empty": "true" if hide_empty else "false",
}
tags = await self.client.get("tags", params=params)
result = {
"total": len(tags),
"tags": [
{
"id": tag["id"],
"name": tag["name"],
"slug": tag["slug"],
"description": tag.get("description", ""),
"count": tag.get("count", 0),
}
for tag in tags
],
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to list tags: {str(e)}"}, indent=2
)
async def create_tag(self, name: str, description: str | None = None) -> str:
"""
Create a new WordPress tag.
Args:
name: Tag name
description: Tag description
Returns:
JSON string with created tag data
"""
try:
data = {"name": name}
if description:
data["description"] = description
tag = await self.client.post("tags", json_data=data)
result = {
"id": tag["id"],
"name": tag["name"],
"slug": tag["slug"],
"message": f"Tag '{name}' created successfully with ID {tag['id']}",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to create tag: {str(e)}"}, indent=2
)
async def update_tag(
self, tag_id: int, name: str | None = None, description: str | None = None
) -> str:
"""
Update an existing WordPress tag.
Args:
tag_id: Tag ID to update
name: New tag name
description: New tag description
Returns:
JSON string with updated tag data
"""
try:
# Build data dict with only provided values
data = {}
if name is not None:
data["name"] = name
if description is not None:
data["description"] = description
tag = await self.client.post(f"tags/{tag_id}", json_data=data)
result = {
"id": tag["id"],
"name": tag["name"],
"slug": tag["slug"],
"message": f"Tag {tag_id} updated successfully",
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to update tag {tag_id}: {str(e)}"}, indent=2
)
async def delete_tag(self, tag_id: int, force: bool = False) -> str:
"""
Delete a WordPress tag.
Args:
tag_id: Tag ID to delete
force: Force permanent deletion
Returns:
JSON string with deletion result
"""
try:
params = {"force": "true" if force else "false"}
result = await self.client.delete(f"tags/{tag_id}", params=params)
message = f"Tag {tag_id} deleted successfully"
return json.dumps({"success": True, "message": message, "result": result}, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to delete tag {tag_id}: {str(e)}"}, indent=2
)
# === CUSTOM TAXONOMIES ===
async def list_taxonomies(self) -> str:
"""
List all registered taxonomies.
Returns list of all taxonomies including built-in (category, post_tag)
and custom taxonomies (portfolio_category, etc.).
Returns:
JSON string with total count and taxonomies list
"""
try:
taxonomies_data = await self.client.get("taxonomies")
# Convert dict to list
taxonomies = []
if isinstance(taxonomies_data, dict):
for slug, data in taxonomies_data.items():
taxonomies.append(
{
"slug": slug,
"name": data.get("name", slug),
"description": data.get("description", ""),
"types": data.get("types", []),
"hierarchical": data.get("hierarchical", False),
"rest_base": data.get("rest_base", slug),
}
)
result = {"total": len(taxonomies), "taxonomies": taxonomies}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to list taxonomies: {str(e)}"}, indent=2
)
async def list_taxonomy_terms(
self,
taxonomy: str,
per_page: int = 100,
page: int = 1,
hide_empty: bool = False,
parent: int | None = None,
) -> str:
"""
List terms of a specific taxonomy.
Args:
taxonomy: Taxonomy slug (e.g., 'category', 'product_category')
per_page: Number of terms per page
page: Page number
hide_empty: Hide terms with no posts
parent: Filter by parent term ID
Returns:
JSON string with terms list
"""
try:
params = {
"per_page": per_page,
"page": page,
"hide_empty": "true" if hide_empty else "false",
}
if parent is not None:
params["parent"] = parent
terms = await self.client.get(taxonomy, params=params)
result = {
"total": len(terms) if isinstance(terms, list) else 0,
"taxonomy": taxonomy,
"page": page,
"per_page": per_page,
"terms": terms,
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{
"error": str(e),
"message": f"Failed to list taxonomy terms for '{taxonomy}': {str(e)}",
},
indent=2,
)
async def create_taxonomy_term(
self, taxonomy: str, name: str, description: str | None = None, parent: int | None = None
) -> str:
"""
Create a new term in a taxonomy.
Args:
taxonomy: Taxonomy slug (e.g., 'category', 'product_category')
name: Term name
description: Term description
parent: Parent term ID for hierarchical taxonomies
Returns:
JSON string with created term details
"""
try:
data = {"name": name}
if description:
data["description"] = description
if parent:
data["parent"] = parent
term = await self.client.post(taxonomy, json_data=data)
return json.dumps(term, indent=2)
except Exception as e:
return json.dumps(
{
"error": str(e),
"message": f"Failed to create taxonomy term in '{taxonomy}': {str(e)}",
},
indent=2,
)

View File

@@ -0,0 +1,134 @@
"""Users Handler - manages WordPress user operations"""
import json
from typing import Any
from plugins.wordpress.client import WordPressClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
# === USERS ===
{
"name": "list_users",
"method_name": "list_users",
"description": "List WordPress users. Returns paginated list of users with name, username, email, and roles.",
"schema": {
"type": "object",
"properties": {
"per_page": {
"type": "integer",
"description": "Number of users per page (1-100)",
"default": 10,
"minimum": 1,
"maximum": 100,
},
"page": {
"type": "integer",
"description": "Page number",
"default": 1,
"minimum": 1,
},
"roles": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Filter by user roles (e.g., ['administrator', 'editor', 'author'])",
},
},
},
"scope": "read",
},
{
"name": "get_current_user",
"method_name": "get_current_user",
"description": "Get information about the currently authenticated user including ID, name, username, email, and roles.",
"schema": {"type": "object", "properties": {}},
"scope": "read",
},
]
class UsersHandler:
"""Handle user-related operations for WordPress"""
def __init__(self, client: WordPressClient):
"""
Initialize users handler.
Args:
client: WordPress API client instance
"""
self.client = client
# === USERS ===
async def list_users(
self, per_page: int = 10, page: int = 1, roles: list[str] | None = None
) -> str:
"""
List WordPress users.
Args:
per_page: Number of users per page (1-100)
page: Page number
roles: Filter by user roles (e.g., ['administrator', 'editor'])
Returns:
JSON string with users list
"""
try:
params = {"per_page": per_page, "page": page}
if roles:
params["roles"] = ",".join(roles)
users = await self.client.get("users", params=params)
# Format response
result = {
"total": len(users),
"page": page,
"per_page": per_page,
"users": [
{
"id": user["id"],
"name": user["name"],
"username": user["slug"],
"email": user.get("email", "N/A"),
"roles": user.get("roles", []),
"link": user.get("link", ""),
}
for user in users
],
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to list users: {str(e)}"}, indent=2
)
async def get_current_user(self) -> str:
"""
Get current authenticated user.
Returns:
JSON string with current user data
"""
try:
user = await self.client.get("users/me")
result = {
"id": user["id"],
"name": user["name"],
"username": user["slug"],
"email": user.get("email", "N/A"),
"roles": user.get("roles", []),
"capabilities": user.get("capabilities", {}),
"description": user.get("description", ""),
"link": user.get("link", ""),
"registered_date": user.get("registered_date", ""),
}
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to get current user: {str(e)}"}, indent=2
)

View File

@@ -0,0 +1,520 @@
"""WP-CLI Handler - manages WordPress WP-CLI operations"""
import json
from typing import Any
from plugins.wordpress.wp_cli import WPCLIManager
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator"""
return [
# === CACHE MANAGEMENT (4 tools) ===
{
"name": "wp_cache_flush",
"method_name": "wp_cache_flush",
"description": "Flush WordPress object cache. Clears all cached objects from Redis, Memcached, or file cache. Safe to run anytime.",
"schema": {"type": "object", "properties": {}},
"scope": "write",
},
{
"name": "wp_cache_type",
"method_name": "wp_cache_type",
"description": "Get the object cache type being used (Redis, Memcached, file-based, etc.).",
"schema": {"type": "object", "properties": {}},
"scope": "write",
},
{
"name": "wp_transient_delete_all",
"method_name": "wp_transient_delete_all",
"description": "Delete all expired transients from the database. Improves database performance by cleaning up temporary cached data.",
"schema": {"type": "object", "properties": {}},
"scope": "write",
},
{
"name": "wp_transient_list",
"method_name": "wp_transient_list",
"description": "List transients in the database (limited to first 100). Shows transient keys with expiration times. Useful for debugging caching issues.",
"schema": {"type": "object", "properties": {}},
"scope": "write",
},
# === DATABASE OPERATIONS (3 tools) ===
{
"name": "wp_db_check",
"method_name": "wp_db_check",
"description": "Check WordPress database for errors. Runs integrity checks to ensure tables are healthy. Read-only operation.",
"schema": {"type": "object", "properties": {}},
"scope": "write",
},
{
"name": "wp_db_optimize",
"method_name": "wp_db_optimize",
"description": "Optimize WordPress database tables. Runs OPTIMIZE TABLE on all WordPress tables to reclaim space and improve performance.",
"schema": {"type": "object", "properties": {}},
"scope": "write",
},
{
"name": "wp_db_export",
"method_name": "wp_db_export",
"description": "Export WordPress database to SQL file in /tmp directory. Creates timestamped backup file for database recovery.",
"schema": {"type": "object", "properties": {}},
"scope": "write",
},
# === PLUGIN/THEME INFO (4 tools) ===
{
"name": "wp_plugin_list_detailed",
"method_name": "wp_plugin_list_detailed",
"description": "List all WordPress plugins with detailed information including versions, status (active/inactive), and available updates.",
"schema": {"type": "object", "properties": {}},
"scope": "write",
},
{
"name": "wp_theme_list_detailed",
"method_name": "wp_theme_list_detailed",
"description": "List all WordPress themes with detailed information including versions, status, and active theme identification.",
"schema": {"type": "object", "properties": {}},
"scope": "write",
},
{
"name": "wp_plugin_verify_checksums",
"method_name": "wp_plugin_verify_checksums",
"description": "Verify plugin file integrity against WordPress.org checksums. Detects tampering or corruption. Only works for plugins from WordPress.org repository.",
"schema": {"type": "object", "properties": {}},
"scope": "write",
},
{
"name": "wp_core_verify_checksums",
"method_name": "wp_core_verify_checksums",
"description": "Verify WordPress core files against official checksums. Critical security tool for detecting tampering or unauthorized modifications.",
"schema": {"type": "object", "properties": {}},
"scope": "write",
},
# === SEARCH & REPLACE + UPDATES (4 tools) ===
{
"name": "wp_search_replace_dry_run",
"method_name": "wp_search_replace_dry_run",
"description": "Search and replace in database (DRY RUN ONLY). Previews what would be changed. NEVER makes actual changes. Use for migration planning.",
"schema": {
"type": "object",
"properties": {
"old_string": {
"type": "string",
"description": "String to search for in database",
"minLength": 1,
"maxLength": 500,
},
"new_string": {
"type": "string",
"description": "String to replace with",
"minLength": 1,
"maxLength": 500,
},
"tables": {
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
"description": "Optional list of specific tables to search (default: all tables)",
},
},
"required": ["old_string", "new_string"],
},
"scope": "write",
},
{
"name": "wp_plugin_update",
"method_name": "wp_plugin_update",
"description": "Update WordPress plugin(s). Default is DRY RUN mode (shows available updates). Set dry_run=false to apply updates. Always backup first!",
"schema": {
"type": "object",
"properties": {
"plugin_name": {
"type": "string",
"description": "Plugin slug to update, or 'all' for all plugins",
"minLength": 1,
},
"dry_run": {
"type": "boolean",
"description": "If true, only show available updates without applying them (default: true)",
"default": True,
},
},
"required": ["plugin_name"],
},
"scope": "write",
},
{
"name": "wp_theme_update",
"method_name": "wp_theme_update",
"description": "Update WordPress theme(s). Default is DRY RUN mode (shows available updates). Set dry_run=false to apply updates. WARNING: Updating active theme can break site appearance!",
"schema": {
"type": "object",
"properties": {
"theme_name": {
"type": "string",
"description": "Theme slug to update, or 'all' for all themes",
"minLength": 1,
},
"dry_run": {
"type": "boolean",
"description": "If true, only show available updates without applying them (default: true)",
"default": True,
},
},
"required": ["theme_name"],
},
"scope": "write",
},
{
"name": "wp_core_update",
"method_name": "wp_core_update",
"description": "Update WordPress core. Default is DRY RUN mode (shows available updates). Set dry_run=false to apply updates. CRITICAL: Always backup before core updates!",
"schema": {
"type": "object",
"properties": {
"version": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Specific version to update to, or null for latest version",
},
"dry_run": {
"type": "boolean",
"description": "If true, only show available updates without applying them (default: true)",
"default": True,
},
},
},
"scope": "write",
},
]
class WPCLIHandler:
"""Handle WP-CLI operations for WordPress"""
def __init__(self, wp_cli: WPCLIManager):
"""
Initialize WP-CLI handler.
Args:
wp_cli: WPCLIManager instance for executing WP-CLI commands
"""
self.wp_cli = wp_cli
# === CACHE MANAGEMENT ===
async def wp_cache_flush(self) -> str:
"""
Flush WordPress object cache.
Clears all cached objects from the object cache (Redis, Memcached, or file).
Safe to run anytime - will not affect database or content.
Returns:
JSON string with flush status and message
"""
try:
result = await self.wp_cli.wp_cache_flush()
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to flush cache: {str(e)}"}, indent=2
)
async def wp_cache_type(self) -> str:
"""
Get the object cache type being used.
Shows which caching backend is active (e.g., Redis, Memcached, file-based).
Returns:
JSON string with cache type information
"""
try:
result = await self.wp_cli.wp_cache_type()
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to get cache type: {str(e)}"}, indent=2
)
async def wp_transient_delete_all(self) -> str:
"""
Delete all expired transients from the database.
Transients are temporary cached data stored in the WordPress database.
This command only deletes expired transients, improving database performance.
Returns:
JSON string with count of deleted transients
"""
try:
result = await self.wp_cli.wp_transient_delete_all()
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to delete transients: {str(e)}"}, indent=2
)
async def wp_transient_list(self) -> str:
"""
List transients in the database (limited to first 100).
Shows transient keys with their expiration times.
Useful for debugging caching issues.
Returns:
JSON string with total count and list of transients (max 100)
"""
try:
result = await self.wp_cli.wp_transient_list()
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to list transients: {str(e)}"}, indent=2
)
# === DATABASE OPERATIONS ===
async def wp_db_check(self) -> str:
"""
Check WordPress database for errors.
Runs database integrity checks to ensure tables are healthy.
Safe to run - read-only operation.
Returns:
JSON string with health status and tables checked
"""
try:
result = await self.wp_cli.wp_db_check()
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to check database: {str(e)}"}, indent=2
)
async def wp_db_optimize(self) -> str:
"""
Optimize WordPress database tables.
Runs OPTIMIZE TABLE on all WordPress tables to reclaim space
and improve performance. Safe operation - non-destructive.
Returns:
JSON string with optimization results
"""
try:
result = await self.wp_cli.wp_db_optimize()
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to optimize database: {str(e)}"}, indent=2
)
async def wp_db_export(self) -> str:
"""
Export WordPress database to SQL file in /tmp.
Creates a database backup in the /tmp directory with timestamp.
Safe - exports are only saved to /tmp for security.
Returns:
JSON string with export file path and size
"""
try:
result = await self.wp_cli.wp_db_export()
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to export database: {str(e)}"}, indent=2
)
# === PLUGIN/THEME INFO ===
async def wp_plugin_list_detailed(self) -> str:
"""
List all WordPress plugins with detailed information.
Shows plugin names, versions, status (active/inactive), and available updates.
Useful for inventory management and update planning.
Returns:
JSON string with total count and plugin list
"""
try:
result = await self.wp_cli.wp_plugin_list_detailed()
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to list plugins: {str(e)}"}, indent=2
)
async def wp_theme_list_detailed(self) -> str:
"""
List all WordPress themes with detailed information.
Shows theme names, versions, status, and identifies the active theme.
Useful for theme management and updates.
Returns:
JSON string with total count, theme list, and active theme
"""
try:
result = await self.wp_cli.wp_theme_list_detailed()
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to list themes: {str(e)}"}, indent=2
)
async def wp_plugin_verify_checksums(self) -> str:
"""
Verify plugin file integrity against WordPress.org checksums.
Checks all plugins against official checksums to detect tampering or corruption.
Important security tool for detecting malware or unauthorized modifications.
Note: Only works for plugins from WordPress.org repository.
Premium/custom plugins will be skipped.
Returns:
JSON string with verification results
"""
try:
result = await self.wp_cli.wp_plugin_verify_checksums()
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to verify plugin checksums: {str(e)}"},
indent=2,
)
async def wp_core_verify_checksums(self) -> str:
"""
Verify WordPress core files against official checksums.
Checks WordPress core files for tampering, corruption, or unauthorized modifications.
Critical security tool for ensuring WordPress integrity.
Returns:
JSON string with verification status and any modified files
"""
try:
result = await self.wp_cli.wp_core_verify_checksums()
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to verify core checksums: {str(e)}"}, indent=2
)
# === SEARCH & REPLACE + UPDATES ===
async def wp_search_replace_dry_run(
self, old_string: str, new_string: str, tables: list[str] | None = None
) -> str:
"""
Search and replace in database (DRY RUN ONLY - no actual changes).
Previews what would be changed if you run search-replace.
ALWAYS runs in dry-run mode - never makes actual changes.
Security: This tool ONLY shows what would be changed. To make actual
changes, you must use WP-CLI directly with appropriate backups.
Args:
old_string: String to search for
new_string: String to replace with
tables: Optional list of specific tables to search (default: all tables)
Returns:
JSON string with preview of changes
"""
try:
result = await self.wp_cli.wp_search_replace_dry_run(
old_string=old_string, new_string=new_string, tables=tables
)
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to run search-replace dry run: {str(e)}"},
indent=2,
)
async def wp_plugin_update(self, plugin_name: str, dry_run: bool = True) -> str:
"""
Update WordPress plugin(s) - DRY RUN by default.
Shows available updates or performs actual update.
Default behavior is DRY RUN for safety.
Security:
- Default: dry_run=True (only shows what would be updated)
- Before actual update: backup database and files
- Check plugin compatibility before major version updates
Args:
plugin_name: Plugin slug or "all" for all plugins
dry_run: If True, only show available updates (default: True)
Returns:
JSON string with update information or results
"""
try:
result = await self.wp_cli.wp_plugin_update(plugin_name=plugin_name, dry_run=dry_run)
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to update plugin: {str(e)}"}, indent=2
)
async def wp_theme_update(self, theme_name: str, dry_run: bool = True) -> str:
"""
Update WordPress theme(s) - DRY RUN by default.
Shows available updates or performs actual update.
Default behavior is DRY RUN for safety.
Security:
- Default: dry_run=True (only shows what would be updated)
- Before actual update: backup database and files
- Test theme compatibility after updates
- WARNING: Updating active theme can break site appearance
Args:
theme_name: Theme slug or "all" for all themes
dry_run: If True, only show available updates (default: True)
Returns:
JSON string with update information or results
"""
try:
result = await self.wp_cli.wp_theme_update(theme_name=theme_name, dry_run=dry_run)
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to update theme: {str(e)}"}, indent=2
)
async def wp_core_update(self, version: str | None = None, dry_run: bool = True) -> str:
"""
Update WordPress core - DRY RUN by default.
Shows available updates or performs actual core update.
Default behavior is DRY RUN for safety.
Security:
- Default: dry_run=True (only shows what would be updated)
- CRITICAL: Always backup database and files before core updates
- Check plugin/theme compatibility before major version updates
- Test thoroughly on staging environment first
- Major version updates may have breaking changes
Args:
version: Specific version to update to, or None for latest (default: None)
dry_run: If True, only show available updates (default: True)
Returns:
JSON string with update information or results
"""
try:
result = await self.wp_cli.wp_core_update(version=version, dry_run=dry_run)
return json.dumps(result, indent=2)
except Exception as e:
return json.dumps(
{"error": str(e), "message": f"Failed to update WordPress core: {str(e)}"}, indent=2
)