| import gradio as gr |
| import json |
| import os |
| from pathlib import Path |
|
|
| |
| CATEGORY_COLORS = { |
| 'ai-dev': '#8b5cf6', |
| 'ai-engineering': '#7c3aed', |
| 'claude-code': '#2563eb', |
| 'common-tasks': '#059669', |
| 'conv-mgmt': '#0891b2', |
| 'cybersec': '#dc2626', |
| 'development': '#3b82f6', |
| 'documentation': '#6366f1', |
| 'educational': '#f59e0b', |
| 'experiments': '#ec4899', |
| 'filesystem-ops': '#84cc16', |
| 'for-fun': '#f97316', |
| 'general-purpose': '#64748b', |
| 'ideation': '#a855f7', |
| 'local-ai': '#6366f1', |
| 'media': '#06b6d4', |
| 'misc': '#9ca3af', |
| 'operations': '#10b981', |
| 'seo-web': '#14b8a6', |
| 'sysadmin': '#22c55e', |
| 'tech-docs': '#3b82f6', |
| 'writing-and-editing': '#f472b6' |
| } |
|
|
| |
| def load_commands(): |
| """Load slash commands from categorized folder structure""" |
| commands = [] |
| commands_dir = Path("commands") |
|
|
| if not commands_dir.exists(): |
| return [] |
|
|
| |
| for cmd_file in commands_dir.rglob("*.md"): |
| |
| parts = cmd_file.relative_to(commands_dir).parts |
| category = parts[0] if parts else 'misc' |
|
|
| commands.append({ |
| 'name': cmd_file.stem, |
| 'path': str(cmd_file), |
| 'category': category |
| }) |
|
|
| |
| commands.sort(key=lambda x: x['name'].lower()) |
|
|
| return commands |
|
|
| def load_command_content(path): |
| """Load the content of a specific command file""" |
| file_path = Path(path) |
| if file_path.exists(): |
| with open(file_path, 'r') as f: |
| content = f.read() |
| |
| if content.startswith('---'): |
| parts = content.split('---', 2) |
| if len(parts) >= 3: |
| return parts[2].strip() |
| return content |
| return "Content not found" |
|
|
| def search_commands(search_term, commands_data): |
| """Filter commands based on search term""" |
| if not search_term: |
| return commands_data |
|
|
| search_term = search_term.lower() |
| filtered = [ |
| cmd for cmd in commands_data |
| if search_term in cmd['name'].lower() |
| ] |
| return filtered |
|
|
| def create_command_card(command): |
| """Create an HTML card for a command""" |
| import html |
|
|
| name = command['name'] |
| path = command['path'] |
| category = command.get('category', 'misc') |
| content = load_command_content(path) |
|
|
| |
| description = "" |
| lines = content.split('\n') |
| if lines: |
| |
| for line in lines[1:6]: |
| if line.strip() and not line.startswith('#'): |
| description = line.strip() |
| break |
|
|
| |
| category_color = CATEGORY_COLORS.get(category, '#9ca3af') |
| category_display = category.replace('-', ' ').title() |
|
|
| |
| content_json = json.dumps(content) |
|
|
| |
| unique_id = f"cmd-{name.replace(' ', '-').replace('/', '-')}" |
|
|
| card_html = f""" |
| <details style="border: 1px solid #e5e7eb; border-radius: 8px; margin: 10px 0; background: white; overflow: hidden;"> |
| <summary style="padding: 16px; cursor: pointer; list-style: none; user-select: none;"> |
| <div style="display: flex; justify-content: space-between; align-items: center; gap: 12px;"> |
| <div style="flex: 1;"> |
| <div style="display: flex; align-items: center; gap: 8px;"> |
| <span style="font-size: 18px; font-weight: 600; color: #1f2937;">/{html.escape(name)}</span> |
| <span style="display: inline-block; padding: 4px 10px; background: {category_color}; color: white; border-radius: 12px; font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">{html.escape(category_display)}</span> |
| </div> |
| </div> |
| <svg width="20" height="20" viewBox="0 0 20 20" fill="none" style="flex-shrink: 0; transition: transform 0.2s;"> |
| <path d="M6 8L10 12L14 8" stroke="#9ca3af" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> |
| </svg> |
| </div> |
| </summary> |
| <div style="padding: 0 16px 16px 16px; border-top: 1px solid #f3f4f6;"> |
| <div style="position: relative; background: #f9fafb; padding: 16px; border-radius: 6px; margin-top: 12px;"> |
| <button id="copy-btn-{unique_id}" |
| onclick="copyToClipboard{unique_id}()" |
| style="position: absolute; top: 12px; right: 12px; background: #2563eb; color: white; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 16px; transition: background 0.2s;" |
| onmouseover="this.style.background='#1d4ed8'" |
| onmouseout="this.style.background='#2563eb'"> |
| 📋 |
| </button> |
| <script> |
| function copyToClipboard{unique_id}() {{ |
| const text = {content_json}; |
| navigator.clipboard.writeText(text).then(() => {{ |
| const btn = document.getElementById('copy-btn-{unique_id}'); |
| btn.innerHTML = '✓ Copied'; |
| setTimeout(() => {{ |
| btn.innerHTML = '📋'; |
| }}, 1500); |
| }}).catch(err => {{ |
| console.error('Failed to copy:', err); |
| alert('Failed to copy to clipboard'); |
| }}); |
| }} |
| </script> |
| <pre style="margin: 0; white-space: pre-wrap; font-size: 13px; line-height: 1.6; color: #374151; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; padding-right: 60px;">{html.escape(content)}</pre> |
| </div> |
| </div> |
| </details> |
| """ |
| return card_html |
|
|
| def display_commands(search_term): |
| """Main function to display filtered commands""" |
| commands_data = load_commands() |
| filtered_commands = search_commands(search_term, commands_data) |
|
|
| if not filtered_commands: |
| return "<p style='text-align: center; color: #666; padding: 40px;'>No commands found matching your search.</p>" |
|
|
| html_output = f"<div style='max-width: 900px; margin: 0 auto;'>" |
| html_output += f"<p style='color: #666; margin-bottom: 20px;'>Showing {len(filtered_commands)} command(s)</p>" |
|
|
| for command in filtered_commands: |
| html_output += create_command_card(command) |
|
|
| html_output += "</div>" |
| return html_output |
|
|
| |
| with gr.Blocks( |
| title="Claude Code Slash Commands", |
| theme=gr.themes.Soft(), |
| css=""" |
| .gradio-container { |
| max-width: 1200px !important; |
| } |
| details[open] summary svg { |
| transform: rotate(180deg); |
| } |
| details summary::-webkit-details-marker { |
| display: none; |
| } |
| """ |
| ) as demo: |
|
|
| gr.Markdown(""" |
| # Claude Code Slash Commands Collection |
| |
| Browse and search through a comprehensive collection of slash commands for Claude Code CLI. |
| Use the search box to filter commands by name, then expand any command to view its full content and copy it to your clipboard. |
| |
| **Source:** [github.com/danielrosehill/Claude-Slash-Commands](https://github.com/danielrosehill/Claude-Slash-Commands) |
| """) |
|
|
| with gr.Row(): |
| search_box = gr.Textbox( |
| label="Search Commands", |
| placeholder="Type to filter commands (e.g., 'git', 'python', 'setup')...", |
| scale=4 |
| ) |
| search_btn = gr.Button("Search", scale=1, variant="primary") |
|
|
| commands_display = gr.HTML(value=display_commands("")) |
|
|
| |
| search_box.change( |
| fn=display_commands, |
| inputs=[search_box], |
| outputs=[commands_display] |
| ) |
|
|
| search_btn.click( |
| fn=display_commands, |
| inputs=[search_box], |
| outputs=[commands_display] |
| ) |
|
|
| gr.Markdown(""" |
| --- |
| |
| ### About |
| |
| This Space displays slash commands for Claude Code, Anthropic's official CLI tool. |
| Each command is a reusable prompt template that can be used to automate common tasks. |
| |
| **How to use:** |
| 1. Search for a command using keywords |
| 2. Click to expand and view the full command content |
| 3. Click "Copy to Clipboard" to copy the command |
| 4. Add it to your `.claude/commands/` directory |
| |
| **Repository:** [danielrosehill/Claude-Slash-Commands](https://github.com/danielrosehill/Claude-Slash-Commands) |
| """) |
|
|
| if __name__ == "__main__": |
| demo.launch() |
|
|