danielrosehill/Claude-Code-Slash-Commands-Linux-Desktop
1
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 