diff --git a/README.md b/README.md index 1a12f874..8acfc2d3 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - Awesome Claude Code Terminal + Awesome Claude Code Terminal @@ -174,12 +174,23 @@
- - - EXTREME REPO MAKEOVER BY CLAUDE CODE WEB! + + + LATEST ADDITIONS
+Claude Code Output Styles - Debugging +_A small set of well-written output styles, specifically focused on debugging - root cause analysis, systematic, methodical debugging, encouraging a more careful approach to bug-squashing from Claude Code._ +![GitHub Stats for claude-output-styles](https://github-readme-stats-plus-theta.vercel.app/api/pin/?repo=claude-output-styles&username=JamieM0&all_stats=true&stats_only=true&hide_border=true&bg_color=00000000&icon_color=FF0000&text_color=FF0000) + +CCometixLine - Claude Code Statusline +_A high-performance Claude Code statusline tool written in Rust with Git integration, usage tracking, interactive TUI configuration, and Claude Code enhancement utilities._ +![GitHub Stats for CCometixLine](https://github-readme-stats-plus-theta.vercel.app/api/pin/?repo=CCometixLine&username=Haleclipse&all_stats=true&stats_only=true&hide_border=true&bg_color=00000000&icon_color=FF0000&text_color=FF0000) + +Claude Code Handbook +_Collection of best practices, tips, and techniques for Claude Code development workflows, enhanced with distributable plugins_ +
diff --git a/README_CLASSIC.md b/README_CLASSIC.md index 1af30bad..6d6d9113 100644 --- a/README_CLASSIC.md +++ b/README_CLASSIC.md @@ -35,11 +35,33 @@ Claude Code is a cutting-edge CLI-based coding assistant and agent released by [ -## This Week's Additions ✨ [🔝](#awesome-claude-code) +## Latest Additions ✨ [🔝](#awesome-claude-code) -> Resources added in the past 7 days -*No new resources added this week.* +[`Claude Code Output Styles - Debugging`](https://github.com/JamieM0/claude-output-styles)   by   [Jamie Matthews](https://github.com/JamieM0)   ⚖️  MIT +A small set of well-written output styles, specifically focused on debugging - root cause analysis, systematic, methodical debugging, encouraging a more careful approach to bug-squashing from Claude Code. + +
+📊 GitHub Stats + +![GitHub Stats for claude-output-styles](https://github-readme-stats-plus-theta.vercel.app/api/pin/?repo=claude-output-styles&username=JamieM0&all_stats=true&stats_only=true) + +
+
+ +[`CCometixLine - Claude Code Statusline`](https://github.com/Haleclipse/CCometixLine)   by   [Haleclipse](https://github.com/Haleclipse) +A high-performance Claude Code statusline tool written in Rust with Git integration, usage tracking, interactive TUI configuration, and Claude Code enhancement utilities. + +
+📊 GitHub Stats + +![GitHub Stats for CCometixLine](https://github-readme-stats-plus-theta.vercel.app/api/pin/?repo=CCometixLine&username=Haleclipse&all_stats=true&stats_only=true) + +
+
+ +[`Claude Code Handbook`](https://nikiforovall.blog/claude-code-rules/)   by   [nikiforovall](https://github.com/nikiforovall)   ⚖️  MIT +Collection of best practices, tips, and techniques for Claude Code development workflows, enhanced with distributable plugins ## Contents [🔝](#awesome-claude-code) diff --git a/assets/badge-claude-code-output-styles-debugging.svg b/assets/badge-claude-code-output-styles-debugging.svg new file mode 100644 index 00000000..fbd335bc --- /dev/null +++ b/assets/badge-claude-code-output-styles-debugging.svg @@ -0,0 +1,32 @@ + + + + + + + + + CC + + + Claude Code Output Styles - Debugging + by Jamie Matthews + + + + \ No newline at end of file diff --git a/assets/latest-additions-header-light.svg b/assets/latest-additions-header-light.svg new file mode 100644 index 00000000..012a869a --- /dev/null +++ b/assets/latest-additions-header-light.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + LATEST ADDITIONS + + + + + + + + diff --git a/assets/latest-additions-header.svg b/assets/latest-additions-header.svg new file mode 100644 index 00000000..4bf043b1 --- /dev/null +++ b/assets/latest-additions-header.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ╭─ + ─╮ + ╰─ + ─╯ + + + + + + + + + + LATEST ADDITIONS + + + + + + + + diff --git a/scripts/create_resource_pr.py b/scripts/create_resource_pr.py index 3385209c..614d8fd9 100755 --- a/scripts/create_resource_pr.py +++ b/scripts/create_resource_pr.py @@ -7,6 +7,7 @@ import argparse import json import os +import re import subprocess import sys from datetime import datetime @@ -29,6 +30,16 @@ def create_unique_branch_name(base_name: str) -> str: return f"{base_name}-{timestamp}" +def get_badge_filename(display_name: str) -> str: + """Compute the badge filename for a resource. + + Uses the same logic as save_resource_badge_svg in generate_readme.py. + """ + safe_name = re.sub(r"[^a-zA-Z0-9]", "-", display_name.lower()) + safe_name = re.sub(r"-+", "-", safe_name).strip("-") + return f"badge-{safe_name}.svg" + + def main(): """Main entry point.""" parser = argparse.ArgumentParser(description="Create PR from approved resource submission") @@ -112,8 +123,28 @@ def main(): status_result = run_command(["git", "status", "--porcelain"]) print(f"Git status after README generation:\n{status_result.stdout}", file=sys.stderr) + # Compute badge path and check if it was generated + script_dir_abs = os.path.dirname(os.path.abspath(__file__)) + repo_root = os.path.dirname(script_dir_abs) + badge_filename = get_badge_filename(resource_data["display_name"]) + badge_path = os.path.join(repo_root, "assets", badge_filename) + badge_warning = "" + # Stage changes (includes both README.md and README_CLASSIC.md) - run_command(["git", "add", "THE_RESOURCES_TABLE.csv", "README.md", "README_CLASSIC.md"]) + files_to_stage = ["THE_RESOURCES_TABLE.csv", "README.md", "README_CLASSIC.md"] + + if os.path.exists(badge_path): + # Stage the badge file relative to repo root + files_to_stage.append(f"assets/{badge_filename}") + print(f"Badge file found and will be staged: {badge_filename}", file=sys.stderr) + else: + print(f"Warning: Badge file not generated: {badge_path}", file=sys.stderr) + badge_warning = ( + f"\n\n> **Warning**: Badge SVG (`assets/{badge_filename}`) was not generated. " + "Manual attention may be required." + ) + + run_command(["git", "add", *files_to_stage]) # Commit commit_message = f"Add resource: {resource_data['display_name']}\n\n" @@ -131,6 +162,7 @@ def main(): # Create PR pr_title = f"Add resource: {resource_data['display_name']}" pr_body = generate_pr_content(resource) + pr_body += badge_warning # Empty string if badge was generated successfully pr_body += f"\n\n---\n\nResolves #{args.issue_number}" # Use gh CLI to create PR diff --git a/scripts/generate_readme.py b/scripts/generate_readme.py index d4fd5dce..a8027e65 100644 --- a/scripts/generate_readme.py +++ b/scripts/generate_readme.py @@ -1597,26 +1597,98 @@ def parse_resource_date(date_string): return None -def generate_weekly_section(csv_data): - """Generate the weekly resources section that appears above Contents.""" +def generate_weekly_section(csv_data, assets_dir=None): + """Generate the latest additions section that appears above Contents.""" lines = [] - # EXTREME MAKEOVER BANNER! + # Latest Additions header with light/dark mode support lines.append('
') lines.append(" ") lines.append( - ' ' + ' ' ) lines.append( - ' ' - ) - lines.append( - ' EXTREME REPO MAKEOVER BY CLAUDE CODE WEB!' + ' ' ) + lines.append(' LATEST ADDITIONS') lines.append(" ") lines.append("
") lines.append("") + # Get rows sorted by date added (newest first) + resources_sorted_by_date = [] + for row in csv_data: + date_added = row.get("Date Added", "").strip() + if date_added: + parsed_date = parse_resource_date(date_added) + if parsed_date: + resources_sorted_by_date.append((parsed_date, row)) + resources_sorted_by_date.sort(key=lambda x: x[0], reverse=True) + + # Add all resources added in the past 7 days + latest_additions = [] + cutoff_date = datetime.now() - timedelta(days=7) + for dated_resource in resources_sorted_by_date: + if dated_resource[0] >= cutoff_date or len(latest_additions) < 3: + latest_additions.append(dated_resource[1]) + else: + break + + for resource in latest_additions: + lines.append( + format_resource_entry(resource, assets_dir=assets_dir, include_separator=False) + ) + lines.append("") + + # # Get all resources with dates, sorted newest first + # cutoff_date = datetime.now() - timedelta(days=7) + # all_dated_resources = [] + + # for row in csv_data: + # date_added = row.get("Date Added", "").strip() + # if date_added: + # parsed_date = parse_resource_date(date_added) + # if parsed_date: + # all_dated_resources.append((parsed_date, row)) + + # # Sort by date (newest first) + # all_dated_resources.sort(key=lambda x: x[0], reverse=True) + + # # Get resources from past 7 days + # recent_resources = [(d, r) for d, r in all_dated_resources if d >= cutoff_date] + + # # Ensure at least 3 entries (fill with most recent if needed) + # min_entries = 3 + # if len(recent_resources) < min_entries: + # # Add more recent entries to reach minimum + # for dated_resource in all_dated_resources: + # if dated_resource not in recent_resources: + # recent_resources.append(dated_resource) + # if len(recent_resources) >= min_entries: + # break + # # Re-sort after adding extras + # recent_resources.sort(key=lambda x: x[0], reverse=True) + + # if recent_resources: + # lines.append("
") + # lines.append("") + # lines.append("The latest resources added to the list") + # lines.append("") + # lines.append("
") + # lines.append("") + # for _, resource in recent_resources: + # lines.append( + # format_resource_entry(resource, assets_dir=assets_dir, include_separator=False) + # ) + # lines.append("") + # else: + # lines.append("
") + # lines.append("") + # lines.append("*No resources with dates found.*") + # lines.append("") + # lines.append("
") + # lines.append("") + return "\n".join(lines).rstrip() + "\n" @@ -1905,7 +1977,7 @@ def generate_readme_from_templates(csv_path, template_dir, output_path): toc_content = generate_toc_from_categories(csv_data, general_anchor_map) # Generate weekly section - weekly_section = generate_weekly_section(csv_data) + weekly_section = generate_weekly_section(csv_data, assets_dir=assets_dir) # Generate body sections body_sections = [] @@ -2105,8 +2177,8 @@ def generate_toc(self) -> str: return generate_toc_from_categories(self.csv_data, self.general_anchor_map) def generate_weekly_section(self) -> str: - """Generate weekly section with banner SVG.""" - return generate_weekly_section(self.csv_data) + """Generate latest additions section with header SVG.""" + return generate_weekly_section(self.csv_data, assets_dir=self.assets_dir) def generate_section_content(self, category: dict, section_index: int) -> str: """Generate section with SVG headers and desc boxes.""" @@ -2255,32 +2327,32 @@ def generate_toc(self) -> str: def generate_weekly_section(self) -> str: """Generate weekly section with plain markdown.""" lines = [] - lines.append("## This Week's Additions ✨ [🔝](#awesome-claude-code)") + lines.append("## Latest Additions ✨ [🔝](#awesome-claude-code)") lines.append("") - lines.append("> Resources added in the past 7 days") - - # Get recent resources - cutoff_date = datetime.now() - timedelta(days=7) - recent_resources = [] + # Get rows sorted by date added (newest first) + resources_sorted_by_date = [] for row in self.csv_data: date_added = row.get("Date Added", "").strip() if date_added: parsed_date = parse_resource_date(date_added) - if parsed_date and parsed_date >= cutoff_date: - recent_resources.append((parsed_date, row)) + if parsed_date: + resources_sorted_by_date.append((parsed_date, row)) + resources_sorted_by_date.sort(key=lambda x: x[0], reverse=True) - # Sort by date (newest first) - recent_resources.sort(key=lambda x: x[0], reverse=True) + # Add all resources added in the past 7 days + latest_additions: list[dict[str, str]] = [] + cutoff_date = datetime.now() - timedelta(days=7) + for dated_resource in resources_sorted_by_date: + if dated_resource[0] >= cutoff_date or len(latest_additions) < 3: + latest_additions.append(dated_resource[1]) + else: + break - if recent_resources: - lines.append("") - for _, resource in recent_resources: - lines.append(self.format_resource_entry(resource, include_separator=False)) - lines.append("") - else: + lines.append("") + for resource in latest_additions: + lines.append(self.format_resource_entry(resource, include_separator=False)) lines.append("") - lines.append("*No new resources added this week.*") return "\n".join(lines).rstrip() + "\n" diff --git a/scripts/generate_readme_bak_2.py b/scripts/generate_readme_bak_2.py index 9093426c..74e384f1 100644 --- a/scripts/generate_readme_bak_2.py +++ b/scripts/generate_readme_bak_2.py @@ -9,7 +9,7 @@ import re import shutil import sys -from datetime import datetime, timedelta +from datetime import datetime import yaml # type: ignore[import-untyped] from validate_links import parse_github_url # type: ignore[import-not-found] @@ -24,9 +24,9 @@ def load_template(template_path): def create_h2_svg_file(text, filename, assets_dir): """Create an animated hero-centered H2 header SVG file.""" # Escape XML special characters - text_escaped = text.replace('&', '&').replace('<', '<').replace('>', '>') + text_escaped = text.replace("&", "&").replace("<", "<").replace(">", ">") - svg_content = f''' + svg_content = f""" @@ -121,11 +121,11 @@ def create_h2_svg_file(text, filename, assets_dir): -''' +""" # Write SVG file filepath = os.path.join(assets_dir, filename) - with open(filepath, 'w', encoding='utf-8') as f: + with open(filepath, "w", encoding="utf-8") as f: f.write(svg_content) return filename @@ -134,13 +134,13 @@ def create_h2_svg_file(text, filename, assets_dir): def create_h3_svg_file(text, filename, assets_dir): """Create an animated minimal-inline H3 header SVG file.""" # Escape XML special characters - text_escaped = text.replace('&', '&').replace('<', '<').replace('>', '>') + text_escaped = text.replace("&", "&").replace("<", "<").replace(">", ">") # Calculate approximate text width (rough estimate: 10px per character for 18px font) text_width = len(text) * 10 total_width = text_width + 50 # Add padding for decorative elements - svg_content = f''' + svg_content = f""" @@ -175,11 +175,11 @@ def create_h3_svg_file(text, filename, assets_dir): {text_escaped} -''' +""" # Write SVG file filepath = os.path.join(assets_dir, filename) - with open(filepath, 'w', encoding='utf-8') as f: + with open(filepath, "w", encoding="utf-8") as f: f.write(svg_content) return filename @@ -325,6 +325,7 @@ def get_anchor_suffix_for_icon(icon): # SVG GENERATORS - Auto-generate assets for new categories/subcategories # ============================================================================= + def generate_category_header_light_svg(title, section_number="01"): """Generate a light-mode category header SVG in vintage technical manual style. @@ -336,7 +337,7 @@ def generate_category_header_light_svg(title, section_number="01"): title_width = len(title) * 14 # Approximate width per character line_end_x = max(640, 220 + title_width + 50) - return f''' + return f""" -''' +""" def generate_section_divider_light_svg(variant=1): @@ -390,7 +391,7 @@ def generate_section_divider_light_svg(variant=1): """ if variant == 1: # Diagram/schematic style with nodes - return ''' + return """ @@ -443,11 +444,11 @@ def generate_section_divider_light_svg(variant=1): -''' +""" elif variant == 2: # Wave/organic style - return ''' + return """ @@ -477,11 +478,11 @@ def generate_section_divider_light_svg(variant=1): -''' +""" else: # variant == 3 # Bracket style with layered drafts - return ''' + return """ @@ -523,7 +524,7 @@ def generate_section_divider_light_svg(variant=1): -''' +""" def generate_desc_box_light_svg(position="top"): @@ -533,7 +534,7 @@ def generate_desc_box_light_svg(position="top"): position: "top" or "bottom" """ if position == "top": - return ''' + return """ @@ -591,9 +592,9 @@ def generate_desc_box_light_svg(position="top"): -''' +""" else: # bottom - return ''' + return """ @@ -650,7 +651,7 @@ def generate_desc_box_light_svg(position="top"): -''' +""" def generate_toc_row_svg(directory_name, description): @@ -661,10 +662,10 @@ def generate_toc_row_svg(directory_name, description): description: Short description for the comment """ # Escape XML entities - desc_escaped = description.replace('&', '&').replace('<', '<').replace('>', '>') - dir_escaped = directory_name.replace('&', '&').replace('<', '<').replace('>', '>') + desc_escaped = description.replace("&", "&").replace("<", "<").replace(">", ">") + dir_escaped = directory_name.replace("&", "&").replace("<", "<").replace(">", ">") - return f''' + return f""" @@ -707,7 +708,7 @@ def generate_toc_row_svg(directory_name, description): # {desc_escaped} -''' +""" def generate_toc_sub_svg(directory_name, description): @@ -717,10 +718,10 @@ def generate_toc_sub_svg(directory_name, description): directory_name: The subdirectory name (e.g., "general/") description: Short description for the comment """ - desc_escaped = description.replace('&', '&').replace('<', '<').replace('>', '>') - dir_escaped = directory_name.replace('&', '&').replace('<', '<').replace('>', '>') + desc_escaped = description.replace("&", "&").replace("<", "<").replace(">", ">") + dir_escaped = directory_name.replace("&", "&").replace("<", "<").replace(">", ">") - return f''' + return f""" @@ -757,20 +758,21 @@ def generate_toc_sub_svg(directory_name, description): # {desc_escaped} -''' +""" # ============================================================================= # ASSET SAVING HELPERS - Save generated assets to disk # ============================================================================= + def ensure_category_header_exists(category_id, title, section_number, assets_dir): """Ensure category header SVGs exist, generating them if needed. Returns tuple of (dark_filename, light_filename). """ # Define filenames - safe_name = category_id.replace('-', '_') + safe_name = category_id.replace("-", "_") dark_filename = f"header_{safe_name}.svg" light_filename = f"header_{safe_name}-light-v3.svg" @@ -778,7 +780,7 @@ def ensure_category_header_exists(category_id, title, section_number, assets_dir light_path = os.path.join(assets_dir, light_filename) if not os.path.exists(light_path): svg_content = generate_category_header_light_svg(title, section_number) - with open(light_path, 'w', encoding='utf-8') as f: + with open(light_path, "w", encoding="utf-8") as f: f.write(svg_content) return (dark_filename, light_filename) @@ -795,7 +797,7 @@ def ensure_section_divider_exists(variant, assets_dir): light_path = os.path.join(assets_dir, light_filename) if not os.path.exists(light_path): svg_content = generate_section_divider_light_svg(variant) - with open(light_path, 'w', encoding='utf-8') as f: + with open(light_path, "w", encoding="utf-8") as f: f.write(svg_content) return (dark_filename, light_filename) @@ -812,7 +814,7 @@ def ensure_desc_box_exists(position, assets_dir): if not os.path.exists(filepath): svg_content = generate_desc_box_light_svg(position) - with open(filepath, 'w', encoding='utf-8') as f: + with open(filepath, "w", encoding="utf-8") as f: f.write(svg_content) return filename @@ -825,7 +827,7 @@ def ensure_toc_row_exists(category_id, directory_name, description, assets_dir): if not os.path.exists(filepath): svg_content = generate_toc_row_svg(directory_name, description) - with open(filepath, 'w', encoding='utf-8') as f: + with open(filepath, "w", encoding="utf-8") as f: f.write(svg_content) return filename @@ -838,7 +840,7 @@ def ensure_toc_sub_exists(subcat_id, directory_name, description, assets_dir): if not os.path.exists(filepath): svg_content = generate_toc_sub_svg(directory_name, description) - with open(filepath, 'w', encoding='utf-8') as f: + with open(filepath, "w", encoding="utf-8") as f: f.write(svg_content) return filename @@ -848,57 +850,69 @@ def ensure_toc_sub_exists(subcat_id, directory_name, description, assets_dir): # MAPPING FUNCTIONS - Map IDs to filenames # ============================================================================= + def get_category_svg_filename(category_id): """Map category ID to SVG filename.""" svg_map = { - 'skills': 'toc-row-skills.svg', - 'workflows': 'toc-row-workflows.svg', - 'tooling': 'toc-row-tooling.svg', - 'statusline': 'toc-row-statusline.svg', - 'hooks': 'toc-row-custom.svg', - 'slash-commands': 'toc-row-commands.svg', - 'claude-md-files': 'toc-row-config.svg', - 'alternative-clients': 'toc-row-clients.svg', - 'official-documentation': 'toc-row-docs.svg', + "skills": "toc-row-skills.svg", + "workflows": "toc-row-workflows.svg", + "tooling": "toc-row-tooling.svg", + "statusline": "toc-row-statusline.svg", + "hooks": "toc-row-custom.svg", + "slash-commands": "toc-row-commands.svg", + "claude-md-files": "toc-row-config.svg", + "alternative-clients": "toc-row-clients.svg", + "official-documentation": "toc-row-docs.svg", } - return svg_map.get(category_id, f'toc-row-{category_id}.svg') + return svg_map.get(category_id, f"toc-row-{category_id}.svg") def get_subcategory_svg_filename(subcat_id): """Map subcategory ID to SVG filename.""" svg_map = { - 'general': 'toc-sub-general.svg', - 'ide-integrations': 'toc-sub-ide.svg', - 'usage-monitors': 'toc-sub-monitors.svg', - 'orchestrators': 'toc-sub-orchestrators.svg', - 'version-control-git': 'toc-sub-git.svg', - 'code-analysis-testing': 'toc-sub-code-analysis.svg', - 'context-loading-priming': 'toc-sub-context.svg', - 'documentation-changelogs': 'toc-sub-documentation.svg', - 'ci-deployment': 'toc-sub-ci.svg', - 'project-task-management': 'toc-sub-project-mgmt.svg', - 'miscellaneous': 'toc-sub-misc.svg', - 'language-specific': 'toc-sub-language.svg', - 'domain-specific': 'toc-sub-domain.svg', - 'project-scaffolding-mcp': 'toc-sub-scaffolding.svg', + "general": "toc-sub-general.svg", + "ide-integrations": "toc-sub-ide.svg", + "usage-monitors": "toc-sub-monitors.svg", + "orchestrators": "toc-sub-orchestrators.svg", + "version-control-git": "toc-sub-git.svg", + "code-analysis-testing": "toc-sub-code-analysis.svg", + "context-loading-priming": "toc-sub-context.svg", + "documentation-changelogs": "toc-sub-documentation.svg", + "ci-deployment": "toc-sub-ci.svg", + "project-task-management": "toc-sub-project-mgmt.svg", + "miscellaneous": "toc-sub-misc.svg", + "language-specific": "toc-sub-language.svg", + "domain-specific": "toc-sub-domain.svg", + "project-scaffolding-mcp": "toc-sub-scaffolding.svg", } - return svg_map.get(subcat_id, f'toc-sub-{subcat_id}.svg') + return svg_map.get(subcat_id, f"toc-sub-{subcat_id}.svg") def get_category_header_svg(category_id): """Map category ID to pre-made header SVG filenames (dark and light variants).""" header_map = { - 'skills': ('header_agent_skills.svg', 'header_agent_skills-light-v3.svg'), - 'workflows': ('header_workflows_knowledge_guides.svg', 'header_workflows_knowledge_guides-light-v3.svg'), - 'tooling': ('header_tooling.svg', 'header_tooling-light-v3.svg'), - 'statusline': ('header_status_lines.svg', 'header_status_lines-light-v3.svg'), - 'hooks': ('header_hooks.svg', 'header_hooks-light-v3.svg'), - 'slash-commands': ('header_slash_commands.svg', 'header_slash_commands-light-v3.svg'), - 'claude-md-files': ('header_claudemd_files.svg', 'header_claudemd_files-light-v3.svg'), - 'alternative-clients': ('header_alternative_clients.svg', 'header_alternative_clients-light-v3.svg'), - 'official-documentation': ('header_official_documentation.svg', 'header_official_documentation-light-v3.svg'), + "skills": ("header_agent_skills.svg", "header_agent_skills-light-v3.svg"), + "workflows": ( + "header_workflows_knowledge_guides.svg", + "header_workflows_knowledge_guides-light-v3.svg", + ), + "tooling": ("header_tooling.svg", "header_tooling-light-v3.svg"), + "statusline": ("header_status_lines.svg", "header_status_lines-light-v3.svg"), + "hooks": ("header_hooks.svg", "header_hooks-light-v3.svg"), + "slash-commands": ("header_slash_commands.svg", "header_slash_commands-light-v3.svg"), + "claude-md-files": ("header_claudemd_files.svg", "header_claudemd_files-light-v3.svg"), + "alternative-clients": ( + "header_alternative_clients.svg", + "header_alternative_clients-light-v3.svg", + ), + "official-documentation": ( + "header_official_documentation.svg", + "header_official_documentation-light-v3.svg", + ), } - return header_map.get(category_id, (f'header_{category_id}.svg', f'header_{category_id}-light-v3.svg')) + return header_map.get( + category_id, (f"header_{category_id}.svg", f"header_{category_id}-light-v3.svg") + ) # Global counter for cycling section dividers (light mode only) @@ -914,7 +928,7 @@ def get_section_divider_svg(): global _section_divider_counter variant = (_section_divider_counter % 3) + 1 # 1, 2, 3 _section_divider_counter += 1 - return ('section-divider-alt2.svg', f'section-divider-light-manual-v{variant}.svg') + return ("section-divider-alt2.svg", f"section-divider-light-manual-v{variant}.svg") def sanitize_filename_from_anchor(anchor: str) -> str: @@ -1003,14 +1017,18 @@ def generate_toc_from_categories(csv_data=None, general_map=None): # Add main category row with theme-adaptive picture element dark_svg = svg_filename - light_svg = svg_filename.replace('.svg', '-light-anim-scanline.svg') + light_svg = svg_filename.replace(".svg", "-light-anim-scanline.svg") toc_lines.append(f'') - toc_lines.append(f' ') - toc_lines.append(f' ') - toc_lines.append(f' ') + toc_lines.append(" ") + toc_lines.append( + f' ' + ) + toc_lines.append( + f' ' + ) toc_lines.append(f' {section_title}') - toc_lines.append(f' ') - toc_lines.append(f'') + toc_lines.append(" ") + toc_lines.append("") toc_lines.append('
') # Check if this category has subcategories @@ -1061,14 +1079,18 @@ def generate_toc_from_categories(csv_data=None, general_map=None): # Add subcategory row with theme-adaptive picture element dark_svg = svg_filename - light_svg = svg_filename.replace('.svg', '-light-anim-scanline.svg') + light_svg = svg_filename.replace(".svg", "-light-anim-scanline.svg") toc_lines.append(f'') - toc_lines.append(f' ') - toc_lines.append(f' ') - toc_lines.append(f' ') + toc_lines.append(" ") + toc_lines.append( + f' ' + ) + toc_lines.append( + f' ' + ) toc_lines.append(f' {sub_title}') - toc_lines.append(f' ') - toc_lines.append(f'') + toc_lines.append(" ") + toc_lines.append("") toc_lines.append('
') return "\n".join(toc_lines).strip() @@ -1089,8 +1111,20 @@ def generate_resource_badge_svg(display_name, author_name=""): initials = display_name[:2].upper() # Escape XML special characters - name_escaped = display_name.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"') - author_escaped = author_name.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"') if author_name else "" + name_escaped = ( + display_name.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace('"', """) + ) + author_escaped = ( + author_name.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace('"', """) + if author_name + else "" + ) # Calculate width based on text length (approximate) - larger fonts need more space name_width = len(display_name) * 10 @@ -1104,10 +1138,10 @@ def generate_resource_badge_svg(display_name, author_name=""): # Build author text element if author provided author_element = "" if author_name: - author_element = f''' - by {author_escaped}''' + author_element = f""" + by {author_escaped}""" - svg = f''' + svg = f""" ''' +""" return svg def save_resource_badge_svg(display_name, author_name, assets_dir): """Save a resource name SVG badge to the assets directory and return the filename.""" # Create a safe filename from the display name (no -light suffix, badge is theme-adaptive) - safe_name = re.sub(r'[^a-zA-Z0-9]', '-', display_name.lower()) - safe_name = re.sub(r'-+', '-', safe_name).strip('-') + safe_name = re.sub(r"[^a-zA-Z0-9]", "-", display_name.lower()) + safe_name = re.sub(r"-+", "-", safe_name).strip("-") filename = f"badge-{safe_name}.svg" # Generate the SVG content (theme-adaptive via CSS media queries) @@ -1153,7 +1187,7 @@ def save_resource_badge_svg(display_name, author_name, assets_dir): # Save to file filepath = os.path.join(assets_dir, filename) - with open(filepath, 'w', encoding='utf-8') as f: + with open(filepath, "w", encoding="utf-8") as f: f.write(svg_content) return filename @@ -1164,7 +1198,7 @@ def generate_entry_separator_svg(): Uses bolder 'layered drafts' aesthetic with ghost circles for depth. """ - return ''' + return """ @@ -1174,7 +1208,7 @@ def generate_entry_separator_svg(): -''' +""" def ensure_separator_svg_exists(assets_dir): @@ -1207,7 +1241,7 @@ def format_resource_entry(row, assets_dir=None, include_separator=True): # Link with SVG badge image parts.append(f'') parts.append(f'{display_name}') - parts.append('') + parts.append("") else: # Fallback to text link parts.append(f"[`{display_name}`]({primary_link})") @@ -1240,7 +1274,7 @@ def format_resource_entry(row, assets_dir=None, include_separator=True): parts.append("\n\n") parts.append('
') parts.append(f'') - parts.append('
') + parts.append("") parts.append("\n") return "".join(parts) @@ -1281,12 +1315,18 @@ def generate_weekly_section(csv_data): # EXTREME MAKEOVER BANNER! lines.append('
') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' EXTREME REPO MAKEOVER BY CLAUDE CODE WEB!') - lines.append(' ') - lines.append('
') + lines.append(" ") + lines.append( + ' ' + ) + lines.append( + ' ' + ) + lines.append( + ' EXTREME REPO MAKEOVER BY CLAUDE CODE WEB!' + ) + lines.append(" ") + lines.append("") lines.append("") return "\n".join(lines).rstrip() + "\n" @@ -1297,9 +1337,9 @@ def generate_section_content(category, csv_data, general_map=None, assets_dir=No lines = [] # Get category details - id = category.get("id", "") + # id = category.get("id", "") title = category.get("name", "") - icon = category.get("icon", "") + # icon = category.get("icon", "") description = category.get("description", "").strip() category_name = category.get("name", "") subcategories = category.get("subcategories", []) @@ -1307,25 +1347,25 @@ def generate_section_content(category, csv_data, general_map=None, assets_dir=No # Add decorative section divider before each major category (cycles through v1, v2, v3) dark_divider, light_divider = get_section_divider_svg() lines.append('
') - lines.append(' ') - lines.append(f' ') - lines.append(f' ') - lines.append(f' ') - lines.append(' ') - lines.append('
') - lines.append('') - + lines.append(" ") + lines.append( + f' ' + ) + lines.append( + f' ' + ) + lines.append( + f' ' + ) + lines.append(" ") + lines.append("") + lines.append("") + # Has subcategories - use regular header (not collapsible at category level) - header_text = f"{title} {icon}" if icon else title + # header_text = f"{title} {icon}" if icon else title # Generate anchor ID matching TOC format - anchor = ( - title.lower() - .replace(" ", "-") - .replace("&", "") - .replace("/", "") - .replace(".", "") - ) + anchor = title.lower().replace(" ", "-").replace("&", "").replace("/", "").replace(".", "") anchor_id = f"{anchor}-" # Category headers have "-" suffix # Get pre-made header SVG files for this category @@ -1335,36 +1375,50 @@ def generate_section_content(category, csv_data, general_map=None, assets_dir=No # Add header with proper ID and theme-adaptive picture element lines.append(f'

') lines.append('
') - lines.append(' ') + lines.append(" ") lines.append(f' ') - lines.append(f' ') + lines.append( + f' ' + ) lines.append(f' {title}') - lines.append(' ') - lines.append('
') - lines.append('

') - lines.append(f'
🔝 Back to top
') + lines.append(" ") + lines.append("") + lines.append("") + lines.append('
🔝 Back to top
') lines.append("") # Add section description if present, wrapped in decorative box if description: lines.append("") lines.append('
') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append('
') + lines.append(" ") + lines.append( + ' ' + ) + lines.append( + ' ' + ) + lines.append( + ' ' + ) + lines.append(" ") + lines.append("") # lines.append('') lines.append(f"

{description}

") # lines.append('') lines.append('
') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append('
') + lines.append(" ") + lines.append( + ' ' + ) + lines.append( + ' ' + ) + lines.append( + ' ' + ) + lines.append(" ") + lines.append("") # First render main category resources without subcategory # main_resources = [ @@ -1393,9 +1447,7 @@ def generate_section_content(category, csv_data, general_map=None, assets_dir=No lines.append("") # Generate anchor ID for subcategory (matching TOC format) - sub_anchor = ( - sub_title.lower().replace(" ", "-").replace("&", "").replace("/", "") - ) + sub_anchor = sub_title.lower().replace(" ", "-").replace("&", "").replace("/", "") # Special handling for "General" to keep anchors in sync with TOC if sub_title == "General": @@ -1415,12 +1467,16 @@ def generate_section_content(category, csv_data, general_map=None, assets_dir=No # Create SVG file for this subsection safe_filename = sanitize_filename_from_anchor(sub_anchor) svg_filename = f"subheader_{safe_filename}.svg" - assets_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets") + assets_dir = os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets" + ) create_h3_svg_file(sub_title, svg_filename, assets_dir) # Start subcategory disclosure element with the SVG inside the summary lines.append(f'
') - lines.append(f'{sub_title}') + lines.append( + f'{sub_title}' + ) lines.append("") for resource in resources: @@ -1537,7 +1593,9 @@ def generate_readme_from_templates(csv_path, template_dir, output_path): # Generate body sections body_sections = [] for category in categories: - section_content = generate_section_content(category, csv_data, general_anchor_map, assets_dir=assets_dir) + section_content = generate_section_content( + category, csv_data, general_anchor_map, assets_dir=assets_dir + ) body_sections.append(section_content) # Replace placeholders in template diff --git a/scripts/generate_ticker_svg.py b/scripts/generate_ticker_svg.py index 1f2ee3bc..df634af3 100755 --- a/scripts/generate_ticker_svg.py +++ b/scripts/generate_ticker_svg.py @@ -158,29 +158,29 @@ def star_snippet(y_pos: int) -> str: star_x = owner_start_x + (len(owner) * approx_char_width) + 22 return f""" {metrics}""" + fill="{colors["stars"]}">{metrics}""" if not flip: # Names on top, owner just below - return f""" + return f""" {truncated_repo_name} + fill="{colors["text"]}">{truncated_repo_name} {owner}{star_snippet(64)} + fill="{colors["text"]}" opacity="1.0">{owner}{star_snippet(64)} """ else: # Owner on top, name just below (in lower half) - return f""" + return f""" {owner}{star_snippet(102)} + fill="{colors["text"]}" opacity="1.0">{owner}{star_snippet(102)} {truncated_repo_name} + fill="{colors["text"]}">{truncated_repo_name} """ @@ -279,30 +279,30 @@ def generate_ticker_svg(repos: list[dict[str, Any]], theme: str = "dark") -> str - - - + + + - + - + - + - + - + @@ -320,12 +320,12 @@ def generate_ticker_svg(repos: list[dict[str, Any]], theme: str = "dark") -> str - - + + - - + + @@ -339,25 +339,25 @@ def generate_ticker_svg(repos: list[dict[str, Any]], theme: str = "dark") -> str - + - + + fill="{colors["label_title"]}" text-anchor="middle" filter="url(#textGlow)"> CLAUDE CODE + fill="{colors["label_subtitle"]}" text-anchor="middle" filter="url(#textGlow)"> REPOS LIVE + fill="{colors["delta_positive"]}" text-anchor="middle"> DAILY Δ - + diff --git a/templates/README.template.md b/templates/README.template.md index 036248e9..dd40c97f 100644 --- a/templates/README.template.md +++ b/templates/README.template.md @@ -12,7 +12,7 @@ - Awesome Claude Code Terminal + Awesome Claude Code Terminal diff --git a/tests/test_badge_notification_validation.py b/tests/test_badge_notification_validation.py index dfae1091..2d7acaf0 100644 --- a/tests/test_badge_notification_validation.py +++ b/tests/test_badge_notification_validation.py @@ -156,9 +156,9 @@ def test_notification_creation_flow() -> None: ) assert not result["success"], "Should have failed with dangerous input" - assert ( - "Security validation failed" in result["message"] - ), f"Wrong error message: {result['message']}" + assert "Security validation failed" in result["message"], ( + f"Wrong error message: {result['message']}" + ) print(" ✓ Notification creation blocked for dangerous input") diff --git a/tests/test_get_last_resource.py b/tests/test_get_last_resource.py index 0d19d413..2bf8b7c0 100644 --- a/tests/test_get_last_resource.py +++ b/tests/test_get_last_resource.py @@ -36,9 +36,9 @@ def test_slugify() -> None: for input_text, expected in test_cases: result = submitter.slugify(input_text) - assert ( - result == expected - ), f"slugify('{input_text}') should return '{expected}', got '{result}'" + assert result == expected, ( + f"slugify('{input_text}') should return '{expected}', got '{result}'" + ) def test_integration() -> None: