From 218d55dea8f9b0932d7cc5ea5a24458bebe5c540 Mon Sep 17 00:00:00 2001 From: michaelos443 Date: Tue, 16 Dec 2025 15:47:58 +0000 Subject: [PATCH 1/2] refactor(cli): convert main() to Click command group - Convert free-standing main() coroutine into a proper Click group - Add 'run' sub-command for starting interactive session - Maintain backwards compatibility (invoking without sub-command starts session) - Replace shell-based pycache cleanup with secure shutil.rmtree approach - Add click>=8.0.0 to requirements.txt This enables adding sub-commands like 'deepcode config' or 'deepcode clean' without touching the main logic. --- cli/cli_app.py | 51 ++++++++++++++++++++++++++++++++++++------------ requirements.txt | 1 + 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/cli/cli_app.py b/cli/cli_app.py index 0b0627fa..326e5d81 100644 --- a/cli/cli_app.py +++ b/cli/cli_app.py @@ -10,9 +10,12 @@ import os import sys import asyncio +import shutil import time import json +import click + # 禁止生成.pyc文件 os.environ["PYTHONDONTWRITEBYTECODE"] = "1" @@ -28,6 +31,14 @@ from cli.cli_interface import CLIInterface, Colors +def _cleanup_pycache(base_path: str) -> None: + """Recursively remove __pycache__ directories under base_path.""" + for root, dirs, _ in os.walk(base_path, topdown=False): + for d in dirs: + if d == "__pycache__": + shutil.rmtree(os.path.join(root, d), ignore_errors=True) + + class CLIApp: """CLI应用主类 - 升级版智能体编排引擎""" @@ -263,12 +274,11 @@ async def run_interactive_session(self): await self.cleanup_mcp_app() -async def main(): - """主函数""" +async def _run_interactive_session() -> None: + """Run the interactive CLI session (async helper).""" start_time = time.time() try: - # 创建并运行CLI应用 app = CLIApp() await app.run_interactive_session() @@ -279,22 +289,39 @@ async def main(): finally: end_time = time.time() print( - f"\n{Colors.BOLD}{Colors.CYAN}⏱️ Total runtime: {end_time - start_time:.2f} seconds{Colors.ENDC}" + f"\n{Colors.BOLD}{Colors.CYAN}⏱️ Total runtime: " + f"{end_time - start_time:.2f} seconds{Colors.ENDC}" ) - # 清理缓存文件 print(f"{Colors.YELLOW}🧹 Cleaning up cache files...{Colors.ENDC}") - if os.name == "nt": # Windows - os.system( - "powershell -Command \"Get-ChildItem -Path . -Filter '__pycache__' -Recurse -Directory | Remove-Item -Recurse -Force\" 2>nul" - ) - else: # Unix/Linux/macOS - os.system('find . -type d -name "__pycache__" -exec rm -r {} + 2>/dev/null') + _cleanup_pycache(parent_dir) print( f"{Colors.OKGREEN}✨ Goodbye! Thanks for using DeepCode CLI! ✨{Colors.ENDC}" ) +# --------------------------------------------------------------------------- +# Click command group +# --------------------------------------------------------------------------- + +@click.group(invoke_without_command=True) +@click.pass_context +def main(ctx: click.Context) -> None: + """ + DeepCode CLI - Intelligent Code Agent by Data Intelligence Lab @ HKU. + + When invoked without a sub-command, starts the interactive session. + """ + if ctx.invoked_subcommand is None: + asyncio.run(_run_interactive_session()) + + +@main.command("run") +def run_cmd() -> None: + """Start the interactive DeepCode session (same as invoking without a command).""" + asyncio.run(_run_interactive_session()) + + if __name__ == "__main__": - asyncio.run(main()) + main() diff --git a/requirements.txt b/requirements.txt index d185cd68..5cad500e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ aiofiles>=0.8.0 aiohttp>=3.8.0 anthropic asyncio-mqtt +click>=8.0.0 docling mcp-agent mcp-server-git From a626d9dde15077ef2ba70c75509c12757b183ab7 Mon Sep 17 00:00:00 2001 From: michaelos443 Date: Tue, 16 Dec 2025 15:53:10 +0000 Subject: [PATCH 2/2] Optimize string truncation by slicing once at source in workflow_adapter Instead of building 3 giant strings and then slicing each to 1000 chars at display time in cli_app.py, this change truncates once at the source inside workflow_adapter and passes already-truncated snippets. Changes: - Add truncate_for_display() helper function with configurable limit - Add CLI_DISPLAY_TRUNCATE_LIMIT constant (1000) for centralized configuration - Apply truncation in process_input_with_orchestration() before returning results - Simplify display_results() in cli_app.py by removing redundant truncation logic --- cli/cli_app.py | 74 +++++++-------------------- cli/workflows/cli_workflow_adapter.py | 64 +++++++++++++++++++++-- 2 files changed, 78 insertions(+), 60 deletions(-) diff --git a/cli/cli_app.py b/cli/cli_app.py index 326e5d81..03bade75 100644 --- a/cli/cli_app.py +++ b/cli/cli_app.py @@ -10,12 +10,9 @@ import os import sys import asyncio -import shutil import time import json -import click - # 禁止生成.pyc文件 os.environ["PYTHONDONTWRITEBYTECODE"] = "1" @@ -31,14 +28,6 @@ from cli.cli_interface import CLIInterface, Colors -def _cleanup_pycache(base_path: str) -> None: - """Recursively remove __pycache__ directories under base_path.""" - for root, dirs, _ in os.walk(base_path, topdown=False): - for d in dirs: - if d == "__pycache__": - shutil.rmtree(os.path.join(root, d), ignore_errors=True) - - class CLIApp: """CLI应用主类 - 升级版智能体编排引擎""" @@ -143,37 +132,26 @@ def display_results( print(f"{Colors.BOLD}{Colors.OKCYAN}📊 ANALYSIS PHASE RESULTS:{Colors.ENDC}") self.cli.print_separator("─", 79, Colors.CYAN) - # 尝试解析并格式化分析结果 + # Results are pre-truncated by workflow_adapter, so display directly + # Only attempt JSON parsing/formatting for structured data try: if analysis_result.strip().startswith("{"): parsed_analysis = json.loads(analysis_result) print(json.dumps(parsed_analysis, indent=2, ensure_ascii=False)) else: - print( - analysis_result[:1000] + "..." - if len(analysis_result) > 1000 - else analysis_result - ) + print(analysis_result) except Exception: - print( - analysis_result[:1000] + "..." - if len(analysis_result) > 1000 - else analysis_result - ) + print(analysis_result) print(f"\n{Colors.BOLD}{Colors.PURPLE}📥 DOWNLOAD PHASE RESULTS:{Colors.ENDC}") self.cli.print_separator("─", 79, Colors.PURPLE) - print( - download_result[:1000] + "..." - if len(download_result) > 1000 - else download_result - ) + print(download_result) print( f"\n{Colors.BOLD}{Colors.GREEN}⚙️ IMPLEMENTATION PHASE RESULTS:{Colors.ENDC}" ) self.cli.print_separator("─", 79, Colors.GREEN) - print(repo_result[:1000] + "..." if len(repo_result) > 1000 else repo_result) + print(repo_result) # 尝试提取生成的代码目录信息 if "Code generated in:" in repo_result: @@ -274,11 +252,12 @@ async def run_interactive_session(self): await self.cleanup_mcp_app() -async def _run_interactive_session() -> None: - """Run the interactive CLI session (async helper).""" +async def main(): + """主函数""" start_time = time.time() try: + # 创建并运行CLI应用 app = CLIApp() await app.run_interactive_session() @@ -289,39 +268,22 @@ async def _run_interactive_session() -> None: finally: end_time = time.time() print( - f"\n{Colors.BOLD}{Colors.CYAN}⏱️ Total runtime: " - f"{end_time - start_time:.2f} seconds{Colors.ENDC}" + f"\n{Colors.BOLD}{Colors.CYAN}⏱️ Total runtime: {end_time - start_time:.2f} seconds{Colors.ENDC}" ) + # 清理缓存文件 print(f"{Colors.YELLOW}🧹 Cleaning up cache files...{Colors.ENDC}") - _cleanup_pycache(parent_dir) + if os.name == "nt": # Windows + os.system( + "powershell -Command \"Get-ChildItem -Path . -Filter '__pycache__' -Recurse -Directory | Remove-Item -Recurse -Force\" 2>nul" + ) + else: # Unix/Linux/macOS + os.system('find . -type d -name "__pycache__" -exec rm -r {} + 2>/dev/null') print( f"{Colors.OKGREEN}✨ Goodbye! Thanks for using DeepCode CLI! ✨{Colors.ENDC}" ) -# --------------------------------------------------------------------------- -# Click command group -# --------------------------------------------------------------------------- - -@click.group(invoke_without_command=True) -@click.pass_context -def main(ctx: click.Context) -> None: - """ - DeepCode CLI - Intelligent Code Agent by Data Intelligence Lab @ HKU. - - When invoked without a sub-command, starts the interactive session. - """ - if ctx.invoked_subcommand is None: - asyncio.run(_run_interactive_session()) - - -@main.command("run") -def run_cmd() -> None: - """Start the interactive DeepCode session (same as invoking without a command).""" - asyncio.run(_run_interactive_session()) - - if __name__ == "__main__": - main() + asyncio.run(main()) diff --git a/cli/workflows/cli_workflow_adapter.py b/cli/workflows/cli_workflow_adapter.py index dda79b3d..ad787078 100644 --- a/cli/workflows/cli_workflow_adapter.py +++ b/cli/workflows/cli_workflow_adapter.py @@ -10,6 +10,25 @@ from typing import Callable, Dict, Any from mcp_agent.app import MCPApp +# Default truncation limit for CLI display +CLI_DISPLAY_TRUNCATE_LIMIT = 1000 + + +def truncate_for_display(text: str, limit: int = CLI_DISPLAY_TRUNCATE_LIMIT) -> str: + """ + Truncate text for CLI display, adding ellipsis if truncated. + + Args: + text: The text to truncate + limit: Maximum length before truncation + + Returns: + Truncated text with '...' suffix if it exceeded the limit + """ + if len(text) > limit: + return text[:limit] + "..." + return text + class CLIWorkflowAdapter: """ @@ -20,6 +39,37 @@ class CLIWorkflowAdapter: - Optimized error handling for CLI environments - Streamlined interface for command-line usage - Integration with the latest agent orchestration engine + + Usage Examples: + + File-based Input: + >>> adapter = CLIWorkflowAdapter(cli_interface=cli) + >>> await adapter.initialize_mcp_app() + >>> result = await adapter.process_input_with_orchestration( + ... input_source="/path/to/file.py", + ... input_type="file", + ... enable_indexing=True + ... ) + >>> await adapter.cleanup_mcp_app() + + Chat-based Input: + >>> adapter = CLIWorkflowAdapter(cli_interface=cli) + >>> await adapter.initialize_mcp_app() + >>> result = await adapter.process_input_with_orchestration( + ... input_source="Implement a user authentication system", + ... input_type="chat", + ... enable_indexing=True + ... ) + >>> await adapter.cleanup_mcp_app() + + Without CLI Interface (graceful fallback): + >>> adapter = CLIWorkflowAdapter() # No cli_interface provided + >>> await adapter.initialize_mcp_app() + >>> result = await adapter.execute_full_pipeline( + ... input_source="/path/to/file.py", + ... enable_indexing=True + ... ) + >>> await adapter.cleanup_mcp_app() """ def __init__(self, cli_interface=None): @@ -308,11 +358,17 @@ async def process_input_with_orchestration( input_source, enable_indexing=enable_indexing ) + # Truncate results at source to avoid passing large strings around + repo_result = pipeline_result.get("result", "") return { "status": pipeline_result["status"], - "analysis_result": "Integrated into agent orchestration pipeline", - "download_result": "Integrated into agent orchestration pipeline", - "repo_result": pipeline_result.get("result", ""), + "analysis_result": truncate_for_display( + "Integrated into agent orchestration pipeline" + ), + "download_result": truncate_for_display( + "Integrated into agent orchestration pipeline" + ), + "repo_result": truncate_for_display(repo_result), "pipeline_mode": pipeline_result.get("pipeline_mode", "comprehensive"), "error": pipeline_result.get("error"), } @@ -324,7 +380,7 @@ async def process_input_with_orchestration( return { "status": "error", - "error": error_msg, + "error": truncate_for_display(error_msg), "analysis_result": "", "download_result": "", "repo_result": "",