Team Ai
Apppublic

danielrosehill/Claude-Code-Slash-Commands-Linux-Desktop

sourceHugging Faceupdated 1y agoView on Hugging Face
1likes
generate_site.py670 linesDownload Raw Back to root
1#!/usr/bin/env python32"""3Generate static site from slash command markdown files4"""5import os6import json7import re8from pathlib import Path9from typing import Dict, List10 11def parse_frontmatter(content: str) -> tuple[Dict, str]:12    """Parse YAML frontmatter from markdown content"""13    frontmatter = {}14    body = content15 16    if content.startswith('---'):17        parts = content.split('---', 2)18        if len(parts) >= 3:19            fm_text = parts[1].strip()20            body = parts[2].strip()21 22            for line in fm_text.split('\n'):23                if ':' in line:24                    key, value = line.split(':', 1)25                    key = key.strip()26                    value = value.strip()27 28                    if value.startswith('[') and value.endswith(']'):29                        value = [v.strip() for v in value[1:-1].split(',')]30 31                    frontmatter[key] = value32 33    return frontmatter, body34 35def parse_command_file(filepath: Path) -> Dict:36    """Parse a single command markdown file"""37    with open(filepath, 'r', encoding='utf-8') as f:38        content = f.read()39 40    frontmatter, body = parse_frontmatter(content)41 42    command_name = filepath.stem43    category = filepath.parent.name44 45    return {46        'name': command_name,47        'category': category,48        'description': frontmatter.get('description', ''),49        'tags': frontmatter.get('tags', []),50        'content': body,51        'filepath': str(filepath.relative_to('commands'))52    }53 54def scan_commands(commands_dir: Path = Path('commands')) -> Dict[str, List[Dict]]:55    """Scan all command files and organize by category"""56    categories = {}57 58    for md_file in commands_dir.rglob('*.md'):59        command = parse_command_file(md_file)60        category = command['category']61 62        if category not in categories:63            categories[category] = []64 65        categories[category].append(command)66 67    for category in categories:68        categories[category].sort(key=lambda x: x['name'])69 70    return categories71 72def generate_index_html(categories: Dict[str, List[Dict]]) -> str:73    """Generate the main index.html page"""74    category_cards = []75 76    for category, commands in sorted(categories.items()):77        category_display = category.replace('-', ' ').replace('_', ' ').title()78        command_count = len(commands)79 80        category_cards.append(f'''81        <div class="category-card" onclick="window.location.href='category.html?cat={category}'">82            <h3>{category_display}</h3>83            <p class="command-count">{command_count} command{"s" if command_count != 1 else ""}</p>84        </div>85        ''')86 87    return f'''<!DOCTYPE html>88<html lang="en">89<head>90    <meta charset="UTF-8">91    <meta name="viewport" content="width=device-width, initial-scale=1.0">92    <title>Claude Code Linux Desktop Slash Commands</title>93    <link rel="stylesheet" href="styles.css">94</head>95<body>96    <header>97        <div class="container">98            <h1>Claude Code Slash Commands</h1>99            <p class="subtitle">Linux Desktop System Administration Commands</p>100        </div>101    </header>102 103    <main class="container">104        <div class="intro">105            <p>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.</p>106        </div>107 108        <div class="search-box">109            <input type="text" id="searchInput" placeholder="Search commands..." onkeyup="filterCategories()">110        </div>111 112        <div class="categories-grid" id="categoriesGrid">113            {''.join(category_cards)}114        </div>115    </main>116 117    <footer>118        <div class="container">119            <p>Created by Daniel Rosehill | <a href="https://github.com/danielrosehill/Claude-Code-Linux-Desktop-Slash-Commands" target="_blank">GitHub Repository</a></p>120        </div>121    </footer>122 123    <script src="script.js"></script>124</body>125</html>'''126 127def generate_category_html() -> str:128    """Generate the category page template (uses JS to load content)"""129    return '''<!DOCTYPE html>130<html lang="en">131<head>132    <meta charset="UTF-8">133    <meta name="viewport" content="width=device-width, initial-scale=1.0">134    <title>Category - Claude Code Commands</title>135    <link rel="stylesheet" href="styles.css">136</head>137<body>138    <header>139        <div class="container">140            <a href="index.html" class="back-link">← Back to Categories</a>141            <h1 id="categoryTitle">Commands</h1>142        </div>143    </header>144 145    <main class="container">146        <div class="search-box">147            <input type="text" id="searchInput" placeholder="Search commands in this category..." onkeyup="filterCommands()">148        </div>149 150        <div class="commands-list" id="commandsList">151            <!-- Commands will be loaded here by JavaScript -->152        </div>153    </main>154 155    <footer>156        <div class="container">157            <p>Created by Daniel Rosehill | <a href="https://github.com/danielrosehill/Claude-Code-Linux-Desktop-Slash-Commands" target="_blank">GitHub Repository</a></p>158        </div>159    </footer>160 161    <script src="script.js"></script>162</body>163</html>'''164 165def generate_css() -> str:166    """Generate the CSS stylesheet"""167    return '''* {168    margin: 0;169    padding: 0;170    box-sizing: border-box;171}172 173:root {174    --primary-color: #2563eb;175    --secondary-color: #1e40af;176    --accent-color: #3b82f6;177    --bg-color: #f8fafc;178    --card-bg: #ffffff;179    --text-color: #1e293b;180    --text-secondary: #64748b;181    --border-color: #e2e8f0;182    --code-bg: #f1f5f9;183    --success-color: #10b981;184    --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);185    --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);186}187 188body {189    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;190    background-color: var(--bg-color);191    color: var(--text-color);192    line-height: 1.6;193}194 195.container {196    max-width: 1200px;197    margin: 0 auto;198    padding: 0 20px;199}200 201header {202    background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);203    color: white;204    padding: 40px 0;205    margin-bottom: 40px;206    box-shadow: var(--shadow-lg);207}208 209header h1 {210    font-size: 2.5rem;211    margin-bottom: 8px;212}213 214.subtitle {215    font-size: 1.1rem;216    opacity: 0.9;217}218 219.back-link {220    display: inline-block;221    color: white;222    text-decoration: none;223    margin-bottom: 20px;224    font-size: 1rem;225    opacity: 0.9;226    transition: opacity 0.2s;227}228 229.back-link:hover {230    opacity: 1;231}232 233.intro {234    background: var(--card-bg);235    padding: 24px;236    border-radius: 12px;237    margin-bottom: 30px;238    box-shadow: var(--shadow);239    border-left: 4px solid var(--primary-color);240}241 242.search-box {243    margin-bottom: 30px;244}245 246#searchInput {247    width: 100%;248    padding: 14px 20px;249    font-size: 1rem;250    border: 2px solid var(--border-color);251    border-radius: 8px;252    background: var(--card-bg);253    transition: border-color 0.2s;254}255 256#searchInput:focus {257    outline: none;258    border-color: var(--primary-color);259}260 261.categories-grid {262    display: grid;263    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));264    gap: 20px;265    margin-bottom: 40px;266}267 268.category-card {269    background: var(--card-bg);270    padding: 30px;271    border-radius: 12px;272    box-shadow: var(--shadow);273    cursor: pointer;274    transition: all 0.3s ease;275    border: 2px solid transparent;276}277 278.category-card:hover {279    transform: translateY(-4px);280    box-shadow: var(--shadow-lg);281    border-color: var(--primary-color);282}283 284.category-card h3 {285    color: var(--primary-color);286    font-size: 1.4rem;287    margin-bottom: 8px;288}289 290.command-count {291    color: var(--text-secondary);292    font-size: 0.95rem;293}294 295.commands-list {296    display: flex;297    flex-direction: column;298    gap: 20px;299    margin-bottom: 40px;300}301 302.command-card {303    background: var(--card-bg);304    border-radius: 12px;305    box-shadow: var(--shadow);306    overflow: hidden;307    border: 1px solid var(--border-color);308}309 310.command-header {311    padding: 20px 24px;312    cursor: pointer;313    display: flex;314    justify-content: space-between;315    align-items: center;316    background: linear-gradient(to right, var(--card-bg), #f8fafc);317    transition: background 0.2s;318}319 320.command-header:hover {321    background: var(--code-bg);322}323 324.command-title {325    display: flex;326    flex-direction: column;327    gap: 6px;328    flex: 1;329}330 331.command-name {332    font-size: 1.3rem;333    color: var(--primary-color);334    font-weight: 600;335    font-family: 'Monaco', 'Menlo', monospace;336}337 338.command-description {339    color: var(--text-secondary);340    font-size: 0.95rem;341}342 343.command-actions {344    display: flex;345    gap: 10px;346    align-items: center;347}348 349.copy-btn {350    background: var(--primary-color);351    color: white;352    border: none;353    padding: 8px 16px;354    border-radius: 6px;355    cursor: pointer;356    font-size: 0.9rem;357    transition: all 0.2s;358    font-weight: 500;359}360 361.copy-btn:hover {362    background: var(--secondary-color);363    transform: scale(1.05);364}365 366.copy-btn.copied {367    background: var(--success-color);368}369 370.expand-icon {371    color: var(--text-secondary);372    font-size: 1.2rem;373    transition: transform 0.3s;374}375 376.command-header.expanded .expand-icon {377    transform: rotate(180deg);378}379 380.command-content {381    max-height: 0;382    overflow: hidden;383    transition: max-height 0.3s ease;384    background: var(--bg-color);385}386 387.command-content.expanded {388    max-height: 2000px;389}390 391.command-body {392    padding: 24px;393}394 395.command-tags {396    display: flex;397    flex-wrap: wrap;398    gap: 8px;399    margin-bottom: 20px;400}401 402.tag {403    background: var(--code-bg);404    color: var(--text-secondary);405    padding: 4px 12px;406    border-radius: 12px;407    font-size: 0.85rem;408    border: 1px solid var(--border-color);409}410 411.command-body pre {412    background: var(--code-bg);413    padding: 16px;414    border-radius: 8px;415    overflow-x: auto;416    border: 1px solid var(--border-color);417    margin: 12px 0;418}419 420.command-body code {421    font-family: 'Monaco', 'Menlo', 'Courier New', monospace;422    font-size: 0.9rem;423    color: var(--text-color);424}425 426.command-body h2 {427    color: var(--primary-color);428    margin-top: 24px;429    margin-bottom: 12px;430    font-size: 1.3rem;431}432 433.command-body h3 {434    color: var(--secondary-color);435    margin-top: 20px;436    margin-bottom: 10px;437    font-size: 1.1rem;438}439 440.command-body ul, .command-body ol {441    margin-left: 24px;442    margin-bottom: 12px;443}444 445.command-body li {446    margin-bottom: 6px;447}448 449.command-body p {450    margin-bottom: 12px;451}452 453.command-body strong {454    color: var(--text-color);455    font-weight: 600;456}457 458footer {459    background: var(--card-bg);460    border-top: 1px solid var(--border-color);461    padding: 30px 0;462    margin-top: 60px;463    text-align: center;464    color: var(--text-secondary);465}466 467footer a {468    color: var(--primary-color);469    text-decoration: none;470}471 472footer a:hover {473    text-decoration: underline;474}475 476.hidden {477    display: none !important;478}479 480@media (max-width: 768px) {481    header h1 {482        font-size: 2rem;483    }484 485    .categories-grid {486        grid-template-columns: 1fr;487    }488 489    .command-header {490        flex-direction: column;491        align-items: flex-start;492        gap: 12px;493    }494 495    .command-actions {496        width: 100%;497        justify-content: flex-end;498    }499}'''500 501def generate_js(categories: Dict[str, List[Dict]]) -> str:502    """Generate the JavaScript file with embedded data"""503    commands_json = json.dumps(categories, indent=2)504 505    return f'''// Commands data506const commandsData = {commands_json};507 508// Index page functionality509function filterCategories() {{510    const searchTerm = document.getElementById('searchInput').value.toLowerCase();511    const cards = document.querySelectorAll('.category-card');512 513    cards.forEach(card => {{514        const categoryName = card.querySelector('h3').textContent.toLowerCase();515        if (categoryName.includes(searchTerm)) {{516            card.classList.remove('hidden');517        }} else {{518            card.classList.add('hidden');519        }}520    }});521}}522 523// Category page functionality524function loadCategoryPage() {{525    const urlParams = new URLSearchParams(window.location.search);526    const category = urlParams.get('cat');527 528    if (!category || !commandsData[category]) {{529        window.location.href = 'index.html';530        return;531    }}532 533    const categoryTitle = document.getElementById('categoryTitle');534    const commandsList = document.getElementById('commandsList');535 536    categoryTitle.textContent = category.replace(/-/g, ' ').replace(/_/g, ' ').replace(/\\b\\w/g, l => l.toUpperCase()) + ' Commands';537 538    const commands = commandsData[category];539    commandsList.innerHTML = commands.map((cmd, index) => `540        <div class="command-card" data-command-name="${{cmd.name}}">541            <div class="command-header" onclick="toggleCommand(${{index}})">542                <div class="command-title">543                    <div class="command-name">/${{cmd.name}}</div>544                    <div class="command-description">${{cmd.description}}</div>545                </div>546                <div class="command-actions">547                    <button class="copy-btn" onclick="copyCommand(event, '/${{cmd.name}}', this)">Copy</button>548                    <span class="expand-icon">▼</span>549                </div>550            </div>551            <div class="command-content" id="content-${{index}}">552                <div class="command-body">553                    ${{cmd.tags && cmd.tags.length ? `554                        <div class="command-tags">555                            ${{cmd.tags.map(tag => `<span class="tag">${{tag}}</span>`).join('')}}556                        </div>557                    ` : ''}}558                    <div class="markdown-content">${{formatMarkdown(cmd.content)}}</div>559                </div>560            </div>561        </div>562    `).join('');563}}564 565function toggleCommand(index) {{566    const content = document.getElementById(`content-${{index}}`);567    const header = content.previousElementSibling;568 569    content.classList.toggle('expanded');570    header.classList.toggle('expanded');571}}572 573function copyCommand(event, commandName, button) {{574    event.stopPropagation();575 576    navigator.clipboard.writeText(commandName).then(() => {{577        const originalText = button.textContent;578        button.textContent = 'Copied!';579        button.classList.add('copied');580 581        setTimeout(() => {{582            button.textContent = originalText;583            button.classList.remove('copied');584        }}, 2000);585    }});586}}587 588function filterCommands() {{589    const searchTerm = document.getElementById('searchInput').value.toLowerCase();590    const cards = document.querySelectorAll('.command-card');591 592    cards.forEach(card => {{593        const commandName = card.querySelector('.command-name').textContent.toLowerCase();594        const commandDesc = card.querySelector('.command-description').textContent.toLowerCase();595 596        if (commandName.includes(searchTerm) || commandDesc.includes(searchTerm)) {{597            card.classList.remove('hidden');598        }} else {{599            card.classList.add('hidden');600        }}601    }});602}}603 604function formatMarkdown(text) {{605    // Simple markdown formatting606    let html = text;607 608    // Code blocks609    html = html.replace(/```([\\s\\S]*?)```/g, '<pre><code>$1</code></pre>');610 611    // Inline code612    html = html.replace(/`([^`]+)`/g, '<code>$1</code>');613 614    // Bold615    html = html.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>');616 617    // Headers618    html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');619    html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');620 621    // Bullet lists622    html = html.replace(/^\\s*[-*] (.+)$/gm, '<li>$1</li>');623    html = html.replace(/(<li>.*<\\/li>)/s, '<ul>$1</ul>');624 625    // Paragraphs626    html = html.replace(/^(?!<[hup]|<li)(.+)$/gm, '<p>$1</p>');627 628    return html;629}}630 631// Initialize the appropriate page632if (window.location.pathname.includes('category.html')) {{633    document.addEventListener('DOMContentLoaded', loadCategoryPage);634}}'''635 636def main():637    """Main execution"""638    print("Scanning command files...")639    categories = scan_commands()640 641    print(f"Found {len(categories)} categories with {sum(len(cmds) for cmds in categories.values())} total commands")642 643    print("Generating index.html...")644    with open('index.html', 'w', encoding='utf-8') as f:645        f.write(generate_index_html(categories))646 647    print("Generating category.html...")648    with open('category.html', 'w', encoding='utf-8') as f:649        f.write(generate_category_html())650 651    print("Generating styles.css...")652    with open('styles.css', 'w', encoding='utf-8') as f:653        f.write(generate_css())654 655    print("Generating script.js...")656    with open('script.js', 'w', encoding='utf-8') as f:657        f.write(generate_js(categories))658 659    print("✓ Static site generated successfully!")660    print("\nGenerated files:")661    print("  - index.html (main page)")662    print("  - category.html (category view)")663    print("  - styles.css (stylesheet)")664    print("  - script.js (JavaScript with data)")665 666    print(f"\nCategories: {', '.join(sorted(categories.keys()))}")667 668if __name__ == '__main__':669    main()670