use std::io::{self, Write};
use std::process;
use std::sync::Arc;
use anyhow::Result;
use clap::{Parser, Subcommand, Args};
use crate::config::Config;
use crate::agent::FileOperationAgent;
use crate::tool_registry::ToolRegistry;
use crate::prompt_registry::PromptRegistry;
use crate::env_config::*;
#[derive(Parser)]
#[command(name = "alou")]
#[command(version = "0.1.0")]
#[command(about = "智能文件操作助手 - 基于Rust的交互式CLI代理")]
#[command(long_about = "Alou是一个智能文件操作助手,专注于软件工程任务。它可以帮助你进行文件操作、代码分析、项目重构等各种开发任务。")]
pub struct Cli {
#[arg(short, long)]
pub command: Option<String>,
#[arg(short, long)]
pub interactive: bool,
#[arg(long)]
pub list_tools: bool,
#[arg(long)]
pub list_prompts: bool,
#[arg(long)]
pub show_config: bool,
#[arg(long)]
pub config: Option<String>,
#[arg(long)]
pub workspace: Option<String>,
#[arg(long)]
pub session: Option<String>,
#[arg(long)]
pub model: Option<String>,
#[arg(long)]
pub max_tokens: Option<usize>,
#[arg(long)]
pub temperature: Option<f64>,
#[arg(long)]
pub debug: bool,
#[arg(long)]
pub sandbox: bool,
#[arg(long)]
pub sandbox_dir: Option<String>,
#[command(subcommand)]
pub subcommand: Option<Commands>,
}
#[derive(Subcommand)]
pub enum Commands {
Chat(ChatArgs),
Run(RunArgs),
Tools(ToolsArgs),
Config(ConfigArgs),
MCP(MCPArgs),
Info(HelpArgs),
}
#[derive(Args)]
pub struct ChatArgs {
#[arg(long)]
pub system_prompt: Option<String>,
#[arg(long)]
pub user_memory: Option<String>,
#[arg(long, default_value = "100")]
pub max_rounds: usize,
#[arg(long)]
pub auto_save: bool,
}
#[derive(Args)]
pub struct RunArgs {
pub command: String,
pub args: Vec<String>,
#[arg(long)]
pub work_dir: Option<String>,
#[arg(long, default_value = "30")]
pub timeout: u64,
#[arg(long, default_value = "3")]
pub retries: usize,
}
#[derive(Args)]
pub struct ToolsArgs {
#[command(subcommand)]
pub subcommand: ToolsSubcommand,
}
#[derive(Subcommand)]
pub enum ToolsSubcommand {
List,
Show { name: String },
Test { name: String },
Enable { name: String },
Disable { name: String },
}
#[derive(Args)]
pub struct ConfigArgs {
#[command(subcommand)]
pub subcommand: ConfigSubcommand,
}
#[derive(Subcommand)]
pub enum ConfigSubcommand {
Show,
Set { key: String, value: String },
Reset,
Validate,
Export { path: String },
Import { path: String },
}
#[derive(Args)]
pub struct MCPArgs {
#[command(subcommand)]
pub subcommand: MCPSubcommand,
}
#[derive(Subcommand)]
pub enum MCPSubcommand {
Discover,
Test { server: String },
Export { output: String },
Auto { output: String },
}
#[derive(Args)]
pub struct HelpArgs {
pub topic: Option<String>,
}
pub struct CliApp {
config: Config,
agent: FileOperationAgent,
tool_registry: ToolRegistry,
prompt_registry: PromptRegistry,
}
impl CliApp {
pub async fn new() -> Result<Self> {
init_env_config()?;
let config = Config::new()?;
let tool_registry = ToolRegistry::new();
let prompt_registry = PromptRegistry::new();
let agent = FileOperationAgent::new_initialized(
config.clone(),
Arc::new(tool_registry.clone()),
Arc::new(prompt_registry.clone())
).await?;
Ok(CliApp {
config,
agent,
tool_registry,
prompt_registry,
})
}
pub async fn run(&mut self, cli: Cli) -> Result<()> {
self.handle_global_options(&cli)?;
if let Some(subcommand) = cli.subcommand {
self.handle_subcommand(subcommand).await?;
} else if cli.interactive {
self.run_interactive_mode().await?;
} else if let Some(command) = cli.command {
self.run_single_command(&command).await?;
} else if cli.list_tools {
self.list_tools().await?;
} else if cli.list_prompts {
self.list_prompts()?;
} else if cli.show_config {
self.show_config()?;
} else {
self.run_interactive_mode().await?;
}
Ok(())
}
fn handle_global_options(&mut self, cli: &Cli) -> Result<()> {
if cli.debug {
std::env::set_var("DEBUG", "true");
std::env::set_var("RUST_LOG", "debug");
}
if let Some(workspace) = &cli.workspace {
self.config.workspace_root = std::path::PathBuf::from(workspace);
}
if let Some(session) = &cli.session {
self.config.session_id = session.clone();
}
if let Some(model) = &cli.model {
self.config.update_default_model(model.clone());
}
if let Some(max_tokens) = cli.max_tokens {
self.config.update_max_tokens(max_tokens);
}
if let Some(temperature) = cli.temperature {
self.config.update_temperature(temperature);
}
if cli.sandbox {
let sandbox_dir = cli.sandbox_dir.as_ref()
.map(|s| std::path::PathBuf::from(s));
self.config.update_sandbox(true, sandbox_dir);
}
if let Some(config_path) = &cli.config {
let file_config = Config::from_file(config_path)?;
self.config = file_config;
}
Ok(())
}
async fn handle_subcommand(&mut self, subcommand: Commands) -> Result<()> {
match subcommand {
Commands::Chat(args) => self.run_chat_mode(args).await?,
Commands::Run(args) => self.run_command(args).await?,
Commands::Tools(args) => self.handle_tools_command(args).await?,
Commands::Config(args) => self.handle_config_command(args)?,
Commands::MCP(args) => self.handle_mcp_command(args).await?,
Commands::Info(args) => self.show_help(args)?,
}
Ok(())
}
async fn run_interactive_mode(&mut self) -> Result<()> {
println!("欢迎使用 Alou - 智能文件操作助手!");
println!("输入 '/help' 查看帮助信息,输入 '/exit' 退出程序。");
println!();
let mut round_count = 0;
const MAX_ROUNDS: usize = 100;
loop {
if round_count >= MAX_ROUNDS {
println!("已达到最大轮次限制,程序退出。");
break;
}
print!("alou> ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim();
if input.is_empty() {
continue;
}
if input.starts_with('/') {
if self.handle_special_command(input).await? {
break;
}
continue;
}
print!("🤔 思考中");
io::stdout().flush()?;
let agent = self.agent.clone();
let input_clone = input.to_string();
let handle = tokio::spawn(async move {
agent.process_request(&input_clone).await
});
let loading_chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
let mut i = 0;
while !handle.is_finished() {
print!("\r🤔 思考中{} ", loading_chars[i % loading_chars.len()]);
io::stdout().flush().unwrap();
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
i += 1;
}
match handle.await? {
Ok(response) => {
print!("\r✅ 完成!\n");
if let Some(content) = response.content {
println!("{}", content);
}
if let Some(memory_updates) = response.memory_updates {
println!("💾 已保存到记忆: {:?}", memory_updates);
}
}
Err(e) => {
print!("\r❌ 错误!\n");
eprintln!("处理请求时出错: {}", e);
}
}
round_count += 1;
}
Ok(())
}
async fn run_chat_mode(&mut self, args: ChatArgs) -> Result<()> {
println!("进入聊天模式...");
if let Some(_system_prompt) = args.system_prompt {
println!("使用自定义系统提示");
}
if let Some(_user_memory) = args.user_memory {
println!("加载用户记忆");
}
let mut round_count = 0;
loop {
if round_count >= args.max_rounds {
println!("已达到最大轮次限制,程序退出。");
break;
}
print!("chat> ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim();
if input.is_empty() {
continue;
}
if input == "/exit" {
break;
}
print!("🤔 思考中");
io::stdout().flush()?;
let agent = self.agent.clone();
let input_clone = input.to_string();
let handle = tokio::spawn(async move {
agent.process_request(&input_clone).await
});
let loading_chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
let mut i = 0;
while !handle.is_finished() {
print!("\r🤔 思考中{} ", loading_chars[i % loading_chars.len()]);
io::stdout().flush().unwrap();
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
i += 1;
}
match handle.await? {
Ok(response) => {
print!("\r✅ 完成!\n");
if let Some(content) = response.content {
println!("{}", content);
}
}
Err(e) => {
print!("\r❌ 错误!\n");
eprintln!("处理请求时出错: {}", e);
}
}
round_count += 1;
}
Ok(())
}
async fn run_single_command(&mut self, command: &str) -> Result<()> {
println!("执行命令: {}", command);
match self.agent.process_request(command).await {
Ok(response) => {
if let Some(content) = response.content {
println!("{}", content);
}
}
Err(e) => {
eprintln!("错误: {}", e);
process::exit(1);
}
}
Ok(())
}
async fn run_command(&mut self, args: RunArgs) -> Result<()> {
let full_command = format!("{} {}", args.command, args.args.join(" "));
println!("执行命令: {}", full_command);
match self.agent.process_request(&full_command).await {
Ok(response) => {
if let Some(content) = response.content {
println!("{}", content);
}
}
Err(e) => {
eprintln!("错误: {}", e);
process::exit(1);
}
}
Ok(())
}
async fn handle_tools_command(&mut self, args: ToolsArgs) -> Result<()> {
match args.subcommand {
ToolsSubcommand::List => self.list_tools().await?,
ToolsSubcommand::Show { name } => self.show_tool(&name).await?,
ToolsSubcommand::Test { name } => self.test_tool(&name)?,
ToolsSubcommand::Enable { name } => self.enable_tool(&name)?,
ToolsSubcommand::Disable { name } => self.disable_tool(&name)?,
}
Ok(())
}
fn handle_config_command(&mut self, args: ConfigArgs) -> Result<()> {
match args.subcommand {
ConfigSubcommand::Show => self.show_config()?,
ConfigSubcommand::Set { key, value } => self.set_config(&key, &value)?,
ConfigSubcommand::Reset => self.reset_config()?,
ConfigSubcommand::Validate => self.validate_config()?,
ConfigSubcommand::Export { path } => self.export_config(&path)?,
ConfigSubcommand::Import { path } => self.import_config(&path)?,
}
Ok(())
}
async fn handle_mcp_command(&mut self, args: MCPArgs) -> Result<()> {
let mcp_tool_registry = ToolRegistry::new();
match args.subcommand {
MCPSubcommand::Discover => self.discover_mcp_servers_with_registry(&mcp_tool_registry).await?,
MCPSubcommand::Test { server } => self.test_mcp_server_with_registry(&mcp_tool_registry, &server).await?,
MCPSubcommand::Export { output } => self.export_mcp_config_with_registry(&mcp_tool_registry, &output).await?,
MCPSubcommand::Auto { output } => self.auto_discover_and_export_with_registry(&mcp_tool_registry, &output).await?,
}
Ok(())
}
fn show_help(&self, args: HelpArgs) -> Result<()> {
if let Some(topic) = args.topic {
self.show_topic_help(&topic)?;
} else {
self.show_general_help()?;
}
Ok(())
}
async fn handle_special_command(&mut self, command: &str) -> Result<bool> {
match command {
"/help" => {
self.show_general_help()?;
Ok(false)
}
"/exit" | "/quit" => {
println!("再见!");
Ok(true)
}
"/tools" => {
println!("工具列表功能暂时不可用");
Ok(false)
}
"/prompts" => {
self.list_prompts()?;
Ok(false)
}
"/config" => {
self.show_config()?;
Ok(false)
}
"/clear" => {
print!("\x1B[2J\x1B[1;1H");
io::stdout().flush()?;
Ok(false)
}
"/status" => {
self.show_status().await?;
Ok(false)
}
_ => {
println!("未知命令: {}", command);
println!("输入 '/help' 查看可用命令");
Ok(false)
}
}
}
async fn list_tools(&self) -> Result<()> {
println!("可用的工具:");
let tools = self.tool_registry.get_all_tools().await;
for tool in tools {
println!(" - {}: {}", tool.name(), tool.description());
}
Ok(())
}
fn list_prompts(&self) -> Result<()> {
println!("可用的提示:");
let prompts = self.prompt_registry.get_all_prompts();
for prompt in prompts {
println!(" - {}: {:?}", prompt.name, prompt.description);
}
Ok(())
}
fn show_config(&self) -> Result<()> {
println!("当前配置:");
let summary = self.config.get_summary();
for (key, value) in summary {
println!(" {}: {}", key, value);
}
Ok(())
}
async fn show_tool(&self, name: &str) -> Result<()> {
if let Some(tool) = self.tool_registry.get_tool(name).await {
println!("工具: {}", tool.name());
println!("描述: {}", tool.description());
println!("参数: {:?}", tool.parameter_schema());
} else {
println!("未找到工具: {}", name);
}
Ok(())
}
fn test_tool(&self, name: &str) -> Result<()> {
println!("测试工具: {}", name);
Ok(())
}
fn enable_tool(&mut self, name: &str) -> Result<()> {
println!("启用工具: {}", name);
Ok(())
}
fn disable_tool(&mut self, name: &str) -> Result<()> {
println!("禁用工具: {}", name);
Ok(())
}
fn set_config(&mut self, key: &str, value: &str) -> Result<()> {
match key {
"model" => self.config.update_default_model(value.to_string()),
"max_tokens" => {
if let Ok(tokens) = value.parse::<usize>() {
self.config.update_max_tokens(tokens);
} else {
return Err(anyhow::anyhow!("无效的最大令牌数: {}", value));
}
}
"temperature" => {
if let Ok(temp) = value.parse::<f64>() {
self.config.update_temperature(temp);
} else {
return Err(anyhow::anyhow!("无效的温度值: {}", value));
}
}
_ => return Err(anyhow::anyhow!("未知的配置项: {}", key)),
}
println!("已设置 {} = {}", key, value);
Ok(())
}
fn reset_config(&mut self) -> Result<()> {
self.config = Config::new()?;
println!("配置已重置");
Ok(())
}
fn validate_config(&self) -> Result<()> {
match self.config.validate() {
Ok(_) => {
println!("配置验证通过");
Ok(())
}
Err(e) => {
eprintln!("配置验证失败: {}", e);
Err(e)
}
}
}
fn export_config(&self, path: &str) -> Result<()> {
self.config.save_to_file(path)?;
println!("配置已导出到: {}", path);
Ok(())
}
fn import_config(&mut self, path: &str) -> Result<()> {
self.config = Config::from_file(path)?;
println!("配置已从 {} 导入", path);
Ok(())
}
async fn show_status(&self) -> Result<()> {
println!("Alou 状态:");
println!(" 会话ID: {}", self.config.session_id);
println!(" 工作区: {}", self.config.workspace_root.display());
println!(" 模型: {}", self.config.get_default_model());
println!(" 工具数量: {}", self.tool_registry.get_all_tools().await.len());
println!(" 提示数量: {}", self.prompt_registry.get_all_prompts().len());
Ok(())
}
fn show_general_help(&self) -> Result<()> {
println!("Alou - 智能文件操作助手");
println!();
println!("可用命令:");
println!(" /help - 显示此帮助信息");
println!(" /exit, /quit - 退出程序");
println!(" /tools - 列出所有可用工具");
println!(" /prompts - 列出所有可用提示");
println!(" /config - 显示当前配置");
println!(" /clear - 清屏");
println!(" /status - 显示状态信息");
println!();
println!("使用示例:");
println!(" alou --interactive # 进入交互模式");
println!(" alou --command \"分析这个项目\" # 执行单个命令");
println!(" alou --list-tools # 列出所有工具");
println!(" alou --show-config # 显示配置");
println!(" alou chat --max-rounds 50 # 聊天模式,最多50轮");
println!(" alou run \"ls -la\" # 执行命令");
println!(" alou tools list # 列出工具");
println!(" alou config show # 显示配置");
Ok(())
}
fn show_topic_help(&self, topic: &str) -> Result<()> {
match topic {
"tools" => {
println!("工具帮助:");
println!(" tools list - 列出所有工具");
println!(" tools show <name> - 显示工具详情");
println!(" tools test <name> - 测试工具");
println!(" tools enable <name> - 启用工具");
println!(" tools disable <name> - 禁用工具");
}
"config" => {
println!("配置帮助:");
println!(" config show - 显示当前配置");
println!(" config set <key> <value> - 设置配置项");
println!(" config reset - 重置配置");
println!(" config validate - 验证配置");
println!(" config export <path> - 导出配置");
println!(" config import <path> - 导入配置");
}
"mcp" => {
println!("MCP管理帮助:");
println!(" mcp discover - 发现可用的MCP服务器");
println!(" mcp test <server> - 测试MCP服务器连接");
println!(" mcp export <path> - 导出MCP配置文件");
println!(" mcp auto <path> - 自动发现并导出配置");
}
"chat" => {
println!("聊天模式帮助:");
println!(" chat --system-prompt <prompt> - 设置系统提示");
println!(" chat --user-memory <memory> - 设置用户记忆");
println!(" chat --max-rounds <number> - 设置最大轮次");
println!(" chat --auto-save - 启用自动保存");
}
_ => {
println!("未知主题: {}", topic);
println!("可用主题: tools, config, mcp, chat");
}
}
Ok(())
}
async fn discover_mcp_servers(&self) -> Result<()> {
let configs = self.tool_registry.discover_npm_mcp_servers().await?;
if configs.is_empty() {
println!("❌ 未发现任何可用的MCP服务器");
println!("💡 提示: 确保已安装Node.js和npm,并且网络连接正常");
} else {
println!("🎉 发现 {} 个可用的MCP服务器:", configs.len());
for config in configs {
if let Some(command) = &config.command {
println!(" ✅ {} - {}", "server", command);
}
}
}
Ok(())
}
async fn discover_mcp_servers_with_registry(&self, tool_registry: &ToolRegistry) -> Result<()> {
let configs = tool_registry.discover_npm_mcp_servers().await?;
if configs.is_empty() {
println!("❌ 未发现任何可用的MCP服务器");
println!("💡 提示: 确保已安装Node.js和npm,并且网络连接正常");
} else {
println!("🎉 发现 {} 个可用的MCP服务器:", configs.len());
for config in configs {
if let Some(command) = &config.command {
println!(" ✅ {} - {}", "server", command);
}
}
}
Ok(())
}
async fn test_mcp_server(&self, server: &str) -> Result<()> {
let configs = self.tool_registry.discover_npm_mcp_servers().await?;
let found_config = configs.iter().find(|config| {
config.command.as_ref().map(|cmd| cmd.contains(server)).unwrap_or(false) ||
config.args.as_ref().map(|args| args.contains(&server.to_string())).unwrap_or(false)
});
match found_config {
Some(config) => {
println!("✅ 服务器测试成功: {}", server);
if let Some(command) = &config.command {
println!(" 命令: {}", command);
}
if let Some(args) = &config.args {
println!(" 参数: {:?}", args);
}
}
None => {
println!("❌ 服务器测试失败: 未找到服务器 {}", server);
println!("💡 提示: 检查服务器名称是否正确,或尝试使用完整包名");
}
}
Ok(())
}
async fn test_mcp_server_with_registry(&self, tool_registry: &ToolRegistry, server: &str) -> Result<()> {
let configs = tool_registry.discover_npm_mcp_servers().await?;
let found_config = configs.iter().find(|config| {
config.command.as_ref().map(|cmd| cmd.contains(server)).unwrap_or(false) ||
config.args.as_ref().map(|args| args.contains(&server.to_string())).unwrap_or(false)
});
match found_config {
Some(config) => {
println!("✅ 服务器测试成功: {}", server);
if let Some(command) = &config.command {
println!(" 命令: {}", command);
}
if let Some(args) = &config.args {
println!(" 参数: {:?}", args);
}
}
None => {
println!("❌ 服务器测试失败: 未找到服务器 {}", server);
println!("💡 提示: 检查服务器名称是否正确,或尝试使用完整包名");
}
}
Ok(())
}
async fn export_mcp_config(&self, output: &str) -> Result<()> {
let configs = self.tool_registry.discover_npm_mcp_servers().await?;
if configs.is_empty() {
println!("❌ 没有可用的MCP服务器配置可导出");
return Ok(());
}
self.tool_registry.export_mcp_config(configs.clone(), output)?;
println!("✅ 已导出 {} 个MCP服务器配置到: {}", configs.len(), output);
Ok(())
}
async fn export_mcp_config_with_registry(&self, tool_registry: &ToolRegistry, output: &str) -> Result<()> {
let configs = tool_registry.discover_npm_mcp_servers().await?;
if configs.is_empty() {
println!("❌ 没有可用的MCP服务器配置可导出");
return Ok(());
}
tool_registry.export_mcp_config(configs.clone(), output)?;
println!("✅ 已导出 {} 个MCP服务器配置到: {}", configs.len(), output);
Ok(())
}
async fn auto_discover_and_export(&self, output: &str) -> Result<()> {
self.tool_registry.auto_discover_and_export(output).await
}
async fn auto_discover_and_export_with_registry(&self, tool_registry: &ToolRegistry, output: &str) -> Result<()> {
tool_registry.auto_discover_and_export(output).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cli_parsing() {
let cli = Cli::try_parse_from(&["alou", "--interactive"]);
assert!(cli.is_ok());
let cli = cli.unwrap();
assert!(cli.interactive);
}
#[test]
fn test_cli_commands() {
let cli = Cli::try_parse_from(&["alou", "chat", "--max-rounds", "10"]);
assert!(cli.is_ok());
let cli = cli.unwrap();
assert!(matches!(cli.subcommand, Some(Commands::Chat(_))));
}
#[test]
fn test_cli_tools() {
let cli = Cli::try_parse_from(&["alou", "tools", "list"]);
assert!(cli.is_ok());
let cli = cli.unwrap();
assert!(matches!(cli.subcommand, Some(Commands::Tools(_))));
}
#[test]
fn test_cli_config() {
let cli = Cli::try_parse_from(&["alou", "config", "show"]);
assert!(cli.is_ok());
let cli = cli.unwrap();
assert!(matches!(cli.subcommand, Some(Commands::Config(_))));
}
}