use serde_json::{json, Value};
use std::path::PathBuf;
use tokio::fs;
use tokio::process::Command;
use tracing::{info, warn};
use crate::mcp::protocol::*;
use crate::turbo::TurboMode;
use crate::Result;
pub struct TurboTools {
turbo: Option<std::sync::Arc<TurboMode>>,
}
impl TurboTools {
pub fn new(turbo: Option<std::sync::Arc<TurboMode>>) -> Self {
Self { turbo }
}
pub fn list_tools(&self) -> Vec<Tool> {
vec![
Tool {
name: "file.read".to_string(),
description: "Read file contents".to_string(),
input_schema: json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "File path to read"
}
},
"required": ["path"]
}),
},
Tool {
name: "file.write".to_string(),
description: "Write content to file".to_string(),
input_schema: json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "File path to write"
},
"content": {
"type": "string",
"description": "Content to write"
}
},
"required": ["path", "content"]
}),
},
Tool {
name: "file.edit".to_string(),
description: "Edit file with find/replace".to_string(),
input_schema: json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "File path to edit"
},
"find": {
"type": "string",
"description": "Text to find"
},
"replace": {
"type": "string",
"description": "Text to replace with"
}
},
"required": ["path", "find", "replace"]
}),
},
Tool {
name: "command.execute".to_string(),
description: "Execute shell command".to_string(),
input_schema: json!({
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "Command to execute"
},
"cwd": {
"type": "string",
"description": "Working directory (optional)"
}
},
"required": ["command"]
}),
},
Tool {
name: "turbo.checkpoint".to_string(),
description: "Create a rollback checkpoint".to_string(),
input_schema: json!({
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "Checkpoint description"
}
},
"required": ["message"]
}),
},
]
}
pub async fn handle_tool_call(
&self,
tool_name: &str,
args: Option<Value>,
) -> Result<Value> {
let auto_approve = if let Some(turbo) = &self.turbo {
turbo.is_yolo().await
} else {
false
};
if auto_approve {
info!("🚀 YOLO: Auto-approving {}", tool_name);
} else {
warn!("Tool {} called without YOLO mode", tool_name);
}
match tool_name {
"file.read" => self.handle_file_read(args).await,
"file.write" => self.handle_file_write(args).await,
"file.edit" => self.handle_file_edit(args).await,
"command.execute" => self.handle_command_execute(args).await,
"turbo.checkpoint" => self.handle_turbo_checkpoint(args).await,
_ => Err(crate::ClaudeUtilsError::McpProtocol(
format!("Unknown tool: {}", tool_name)
)),
}
}
async fn handle_file_read(&self, args: Option<Value>) -> Result<Value> {
let args = args.ok_or_else(||
crate::ClaudeUtilsError::McpProtocol("Missing arguments".into())
)?;
let path = args["path"].as_str().ok_or_else(||
crate::ClaudeUtilsError::McpProtocol("Missing path".into())
)?;
let content = fs::read_to_string(path).await?;
Ok(json!({
"content": content,
"path": path,
"turbo": true
}))
}
async fn handle_file_write(&self, args: Option<Value>) -> Result<Value> {
let args = args.ok_or_else(||
crate::ClaudeUtilsError::McpProtocol("Missing arguments".into())
)?;
let path = args["path"].as_str().ok_or_else(||
crate::ClaudeUtilsError::McpProtocol("Missing path".into())
)?;
let content = args["content"].as_str().ok_or_else(||
crate::ClaudeUtilsError::McpProtocol("Missing content".into())
)?;
if let Some(turbo) = &self.turbo {
use crate::turbo::rollback::{Operation, OperationType};
let original_content = fs::read_to_string(path).await.ok();
let op = Operation {
op_type: if original_content.is_some() {
OperationType::FileEdit
} else {
OperationType::FileCreate
},
target: path.to_string(),
backup_path: None,
original_content,
};
turbo.rollback_manager.record_operation(op).await?;
}
fs::write(path, content).await?;
Ok(json!({
"success": true,
"path": path,
"turbo": true
}))
}
async fn handle_file_edit(&self, args: Option<Value>) -> Result<Value> {
let args = args.ok_or_else(||
crate::ClaudeUtilsError::McpProtocol("Missing arguments".into())
)?;
let path = args["path"].as_str().ok_or_else(||
crate::ClaudeUtilsError::McpProtocol("Missing path".into())
)?;
let find = args["find"].as_str().ok_or_else(||
crate::ClaudeUtilsError::McpProtocol("Missing find".into())
)?;
let replace = args["replace"].as_str().ok_or_else(||
crate::ClaudeUtilsError::McpProtocol("Missing replace".into())
)?;
let content = fs::read_to_string(path).await?;
if let Some(turbo) = &self.turbo {
use crate::turbo::rollback::{Operation, OperationType};
let op = Operation {
op_type: OperationType::FileEdit,
target: path.to_string(),
backup_path: None,
original_content: Some(content.clone()),
};
turbo.rollback_manager.record_operation(op).await?;
}
let new_content = content.replace(find, replace);
fs::write(path, &new_content).await?;
Ok(json!({
"success": true,
"path": path,
"replacements": content.matches(find).count(),
"turbo": true
}))
}
async fn handle_command_execute(&self, args: Option<Value>) -> Result<Value> {
let args = args.ok_or_else(||
crate::ClaudeUtilsError::McpProtocol("Missing arguments".into())
)?;
let command = args["command"].as_str().ok_or_else(||
crate::ClaudeUtilsError::McpProtocol("Missing command".into())
)?;
let cwd = args["cwd"].as_str().map(PathBuf::from);
let mut cmd = Command::new("sh");
cmd.arg("-c").arg(command);
if let Some(cwd) = cwd {
cmd.current_dir(cwd);
}
let output = cmd.output().await?;
Ok(json!({
"stdout": String::from_utf8_lossy(&output.stdout),
"stderr": String::from_utf8_lossy(&output.stderr),
"success": output.status.success(),
"code": output.status.code(),
"turbo": true
}))
}
async fn handle_turbo_checkpoint(&self, args: Option<Value>) -> Result<Value> {
let args = args.ok_or_else(||
crate::ClaudeUtilsError::McpProtocol("Missing arguments".into())
)?;
let message = args["message"].as_str().ok_or_else(||
crate::ClaudeUtilsError::McpProtocol("Missing message".into())
)?;
if let Some(turbo) = &self.turbo {
let checkpoint_id = turbo.rollback_manager
.create_checkpoint(message.to_string())
.await?;
Ok(json!({
"checkpoint_id": checkpoint_id,
"message": message
}))
} else {
Err(crate::ClaudeUtilsError::Turbo(
"Turbo mode not enabled".into()
))
}
}
}