smart-tree 8.0.1

Smart Tree - An intelligent, AI-friendly directory visualization tool
Documentation
//! MCP prompts implementation for Smart Tree

use super::McpContext;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::sync::Arc;

#[derive(Debug, Serialize, Deserialize)]
struct PromptDefinition {
    name: String,
    description: String,
    arguments: Vec<PromptArgument>,
}

#[derive(Debug, Serialize, Deserialize)]
struct PromptArgument {
    name: String,
    description: String,
    required: bool,
}

#[allow(dead_code)]
pub async fn handle_prompts_list(_params: Option<Value>, _ctx: Arc<McpContext>) -> Result<Value> {
    let prompts = vec![
        PromptDefinition {
            name: "analyze_codebase".to_string(),
            description: "Analyze a code repository with AI-optimized output".to_string(),
            arguments: vec![
                PromptArgument {
                    name: "path".to_string(),
                    description: "Path to the codebase".to_string(),
                    required: true,
                },
                PromptArgument {
                    name: "include_hidden".to_string(),
                    description: "Include hidden files and directories".to_string(),
                    required: false,
                },
            ],
        },
        PromptDefinition {
            name: "find_large_files".to_string(),
            description: "Find the largest files in a directory tree".to_string(),
            arguments: vec![
                PromptArgument {
                    name: "path".to_string(),
                    description: "Path to search".to_string(),
                    required: true,
                },
                PromptArgument {
                    name: "min_size".to_string(),
                    description: "Minimum file size (e.g., '10M')".to_string(),
                    required: false,
                },
                PromptArgument {
                    name: "limit".to_string(),
                    description: "Number of files to show".to_string(),
                    required: false,
                },
            ],
        },
        PromptDefinition {
            name: "recent_changes".to_string(),
            description: "Find recently modified files".to_string(),
            arguments: vec![
                PromptArgument {
                    name: "path".to_string(),
                    description: "Path to search".to_string(),
                    required: true,
                },
                PromptArgument {
                    name: "days".to_string(),
                    description: "Number of days to look back".to_string(),
                    required: false,
                },
            ],
        },
        PromptDefinition {
            name: "project_structure".to_string(),
            description: "Get a clean project structure overview".to_string(),
            arguments: vec![
                PromptArgument {
                    name: "path".to_string(),
                    description: "Path to the project".to_string(),
                    required: true,
                },
                PromptArgument {
                    name: "max_depth".to_string(),
                    description: "Maximum directory depth".to_string(),
                    required: false,
                },
            ],
        },
    ];

    Ok(json!({
        "prompts": prompts
    }))
}

#[allow(dead_code)]
pub async fn handle_prompts_get(params: Value, _ctx: Arc<McpContext>) -> Result<Value> {
    let name = params["name"]
        .as_str()
        .ok_or_else(|| anyhow::anyhow!("Missing prompt name"))?;
    let arguments = params["arguments"].clone();

    match name {
        "analyze_codebase" => get_analyze_codebase_prompt(arguments),
        "find_large_files" => get_find_large_files_prompt(arguments),
        "recent_changes" => get_recent_changes_prompt(arguments),
        "project_structure" => get_project_structure_prompt(arguments),
        _ => Err(anyhow::anyhow!("Unknown prompt: {}", name)),
    }
}

#[allow(dead_code)]
fn get_analyze_codebase_prompt(args: Value) -> Result<Value> {
    let path = args["path"]
        .as_str()
        .ok_or_else(|| anyhow::anyhow!("Missing path argument"))?;
    let include_hidden = args["include_hidden"].as_bool().unwrap_or(false);

    let messages = vec![json!({
        "role": "user",
        "content": {
            "type": "text",
            "text": format!(
                "Please analyze the codebase at {} using Smart Tree. \
                First use quick_tree to get a 3-level overview, then use analyze_directory with mode='ai' (default) for details. \
                For large codebases (>10k files), switch to mode='summary-ai' with compress=true for 10x compression! \
                {}",
                path,
                if include_hidden { "Include hidden files." } else { "" }
            )
        }
    })];

    Ok(json!({
        "description": "Analyzes a codebase with AI-optimized output",
        "messages": messages
    }))
}

#[allow(dead_code)]
fn get_find_large_files_prompt(args: Value) -> Result<Value> {
    let path = args["path"]
        .as_str()
        .ok_or_else(|| anyhow::anyhow!("Missing path argument"))?;
    let min_size = args["min_size"].as_str().unwrap_or("10M");
    let limit = args["limit"].as_u64().unwrap_or(10);

    let messages = vec![json!({
        "role": "user",
        "content": {
            "type": "text",
            "text": format!(
                "Find the {} largest files in {} that are at least {} in size. \
                Use the find_files tool with appropriate size filters, then sort and limit the results.",
                limit, path, min_size
            )
        }
    })];

    Ok(json!({
        "description": "Finds large files in a directory tree",
        "messages": messages
    }))
}

#[allow(dead_code)]
fn get_recent_changes_prompt(args: Value) -> Result<Value> {
    let path = args["path"]
        .as_str()
        .ok_or_else(|| anyhow::anyhow!("Missing path argument"))?;
    let days = args["days"].as_u64().unwrap_or(7);

    // Calculate date string
    use chrono::{Duration, Local};
    let date = Local::now() - Duration::days(days as i64);
    let date_str = date.format("%Y-%m-%d").to_string();

    let messages = vec![json!({
        "role": "user",
        "content": {
            "type": "text",
            "text": format!(
                "Find all files modified in the last {} days (since {}) in {}. \
                Use the find_files tool with newer_than='{}' parameter.",
                days, date_str, path, date_str
            )
        }
    })];

    Ok(json!({
        "description": "Finds recently modified files",
        "messages": messages
    }))
}

#[allow(dead_code)]
fn get_project_structure_prompt(args: Value) -> Result<Value> {
    let path = args["path"]
        .as_str()
        .ok_or_else(|| anyhow::anyhow!("Missing path argument"))?;
    let max_depth = args["max_depth"].as_u64().unwrap_or(3);

    let messages = vec![json!({
        "role": "user",
        "content": {
            "type": "text",
            "text": format!(
                "Generate a clean project structure overview for {}. \
                Use the analyze_directory tool with mode='classic', max_depth={}, \
                and show_ignored=false to get a clear view of the project layout.",
                path, max_depth
            )
        }
    })];

    Ok(json!({
        "description": "Gets a clean project structure overview",
        "messages": messages
    }))
}