ai-coding-assistant 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. ai_coding_assistant-0.5.0.dist-info/METADATA +226 -0
  2. ai_coding_assistant-0.5.0.dist-info/RECORD +89 -0
  3. ai_coding_assistant-0.5.0.dist-info/WHEEL +4 -0
  4. ai_coding_assistant-0.5.0.dist-info/entry_points.txt +3 -0
  5. ai_coding_assistant-0.5.0.dist-info/licenses/LICENSE +21 -0
  6. coding_assistant/__init__.py +3 -0
  7. coding_assistant/__main__.py +19 -0
  8. coding_assistant/cli/__init__.py +1 -0
  9. coding_assistant/cli/app.py +158 -0
  10. coding_assistant/cli/commands/__init__.py +19 -0
  11. coding_assistant/cli/commands/ask.py +178 -0
  12. coding_assistant/cli/commands/config.py +438 -0
  13. coding_assistant/cli/commands/diagram.py +267 -0
  14. coding_assistant/cli/commands/document.py +410 -0
  15. coding_assistant/cli/commands/explain.py +192 -0
  16. coding_assistant/cli/commands/fix.py +249 -0
  17. coding_assistant/cli/commands/index.py +162 -0
  18. coding_assistant/cli/commands/refactor.py +245 -0
  19. coding_assistant/cli/commands/search.py +182 -0
  20. coding_assistant/cli/commands/serve_docs.py +128 -0
  21. coding_assistant/cli/repl.py +381 -0
  22. coding_assistant/cli/theme.py +90 -0
  23. coding_assistant/codebase/__init__.py +1 -0
  24. coding_assistant/codebase/crawler.py +93 -0
  25. coding_assistant/codebase/parser.py +266 -0
  26. coding_assistant/config/__init__.py +25 -0
  27. coding_assistant/config/config_manager.py +615 -0
  28. coding_assistant/config/settings.py +82 -0
  29. coding_assistant/context/__init__.py +19 -0
  30. coding_assistant/context/chunker.py +443 -0
  31. coding_assistant/context/enhanced_retriever.py +322 -0
  32. coding_assistant/context/hybrid_search.py +311 -0
  33. coding_assistant/context/ranker.py +355 -0
  34. coding_assistant/context/retriever.py +119 -0
  35. coding_assistant/context/window.py +362 -0
  36. coding_assistant/documentation/__init__.py +23 -0
  37. coding_assistant/documentation/agents/__init__.py +27 -0
  38. coding_assistant/documentation/agents/coordinator.py +510 -0
  39. coding_assistant/documentation/agents/module_documenter.py +111 -0
  40. coding_assistant/documentation/agents/synthesizer.py +139 -0
  41. coding_assistant/documentation/agents/task_delegator.py +100 -0
  42. coding_assistant/documentation/decomposition/__init__.py +21 -0
  43. coding_assistant/documentation/decomposition/context_preserver.py +477 -0
  44. coding_assistant/documentation/decomposition/module_detector.py +302 -0
  45. coding_assistant/documentation/decomposition/partitioner.py +621 -0
  46. coding_assistant/documentation/generators/__init__.py +14 -0
  47. coding_assistant/documentation/generators/dataflow_generator.py +440 -0
  48. coding_assistant/documentation/generators/diagram_generator.py +511 -0
  49. coding_assistant/documentation/graph/__init__.py +13 -0
  50. coding_assistant/documentation/graph/dependency_builder.py +468 -0
  51. coding_assistant/documentation/graph/module_analyzer.py +475 -0
  52. coding_assistant/documentation/writers/__init__.py +11 -0
  53. coding_assistant/documentation/writers/markdown_writer.py +322 -0
  54. coding_assistant/embeddings/__init__.py +0 -0
  55. coding_assistant/embeddings/generator.py +89 -0
  56. coding_assistant/embeddings/store.py +187 -0
  57. coding_assistant/exceptions/__init__.py +50 -0
  58. coding_assistant/exceptions/base.py +110 -0
  59. coding_assistant/exceptions/llm.py +249 -0
  60. coding_assistant/exceptions/recovery.py +263 -0
  61. coding_assistant/exceptions/storage.py +213 -0
  62. coding_assistant/exceptions/validation.py +230 -0
  63. coding_assistant/llm/__init__.py +1 -0
  64. coding_assistant/llm/client.py +277 -0
  65. coding_assistant/llm/gemini_client.py +181 -0
  66. coding_assistant/llm/groq_client.py +160 -0
  67. coding_assistant/llm/prompts.py +98 -0
  68. coding_assistant/llm/together_client.py +160 -0
  69. coding_assistant/operations/__init__.py +13 -0
  70. coding_assistant/operations/differ.py +369 -0
  71. coding_assistant/operations/generator.py +347 -0
  72. coding_assistant/operations/linter.py +430 -0
  73. coding_assistant/operations/validator.py +406 -0
  74. coding_assistant/storage/__init__.py +9 -0
  75. coding_assistant/storage/database.py +363 -0
  76. coding_assistant/storage/session.py +231 -0
  77. coding_assistant/utils/__init__.py +31 -0
  78. coding_assistant/utils/cache.py +477 -0
  79. coding_assistant/utils/hardware.py +132 -0
  80. coding_assistant/utils/keystore.py +206 -0
  81. coding_assistant/utils/logger.py +32 -0
  82. coding_assistant/utils/progress.py +311 -0
  83. coding_assistant/validation/__init__.py +13 -0
  84. coding_assistant/validation/files.py +305 -0
  85. coding_assistant/validation/inputs.py +335 -0
  86. coding_assistant/validation/params.py +280 -0
  87. coding_assistant/validation/sanitizers.py +243 -0
  88. coding_assistant/vcs/__init__.py +5 -0
  89. coding_assistant/vcs/git.py +269 -0
@@ -0,0 +1,245 @@
1
+ """Refactor command - Refactor code with validation and preview."""
2
+
3
+ import typer
4
+ from rich.console import Console
5
+ from rich.panel import Panel
6
+ from rich.syntax import Syntax
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ from coding_assistant.config.settings import settings
11
+ from coding_assistant.context.enhanced_retriever import EnhancedSemanticRetriever
12
+ from coding_assistant.operations.generator import CodeGenerator
13
+ from coding_assistant.operations.validator import SyntaxValidator
14
+ from coding_assistant.operations.linter import LinterIntegration
15
+ from coding_assistant.operations.differ import DiffGenerator
16
+ from coding_assistant.llm.client import LLMClientFactory
17
+
18
+ console = Console()
19
+
20
+
21
+ def refactor_command(
22
+ target: str = typer.Argument(..., help="File path or file::function to refactor"),
23
+ strategy: str = typer.Option(
24
+ "improve readability and efficiency",
25
+ "--strategy",
26
+ "-s",
27
+ help="Refactoring strategy"
28
+ ),
29
+ auto_apply: bool = typer.Option(
30
+ False,
31
+ "--auto-apply",
32
+ "-y",
33
+ help="Apply changes without confirmation"
34
+ ),
35
+ run_linter: bool = typer.Option(
36
+ True,
37
+ "--lint/--no-lint",
38
+ help="Run linter on refactored code"
39
+ ),
40
+ ):
41
+ """Refactor code with AI assistance, validation, and preview.
42
+
43
+ The refactoring process:
44
+ 1. Read original code
45
+ 2. Get LLM refactoring suggestion
46
+ 3. Extract and validate refactored code
47
+ 4. Run linter (optional)
48
+ 5. Show diff preview
49
+ 6. Ask for confirmation (unless --auto-apply)
50
+ 7. Apply changes
51
+
52
+ Examples:
53
+ assistant refactor src/utils.py
54
+ assistant refactor src/auth.py --strategy "extract helper functions"
55
+ assistant refactor src/module.py::process_data --auto-apply
56
+ """
57
+
58
+ console.print("\n🔧 [bold cyan]Code Refactoring[/bold cyan]\n")
59
+
60
+ # Parse target
61
+ if '::' in target:
62
+ file_path_str, function_name = target.split('::', 1)
63
+ else:
64
+ file_path_str = target
65
+ function_name = None
66
+
67
+ file_path = Path(file_path_str)
68
+
69
+ # Check file exists
70
+ if not file_path.exists():
71
+ console.print(f"[red]✗ File not found: {file_path}[/red]")
72
+ raise typer.Exit(1)
73
+
74
+ # Detect language
75
+ language_map = {
76
+ '.py': 'python',
77
+ '.js': 'javascript',
78
+ '.ts': 'typescript',
79
+ '.jsx': 'jsx',
80
+ '.tsx': 'tsx'
81
+ }
82
+ language = language_map.get(file_path.suffix, 'python')
83
+
84
+ # Read original code
85
+ try:
86
+ original_code = file_path.read_text(encoding='utf-8')
87
+ except Exception as e:
88
+ console.print(f"[red]✗ Error reading file: {e}[/red]")
89
+ raise typer.Exit(1)
90
+
91
+ # If function specified, extract it
92
+ target_code = original_code
93
+ if function_name:
94
+ # TODO: Extract specific function
95
+ console.print(f"[dim]Targeting function: {function_name}[/dim]")
96
+
97
+ # Show original code preview
98
+ console.print("[bold]Original Code:[/bold]")
99
+ console.print(Panel(
100
+ Syntax(original_code[:300] + ("..." if len(original_code) > 300 else ""),
101
+ language, theme="monokai", line_numbers=True),
102
+ title=str(file_path),
103
+ border_style="dim"
104
+ ))
105
+ console.print()
106
+
107
+ # Get context for better refactoring
108
+ context_chunks = []
109
+ try:
110
+ retriever = EnhancedSemanticRetriever(settings.project_path)
111
+ stats = retriever.get_stats()
112
+
113
+ if stats['total_chunks'] > 0:
114
+ with console.status("[bold green]Finding related code for context..."):
115
+ results = retriever.retrieve(
116
+ query=f"code related to {file_path.name}",
117
+ k=3,
118
+ current_file=str(file_path),
119
+ language=language
120
+ )
121
+ context_chunks = results
122
+ except:
123
+ pass
124
+
125
+ # Build refactoring prompt
126
+ with console.status("[bold green]Generating refactoring suggestions..."):
127
+ llm = LLMClientFactory.create_client(settings.llm_provider)
128
+
129
+ system_prompt = f"""You are an expert code refactoring assistant.
130
+
131
+ Your task: Refactor the provided {language} code according to the strategy: "{strategy}"
132
+
133
+ Guidelines:
134
+ - Preserve functionality (same inputs → same outputs)
135
+ - Improve code quality, readability, and efficiency
136
+ - Follow {language} best practices
137
+ - Add helpful comments where appropriate
138
+ - Return ONLY the refactored code in a code block
139
+ - Do not include explanations outside the code block
140
+
141
+ Strategy: {strategy}"""
142
+
143
+ user_prompt = f"""Refactor this {language} code:
144
+
145
+ ```{language}
146
+ {target_code}
147
+ ```
148
+ """
149
+
150
+ if context_chunks:
151
+ user_prompt += "\n\nRelated code for context:\n"
152
+ for chunk in context_chunks[:2]:
153
+ user_prompt += f"\n```{chunk.get('language', language)}\n{chunk['content'][:200]}...\n```\n"
154
+
155
+ messages = [
156
+ {"role": "system", "content": system_prompt},
157
+ {"role": "user", "content": user_prompt}
158
+ ]
159
+
160
+ # Get refactored code
161
+ try:
162
+ response = ""
163
+ for chunk in llm.generate(messages, stream=False):
164
+ response += chunk
165
+ except Exception as e:
166
+ console.print(f"[red]✗ Error generating refactoring: {e}[/red]")
167
+ raise typer.Exit(1)
168
+
169
+ # Extract code blocks
170
+ console.print("[bold green]✓[/bold green] Refactoring generated\n")
171
+
172
+ generator = CodeGenerator()
173
+ blocks = generator.extract_code_blocks(response, validate=True)
174
+
175
+ if not blocks:
176
+ console.print("[red]✗ No code found in refactoring response[/red]")
177
+ console.print("[dim]Response was:[/dim]")
178
+ console.print(response[:500])
179
+ raise typer.Exit(1)
180
+
181
+ refactored_code = blocks[0].code
182
+
183
+ # Validate syntax
184
+ console.print("[bold]Validating refactored code...[/bold]")
185
+ validator = SyntaxValidator()
186
+ validation_result = validator.validate(refactored_code, language)
187
+
188
+ if not validation_result.is_valid:
189
+ console.print(f"[red]✗ Refactored code has syntax errors:[/red]")
190
+ console.print(f"[red] Line {validation_result.line}: {validation_result.error_message}[/red]")
191
+ console.print("\n[yellow]Refactoring failed. Original code unchanged.[/yellow]\n")
192
+ raise typer.Exit(1)
193
+
194
+ console.print("[green]✓ Syntax valid[/green]\n")
195
+
196
+ # Run linter if requested
197
+ if run_linter:
198
+ console.print("[bold]Running linter...[/bold]")
199
+ linter = LinterIntegration(project_root=str(settings.project_path))
200
+ lint_result = linter.lint(refactored_code, language, auto_fix=True)
201
+
202
+ if lint_result.error_count > 0:
203
+ console.print(f"[yellow]⚠️ Found {lint_result.error_count} linting issues[/yellow]")
204
+ linter.display_issues(lint_result)
205
+
206
+ if lint_result.fixed_code:
207
+ console.print("[green]✓ Auto-fixed linting issues[/green]")
208
+ refactored_code = lint_result.fixed_code
209
+ else:
210
+ console.print("[green]✓ No linting issues[/green]")
211
+ console.print()
212
+
213
+ # Generate and show diff
214
+ console.print("[bold]Changes Preview:[/bold]\n")
215
+ differ = DiffGenerator()
216
+ diff = differ.generate_diff(original_code, refactored_code, str(file_path))
217
+ differ.display_diff(diff, title=f"Refactoring: {file_path}")
218
+
219
+ stats = differ.get_change_stats(diff)
220
+ console.print(f"\n[bold]Change Summary:[/bold]")
221
+ console.print(f" Lines added: [green]{stats['lines_added']}[/green]")
222
+ console.print(f" Lines removed: [red]{stats['lines_removed']}[/red]")
223
+ console.print(f" Net change: {stats['net_change']:+d}")
224
+ console.print()
225
+
226
+ # Ask for confirmation
227
+ if not auto_apply:
228
+ if not typer.confirm("Apply these changes?"):
229
+ console.print("[yellow]Refactoring cancelled. No changes made.[/yellow]\n")
230
+ raise typer.Exit(0)
231
+
232
+ # Apply changes
233
+ try:
234
+ file_path.write_text(refactored_code, encoding='utf-8')
235
+ console.print(f"\n[bold green]✓ Refactoring applied to {file_path}[/bold green]\n")
236
+
237
+ # Show next steps
238
+ console.print("[dim]Next steps:")
239
+ console.print(f" • Review: git diff {file_path}")
240
+ console.print(f" • Test: Run your tests")
241
+ console.print(f" • Commit: git commit -m 'Refactor {file_path.name}'[/dim]\n")
242
+
243
+ except Exception as e:
244
+ console.print(f"[red]✗ Error writing file: {e}[/red]")
245
+ raise typer.Exit(1)
@@ -0,0 +1,182 @@
1
+ """Search command - Search codebase with hybrid search."""
2
+
3
+ import typer
4
+ from rich.console import Console
5
+ from rich.syntax import Syntax
6
+ from rich.table import Table
7
+ from rich.panel import Panel
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ from coding_assistant.config.settings import settings
12
+ from coding_assistant.context.enhanced_retriever import EnhancedSemanticRetriever
13
+
14
+ console = Console()
15
+
16
+
17
+ def search_command(
18
+ query: str = typer.Argument(..., help="Search query (natural language)"),
19
+ limit: int = typer.Option(
20
+ 5,
21
+ "--limit",
22
+ "-l",
23
+ help="Number of results to return"
24
+ ),
25
+ file_type: Optional[str] = typer.Option(
26
+ None,
27
+ "--type",
28
+ "-t",
29
+ help="Filter by file type (e.g., python, javascript)"
30
+ ),
31
+ directory: Optional[str] = typer.Option(
32
+ None,
33
+ "--dir",
34
+ "-d",
35
+ help="Filter by directory"
36
+ ),
37
+ show_content: bool = typer.Option(
38
+ True,
39
+ "--content/--no-content",
40
+ help="Show code content in results"
41
+ ),
42
+ ):
43
+ """Search your codebase with hybrid semantic + keyword search.
44
+
45
+ Examples:
46
+ assistant search "authentication implementation"
47
+ assistant search "JWT validation" --type python
48
+ assistant search "database queries" --dir src/db --limit 10
49
+ """
50
+
51
+ console.print("\n🔍 [bold cyan]Searching Codebase[/bold cyan]\n")
52
+ console.print(f"Query: [bold]{query}[/bold]\n")
53
+
54
+ # Initialize retriever
55
+ try:
56
+ retriever = EnhancedSemanticRetriever(settings.project_path)
57
+ except Exception as e:
58
+ console.print(f"[red]✗ Error initializing retriever: {e}[/red]")
59
+ raise typer.Exit(1)
60
+
61
+ # Check if indexed
62
+ stats = retriever.get_stats()
63
+ if stats['total_chunks'] == 0:
64
+ console.print("[yellow]⚠️ Codebase not indexed.[/yellow]")
65
+ console.print("[yellow] Run 'assistant index' first to enable search.[/yellow]\n")
66
+ raise typer.Exit(1)
67
+
68
+ # Show search stats
69
+ if settings.verbose:
70
+ console.print(f"[dim]Index stats:[/dim]")
71
+ console.print(f"[dim] • Total chunks: {stats['total_chunks']}[/dim]")
72
+ console.print(f"[dim] • Hybrid search: {stats['hybrid_search_enabled']}[/dim]")
73
+ console.print()
74
+
75
+ # Perform search
76
+ try:
77
+ with console.status("[bold green]Searching..."):
78
+ results = retriever.retrieve(
79
+ query=query,
80
+ k=limit * 2, # Get more, then filter
81
+ language=file_type,
82
+ use_hybrid=True,
83
+ use_ranking=True
84
+ )
85
+
86
+ # Apply filters
87
+ if directory:
88
+ dir_path = Path(directory)
89
+ results = [r for r in results if str(dir_path) in r['path']]
90
+
91
+ if file_type:
92
+ results = [r for r in results if r.get('language') == file_type]
93
+
94
+ # Limit results
95
+ results = results[:limit]
96
+
97
+ except Exception as e:
98
+ console.print(f"[red]✗ Search error: {e}[/red]")
99
+ if settings.verbose:
100
+ import traceback
101
+ traceback.print_exc()
102
+ raise typer.Exit(1)
103
+
104
+ # Display results
105
+ if not results:
106
+ console.print("[yellow]No results found.[/yellow]\n")
107
+ console.print("[dim]Try:")
108
+ console.print(" • Broadening your search query")
109
+ console.print(" • Removing filters")
110
+ console.print(" • Re-indexing with 'assistant index --force'[/dim]\n")
111
+ return
112
+
113
+ console.print(f"[bold green]✓ Found {len(results)} results[/bold green]\n")
114
+
115
+ # Create results table
116
+ table = Table(
117
+ title=f"Search Results for: '{query}'",
118
+ show_header=True,
119
+ header_style="bold magenta",
120
+ border_style="blue",
121
+ show_lines=True
122
+ )
123
+
124
+ table.add_column("#", width=3, style="dim")
125
+ table.add_column("File", style="cyan")
126
+ table.add_column("Type", width=10)
127
+ table.add_column("Name", style="bold")
128
+ table.add_column("Score", width=8, justify="right")
129
+ table.add_column("Lines", width=10, justify="center")
130
+
131
+ for i, result in enumerate(results, 1):
132
+ # Determine score color
133
+ score = result.get('rank_score', result.get('similarity', 0.0))
134
+ if score > 0.8:
135
+ score_style = "bold green"
136
+ elif score > 0.6:
137
+ score_style = "yellow"
138
+ else:
139
+ score_style = "dim"
140
+
141
+ table.add_row(
142
+ str(i),
143
+ result['path'],
144
+ result.get('type', 'file'),
145
+ result.get('name', '-'),
146
+ f"[{score_style}]{score:.2f}[/{score_style}]",
147
+ f"{result.get('start_line', 0)}-{result.get('end_line', 0)}"
148
+ )
149
+
150
+ console.print(table)
151
+ console.print()
152
+
153
+ # Show detailed content if requested
154
+ if show_content:
155
+ for i, result in enumerate(results[:3], 1): # Show top 3 in detail
156
+ console.print(f"[bold]Result {i}: {result['path']}[/bold]")
157
+
158
+ # Show code with syntax highlighting
159
+ code = result.get('content', '')
160
+ if code:
161
+ lang = result.get('language', 'text')
162
+ syntax = Syntax(
163
+ code[:300] + ("..." if len(code) > 300 else ""),
164
+ lang if lang in ('python', 'javascript', 'typescript') else 'text',
165
+ theme="monokai",
166
+ line_numbers=True,
167
+ start_line=result.get('start_line', 1)
168
+ )
169
+ console.print(Panel(syntax, border_style="dim"))
170
+ console.print()
171
+
172
+ if len(results) > 3:
173
+ console.print(f"[dim]... and {len(results) - 3} more results[/dim]")
174
+ console.print(f"[dim]Use --no-content to see all results in table only[/dim]\n")
175
+
176
+ # Show search tips
177
+ console.print("[dim]💡 Search Tips:[/dim]")
178
+ console.print("[dim] • Use natural language: 'how to authenticate users'[/dim]")
179
+ console.print("[dim] • Or keywords: 'JWT token validation'[/dim]")
180
+ console.print("[dim] • Filter by type: --type python[/dim]")
181
+ console.print("[dim] • Filter by directory: --dir src/auth[/dim]")
182
+ console.print("[dim] • Increase results: --limit 20[/dim]\n")
@@ -0,0 +1,128 @@
1
+ """Documentation server command.
2
+
3
+ This module provides a simple HTTP server for viewing generated
4
+ documentation locally.
5
+ """
6
+
7
+ from pathlib import Path
8
+ import http.server
9
+ import socketserver
10
+ import webbrowser
11
+ import typer
12
+ from rich.console import Console
13
+ from rich.panel import Panel
14
+
15
+ console = Console()
16
+
17
+ # Create Typer app
18
+ serve_docs_app = typer.Typer(
19
+ name="serve-docs",
20
+ help="Serve documentation locally with HTTP server",
21
+ no_args_is_help=True
22
+ )
23
+
24
+
25
+ @serve_docs_app.command()
26
+ def serve_command(
27
+ docs_dir: Path = typer.Option(
28
+ "./docs/generated",
29
+ "--dir",
30
+ "-d",
31
+ help="Documentation directory to serve"
32
+ ),
33
+ port: int = typer.Option(
34
+ 8000,
35
+ "--port",
36
+ "-p",
37
+ help="Server port"
38
+ ),
39
+ open_browser: bool = typer.Option(
40
+ True,
41
+ "--open/--no-open",
42
+ help="Open browser automatically"
43
+ ),
44
+ ):
45
+ """
46
+ Serve documentation locally via HTTP.
47
+
48
+ This command starts a simple HTTP server to view the generated
49
+ documentation in a web browser. Markdown files will be served
50
+ as plain text (for rendering, use a static site generator).
51
+
52
+ Examples:
53
+ assistant serve-docs
54
+ assistant serve-docs --port 3000
55
+ assistant serve-docs --dir ./my-docs --no-open
56
+ """
57
+ if not docs_dir.exists():
58
+ console.print(f"[red]Error:[/red] Documentation directory not found: {docs_dir}")
59
+ console.print(f"[dim]Run:[/dim] [cyan]assistant document generate[/cyan] first")
60
+ raise typer.Exit(1)
61
+
62
+ # Change to docs directory
63
+ import os
64
+ original_dir = os.getcwd()
65
+ os.chdir(docs_dir)
66
+
67
+ try:
68
+ # Create HTTP server
69
+ handler = http.server.SimpleHTTPRequestHandler
70
+
71
+ # Custom handler to add basic routing
72
+ class DocHandler(handler):
73
+ def do_GET(self):
74
+ # Redirect root to README.md
75
+ if self.path == '/':
76
+ self.path = '/README.md'
77
+ return super().do_GET()
78
+
79
+ def log_message(self, format, *args):
80
+ # Custom logging to console
81
+ console.print(f"[dim]{args[0]}[/dim] - {args[1]} - {args[2]}")
82
+
83
+ with socketserver.TCPServer(("", port), DocHandler) as httpd:
84
+ url = f"http://localhost:{port}"
85
+
86
+ console.print(Panel.fit(
87
+ f"[bold cyan]Documentation Server Running[/bold cyan]\\n"
88
+ f"URL: {url}\\n"
89
+ f"Directory: {docs_dir.resolve()}",
90
+ border_style="cyan"
91
+ ))
92
+
93
+ console.print("")
94
+ console.print("[green]✓[/green] Server started successfully")
95
+ console.print(f"[cyan]→[/cyan] Visit {url} in your browser")
96
+ console.print("")
97
+ console.print("[dim]Press Ctrl+C to stop the server[/dim]")
98
+ console.print("")
99
+
100
+ # Open browser if requested
101
+ if open_browser:
102
+ try:
103
+ webbrowser.open(url)
104
+ console.print("[green]✓[/green] Browser opened")
105
+ except Exception:
106
+ pass
107
+
108
+ # Serve forever
109
+ httpd.serve_forever()
110
+
111
+ except KeyboardInterrupt:
112
+ console.print("\\n\\n[yellow]Server stopped[/yellow]")
113
+
114
+ except OSError as e:
115
+ if "Address already in use" in str(e):
116
+ console.print(f"[red]Error:[/red] Port {port} is already in use")
117
+ console.print(f"[dim]Try a different port:[/dim] [cyan]--port {port + 1}[/cyan]")
118
+ else:
119
+ console.print(f"[red]Error:[/red] {e}")
120
+ raise typer.Exit(1)
121
+
122
+ finally:
123
+ # Change back to original directory
124
+ os.chdir(original_dir)
125
+
126
+
127
+ # Export the Typer app
128
+ __all__ = ['serve_docs_app']