From c2f916910d0fb3be5d6a15046f15ea84b81e26a6 Mon Sep 17 00:00:00 2001 From: Fanyang Meng Date: Tue, 11 Feb 2025 15:26:55 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=8A=20Add=20logs=20for=20better=20debu?= =?UTF-8?q?gging,=20fix=20indent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ghost_mcp/tools.py | 446 +++++++++++++++++++++++++++++------------ 1 file changed, 323 insertions(+), 123 deletions(-) diff --git a/src/ghost_mcp/tools.py b/src/ghost_mcp/tools.py index 52f2146..b17a44e 100644 --- a/src/ghost_mcp/tools.py +++ b/src/ghost_mcp/tools.py @@ -18,31 +18,42 @@ async def read_user(user_id: str, ctx: Context = None) -> str: Returns: Formatted string containing the user details """ + if ctx: + ctx.info(f"Reading user details for ID: {user_id}") + try: + if ctx: + ctx.debug("Getting auth headers") headers = await get_auth_headers(STAFF_API_KEY) + + if ctx: + ctx.debug(f"Making API request to /users/{user_id}/") data = await make_ghost_request( f"users/{user_id}/?include=roles", headers, ctx ) + + if ctx: + ctx.debug("Processing user data") user = data["users"][0] roles = [role.get('name') for role in user.get('roles', [])] return f""" - Name: {user.get('name', 'Unknown')} - Email: {user.get('email', 'Unknown')} - Slug: {user.get('slug', 'Unknown')} - Status: {user.get('status', 'Unknown')} - Roles: {', '.join(roles)} - Location: {user.get('location', 'Not specified')} - Website: {user.get('website', 'None')} - Bio: {user.get('bio', 'No bio')} - Profile Image: {user.get('profile_image', 'None')} - Cover Image: {user.get('cover_image', 'None')} - Created: {user.get('created_at', 'Unknown')} - Last Seen: {user.get('last_seen', 'Never')} - """ +Name: {user.get('name', 'Unknown')} +Email: {user.get('email', 'Unknown')} +Slug: {user.get('slug', 'Unknown')} +Status: {user.get('status', 'Unknown')} +Roles: {', '.join(roles)} +Location: {user.get('location', 'Not specified')} +Website: {user.get('website', 'None')} +Bio: {user.get('bio', 'No bio')} +Profile Image: {user.get('profile_image', 'None')} +Cover Image: {user.get('cover_image', 'None')} +Created: {user.get('created_at', 'Unknown')} +Last Seen: {user.get('last_seen', 'Never')} +""" except GhostError as e: return str(e) @@ -56,13 +67,24 @@ async def read_member(member_id: str, ctx: Context = None) -> str: Returns: Formatted string containing the member details """ + if ctx: + ctx.info(f"Reading member details for ID: {member_id}") + try: + if ctx: + ctx.debug("Getting auth headers") headers = await get_auth_headers(STAFF_API_KEY) + + if ctx: + ctx.debug(f"Making API request to /members/{member_id}/") data = await make_ghost_request( f"members/{member_id}/?include=newsletters,subscriptions", headers, ctx ) + + if ctx: + ctx.debug("Processing member response data") member = data["members"][0] newsletters = [nl.get('name') for nl in member.get('newsletters', [])] @@ -80,19 +102,21 @@ async def read_member(member_id: str, ctx: Context = None) -> str: """ return f""" - Name: {member.get('name', 'Unknown')} - Email: {member.get('email', 'Unknown')} - Status: {member.get('status', 'Unknown')} - Newsletters: {', '.join(newsletters) if newsletters else 'None'} - Created: {member.get('created_at', 'Unknown')} - Note: {member.get('note', 'No notes')} - Labels: {', '.join(label.get('name', '') for label in member.get('labels', []))} - Email Count: {member.get('email_count', 0)} - Email Opened Count: {member.get('email_opened_count', 0)} - Email Open Rate: {member.get('email_open_rate', 0)}% - Last Seen At: {member.get('last_seen_at', 'Never')}{subscription_info} - """ +Name: {member.get('name', 'Unknown')} +Email: {member.get('email', 'Unknown')} +Status: {member.get('status', 'Unknown')} +Newsletters: {', '.join(newsletters) if newsletters else 'None'} +Created: {member.get('created_at', 'Unknown')} +Note: {member.get('note', 'No notes')} +Labels: {', '.join(label.get('name', '') for label in member.get('labels', []))} +Email Count: {member.get('email_count', 0)} +Email Opened Count: {member.get('email_opened_count', 0)} +Email Open Rate: {member.get('email_open_rate', 0)}% +Last Seen At: {member.get('last_seen_at', 'Never')}{subscription_info} +""" except GhostError as e: + if ctx: + ctx.error(f"Failed to read member: {str(e)}") return str(e) async def read_tier(tier_id: str, ctx: Context = None) -> str: @@ -105,32 +129,45 @@ async def read_tier(tier_id: str, ctx: Context = None) -> str: Returns: Formatted string containing the tier details """ + if ctx: + ctx.info(f"Reading tier details for ID: {tier_id}") + try: + if ctx: + ctx.debug("Getting auth headers") headers = await get_auth_headers(STAFF_API_KEY) + + if ctx: + ctx.debug(f"Making API request to /tiers/{tier_id}/") data = await make_ghost_request( f"tiers/{tier_id}/?include=monthly_price,yearly_price,benefits", headers, ctx ) + + if ctx: + ctx.debug("Processing tier response data") tier = data["tiers"][0] benefits = tier.get('benefits', []) return f""" - Name: {tier.get('name', 'Unknown')} - Description: {tier.get('description', 'No description')} - Type: {tier.get('type', 'Unknown')} - Active: {tier.get('active', False)} - Welcome Page URL: {tier.get('welcome_page_url', 'None')} - Created: {tier.get('created_at', 'Unknown')} - Updated: {tier.get('updated_at', 'Unknown')} - Monthly Price: {tier.get('monthly_price', 'N/A')} - Yearly Price: {tier.get('yearly_price', 'N/A')} - Currency: {tier.get('currency', 'Unknown')} - Benefits: - {chr(10).join(f'- {benefit}' for benefit in benefits) if benefits else 'No benefits listed'} - """ +Name: {tier.get('name', 'Unknown')} +Description: {tier.get('description', 'No description')} +Type: {tier.get('type', 'Unknown')} +Active: {tier.get('active', False)} +Welcome Page URL: {tier.get('welcome_page_url', 'None')} +Created: {tier.get('created_at', 'Unknown')} +Updated: {tier.get('updated_at', 'Unknown')} +Monthly Price: {tier.get('monthly_price', 'N/A')} +Yearly Price: {tier.get('yearly_price', 'N/A')} +Currency: {tier.get('currency', 'Unknown')} +Benefits: +{chr(10).join(f'- {benefit}' for benefit in benefits) if benefits else 'No benefits listed'} +""" except GhostError as e: + if ctx: + ctx.error(f"Failed to read tier: {str(e)}") return str(e) async def read_offer(offer_id: str, ctx: Context = None) -> str: @@ -143,32 +180,45 @@ async def read_offer(offer_id: str, ctx: Context = None) -> str: Returns: Formatted string containing the offer details """ + if ctx: + ctx.info(f"Reading offer details for ID: {offer_id}") + try: + if ctx: + ctx.debug("Getting auth headers") headers = await get_auth_headers(STAFF_API_KEY) + + if ctx: + ctx.debug(f"Making API request to /offers/{offer_id}/") data = await make_ghost_request( f"offers/{offer_id}/", headers, ctx ) + + if ctx: + ctx.debug("Processing offer response data") offer = data["offers"][0] return f""" - Name: {offer.get('name', 'Unknown')} - Code: {offer.get('code', 'Unknown')} - Display Title: {offer.get('display_title', 'No display title')} - Display Description: {offer.get('display_description', 'No description')} - Type: {offer.get('type', 'Unknown')} - Status: {offer.get('status', 'Unknown')} - Cadence: {offer.get('cadence', 'Unknown')} - Amount: {offer.get('amount', 'Unknown')} - Duration: {offer.get('duration', 'Unknown')} - Currency: {offer.get('currency', 'N/A')} - Tier: {offer.get('tier', {}).get('name', 'Unknown')} - Redemption Count: {offer.get('redemption_count', 0)} - Created: {offer.get('created_at', 'Unknown')} - """ +Name: {offer.get('name', 'Unknown')} +Code: {offer.get('code', 'Unknown')} +Display Title: {offer.get('display_title', 'No display title')} +Display Description: {offer.get('display_description', 'No description')} +Type: {offer.get('type', 'Unknown')} +Status: {offer.get('status', 'Unknown')} +Cadence: {offer.get('cadence', 'Unknown')} +Amount: {offer.get('amount', 'Unknown')} +Duration: {offer.get('duration', 'Unknown')} +Currency: {offer.get('currency', 'N/A')} +Tier: {offer.get('tier', {}).get('name', 'Unknown')} +Redemption Count: {offer.get('redemption_count', 0)} +Created: {offer.get('created_at', 'Unknown')} +""" except GhostError as e: + if ctx: + ctx.error(f"Failed to read offer: {str(e)}") return str(e) async def read_newsletter(newsletter_id: str, ctx: Context = None) -> str: @@ -181,37 +231,50 @@ async def read_newsletter(newsletter_id: str, ctx: Context = None) -> str: Returns: Formatted string containing the newsletter details """ + if ctx: + ctx.info(f"Reading newsletter details for ID: {newsletter_id}") + try: + if ctx: + ctx.debug("Getting auth headers") headers = await get_auth_headers(STAFF_API_KEY) + + if ctx: + ctx.debug(f"Making API request to /newsletters/{newsletter_id}/") data = await make_ghost_request( f"newsletters/{newsletter_id}/", headers, ctx ) + + if ctx: + ctx.debug("Processing newsletter response data") newsletter = data["newsletters"][0] return f""" - Name: {newsletter.get('name', 'Unknown')} - Description: {newsletter.get('description', 'No description')} - Status: {newsletter.get('status', 'Unknown')} - Visibility: {newsletter.get('visibility', 'Unknown')} - Subscribe on Signup: {newsletter.get('subscribe_on_signup', False)} - Sort Order: {newsletter.get('sort_order', 0)} - Sender Name: {newsletter.get('sender_name', 'Unknown')} - Sender Email: {newsletter.get('sender_email', 'Not set')} - Sender Reply To: {newsletter.get('sender_reply_to', 'Not set')} - Show Header Icon: {newsletter.get('show_header_icon', True)} - Show Header Title: {newsletter.get('show_header_title', True)} - Show Header Name: {newsletter.get('show_header_name', True)} - Show Feature Image: {newsletter.get('show_feature_image', True)} - Title Font Category: {newsletter.get('title_font_category', 'Unknown')} - Body Font Category: {newsletter.get('body_font_category', 'Unknown')} - Show Badge: {newsletter.get('show_badge', True)} - Created: {newsletter.get('created_at', 'Unknown')} - Updated: {newsletter.get('updated_at', 'Unknown')} - """ +Name: {newsletter.get('name', 'Unknown')} +Description: {newsletter.get('description', 'No description')} +Status: {newsletter.get('status', 'Unknown')} +Visibility: {newsletter.get('visibility', 'Unknown')} +Subscribe on Signup: {newsletter.get('subscribe_on_signup', False)} +Sort Order: {newsletter.get('sort_order', 0)} +Sender Name: {newsletter.get('sender_name', 'Unknown')} +Sender Email: {newsletter.get('sender_email', 'Not set')} +Sender Reply To: {newsletter.get('sender_reply_to', 'Not set')} +Show Header Icon: {newsletter.get('show_header_icon', True)} +Show Header Title: {newsletter.get('show_header_title', True)} +Show Header Name: {newsletter.get('show_header_name', True)} +Show Feature Image: {newsletter.get('show_feature_image', True)} +Title Font Category: {newsletter.get('title_font_category', 'Unknown')} +Body Font Category: {newsletter.get('body_font_category', 'Unknown')} +Show Badge: {newsletter.get('show_badge', True)} +Created: {newsletter.get('created_at', 'Unknown')} +Updated: {newsletter.get('updated_at', 'Unknown')} +""" except GhostError as e: + if ctx: + ctx.error(f"Failed to read newsletter: {str(e)}") return str(e) async def list_users( @@ -231,35 +294,52 @@ async def list_users( Returns: Formatted string containing user information """ + if ctx: + ctx.info(f"Listing users (page {page}, limit {limit}, format {format})") + try: + if ctx: + ctx.debug("Getting auth headers") headers = await get_auth_headers(STAFF_API_KEY) + + if ctx: + ctx.debug(f"Making API request to /users/ with pagination") data = await make_ghost_request( f"users/?page={page}&limit={limit}&include=roles", headers, ctx ) + if ctx: + ctx.debug("Processing users list response") + users = data.get("users", []) if not users: + if ctx: + ctx.info("No users found in response") return "No users found." if format.lower() == "json": + if ctx: + ctx.debug("Returning JSON format") return json.dumps(users, indent=2) formatted_users = [] for user in users: roles = [role.get('name') for role in user.get('roles', [])] formatted_user = f""" - Name: {user.get('name', 'Unknown')} - Email: {user.get('email', 'Unknown')} - Roles: {', '.join(roles)} - Status: {user.get('status', 'Unknown')} - ID: {user.get('id', 'Unknown')} - """ +Name: {user.get('name', 'Unknown')} +Email: {user.get('email', 'Unknown')} +Roles: {', '.join(roles)} +Status: {user.get('status', 'Unknown')} +ID: {user.get('id', 'Unknown')} +""" formatted_users.append(formatted_user) return "\n---\n".join(formatted_users) except GhostError as e: + if ctx: + ctx.error(f"Failed to list users: {str(e)}") return str(e) async def list_members( @@ -279,36 +359,53 @@ async def list_members( Returns: Formatted string containing member information """ + if ctx: + ctx.info(f"Listing members (page {page}, limit {limit}, format {format})") + try: + if ctx: + ctx.debug("Getting auth headers") headers = await get_auth_headers(STAFF_API_KEY) + + if ctx: + ctx.debug("Making API request to /members/ with pagination") data = await make_ghost_request( f"members/?page={page}&limit={limit}&include=newsletters,subscriptions", headers, ctx ) + if ctx: + ctx.debug("Processing members list response") + members = data.get("members", []) if not members: + if ctx: + ctx.info("No members found in response") return "No members found." if format.lower() == "json": + if ctx: + ctx.debug("Returning JSON format") return json.dumps(members, indent=2) formatted_members = [] for member in members: newsletters = [nl.get('name') for nl in member.get('newsletters', [])] formatted_member = f""" - Name: {member.get('name', 'Unknown')} - Email: {member.get('email', 'Unknown')} - Status: {member.get('status', 'Unknown')} - Newsletters: {', '.join(newsletters) if newsletters else 'None'} - Created: {member.get('created_at', 'Unknown')} - ID: {member.get('id', 'Unknown')} - """ +Name: {member.get('name', 'Unknown')} +Email: {member.get('email', 'Unknown')} +Status: {member.get('status', 'Unknown')} +Newsletters: {', '.join(newsletters) if newsletters else 'None'} +Created: {member.get('created_at', 'Unknown')} +ID: {member.get('id', 'Unknown')} +""" formatted_members.append(formatted_member) return "\n---\n".join(formatted_members) except GhostError as e: + if ctx: + ctx.error(f"Failed to list members: {str(e)}") return str(e) async def list_tiers( @@ -328,38 +425,55 @@ async def list_tiers( Returns: Formatted string containing tier information """ + if ctx: + ctx.info(f"Listing tiers (page {page}, limit {limit}, format {format})") + try: + if ctx: + ctx.debug("Getting auth headers") headers = await get_auth_headers(STAFF_API_KEY) + + if ctx: + ctx.debug("Making API request to /tiers/ with pagination") data = await make_ghost_request( f"tiers/?page={page}&limit={limit}&include=monthly_price,yearly_price,benefits", headers, ctx ) + if ctx: + ctx.debug("Processing tiers list response") + tiers = data.get("tiers", []) if not tiers: + if ctx: + ctx.info("No tiers found in response") return "No tiers found." if format.lower() == "json": + if ctx: + ctx.debug("Returning JSON format") return json.dumps(tiers, indent=2) formatted_tiers = [] for tier in tiers: benefits = tier.get('benefits', []) formatted_tier = f""" - Name: {tier.get('name', 'Unknown')} - Description: {tier.get('description', 'No description')} - Type: {tier.get('type', 'Unknown')} - Active: {tier.get('active', False)} - Monthly Price: {tier.get('monthly_price', 'N/A')} - Yearly Price: {tier.get('yearly_price', 'N/A')} - Benefits: {', '.join(benefits) if benefits else 'None'} - ID: {tier.get('id', 'Unknown')} - """ +Name: {tier.get('name', 'Unknown')} +Description: {tier.get('description', 'No description')} +Type: {tier.get('type', 'Unknown')} +Active: {tier.get('active', False)} +Monthly Price: {tier.get('monthly_price', 'N/A')} +Yearly Price: {tier.get('yearly_price', 'N/A')} +Benefits: {', '.join(benefits) if benefits else 'None'} +ID: {tier.get('id', 'Unknown')} +""" formatted_tiers.append(formatted_tier) return "\n---\n".join(formatted_tiers) except GhostError as e: + if ctx: + ctx.error(f"Failed to list tiers: {str(e)}") return str(e) async def list_offers( @@ -379,38 +493,55 @@ async def list_offers( Returns: Formatted string containing offer information """ + if ctx: + ctx.info(f"Listing offers (page {page}, limit {limit}, format {format})") + try: + if ctx: + ctx.debug("Getting auth headers") headers = await get_auth_headers(STAFF_API_KEY) + + if ctx: + ctx.debug("Making API request to /offers/ with pagination") data = await make_ghost_request( f"offers/?page={page}&limit={limit}", headers, ctx ) + if ctx: + ctx.debug("Processing offers list response") + offers = data.get("offers", []) if not offers: + if ctx: + ctx.info("No offers found in response") return "No offers found." if format.lower() == "json": + if ctx: + ctx.debug("Returning JSON format") return json.dumps(offers, indent=2) formatted_offers = [] for offer in offers: formatted_offer = f""" - Name: {offer.get('name', 'Unknown')} - Code: {offer.get('code', 'Unknown')} - Display Title: {offer.get('display_title', 'No display title')} - Type: {offer.get('type', 'Unknown')} - Amount: {offer.get('amount', 'Unknown')} - Duration: {offer.get('duration', 'Unknown')} - Status: {offer.get('status', 'Unknown')} - Tier: {offer.get('tier', {}).get('name', 'Unknown')} - ID: {offer.get('id', 'Unknown')} - """ +Name: {offer.get('name', 'Unknown')} +Code: {offer.get('code', 'Unknown')} +Display Title: {offer.get('display_title', 'No display title')} +Type: {offer.get('type', 'Unknown')} +Amount: {offer.get('amount', 'Unknown')} +Duration: {offer.get('duration', 'Unknown')} +Status: {offer.get('status', 'Unknown')} +Tier: {offer.get('tier', {}).get('name', 'Unknown')} +ID: {offer.get('id', 'Unknown')} +""" formatted_offers.append(formatted_offer) return "\n---\n".join(formatted_offers) except GhostError as e: + if ctx: + ctx.error(f"Failed to list offers: {str(e)}") return str(e) async def list_newsletters( @@ -430,36 +561,53 @@ async def list_newsletters( Returns: Formatted string containing newsletter information """ + if ctx: + ctx.info(f"Listing newsletters (page {page}, limit {limit}, format {format})") + try: + if ctx: + ctx.debug("Getting auth headers") headers = await get_auth_headers(STAFF_API_KEY) + + if ctx: + ctx.debug("Making API request to /newsletters/ with pagination") data = await make_ghost_request( f"newsletters/?page={page}&limit={limit}", headers, ctx ) + if ctx: + ctx.debug("Processing newsletters list response") + newsletters = data.get("newsletters", []) if not newsletters: + if ctx: + ctx.info("No newsletters found in response") return "No newsletters found." if format.lower() == "json": + if ctx: + ctx.debug("Returning JSON format") return json.dumps(newsletters, indent=2) formatted_newsletters = [] for newsletter in newsletters: formatted_newsletter = f""" - Name: {newsletter.get('name', 'Unknown')} - Description: {newsletter.get('description', 'No description')} - Status: {newsletter.get('status', 'Unknown')} - Visibility: {newsletter.get('visibility', 'Unknown')} - Subscribe on Signup: {newsletter.get('subscribe_on_signup', False)} - Sender Name: {newsletter.get('sender_name', 'Unknown')} - ID: {newsletter.get('id', 'Unknown')} - """ +Name: {newsletter.get('name', 'Unknown')} +Description: {newsletter.get('description', 'No description')} +Status: {newsletter.get('status', 'Unknown')} +Visibility: {newsletter.get('visibility', 'Unknown')} +Subscribe on Signup: {newsletter.get('subscribe_on_signup', False)} +Sender Name: {newsletter.get('sender_name', 'Unknown')} +ID: {newsletter.get('id', 'Unknown')} +""" formatted_newsletters.append(formatted_newsletter) return "\n---\n".join(formatted_newsletters) except GhostError as e: + if ctx: + ctx.error(f"Failed to list newsletters: {str(e)}") return str(e) async def list_posts( @@ -482,19 +630,34 @@ async def list_posts( Raises: GhostError: If there is an error accessing the Ghost API """ + if ctx: + ctx.info(f"Listing posts (page {page}, limit {limit}, format {format})") + try: + if ctx: + ctx.debug("Getting auth headers") headers = await get_auth_headers(STAFF_API_KEY) + + if ctx: + ctx.debug("Making API request to /posts/ with pagination") data = await make_ghost_request( f"posts/?page={page}&limit={limit}", headers, ctx ) + if ctx: + ctx.debug("Processing posts list response") + posts = data.get("posts", []) if not posts: + if ctx: + ctx.info("No posts found in response") return "No posts found." if format.lower() == "json": + if ctx: + ctx.debug("Formatting posts in JSON format") formatted_posts = [{ "id": post.get('id', 'Unknown'), "title": post.get('title', 'Untitled'), @@ -507,16 +670,18 @@ async def list_posts( formatted_posts = [] for post in posts: formatted_post = f""" - Title: {post.get('title', 'Untitled')} - Status: {post.get('status', 'Unknown')} - URL: {post.get('url', 'No URL')} - Created: {post.get('created_at', 'Unknown')} - ID: {post.get('id', 'Unknown')} - """ +Title: {post.get('title', 'Untitled')} +Status: {post.get('status', 'Unknown')} +URL: {post.get('url', 'No URL')} +Created: {post.get('created_at', 'Unknown')} +ID: {post.get('id', 'Unknown')} +""" formatted_posts.append(formatted_post) return "\n---\n".join(formatted_posts) except GhostError as e: + if ctx: + ctx.error(f"Failed to list posts: {str(e)}") return str(e) async def read_post(post_id: str, ctx: Context = None) -> str: @@ -532,14 +697,25 @@ async def read_post(post_id: str, ctx: Context = None) -> str: Raises: GhostError: If there is an error accessing the Ghost API """ + if ctx: + ctx.info(f"Reading post content for ID: {post_id}") + try: + if ctx: + ctx.debug("Getting auth headers") headers = await get_auth_headers(STAFF_API_KEY) + + if ctx: + ctx.debug(f"Making API request to /posts/{post_id}/") data = await make_ghost_request( f"posts/{post_id}/?formats=html,plaintext&include=html,plaintext", headers, ctx ) + if ctx: + ctx.debug("Processing post response data") + post = data["posts"][0] content = post.get('html') or post.get('plaintext') or 'No content available' @@ -553,6 +729,8 @@ async def read_post(post_id: str, ctx: Context = None) -> str: {content} """ except GhostError as e: + if ctx: + ctx.error(f"Failed to read post: {str(e)}") return str(e) async def search_posts_by_title(query: str, exact: bool = False, ctx: Context = None) -> str: @@ -569,35 +747,57 @@ async def search_posts_by_title(query: str, exact: bool = False, ctx: Context = Raises: GhostError: If there is an error accessing the Ghost API """ + if ctx: + ctx.info(f"Searching posts with title query: {query} (exact: {exact})") + try: + if ctx: + ctx.debug("Getting auth headers") headers = await get_auth_headers(STAFF_API_KEY) + + if ctx: + ctx.debug("Making API request to /posts/") data = await make_ghost_request("posts", headers, ctx) + if ctx: + ctx.debug("Processing search results") + posts = data.get("posts", []) matches = [] + if ctx: + ctx.debug(f"Found {len(posts)} total posts to search through") + if exact: + if ctx: + ctx.debug("Performing exact title match") matches = [post for post in posts if post.get('title', '').lower() == query.lower()] else: + if ctx: + ctx.debug("Performing fuzzy title match") titles = [post.get('title', '') for post in posts] matching_titles = get_close_matches(query, titles, n=5, cutoff=0.3) matches = [post for post in posts if post.get('title', '') in matching_titles] if not matches: + if ctx: + ctx.info(f"No posts found matching query: {query}") return f"No posts found matching '{query}'" formatted_matches = [] for post in matches: formatted_match = f""" - Title: {post.get('title', 'Untitled')} - Status: {post.get('status', 'Unknown')} - URL: {post.get('url', 'No URL')} - Created: {post.get('created_at', 'Unknown')} - ID: {post.get('id', 'Unknown')} - """ +Title: {post.get('title', 'Untitled')} +Status: {post.get('status', 'Unknown')} +URL: {post.get('url', 'No URL')} +Created: {post.get('created_at', 'Unknown')} +ID: {post.get('id', 'Unknown')} +""" formatted_matches.append(formatted_match) return "\n---\n".join(formatted_matches) except GhostError as e: + if ctx: + ctx.error(f"Failed to search posts: {str(e)}") return str(e)