#!/usr/bin/env python3 """ ECC Dashboard - Everything Claude Code GUI Cross-platform TkInter application for managing ECC components """ import tkinter as tk from tkinter import ttk, scrolledtext, messagebox import os import json from typing import Dict, List, Optional # ============================================================================ # DATA LOADERS - Load ECC data from the project # ============================================================================ def get_project_path() -> str: """Get the ECC project path - assumes this script is run from the project dir""" return os.path.dirname(os.path.abspath(__file__)) def load_agents(project_path: str) -> List[Dict]: """Load agents from AGENTS.md""" agents_file = os.path.join(project_path, "AGENTS.md") agents = [] if os.path.exists(agents_file): with open(agents_file, 'r', encoding='utf-8') as f: content = f.read() # Parse agent table from AGENTS.md lines = content.split('\n') in_table = False for line in lines: if '| Agent | Purpose | When to Use |' in line: in_table = True continue if in_table and line.startswith('|'): parts = [p.strip() for p in line.split('|')] if len(parts) >= 4 and parts[1] and parts[1] != 'Agent': agents.append({ 'name': parts[1], 'purpose': parts[2], 'when_to_use': parts[3] }) # Fallback default agents if file not found if not agents: agents = [ {'name': 'planner', 'purpose': 'Implementation planning', 'when_to_use': 'Complex features, refactoring'}, {'name': 'architect', 'purpose': 'System design and scalability', 'when_to_use': 'Architectural decisions'}, {'name': 'tdd-guide', 'purpose': 'Test-driven development', 'when_to_use': 'New features, bug fixes'}, {'name': 'code-reviewer', 'purpose': 'Code quality and maintainability', 'when_to_use': 'After writing/modifying code'}, {'name': 'security-reviewer', 'purpose': 'Vulnerability detection', 'when_to_use': 'Before commits, sensitive code'}, {'name': 'build-error-resolver', 'purpose': 'Fix build/type errors', 'when_to_use': 'When build fails'}, {'name': 'e2e-runner', 'purpose': 'End-to-end Playwright testing', 'when_to_use': 'Critical user flows'}, {'name': 'refactor-cleaner', 'purpose': 'Dead code cleanup', 'when_to_use': 'Code maintenance'}, {'name': 'doc-updater', 'purpose': 'Documentation and codemaps', 'when_to_use': 'Updating docs'}, {'name': 'go-reviewer', 'purpose': 'Go code review', 'when_to_use': 'Go projects'}, {'name': 'python-reviewer', 'purpose': 'Python code review', 'when_to_use': 'Python projects'}, {'name': 'typescript-reviewer', 'purpose': 'TypeScript/JavaScript code review', 'when_to_use': 'TypeScript projects'}, {'name': 'rust-reviewer', 'purpose': 'Rust code review', 'when_to_use': 'Rust projects'}, {'name': 'java-reviewer', 'purpose': 'Java and Spring Boot code review', 'when_to_use': 'Java projects'}, {'name': 'kotlin-reviewer', 'purpose': 'Kotlin code review', 'when_to_use': 'Kotlin projects'}, {'name': 'cpp-reviewer', 'purpose': 'C/C++ code review', 'when_to_use': 'C/C++ projects'}, {'name': 'database-reviewer', 'purpose': 'PostgreSQL/Supabase specialist', 'when_to_use': 'Database work'}, {'name': 'loop-operator', 'purpose': 'Autonomous loop execution', 'when_to_use': 'Run loops safely'}, {'name': 'harness-optimizer', 'purpose': 'Harness config tuning', 'when_to_use': 'Reliability, cost, throughput'}, ] return agents def load_skills(project_path: str) -> List[Dict]: """Load skills from skills directory""" skills_dir = os.path.join(project_path, "skills") skills = [] if os.path.exists(skills_dir): for item in os.listdir(skills_dir): skill_path = os.path.join(skills_dir, item) if os.path.isdir(skill_path): skill_file = os.path.join(skill_path, "SKILL.md") description = item.replace('-', ' ').title() if os.path.exists(skill_file): try: with open(skill_file, 'r', encoding='utf-8') as f: content = f.read() # Extract description from first lines lines = content.split('\n') for line in lines: if line.strip() and not line.startswith('#'): description = line.strip()[:100] break if line.startswith('# '): description = line[2:].strip()[:100] break except: pass # Determine category category = "General" item_lower = item.lower() if 'python' in item_lower or 'django' in item_lower: category = "Python" elif 'golang' in item_lower or 'go-' in item_lower: category = "Go" elif 'frontend' in item_lower or 'react' in item_lower: category = "Frontend" elif 'backend' in item_lower or 'api' in item_lower: category = "Backend" elif 'security' in item_lower: category = "Security" elif 'testing' in item_lower or 'tdd' in item_lower: category = "Testing" elif 'docker' in item_lower or 'deployment' in item_lower: category = "DevOps" elif 'swift' in item_lower or 'ios' in item_lower: category = "iOS" elif 'java' in item_lower or 'spring' in item_lower: category = "Java" elif 'rust' in item_lower: category = "Rust" skills.append({ 'name': item, 'description': description, 'category': category, 'path': skill_path }) # Fallback if directory doesn't exist if not skills: skills = [ {'name': 'tdd-workflow', 'description': 'Test-driven development workflow', 'category': 'Testing'}, {'name': 'coding-standards', 'description': 'Baseline coding conventions', 'category': 'General'}, {'name': 'security-review', 'description': 'Security checklist and patterns', 'category': 'Security'}, {'name': 'frontend-patterns', 'description': 'React and Next.js patterns', 'category': 'Frontend'}, {'name': 'backend-patterns', 'description': 'API and database patterns', 'category': 'Backend'}, {'name': 'api-design', 'description': 'REST API design patterns', 'category': 'Backend'}, {'name': 'docker-patterns', 'description': 'Docker and container patterns', 'category': 'DevOps'}, {'name': 'e2e-testing', 'description': 'Playwright E2E testing patterns', 'category': 'Testing'}, {'name': 'verification-loop', 'description': 'Build, test, lint verification', 'category': 'General'}, {'name': 'python-patterns', 'description': 'Python idioms and best practices', 'category': 'Python'}, {'name': 'golang-patterns', 'description': 'Go idioms and best practices', 'category': 'Go'}, {'name': 'django-patterns', 'description': 'Django patterns and best practices', 'category': 'Python'}, {'name': 'springboot-patterns', 'description': 'Java Spring Boot patterns', 'category': 'Java'}, {'name': 'laravel-patterns', 'description': 'Laravel architecture patterns', 'category': 'PHP'}, ] return skills def load_commands(project_path: str) -> List[Dict]: """Load commands from commands directory""" commands_dir = os.path.join(project_path, "commands") commands = [] if os.path.exists(commands_dir): for item in os.listdir(commands_dir): if item.endswith('.md'): cmd_name = item[:-3] description = "" try: with open(os.path.join(commands_dir, item), 'r', encoding='utf-8') as f: content = f.read() lines = content.split('\n') for line in lines: if line.startswith('# '): description = line[2:].strip() break except: pass commands.append({ 'name': cmd_name, 'description': description or cmd_name.replace('-', ' ').title() }) # Fallback commands if not commands: commands = [ {'name': 'plan', 'description': 'Create implementation plan'}, {'name': 'tdd', 'description': 'Test-driven development workflow'}, {'name': 'code-review', 'description': 'Review code for quality and security'}, {'name': 'build-fix', 'description': 'Fix build and TypeScript errors'}, {'name': 'e2e', 'description': 'Generate and run E2E tests'}, {'name': 'refactor-clean', 'description': 'Remove dead code'}, {'name': 'verify', 'description': 'Run verification loop'}, {'name': 'eval', 'description': 'Run evaluation against criteria'}, {'name': 'security', 'description': 'Run comprehensive security review'}, {'name': 'test-coverage', 'description': 'Analyze test coverage'}, {'name': 'update-docs', 'description': 'Update documentation'}, {'name': 'setup-pm', 'description': 'Configure package manager'}, {'name': 'go-review', 'description': 'Go code review'}, {'name': 'go-test', 'description': 'Go TDD workflow'}, {'name': 'python-review', 'description': 'Python code review'}, ] return commands def load_rules(project_path: str) -> List[Dict]: """Load rules from rules directory""" rules_dir = os.path.join(project_path, "rules") rules = [] if os.path.exists(rules_dir): for item in os.listdir(rules_dir): item_path = os.path.join(rules_dir, item) if os.path.isdir(item_path): # Common rules if item == "common": for file in os.listdir(item_path): if file.endswith('.md'): rules.append({ 'name': file[:-3], 'language': 'Common', 'path': os.path.join(item_path, file) }) else: # Language-specific rules for file in os.listdir(item_path): if file.endswith('.md'): rules.append({ 'name': file[:-3], 'language': item.title(), 'path': os.path.join(item_path, file) }) # Fallback rules if not rules: rules = [ {'name': 'coding-style', 'language': 'Common', 'path': ''}, {'name': 'git-workflow', 'language': 'Common', 'path': ''}, {'name': 'testing', 'language': 'Common', 'path': ''}, {'name': 'performance', 'language': 'Common', 'path': ''}, {'name': 'patterns', 'language': 'Common', 'path': ''}, {'name': 'security', 'language': 'Common', 'path': ''}, {'name': 'typescript', 'language': 'TypeScript', 'path': ''}, {'name': 'python', 'language': 'Python', 'path': ''}, {'name': 'golang', 'language': 'Go', 'path': ''}, {'name': 'swift', 'language': 'Swift', 'path': ''}, {'name': 'php', 'language': 'PHP', 'path': ''}, ] return rules # ============================================================================ # MAIN APPLICATION # ============================================================================ class ECCDashboard(tk.Tk): """Main ECC Dashboard Application""" def __init__(self): super().__init__() self.project_path = get_project_path() self.title("ECC Dashboard - Everything Claude Code") self.state('zoomed') try: self.icon_image = tk.PhotoImage(file='assets/images/ecc-logo.png') self.iconphoto(True, self.icon_image) except: pass self.minsize(800, 600) # Load data self.agents = load_agents(self.project_path) self.skills = load_skills(self.project_path) self.commands = load_commands(self.project_path) self.rules = load_rules(self.project_path) # Settings self.settings = { 'project_path': self.project_path, 'theme': 'light' } # Setup UI self.setup_styles() self.create_widgets() # Center window self.center_window() def setup_styles(self): """Setup ttk styles for modern look""" style = ttk.Style() style.theme_use('clam') # Configure tab style style.configure('TNotebook', background='#f0f0f0') style.configure('TNotebook.Tab', padding=[10, 5], font=('Arial', 10)) style.map('TNotebook.Tab', background=[('selected', '#ffffff')]) # Configure Treeview style.configure('Treeview', font=('Arial', 10), rowheight=25) style.configure('Treeview.Heading', font=('Arial', 10, 'bold')) # Configure buttons style.configure('TButton', font=('Arial', 10), padding=5) def center_window(self): """Center the window on screen""" self.update_idletasks() width = self.winfo_width() height = self.winfo_height() x = (self.winfo_screenwidth() // 2) - (width // 2) y = (self.winfo_screenheight() // 2) - (height // 2) self.geometry(f'{width}x{height}+{x}+{y}') def create_widgets(self): """Create all UI widgets""" # Main container main_frame = ttk.Frame(self) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # Header header_frame = ttk.Frame(main_frame) header_frame.pack(fill=tk.X, pady=(0, 10)) try: self.logo_image = tk.PhotoImage(file='assets/images/ecc-logo.png') self.logo_image = self.logo_image.subsample(2, 2) ttk.Label(header_frame, image=self.logo_image).pack(side=tk.LEFT, padx=(0, 10)) except: pass self.title_label = ttk.Label(header_frame, text="ECC Dashboard", font=('Open Sans', 18, 'bold')) self.title_label.pack(side=tk.LEFT) self.version_label = ttk.Label(header_frame, text="v1.10.0", font=('Open Sans', 10), foreground='gray') self.version_label.pack(side=tk.LEFT, padx=(10, 0)) # Notebook (tabs) self.notebook = ttk.Notebook(main_frame) self.notebook.pack(fill=tk.BOTH, expand=True) # Create tabs self.create_agents_tab() self.create_skills_tab() self.create_commands_tab() self.create_rules_tab() self.create_settings_tab() # Status bar status_frame = ttk.Frame(main_frame) status_frame.pack(fill=tk.X, pady=(10, 0)) self.status_label = ttk.Label(status_frame, text=f"Ready | Agents: {len(self.agents)} | Skills: {len(self.skills)} | Commands: {len(self.commands)}", font=('Arial', 9), foreground='gray') self.status_label.pack(side=tk.LEFT) # ========================================================================= # AGENTS TAB # ========================================================================= def create_agents_tab(self): """Create Agents tab""" frame = ttk.Frame(self.notebook) self.notebook.add(frame, text=f"Agents ({len(self.agents)})") # Search bar search_frame = ttk.Frame(frame) search_frame.pack(fill=tk.X, padx=10, pady=10) ttk.Label(search_frame, text="Search:").pack(side=tk.LEFT) self.agent_search = ttk.Entry(search_frame, width=30) self.agent_search.pack(side=tk.LEFT, padx=5) self.agent_search.bind('', self.filter_agents) ttk.Label(search_frame, text="Count:").pack(side=tk.LEFT, padx=(20, 0)) self.agent_count_label = ttk.Label(search_frame, text=str(len(self.agents))) self.agent_count_label.pack(side=tk.LEFT) # Split pane: list + details paned = ttk.PanedWindow(frame, orient=tk.HORIZONTAL) paned.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) # Agent list list_frame = ttk.Frame(paned) paned.add(list_frame, weight=2) columns = ('name', 'purpose') self.agent_tree = ttk.Treeview(list_frame, columns=columns, show='tree headings') self.agent_tree.heading('#0', text='#') self.agent_tree.heading('name', text='Agent Name') self.agent_tree.heading('purpose', text='Purpose') self.agent_tree.column('#0', width=40) self.agent_tree.column('name', width=180) self.agent_tree.column('purpose', width=250) self.agent_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # Scrollbar scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.agent_tree.yview) self.agent_tree.configure(yscrollcommand=scrollbar.set) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # Details panel details_frame = ttk.Frame(paned) paned.add(details_frame, weight=1) ttk.Label(details_frame, text="Details", font=('Arial', 11, 'bold')).pack(anchor=tk.W, pady=5) self.agent_details = scrolledtext.ScrolledText(details_frame, wrap=tk.WORD, height=15) self.agent_details.pack(fill=tk.BOTH, expand=True) # Bind selection self.agent_tree.bind('<>', self.on_agent_select) # Populate list self.populate_agents(self.agents) def populate_agents(self, agents: List[Dict]): """Populate agents list""" for item in self.agent_tree.get_children(): self.agent_tree.delete(item) for i, agent in enumerate(agents, 1): self.agent_tree.insert('', tk.END, text=str(i), values=(agent['name'], agent['purpose'])) def filter_agents(self, event=None): """Filter agents based on search""" query = self.agent_search.get().lower() if not query: filtered = self.agents else: filtered = [a for a in self.agents if query in a['name'].lower() or query in a['purpose'].lower()] self.populate_agents(filtered) self.agent_count_label.config(text=str(len(filtered))) def on_agent_select(self, event): """Handle agent selection""" selection = self.agent_tree.selection() if not selection: return item = self.agent_tree.item(selection[0]) agent_name = item['values'][0] agent = next((a for a in self.agents if a['name'] == agent_name), None) if agent: details = f"""Agent: {agent['name']} Purpose: {agent['purpose']} When to Use: {agent['when_to_use']} --- Usage in Claude Code: Use the /{agent['name']} command or invoke via agent delegation.""" self.agent_details.delete('1.0', tk.END) self.agent_details.insert('1.0', details) # ========================================================================= # SKILLS TAB # ========================================================================= def create_skills_tab(self): """Create Skills tab""" frame = ttk.Frame(self.notebook) self.notebook.add(frame, text=f"Skills ({len(self.skills)})") # Search and filter filter_frame = ttk.Frame(frame) filter_frame.pack(fill=tk.X, padx=10, pady=10) ttk.Label(filter_frame, text="Search:").pack(side=tk.LEFT) self.skill_search = ttk.Entry(filter_frame, width=25) self.skill_search.pack(side=tk.LEFT, padx=5) self.skill_search.bind('', self.filter_skills) ttk.Label(filter_frame, text="Category:").pack(side=tk.LEFT, padx=(20, 0)) self.skill_category = ttk.Combobox(filter_frame, values=['All'] + self.get_categories(), width=15) self.skill_category.set('All') self.skill_category.pack(side=tk.LEFT, padx=5) self.skill_category.bind('<>', self.filter_skills) ttk.Label(filter_frame, text="Count:").pack(side=tk.LEFT, padx=(20, 0)) self.skill_count_label = ttk.Label(filter_frame, text=str(len(self.skills))) self.skill_count_label.pack(side=tk.LEFT) # Split pane paned = ttk.PanedWindow(frame, orient=tk.HORIZONTAL) paned.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) # Skill list list_frame = ttk.Frame(paned) paned.add(list_frame, weight=1) columns = ('name', 'category', 'description') self.skill_tree = ttk.Treeview(list_frame, columns=columns, show='tree headings') self.skill_tree.heading('#0', text='#') self.skill_tree.heading('name', text='Skill Name') self.skill_tree.heading('category', text='Category') self.skill_tree.heading('description', text='Description') self.skill_tree.column('#0', width=40) self.skill_tree.column('name', width=180) self.skill_tree.column('category', width=100) self.skill_tree.column('description', width=300) self.skill_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.skill_tree.yview) self.skill_tree.configure(yscrollcommand=scrollbar.set) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # Details details_frame = ttk.Frame(paned) paned.add(details_frame, weight=1) ttk.Label(details_frame, text="Description", font=('Arial', 11, 'bold')).pack(anchor=tk.W, pady=5) self.skill_details = scrolledtext.ScrolledText(details_frame, wrap=tk.WORD, height=15) self.skill_details.pack(fill=tk.BOTH, expand=True) self.skill_tree.bind('<>', self.on_skill_select) self.populate_skills(self.skills) def get_categories(self) -> List[str]: """Get unique categories from skills""" categories = set(s['category'] for s in self.skills) return sorted(categories) def populate_skills(self, skills: List[Dict]): """Populate skills list""" for item in self.skill_tree.get_children(): self.skill_tree.delete(item) for i, skill in enumerate(skills, 1): self.skill_tree.insert('', tk.END, text=str(i), values=(skill['name'], skill['category'], skill['description'])) def filter_skills(self, event=None): """Filter skills based on search and category""" search = self.skill_search.get().lower() category = self.skill_category.get() filtered = self.skills if category != 'All': filtered = [s for s in filtered if s['category'] == category] if search: filtered = [s for s in filtered if search in s['name'].lower() or search in s['description'].lower()] self.populate_skills(filtered) self.skill_count_label.config(text=str(len(filtered))) def on_skill_select(self, event): """Handle skill selection""" selection = self.skill_tree.selection() if not selection: return item = self.skill_tree.item(selection[0]) skill_name = item['values'][0] skill = next((s for s in self.skills if s['name'] == skill_name), None) if skill: details = f"""Skill: {skill['name']} Category: {skill['category']} Description: {skill['description']} Path: {skill['path']} --- Usage: This skill is automatically activated when working with related technologies.""" self.skill_details.delete('1.0', tk.END) self.skill_details.insert('1.0', details) # ========================================================================= # COMMANDS TAB # ========================================================================= def create_commands_tab(self): """Create Commands tab""" frame = ttk.Frame(self.notebook) self.notebook.add(frame, text=f"Commands ({len(self.commands)})") # Info info_frame = ttk.Frame(frame) info_frame.pack(fill=tk.X, padx=10, pady=10) ttk.Label(info_frame, text="Slash Commands for Claude Code:", font=('Arial', 10, 'bold')).pack(anchor=tk.W) ttk.Label(info_frame, text="Use these commands in Claude Code by typing /command_name", foreground='gray').pack(anchor=tk.W) # Commands list list_frame = ttk.Frame(frame) list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) columns = ('name', 'description') self.command_tree = ttk.Treeview(list_frame, columns=columns, show='tree headings') self.command_tree.heading('#0', text='#') self.command_tree.heading('name', text='Command') self.command_tree.heading('description', text='Description') self.command_tree.column('#0', width=40) self.command_tree.column('name', width=150) self.command_tree.column('description', width=400) self.command_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.command_tree.yview) self.command_tree.configure(yscrollcommand=scrollbar.set) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # Populate for i, cmd in enumerate(self.commands, 1): self.command_tree.insert('', tk.END, text=str(i), values=('/' + cmd['name'], cmd['description'])) # ========================================================================= # RULES TAB # ========================================================================= def create_rules_tab(self): """Create Rules tab""" frame = ttk.Frame(self.notebook) self.notebook.add(frame, text=f"Rules ({len(self.rules)})") # Info info_frame = ttk.Frame(frame) info_frame.pack(fill=tk.X, padx=10, pady=10) ttk.Label(info_frame, text="Coding Rules by Language:", font=('Arial', 10, 'bold')).pack(anchor=tk.W) ttk.Label(info_frame, text="These rules are automatically applied in Claude Code", foreground='gray').pack(anchor=tk.W) # Filter filter_frame = ttk.Frame(frame) filter_frame.pack(fill=tk.X, padx=10, pady=5) ttk.Label(filter_frame, text="Language:").pack(side=tk.LEFT) self.rules_language = ttk.Combobox(filter_frame, values=['All'] + self.get_rule_languages(), width=15) self.rules_language.set('All') self.rules_language.pack(side=tk.LEFT, padx=5) self.rules_language.bind('<>', self.filter_rules) # Rules list list_frame = ttk.Frame(frame) list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) columns = ('name', 'language') self.rules_tree = ttk.Treeview(list_frame, columns=columns, show='tree headings') self.rules_tree.heading('#0', text='#') self.rules_tree.heading('name', text='Rule Name') self.rules_tree.heading('language', text='Language') self.rules_tree.column('#0', width=40) self.rules_tree.column('name', width=250) self.rules_tree.column('language', width=100) self.rules_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.rules_tree.yview) self.rules_tree.configure(yscrollcommand=scrollbar.set) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.populate_rules(self.rules) def get_rule_languages(self) -> List[str]: """Get unique languages from rules""" languages = set(r['language'] for r in self.rules) return sorted(languages) def populate_rules(self, rules: List[Dict]): """Populate rules list""" for item in self.rules_tree.get_children(): self.rules_tree.delete(item) for i, rule in enumerate(rules, 1): self.rules_tree.insert('', tk.END, text=str(i), values=(rule['name'], rule['language'])) def filter_rules(self, event=None): """Filter rules by language""" language = self.rules_language.get() if language == 'All': filtered = self.rules else: filtered = [r for r in self.rules if r['language'] == language] self.populate_rules(filtered) # ========================================================================= # SETTINGS TAB # ========================================================================= def create_settings_tab(self): """Create Settings tab""" frame = ttk.Frame(self.notebook) self.notebook.add(frame, text="Settings") # Project path path_frame = ttk.LabelFrame(frame, text="Project Path", padding=10) path_frame.pack(fill=tk.X, padx=10, pady=10) self.path_entry = ttk.Entry(path_frame, width=60) self.path_entry.insert(0, self.project_path) self.path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) ttk.Button(path_frame, text="Browse...", command=self.browse_path).pack(side=tk.LEFT, padx=5) # Theme theme_frame = ttk.LabelFrame(frame, text="Appearance", padding=10) theme_frame.pack(fill=tk.X, padx=10, pady=10) ttk.Label(theme_frame, text="Theme:").pack(anchor=tk.W) self.theme_var = tk.StringVar(value='light') light_rb = ttk.Radiobutton(theme_frame, text="Light", variable=self.theme_var, value='light', command=self.apply_theme) light_rb.pack(anchor=tk.W) dark_rb = ttk.Radiobutton(theme_frame, text="Dark", variable=self.theme_var, value='dark', command=self.apply_theme) dark_rb.pack(anchor=tk.W) font_frame = ttk.LabelFrame(frame, text="Font", padding=10) font_frame.pack(fill=tk.X, padx=10, pady=10) ttk.Label(font_frame, text="Font Family:").pack(anchor=tk.W) self.font_var = tk.StringVar(value='Open Sans') fonts = ['Open Sans', 'Arial', 'Helvetica', 'Times New Roman', 'Courier New', 'Verdana', 'Georgia', 'Tahoma', 'Trebuchet MS'] self.font_combo = ttk.Combobox(font_frame, textvariable=self.font_var, values=fonts, state='readonly') self.font_combo.pack(anchor=tk.W, fill=tk.X, pady=(5, 0)) self.font_combo.bind('<>', lambda e: self.apply_theme()) ttk.Label(font_frame, text="Font Size:").pack(anchor=tk.W, pady=(10, 0)) self.size_var = tk.StringVar(value='10') sizes = ['8', '9', '10', '11', '12', '14', '16', '18', '20'] self.size_combo = ttk.Combobox(font_frame, textvariable=self.size_var, values=sizes, state='readonly', width=10) self.size_combo.pack(anchor=tk.W, pady=(5, 0)) self.size_combo.bind('<>', lambda e: self.apply_theme()) # Quick Actions actions_frame = ttk.LabelFrame(frame, text="Quick Actions", padding=10) actions_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) ttk.Button(actions_frame, text="Open Project in Terminal", command=self.open_terminal).pack(fill=tk.X, pady=2) ttk.Button(actions_frame, text="Open README", command=self.open_readme).pack(fill=tk.X, pady=2) ttk.Button(actions_frame, text="Open AGENTS.md", command=self.open_agents).pack(fill=tk.X, pady=2) ttk.Button(actions_frame, text="Refresh Data", command=self.refresh_data).pack(fill=tk.X, pady=2) # About about_frame = ttk.LabelFrame(frame, text="About", padding=10) about_frame.pack(fill=tk.X, padx=10, pady=10) about_text = """ECC Dashboard v1.0.0 Everything Claude Code GUI A cross-platform desktop application for managing and exploring ECC components. Version: 1.10.0 Project: github.com/affaan-m/everything-claude-code""" ttk.Label(about_frame, text=about_text, justify=tk.LEFT).pack(anchor=tk.W) def browse_path(self): """Browse for project path""" from tkinter import filedialog path = filedialog.askdirectory(initialdir=self.project_path) if path: self.path_entry.delete(0, tk.END) self.path_entry.insert(0, path) def open_terminal(self): """Open terminal at project path""" import subprocess path = self.path_entry.get() if os.name == 'nt': # Windows subprocess.Popen(['cmd', '/c', 'start', 'cmd', '/k', f'cd /d "{path}"']) elif os.uname().sysname == 'Darwin': # macOS subprocess.Popen(['open', '-a', 'Terminal', path]) else: # Linux subprocess.Popen(['x-terminal-emulator', '-e', f'cd {path}']) def open_readme(self): """Open README in default browser/reader""" import subprocess path = os.path.join(self.path_entry.get(), 'README.md') if os.path.exists(path): subprocess.Popen(['xdg-open' if os.name != 'nt' else 'start', path]) else: messagebox.showerror("Error", "README.md not found") def open_agents(self): """Open AGENTS.md""" import subprocess path = os.path.join(self.path_entry.get(), 'AGENTS.md') if os.path.exists(path): subprocess.Popen(['xdg-open' if os.name != 'nt' else 'start', path]) else: messagebox.showerror("Error", "AGENTS.md not found") def refresh_data(self): """Refresh all data""" self.project_path = self.path_entry.get() self.agents = load_agents(self.project_path) self.skills = load_skills(self.project_path) self.commands = load_commands(self.project_path) self.rules = load_rules(self.project_path) # Update tabs self.notebook.tab(0, text=f"Agents ({len(self.agents)})") self.notebook.tab(1, text=f"Skills ({len(self.skills)})") self.notebook.tab(2, text=f"Commands ({len(self.commands)})") self.notebook.tab(3, text=f"Rules ({len(self.rules)})") # Repopulate self.populate_agents(self.agents) self.populate_skills(self.skills) # Update status self.status_label.config( text=f"Ready | Agents: {len(self.agents)} | Skills: {len(self.skills)} | Commands: {len(self.commands)}" ) messagebox.showinfo("Success", "Data refreshed successfully!") def apply_theme(self): theme = self.theme_var.get() font_family = self.font_var.get() font_size = int(self.size_var.get()) font_tuple = (font_family, font_size) if theme == 'dark': bg_color = '#2b2b2b' fg_color = '#ffffff' entry_bg = '#3c3c3c' frame_bg = '#2b2b2b' select_bg = '#0f5a9e' else: bg_color = '#f0f0f0' fg_color = '#000000' entry_bg = '#ffffff' frame_bg = '#f0f0f0' select_bg = '#e0e0e0' self.configure(background=bg_color) style = ttk.Style() style.configure('.', background=bg_color, foreground=fg_color, font=font_tuple) style.configure('TFrame', background=bg_color, font=font_tuple) style.configure('TLabel', background=bg_color, foreground=fg_color, font=font_tuple) style.configure('TNotebook', background=bg_color, font=font_tuple) style.configure('TNotebook.Tab', background=frame_bg, foreground=fg_color, font=font_tuple) style.map('TNotebook.Tab', background=[('selected', select_bg)]) style.configure('Treeview', background=entry_bg, foreground=fg_color, fieldbackground=entry_bg, font=font_tuple) style.configure('Treeview.Heading', background=frame_bg, foreground=fg_color, font=font_tuple) style.configure('TEntry', fieldbackground=entry_bg, foreground=fg_color, font=font_tuple) style.configure('TButton', background=frame_bg, foreground=fg_color, font=font_tuple) self.title_label.configure(font=(font_family, 18, 'bold')) self.version_label.configure(font=(font_family, 10)) def update_widget_colors(widget): try: widget.configure(background=bg_color) except: pass for child in widget.winfo_children(): try: child.configure(background=bg_color) except: pass try: update_widget_colors(child) except: pass try: update_widget_colors(self) except: pass self.update() # ============================================================================ # MAIN # ============================================================================ def main(): """Main entry point""" app = ECCDashboard() app.mainloop() if __name__ == "__main__": main()