smart-tree 8.0.1

Smart Tree - An intelligent, AI-friendly directory visualization tool
Documentation
// ST Unified Tool System - Replace all traditional tools with Smart Tree!
// "Why use 20 tools when ST can do it all?" - The Cheet 🎸

use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use std::process::Command;

/// Unified ST interface for all file operations
pub struct StUnified {
    st_binary: PathBuf,
}

impl StUnified {
    pub fn new() -> Result<Self> {
        // Find st binary
        let st_binary = std::env::current_exe()
            .ok()
            .and_then(|p| {
                let dir = p.parent()?;
                let st = dir.join("st");
                if st.exists() {
                    Some(st)
                } else {
                    None
                }
            })
            .unwrap_or_else(|| PathBuf::from("./target/release/st"));

        Ok(Self { st_binary })
    }

    /// List files (replaces LS tool)
    pub fn ls(&self, path: &Path, pattern: Option<&str>) -> Result<String> {
        let mut cmd = Command::new(&self.st_binary);
        cmd.arg("--mode")
            .arg("ls")
            .arg("--depth")
            .arg("1")
            .arg("--no-emoji")
            .arg(path);

        if let Some(pat) = pattern {
            cmd.arg("--find").arg(pat);
        }

        let output = cmd.output().context("Failed to run st for ls")?;

        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    }

    /// Read file (replaces Read tool)
    pub fn read(&self, path: &Path, offset: Option<usize>, limit: Option<usize>) -> Result<String> {
        // ST doesn't read file contents, so use standard fs
        let content = std::fs::read_to_string(path).context("Failed to read file")?;

        let lines: Vec<&str> = content.lines().collect();
        let start = offset.unwrap_or(0);
        let end = start + limit.unwrap_or(lines.len());

        Ok(lines[start.min(lines.len())..end.min(lines.len())]
            .iter()
            .enumerate()
            .map(|(i, line)| format!("{:6}→{}", start + i + 1, line))
            .collect::<Vec<_>>()
            .join("\n"))
    }

    /// Search in files (replaces Grep tool)
    pub fn grep(&self, pattern: &str, path: &Path, file_type: Option<&str>) -> Result<String> {
        let mut cmd = Command::new(&self.st_binary);
        cmd.arg("--search")
            .arg(pattern)
            .arg("--mode")
            .arg("ai")
            .arg("--depth")
            .arg("0")
            .arg(path);

        if let Some(ft) = file_type {
            cmd.arg("--type").arg(ft);
        }

        let output = cmd.output().context("Failed to run st for search")?;

        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    }

    /// Find files by pattern (replaces Glob tool)
    pub fn glob(&self, pattern: &str, path: &Path) -> Result<String> {
        let output = Command::new(&self.st_binary)
            .arg("--find")
            .arg(pattern)
            .arg("--mode")
            .arg("json")
            .arg("--depth")
            .arg("0")
            .arg("--compact")
            .arg(path)
            .output()
            .context("Failed to run st for glob")?;

        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    }

    /// Analyze directory (replaces basic tree viewing)
    pub fn analyze(&self, path: &Path, mode: &str, depth: usize) -> Result<String> {
        let output = Command::new(&self.st_binary)
            .arg("--mode")
            .arg(mode)
            .arg("--depth")
            .arg(depth.to_string())
            .arg(path)
            .output()
            .context("Failed to run st for analysis")?;

        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    }

    /// Get file/directory stats
    pub fn stats(&self, path: &Path) -> Result<String> {
        let output = Command::new(&self.st_binary)
            .arg("--mode")
            .arg("stats")
            .arg("--depth")
            .arg("0")
            .arg(path)
            .output()
            .context("Failed to run st for stats")?;

        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    }

    /// Semantic analysis (unique to ST!)
    pub fn semantic_analyze(&self, path: &Path) -> Result<String> {
        let output = Command::new(&self.st_binary)
            .arg("--mode")
            .arg("semantic")
            .arg("--depth")
            .arg("0")
            .arg(path)
            .output()
            .context("Failed to run st for semantic analysis")?;

        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    }

    /// Quick overview (replaces quick checks)
    pub fn quick(&self, path: &Path) -> Result<String> {
        let output = Command::new(&self.st_binary)
            .arg("--mode")
            .arg("summary-ai")
            .arg("--depth")
            .arg("3")
            .arg(path)
            .output()
            .context("Failed to run st for quick view")?;

        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    }

    /// Project understanding (replaces multiple analysis tools)
    pub fn understand_project(&self, path: &Path) -> Result<String> {
        let results = [
            "=== QUICK OVERVIEW ===".to_string(),
            self.quick(path)?,
            "\n=== SEMANTIC GROUPS ===".to_string(),
            self.semantic_analyze(path)?,
            "\n=== STATISTICS ===".to_string(),
            self.stats(path)?,
        ];

        Ok(results.join("\n"))
    }
}