#!/usr/bin/env python3 """ Generate static site from slash command markdown files """ import os import json import re from pathlib import Path from typing import Dict, List def parse_frontmatter(content: str) -> tuple[Dict, str]: """Parse YAML frontmatter from markdown content""" frontmatter = {} body = content if content.startswith('---'): parts = content.split('---', 2) if len(parts) >= 3: fm_text = parts[1].strip() body = parts[2].strip() for line in fm_text.split('\n'): if ':' in line: key, value = line.split(':', 1) key = key.strip() value = value.strip() if value.startswith('[') and value.endswith(']'): value = [v.strip() for v in value[1:-1].split(',')] frontmatter[key] = value return frontmatter, body def parse_command_file(filepath: Path) -> Dict: """Parse a single command markdown file""" with open(filepath, 'r', encoding='utf-8') as f: content = f.read() frontmatter, body = parse_frontmatter(content) command_name = filepath.stem category = filepath.parent.name return { 'name': command_name, 'category': category, 'description': frontmatter.get('description', ''), 'tags': frontmatter.get('tags', []), 'content': body, 'filepath': str(filepath.relative_to('commands')) } def scan_commands(commands_dir: Path = Path('commands')) -> Dict[str, List[Dict]]: """Scan all command files and organize by category""" categories = {} for md_file in commands_dir.rglob('*.md'): command = parse_command_file(md_file) category = command['category'] if category not in categories: categories[category] = [] categories[category].append(command) for category in categories: categories[category].sort(key=lambda x: x['name']) return categories def generate_index_html(categories: Dict[str, List[Dict]]) -> str: """Generate the main index.html page""" category_cards = [] for category, commands in sorted(categories.items()): category_display = category.replace('-', ' ').replace('_', ' ').title() command_count = len(commands) category_cards.append(f'''

{category_display}

{command_count} command{"s" if command_count != 1 else ""}

''') return f''' Claude Code Linux Desktop Slash Commands

Claude Code Slash Commands

Linux Desktop System Administration Commands

A comprehensive collection of Claude Code slash commands for Linux desktop system administration tasks. Browse by category to find commands for AI tools, system health, hardware management, and more.

{''.join(category_cards)}
''' def generate_category_html() -> str: """Generate the category page template (uses JS to load content)""" return ''' Category - Claude Code Commands
← Back to Categories

Commands

''' def generate_css() -> str: """Generate the CSS stylesheet""" return '''* { margin: 0; padding: 0; box-sizing: border-box; } :root { --primary-color: #2563eb; --secondary-color: #1e40af; --accent-color: #3b82f6; --bg-color: #f8fafc; --card-bg: #ffffff; --text-color: #1e293b; --text-secondary: #64748b; --border-color: #e2e8f0; --code-bg: #f1f5f9; --success-color: #10b981; --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1); --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: var(--bg-color); color: var(--text-color); line-height: 1.6; } .container { max-width: 1200px; margin: 0 auto; padding: 0 20px; } header { background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%); color: white; padding: 40px 0; margin-bottom: 40px; box-shadow: var(--shadow-lg); } header h1 { font-size: 2.5rem; margin-bottom: 8px; } .subtitle { font-size: 1.1rem; opacity: 0.9; } .back-link { display: inline-block; color: white; text-decoration: none; margin-bottom: 20px; font-size: 1rem; opacity: 0.9; transition: opacity 0.2s; } .back-link:hover { opacity: 1; } .intro { background: var(--card-bg); padding: 24px; border-radius: 12px; margin-bottom: 30px; box-shadow: var(--shadow); border-left: 4px solid var(--primary-color); } .search-box { margin-bottom: 30px; } #searchInput { width: 100%; padding: 14px 20px; font-size: 1rem; border: 2px solid var(--border-color); border-radius: 8px; background: var(--card-bg); transition: border-color 0.2s; } #searchInput:focus { outline: none; border-color: var(--primary-color); } .categories-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; margin-bottom: 40px; } .category-card { background: var(--card-bg); padding: 30px; border-radius: 12px; box-shadow: var(--shadow); cursor: pointer; transition: all 0.3s ease; border: 2px solid transparent; } .category-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); border-color: var(--primary-color); } .category-card h3 { color: var(--primary-color); font-size: 1.4rem; margin-bottom: 8px; } .command-count { color: var(--text-secondary); font-size: 0.95rem; } .commands-list { display: flex; flex-direction: column; gap: 20px; margin-bottom: 40px; } .command-card { background: var(--card-bg); border-radius: 12px; box-shadow: var(--shadow); overflow: hidden; border: 1px solid var(--border-color); } .command-header { padding: 20px 24px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; background: linear-gradient(to right, var(--card-bg), #f8fafc); transition: background 0.2s; } .command-header:hover { background: var(--code-bg); } .command-title { display: flex; flex-direction: column; gap: 6px; flex: 1; } .command-name { font-size: 1.3rem; color: var(--primary-color); font-weight: 600; font-family: 'Monaco', 'Menlo', monospace; } .command-description { color: var(--text-secondary); font-size: 0.95rem; } .command-actions { display: flex; gap: 10px; align-items: center; } .copy-btn { background: var(--primary-color); color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 0.9rem; transition: all 0.2s; font-weight: 500; } .copy-btn:hover { background: var(--secondary-color); transform: scale(1.05); } .copy-btn.copied { background: var(--success-color); } .expand-icon { color: var(--text-secondary); font-size: 1.2rem; transition: transform 0.3s; } .command-header.expanded .expand-icon { transform: rotate(180deg); } .command-content { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; background: var(--bg-color); } .command-content.expanded { max-height: 2000px; } .command-body { padding: 24px; } .command-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; } .tag { background: var(--code-bg); color: var(--text-secondary); padding: 4px 12px; border-radius: 12px; font-size: 0.85rem; border: 1px solid var(--border-color); } .command-body pre { background: var(--code-bg); padding: 16px; border-radius: 8px; overflow-x: auto; border: 1px solid var(--border-color); margin: 12px 0; } .command-body code { font-family: 'Monaco', 'Menlo', 'Courier New', monospace; font-size: 0.9rem; color: var(--text-color); } .command-body h2 { color: var(--primary-color); margin-top: 24px; margin-bottom: 12px; font-size: 1.3rem; } .command-body h3 { color: var(--secondary-color); margin-top: 20px; margin-bottom: 10px; font-size: 1.1rem; } .command-body ul, .command-body ol { margin-left: 24px; margin-bottom: 12px; } .command-body li { margin-bottom: 6px; } .command-body p { margin-bottom: 12px; } .command-body strong { color: var(--text-color); font-weight: 600; } footer { background: var(--card-bg); border-top: 1px solid var(--border-color); padding: 30px 0; margin-top: 60px; text-align: center; color: var(--text-secondary); } footer a { color: var(--primary-color); text-decoration: none; } footer a:hover { text-decoration: underline; } .hidden { display: none !important; } @media (max-width: 768px) { header h1 { font-size: 2rem; } .categories-grid { grid-template-columns: 1fr; } .command-header { flex-direction: column; align-items: flex-start; gap: 12px; } .command-actions { width: 100%; justify-content: flex-end; } }''' def generate_js(categories: Dict[str, List[Dict]]) -> str: """Generate the JavaScript file with embedded data""" commands_json = json.dumps(categories, indent=2) return f'''// Commands data const commandsData = {commands_json}; // Index page functionality function filterCategories() {{ const searchTerm = document.getElementById('searchInput').value.toLowerCase(); const cards = document.querySelectorAll('.category-card'); cards.forEach(card => {{ const categoryName = card.querySelector('h3').textContent.toLowerCase(); if (categoryName.includes(searchTerm)) {{ card.classList.remove('hidden'); }} else {{ card.classList.add('hidden'); }} }}); }} // Category page functionality function loadCategoryPage() {{ const urlParams = new URLSearchParams(window.location.search); const category = urlParams.get('cat'); if (!category || !commandsData[category]) {{ window.location.href = 'index.html'; return; }} const categoryTitle = document.getElementById('categoryTitle'); const commandsList = document.getElementById('commandsList'); categoryTitle.textContent = category.replace(/-/g, ' ').replace(/_/g, ' ').replace(/\\b\\w/g, l => l.toUpperCase()) + ' Commands'; const commands = commandsData[category]; commandsList.innerHTML = commands.map((cmd, index) => `
/${{cmd.name}}
${{cmd.description}}
${{cmd.tags && cmd.tags.length ? `
${{cmd.tags.map(tag => `${{tag}}`).join('')}}
` : ''}}
${{formatMarkdown(cmd.content)}}
`).join(''); }} function toggleCommand(index) {{ const content = document.getElementById(`content-${{index}}`); const header = content.previousElementSibling; content.classList.toggle('expanded'); header.classList.toggle('expanded'); }} function copyCommand(event, commandName, button) {{ event.stopPropagation(); navigator.clipboard.writeText(commandName).then(() => {{ const originalText = button.textContent; button.textContent = 'Copied!'; button.classList.add('copied'); setTimeout(() => {{ button.textContent = originalText; button.classList.remove('copied'); }}, 2000); }}); }} function filterCommands() {{ const searchTerm = document.getElementById('searchInput').value.toLowerCase(); const cards = document.querySelectorAll('.command-card'); cards.forEach(card => {{ const commandName = card.querySelector('.command-name').textContent.toLowerCase(); const commandDesc = card.querySelector('.command-description').textContent.toLowerCase(); if (commandName.includes(searchTerm) || commandDesc.includes(searchTerm)) {{ card.classList.remove('hidden'); }} else {{ card.classList.add('hidden'); }} }}); }} function formatMarkdown(text) {{ // Simple markdown formatting let html = text; // Code blocks html = html.replace(/```([\\s\\S]*?)```/g, '
$1
'); // Inline code html = html.replace(/`([^`]+)`/g, '$1'); // Bold html = html.replace(/\\*\\*([^*]+)\\*\\*/g, '$1'); // Headers html = html.replace(/^### (.+)$/gm, '

$1

'); html = html.replace(/^## (.+)$/gm, '

$1

'); // Bullet lists html = html.replace(/^\\s*[-*] (.+)$/gm, '
  • $1
  • '); html = html.replace(/(
  • .*<\\/li>)/s, ''); // Paragraphs html = html.replace(/^(?!<[hup]|$1

    '); return html; }} // Initialize the appropriate page if (window.location.pathname.includes('category.html')) {{ document.addEventListener('DOMContentLoaded', loadCategoryPage); }}''' def main(): """Main execution""" print("Scanning command files...") categories = scan_commands() print(f"Found {len(categories)} categories with {sum(len(cmds) for cmds in categories.values())} total commands") print("Generating index.html...") with open('index.html', 'w', encoding='utf-8') as f: f.write(generate_index_html(categories)) print("Generating category.html...") with open('category.html', 'w', encoding='utf-8') as f: f.write(generate_category_html()) print("Generating styles.css...") with open('styles.css', 'w', encoding='utf-8') as f: f.write(generate_css()) print("Generating script.js...") with open('script.js', 'w', encoding='utf-8') as f: f.write(generate_js(categories)) print("✓ Static site generated successfully!") print("\nGenerated files:") print(" - index.html (main page)") print(" - category.html (category view)") print(" - styles.css (stylesheet)") print(" - script.js (JavaScript with data)") print(f"\nCategories: {', '.join(sorted(categories.keys()))}") if __name__ == '__main__': main()