Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions cli/CLI_COMMANDS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# DeepCode CLI Commands

## Overview

The DeepCode CLI has been restructured using Click to support multiple subcommands. This allows for better organization and extensibility.

## Available Commands

### Main Command Group

```bash
python cli/cli_app.py --help
```

Shows all available commands and options.

### Run Interactive Session (Default)

```bash
# All of these are equivalent:
python cli/cli_app.py
python cli/cli_app.py run
```

Launches the interactive DeepCode CLI session where you can:
- Process research papers from URLs
- Upload and process local files
- Chat with the AI to generate code
- View processing history
- Configure settings

### Configuration Management

```bash
python cli/cli_app.py config
```

Shows configuration options (placeholder for future implementation).

**Planned features:**
- View current configuration
- Set default processing mode (comprehensive/optimized)
- Configure API keys and endpoints
- Manage workspace settings

### Cleanup Utility

```bash
# Clean Python cache files
python cli/cli_app.py clean --cache

# Clean log files
python cli/cli_app.py clean --logs

# Clean everything
python cli/cli_app.py clean --all
```

Removes temporary files and caches to free up disk space.

## Adding New Subcommands

To add a new subcommand, simply add a new function decorated with `@cli.command()` in `cli/cli_app.py`:

```python
@cli.command()
@click.option('--option-name', help='Description')
def my_command(option_name):
"""Description of what this command does"""
# Your implementation here
click.echo("Command executed!")
```

## Examples

### Basic Usage

```bash
# Start interactive session
python cli/cli_app.py

# Or explicitly use the run command
python cli/cli_app.py run
```

### Cleanup Examples

```bash
# Clean only cache files
python cli/cli_app.py clean --cache

# Clean only logs
python cli/cli_app.py clean --logs

# Clean everything
python cli/cli_app.py clean --all
```

### Getting Help

```bash
# General help
python cli/cli_app.py --help

# Help for specific command
python cli/cli_app.py clean --help
python cli/cli_app.py config --help
```

## Version Information

```bash
python cli/cli_app.py --version
```

## Migration Notes

### For Developers

The previous `main()` coroutine has been renamed to `run_interactive_cli()` and is now wrapped by Click commands:

- **Old:** `asyncio.run(main())`
- **New:** `cli()` (Click group) → `run()` command → `run_interactive_cli()`

### Backward Compatibility

The CLI launcher (`cli_launcher.py`) has been updated to use the new Click-based structure, maintaining backward compatibility with existing workflows.

## Future Enhancements

Potential subcommands to add:
- `deepcode process --file <path>` - Direct file processing
- `deepcode process --url <url>` - Direct URL processing
- `deepcode history` - View processing history
- `deepcode export` - Export results
- `deepcode doctor` - Check system dependencies and configuration

4 changes: 2 additions & 2 deletions cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
__version__ = "1.0.0"
__author__ = "DeepCode Team - Data Intelligence Lab @ HKU"

from .cli_app import main as cli_main
from .cli_app import cli, run_interactive_cli
from .cli_interface import CLIInterface
from .cli_launcher import main as launcher_main

__all__ = ["cli_main", "CLIInterface", "launcher_main"]
__all__ = ["cli", "run_interactive_cli", "CLIInterface", "launcher_main"]
98 changes: 85 additions & 13 deletions cli/cli_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,23 @@
sys.path.insert(0, parent_dir)

# 导入MCP应用和工作流

from cli.workflows import CLIWorkflowAdapter
from cli.cli_interface import CLIInterface, Colors


class CLIApp:
"""CLI应用主类 - 升级版智能体编排引擎"""

def __init__(self):
def __init__(self) -> None:
self.cli = CLIInterface()
self.workflow_adapter = CLIWorkflowAdapter(cli_interface=self.cli)
self.app = None # Will be initialized by workflow adapter
self.logger = None
self.context = None
# Context for storing last run metadata (input_source, input_type, error_flag)
# 同时用于 /retry-last 聊天命令
self.context = {"last_input": None}
# Document segmentation will be managed by CLI interface
self._interrupt_handled = False # Track if KeyboardInterrupt was already handled

async def initialize_mcp_app(self):
"""初始化MCP应用 - 使用工作流适配器"""
Expand All @@ -49,7 +51,12 @@ async def cleanup_mcp_app(self):
await self.workflow_adapter.cleanup_mcp_app()

async def process_input(self, input_source: str, input_type: str):
"""处理输入源(URL或文件)- 使用升级版智能体编排引擎"""
"""处理输入源(URL或文件/聊天)- 使用升级版智能体编排引擎

同时在 ``self.context["last_input"]`` 中记录最近一次运行的
``(input_source, input_type, error_flag)`` 信息,供 /retry-last 使用。
"""

try:
# Document segmentation configuration is managed by CLI interface

Expand All @@ -68,7 +75,10 @@ async def process_input(self, input_source: str, input_type: str):
enable_indexing=self.cli.enable_indexing,
)

if result["status"] == "success":
# 标记本次运行是否出错
error_flag = result.get("status") != "success"

if not error_flag:
# 显示完成状态
final_stage = 8 if self.cli.enable_indexing else 5
self.cli.display_processing_stages(
Expand All @@ -94,9 +104,18 @@ async def process_input(self, input_source: str, input_type: str):
# 添加到历史记录
self.cli.add_to_history(input_source, result)

# 在上下文中记录最近一次运行的输入信息
if self.context is None or not isinstance(self.context, dict):
self.context = {"last_input": None}
self.context["last_input"] = {
"input_source": input_source,
"input_type": input_type,
"error": error_flag,
}

return result

except Exception as e:
except Exception as e: # noqa: BLE001
error_msg = str(e)
self.cli.print_error_box("Agent Orchestration Error", error_msg)
self.cli.print_status(f"Error during orchestration: {error_msg}", "error")
Expand All @@ -105,6 +124,15 @@ async def process_input(self, input_source: str, input_type: str):
error_result = {"status": "error", "error": error_msg}
self.cli.add_to_history(input_source, error_result)

# 在上下文中记录最近一次失败运行的信息
if self.context is None or not isinstance(self.context, dict):
self.context = {"last_input": None}
self.context["last_input"] = {
"input_source": input_source,
"input_type": input_type,
"error": True,
}

return error_result

def display_results(
Expand Down Expand Up @@ -143,7 +171,7 @@ def display_results(
if len(analysis_result) > 1000
else analysis_result
)
except Exception:
except Exception: # noqa: BLE001
print(
analysis_result[:1000] + "..."
if len(analysis_result) > 1000
Expand Down Expand Up @@ -233,8 +261,43 @@ async def run_interactive_session(self):

elif choice in ["t", "chat", "text"]:
chat_input = self.cli.get_chat_input()
if chat_input:
await self.process_input(chat_input, "chat")
if not chat_input:
# 用户取消或未提供输入
continue

# 处理聊天命令(以 "/" 开头)
if chat_input.strip() == "/retry-last":
last = None
if isinstance(self.context, dict):
last = self.context.get("last_input")

if not last:
self.cli.print_status(
"No previous run available to retry.", "warning"
)
elif not last.get("error"):
self.cli.print_status(
"Last run was successful; nothing to retry.", "info"
)
else:
source = last.get("input_source")
input_type = last.get("input_type", "chat")
if not source:
self.cli.print_status(
"Previous failed run has no input source to retry.",
"error",
)
else:
self.cli.print_status(
"Retrying last failed input...", "processing"
)
await self.process_input(source, input_type)

# 处理完命令后继续主循环
continue

# 普通聊天输入 - 直接作为 chat 类型处理
await self.process_input(chat_input, "chat")

elif choice in ["h", "history"]:
self.cli.show_history()
Expand All @@ -255,8 +318,12 @@ async def run_interactive_session(self):
self.cli.print_status("Session ended by user", "info")

except KeyboardInterrupt:
print(f"\n{Colors.WARNING}⚠️ Process interrupted by user{Colors.ENDC}")
except Exception as e:
if not self._interrupt_handled:
self._interrupt_handled = True
print(
f"\n{Colors.WARNING}⚠️ Process interrupted by user{Colors.ENDC}"
)
except Exception as e: # noqa: BLE001
print(f"\n{Colors.FAIL}❌ Unexpected error: {str(e)}{Colors.ENDC}")
finally:
# 清理资源
Expand All @@ -266,15 +333,20 @@ async def run_interactive_session(self):
async def main():
"""主函数"""
start_time = time.time()
app = None

try:
# 创建并运行CLI应用
app = CLIApp()
await app.run_interactive_session()

except KeyboardInterrupt:
print(f"\n{Colors.WARNING}⚠️ Application interrupted by user{Colors.ENDC}")
except Exception as e:
# Only print if not already handled by run_interactive_session
if app is None or not app._interrupt_handled:
print(
f"\n{Colors.WARNING}⚠️ Application interrupted by user{Colors.ENDC}"
)
except Exception as e: # noqa: BLE001
print(f"\n{Colors.FAIL}❌ Application error: {str(e)}{Colors.ENDC}")
finally:
end_time = time.time()
Expand Down
8 changes: 3 additions & 5 deletions cli/cli_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,12 @@ def main():
# 导入并运行CLI应用
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root)) # 添加项目根目录到路径
from cli.cli_app import main as cli_main
from cli.cli_app import cli

print("\n🎯 Launching CLI application...")

# 使用asyncio运行主函数
import asyncio

asyncio.run(cli_main())
# 使用Click CLI运行主函数 (默认运行 'run' 命令)
cli(["run"])

except KeyboardInterrupt:
print("\n\n🛑 DeepCode CLI stopped by user")
Expand Down
Loading