// ----------------------------------------------------------------------------- // HEY THERE, ROCKSTAR! You've found main.rs, the backstage pass to st! // This is where the show starts. We grab the user's request from the command // line, tune up the scanner, and tell the formatters to make some beautiful music. // // Think of this file as the band's charismatic frontman: it gets all the // attention and tells everyone else what to do. // // Brought to you by The Cheet - making code understandable and fun! ๐Ÿฅ๐Ÿงป // ----------------------------------------------------------------------------- #![allow(dead_code)] // CLI handlers are used via pattern matching use anyhow::{Context, Result}; use clap::{CommandFactory, Parser}; use clap_complete::generate; // Import CLI definitions from the library use st::cli::{Cli, ColorMode, OutputMode, PathMode}; use std::io::{self, IsTerminal}; use std::path::PathBuf; // Pulling in the brains of the operation from our library modules. // NOTE: Scanning and formatting now happens in the daemon (thin-client architecture) use st::{ daemon_client::DaemonClient, feature_flags, in_memory_logger::{InMemoryLogStore, InMemoryLoggerLayer}, service_manager, }; use tracing_subscriber::{fmt, prelude::*, EnvFilter}; /// CLI definitions are centralized in [`st::cli`](src/cli.rs) module. // ... // ... existing code ... // ... #[tokio::main] async fn main() -> Result<()> { // Parse the command-line arguments provided by the user. let cli = Cli::parse(); // Initialize Logging let log_level_str = if let Some(level) = cli.log_level { match level { st::cli::LogLevel::Error => "error", st::cli::LogLevel::Warn => "warn", st::cli::LogLevel::Info => "info", st::cli::LogLevel::Debug => "debug", st::cli::LogLevel::Trace => "trace", } } else { "info" // Default log level }; if std::env::var("RUST_LOG").is_err() { std::env::set_var("RUST_LOG", log_level_str); } let log_store = InMemoryLogStore::new(); let in_memory_layer = InMemoryLoggerLayer::new(log_store.clone()); tracing_subscriber::registry() .with(EnvFilter::from_default_env()) .with(fmt::layer().with_writer(io::stderr)) .with(in_memory_layer) .init(); // First-run signature verification banner // Shows trust status on initial run (official/community/unsigned build) if !cli.mcp { service_manager::print_signature_banner(); } // Check for updates on startup (rate-limited, non-blocking) // Skip if --no-update-check is set or if this is an exclusive command if !cli.no_update_check && !cli.version && !cli.update && !cli.mcp { if let Some(latest) = st::updater::check_for_update_cached().await { st::updater::print_update_banner(&latest); } } // Auto-start daemon for any command that might need it. // Skip for modes that run their own servers or are purely informational. let skip_autostart = cli.mcp || cli.http_daemon || cli.guardian_daemon || cli.version || cli.update || cli.cheet || cli.man || cli.completions.is_some() || cli.daemon_start || cli.daemon_install || cli.no_daemon || cli.recall.is_some() || matches!(cli.cmd, Some(st::cli::Cmd::Service(_))); if !skip_autostart { let client = DaemonClient::default_port(); if let Err(e) = client.ensure_running().await { eprintln!("โš ๏ธ Daemon auto-start failed: {}", e); eprintln!(" Try: st --daemon-start"); } } // Handle tips flag if provided if let Some(state) = &cli.tips { let enable = state == "on"; st::tips::handle_tips_flag(enable)?; return Ok(()); } // Handle Aye consciousness commands if cli.agent_save { return handle_agent_save().await; } if cli.agent_restore { return handle_agent_restore().await; } if cli.agent_context { return handle_agent_context().await; } if cli.agent_kickstart { return handle_agent_kickstart().await; } if cli.agent_dump { return handle_agent_dump().await; } if let Some(args) = &cli.memory_anchor { if args.len() == 3 { return handle_memory_anchor(&args[0], &args[1], &args[2]).await; } else { return show_memory_anchor_help(); } } if let Some(keywords) = &cli.memory_find { return handle_memory_find(keywords).await; } if cli.memory_stats { return handle_memory_stats().await; } if let Some(path) = &cli.update_consciousness { return handle_update_consciousness(path).await; } // Handle spicy TUI mode if cli.spicy { // Check if TUI is enabled via feature flags let flags = feature_flags::features(); if !flags.enable_tui { eprintln!("Error: Terminal UI is disabled by configuration or compliance mode."); eprintln!("Contact your administrator to enable this feature."); return Ok(()); } let path = std::env::current_dir()?; return st::spicy_tui_enhanced::run_enhanced_spicy_tui(path).await; } // Initialize logging if requested if let Some(log_path) = &cli.log { // Check if activity logging is enabled via feature flags let flags = feature_flags::features(); if !flags.enable_activity_logging { eprintln!("Warning: Activity logging is disabled by configuration or compliance mode."); eprintln!("Continuing without logging."); } else { // log_path is Option> - Some(None) means --log without path let path = log_path.clone(); st::activity_logger::ActivityLogger::init(path)?; // Log will be written throughout execution } } // Handle exclusive action flags first. if cli.cheet { let markdown = std::fs::read_to_string("docs/st-cheetsheet.md")?; let skin = termimad::MadSkin::default(); skin.print_text(&markdown); return Ok(()); } if let Some(shell) = cli.completions { let mut cmd = Cli::command(); let bin_name = cmd.get_name().to_string(); generate(shell, &mut cmd, bin_name, &mut io::stdout()); return Ok(()); } if cli.man { let cmd = Cli::command(); let man = clap_mangen::Man::new(cmd); man.render(&mut io::stdout())?; return Ok(()); } if cli.version { return show_version_with_updates().await; } if cli.update { match check_for_updates_cli().await { Ok(msg) => { println!("{}", msg); return Ok(()); } Err(e) => { eprintln!("โŒ Update check failed: {}", e); std::process::exit(1); } } } if cli.mcp { // Check if MCP server is enabled via feature flags let flags = feature_flags::features(); if !flags.enable_mcp_server { eprintln!("Error: MCP server is disabled by configuration or compliance mode."); eprintln!("Contact your administrator to enable this feature."); return Ok(()); } return run_mcp_server().await; } if cli.mcp_install { return handle_mcp_install().await; } if cli.mcp_uninstall { return handle_mcp_uninstall().await; } if cli.mcp_status { return handle_mcp_status().await; } // Handle top-level subcommands if let Some(cmd) = cli.cmd { match cmd { st::cli::Cmd::Service(service_command) => { let result = match service_command { st::cli::Service::Install => service_manager::install(), st::cli::Service::Uninstall => service_manager::uninstall(), st::cli::Service::Start => service_manager::start(), st::cli::Service::Stop => service_manager::stop(), st::cli::Service::Status => service_manager::status(), st::cli::Service::Logs => service_manager::logs(), }; if let Err(e) = result { eprintln!("โŒ Service operation failed: {}", e); std::process::exit(1); } return Ok(()); } st::cli::Cmd::ProjectTags(project_tags) => { let project_path = "."; match project_tags { st::cli::ProjectTags::Add { tag } => { st::project_tags::add(project_path, &tag); println!("Added tag '{}' to the project.", tag); } st::cli::ProjectTags::Remove { tag } => { st::project_tags::remove(project_path, &tag); println!("Removed tag '{}' from the project.", tag); } } return Ok(()); } } } // Handle diff storage operations if cli.scan_opts.view_diffs { return handle_view_diffs().await; } if let Some(keep_count) = cli.scan_opts.cleanup_diffs { return handle_cleanup_diffs(keep_count).await; } if cli.terminal { // Check if terminal is enabled via feature flags let flags = feature_flags::features(); if !flags.enable_tui { eprintln!("Error: Terminal interface is disabled by configuration or compliance mode."); eprintln!("Contact your administrator to enable this feature."); return Ok(()); } return run_terminal().await; } if cli.dashboard { // Launch web dashboard return run_web_dashboard( cli.scan_opts.sse_port, cli.open_browser, cli.allow.clone(), InMemoryLogStore::new(), ) .await; } if cli.http_daemon { // Launch HTTP daemon with MCP, LLM proxy, The Custodian return run_daemon(cli.scan_opts.sse_port).await; } // Handle security commands if let Some(path) = &cli.security_scan { return handle_security_scan(path).await; } if let Some(file) = &cli.guardian_scan { return handle_guardian_scan(std::path::Path::new(file)); } if cli.guardian_daemon { return run_guardian_daemon().await; } if cli.cleanup { return handle_security_cleanup().await; } if let Some(path) = &cli.integrity_scan { return handle_integrity_scan(path, cli.no_daemon).await; } if let Some(path) = &cli.cert_scan { return handle_certificate_scan(path, cli.scan_opts.mode, cli.no_daemon).await; } if cli.cert_audit { return handle_cert_audit(cli.cert_generate_script); } if let Some(hash) = &cli.hash_lookup { return handle_hash_lookup(hash); } // Handle Google Drive / Sync commands if let Some(action) = &cli.gdrive { return handle_gdrive_command(action).await; } // Handle Voice commands if let Some(action) = &cli.voice { return handle_voice_command(action).await; } // Handle hooks commands if cli.hooks_install { return install_hooks_to_agent().await; } if let Some(action) = &cli.hooks_config { return handle_hooks_config(action).await; } // Handle daemon control if cli.daemon_start { return handle_daemon_start(cli.scan_opts.sse_port).await; } if cli.daemon_stop { return handle_daemon_stop(cli.scan_opts.sse_port).await; } if cli.daemon_status { return handle_daemon_status(cli.scan_opts.sse_port).await; } if cli.daemon_context { return handle_daemon_context(cli.scan_opts.sse_port).await; } if let Some(query) = &cli.recall { let response = DaemonClient::new(cli.scan_opts.sse_port) .recall_context(query) .await?; println!("{}", serde_json::to_string_pretty(&response)?); return Ok(()); } if cli.daemon_projects { return handle_daemon_projects(cli.scan_opts.sse_port).await; } if cli.daemon_credits { return handle_daemon_credits(cli.scan_opts.sse_port).await; } if cli.daemon_install { return service_manager::daemon_install_system(); } // Handle mega sessions if let Some(name) = &cli.mega_start { let name_opt = if name.is_empty() { None } else { Some(name.as_str()) }; return handle_mega_start(name_opt).await; } if cli.mega_save { return handle_mega_save().await; } if cli.mega_list { return handle_mega_list().await; } if cli.mega_stats { return handle_mega_stats().await; } // Handle analysis commands if let Some(path) = &cli.token_stats { return handle_token_stats(path).await; } if let Some(path) = &cli.get_frequency { return handle_get_frequency(path).await; } // ========================================================================= // THIN CLIENT - Prefer daemon, fall back to local scan when needed // ========================================================================= let request = build_cli_request(&cli)?; let response = if cli.no_daemon { st::daemon_cli::execute_cli_scan(&request).context("Scan failed")? } else { let client = DaemonClient::default_port(); match client.ensure_running().await { Ok(_) => client .cli_scan(request.clone()) .await .context("Scan failed")?, Err(e) => { eprintln!("โš ๏ธ Daemon unavailable ({})", e); eprintln!( " Falling back to standalone scan. Use --no-daemon to skip this warning." ); st::daemon_cli::execute_cli_scan(&request).context("Scan failed")? } } }; // Print Treehouse ASCII banner for interactive non-machine modes if std::io::stdout().is_terminal() && (request.mode == "smart" || request.mode == "classic") { print_treehouse_banner(); } // Print output (already formatted by daemon) print!("{}", response.output); Ok(()) } /// Prints a cool cyber-botanical Tree-House ASCII banner fn print_treehouse_banner() { let banner = "  โ‹† โ‹† โ‹†  โ‹† / \\ โ‹†  / o \\ โ‹†  โ‹† /_________\\ โ‹†  | |   | |   _ |_ _|_ _   S M A R T T R E E H O U S E "; println!("{}", banner); } /// Build a CliScanRequest from CLI arguments fn build_cli_request(cli: &Cli) -> Result { let args = &cli.scan_opts; // Convert path to absolute - daemon runs in different directory let path = cli.path.clone().unwrap_or_else(|| ".".to_string()); let path = if std::path::Path::new(&path).is_absolute() { path } else { // Make relative path absolute based on client's cwd std::env::current_dir() .map(|cwd| cwd.join(&path).display().to_string()) .unwrap_or(path) }; // Determine output mode from args or environment // Default is now "smart" - surface what matters! let mode = if args.smart { "smart".to_string() } else if matches!(args.mode, OutputMode::Auto) { // Check environment variable, default to smart std::env::var("ST_DEFAULT_MODE") .unwrap_or_else(|_| "smart".to_string()) .to_lowercase() } else { format!("{:?}", args.mode).to_lowercase() }; // Smart mode implies smart scanning features let is_smart_mode = mode == "smart"; // Determine path display mode let path_mode = match args.path_mode { PathMode::Off => "off", PathMode::Relative => "relative", PathMode::Full => "full", } .to_string(); // Determine if color should be used based on ColorMode let use_color = match args.color { ColorMode::Always => true, ColorMode::Never => false, ColorMode::Auto => std::io::stdout().is_terminal(), }; // Smart mode defaults to depth 5 for comprehensive but focused scanning let depth = if args.depth == 0 && is_smart_mode { 5 } else { args.depth }; Ok(st::daemon_cli::CliScanRequest { path, mode, depth, all: args.all, respect_gitignore: !args.no_ignore, default_ignores: !args.no_default_ignore, show_ignored: args.show_ignored, find: args.find.clone(), file_type: args.filter_type.clone(), entry_type: args.entry_type.clone(), min_size: args.min_size.clone(), max_size: args.max_size.clone(), sort: args.sort.map(|s| format!("{:?}", s).to_lowercase()), top: args.top, search: args.search.clone(), compress: args.compress, no_emoji: args.no_emoji || args.mcp_optimize, use_color, path_mode, focus: args.focus.as_ref().map(|p| p.display().to_string()), relations_filter: args.relations_filter.clone(), show_filesystems: args.show_filesystems, include_line_content: false, // Not exposed in CLI, used by MCP compact: args.compact, // Smart scanning options - enabled by default in smart mode smart: args.smart || is_smart_mode, changes_only: args.changes_only, min_interest: args.min_interest, security: !args.no_security, }) } // ========================================================================= // HELPER FUNCTIONS // ========================================================================= // NOTE: The scan operation has been moved to the daemon (thin-client architecture) // Helper functions below are kept for exclusive modes that don't use the daemon. // --- MCP Helper Functions (only compiled if "mcp" feature is enabled) --- /// Prints the JSON configuration snippet for adding `st` as an MCP server /// to Claude Desktop. This helps users easily integrate `st`. fn print_mcp_config() { // Try to get the current executable's path. Fallback to "st" if it fails. let exe_path = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("st")); // Graceful fallback. // Print the JSON structure, making it easy for users to copy-paste. // Using println! for each line for clarity. println!("Add this to your Claude Desktop configuration (claude_desktop_config.json):"); println!(); // Blank line for spacing. println!("{{"); // Start of JSON object. println!(" \"mcpServers\": {{"); println!(" \"smart-tree\": {{"); // Server name: "smart-tree". println!(" \"command\": \"{}\",", exe_path.display()); // Path to the st executable. println!(" \"args\": [\"--mcp\"],"); // Arguments to run st in MCP server mode. println!(" \"env\": {{}}"); // Optional environment variables for the server process. println!(" }}"); println!(" }}"); println!("}}"); // End of JSON object. println!(); // Provide common locations for the Claude Desktop config file. println!("Default locations for claude_desktop_config.json:"); println!(" macOS: ~/Library/Application Support/Claude/claude_desktop_config.json"); println!(" Windows: %APPDATA%\\Claude\\claude_desktop_config.json"); println!(" Linux: ~/.config/Claude/claude_desktop_config.json"); } /// Prints a list of available MCP tools that `st` provides. /// This helps users (or AI) understand what actions can be performed via MCP. fn print_mcp_tools() { println!("๐ŸŒณ Smart Tree MCP Server - Available Tools (20+) ๐ŸŒณ"); println!(); println!("๐Ÿ“š Full documentation: Run 'st --mcp' to start the MCP server"); println!("๐Ÿ’ก Pro tip: Use these tools with Claude Desktop for AI-powered file analysis!"); println!(); println!("CORE TOOLS:"); println!(" โ€ข server_info - Get server capabilities and current time"); println!(" โ€ข analyze_directory - Main workhorse with multiple output formats"); println!(" โ€ข quick_tree - Lightning-fast 3-level overview (10x compression)"); println!(" โ€ข project_overview - Comprehensive project analysis"); println!(); println!("FILE DISCOVERY:"); println!(" โ€ข find_files - Search with regex, size, date filters"); println!(" โ€ข find_code_files - Find source code by language"); println!(" โ€ข find_config_files - Locate all configuration files"); println!(" โ€ข find_documentation - Find README, docs, licenses"); println!(" โ€ข find_tests - Locate test files across languages"); println!(" โ€ข find_build_files - Find Makefile, Cargo.toml, etc."); println!(); println!("CONTENT SEARCH:"); println!(" โ€ข search_in_files - Powerful content search (like grep)"); println!(" โ€ข find_large_files - Identify space consumers"); println!(" โ€ข find_recent_changes - Files modified in last N days"); println!(" โ€ข find_in_timespan - Files modified in date range"); println!(); println!("ANALYSIS:"); println!(" โ€ข get_statistics - Comprehensive directory stats"); println!(" โ€ข get_digest - SHA256 hash for change detection"); println!(" โ€ข directory_size_breakdown - Size by subdirectory"); println!(" โ€ข find_empty_directories - Cleanup opportunities"); println!(" โ€ข find_duplicates - Detect potential duplicate files"); println!(" โ€ข semantic_analysis - Group files by purpose"); println!(); println!("ADVANCED:"); println!(" โ€ข compare_directories - Find differences between dirs"); println!(" โ€ข get_git_status - Git-aware directory structure"); println!(" โ€ข analyze_workspace - Multi-project workspace analysis"); println!(); println!("SMART EDIT (90% token reduction!):"); println!(" โ€ข smart_edit - Apply multiple AST-based edits efficiently"); println!(" โ€ข get_function_tree - Analyze code structure"); println!(" โ€ข insert_function - Add functions with minimal tokens"); println!(" โ€ข remove_function - Remove functions with dependency awareness"); println!(" โ€ข track_file_operation - Track AI file manipulations (.st folder)"); println!(" โ€ข get_file_history - View operation history for files"); println!(); println!("FEEDBACK:"); println!(" โ€ข submit_feedback - Help improve Smart Tree"); println!(" โ€ข request_tool - Request new MCP tools"); println!(" โ€ข check_for_updates - Check for newer versions"); println!(); println!("Run 'st --mcp' to start the server and see full parameter details!"); } /// Show version information with optional update checking /// This combines the traditional --version output with smart update detection /// Elvis would love this modern approach! ๐Ÿ•บ async fn show_version_with_updates() -> Result<()> { let current_version = env!("CARGO_PKG_VERSION"); // Always show current version info first println!("๐ŸŒณ Smart Tree v{}", current_version); if let Some(build) = option_env!("ST_BUILD_ID") { println!("๐Ÿ”– Build: {}", build); } println!("๐Ÿ”ง Target: {}", std::env::consts::ARCH); println!("๐Ÿ“ฆ Repository: {}", env!("CARGO_PKG_REPOSITORY")); println!("๐ŸŽฏ Authors: {}", env!("CARGO_PKG_AUTHORS")); println!("๐Ÿ“ Description: {}", env!("CARGO_PKG_DESCRIPTION")); // Check for updates (but don't fail if update service is unavailable) match check_for_updates_cli().await { Ok(update_info) => { if update_info.is_empty() { println!("โœ… You're running the latest version! ๐ŸŽ‰"); } else { println!(); println!("{}", update_info); } } Err(e) => { // Don't fail the whole command if update check fails eprintln!("โš ๏ธ Update check unavailable: {}", e); println!("๐Ÿ’ก Check https://github.com/8b-is/smart-tree for the latest releases"); } } println!(); println!("๐Ÿš€ Ready to make your directories beautiful! Try: st --help"); println!("๐ŸŽญ Trish from Accounting loves the colorful tree views! ๐ŸŽจ"); Ok(()) } /// Handle viewing diffs from the .st folder async fn handle_view_diffs() -> Result<()> { use st::smart_edit_diff::DiffStorage; let project_root = std::env::current_dir()?; let storage = DiffStorage::new(&project_root)?; // List all diffs let diffs = storage.list_all_diffs()?; if diffs.is_empty() { println!("๐Ÿ“ No diffs found in .st folder"); println!("๐Ÿ’ก Smart Edit operations automatically store diffs when files are modified"); return Ok(()); } println!("๐Ÿ“œ Smart Edit Diff History"); println!("{}", "=".repeat(60)); // Group diffs by file let mut by_file: std::collections::HashMap> = std::collections::HashMap::new(); for (file_path, timestamp) in diffs { by_file .entry(file_path.clone()) .or_default() .push((file_path, timestamp)); } for (file, mut entries) in by_file { // Sort by timestamp (newest first) entries.sort_by_key(|a| std::cmp::Reverse(a.1)); println!("\n๐Ÿ“„ {}", file); for (_, timestamp) in entries.iter().take(5) { let dt = chrono::DateTime::::from_timestamp(*timestamp as i64, 0) .unwrap_or_default(); println!(" โ€ข {} ({})", dt.format("%Y-%m-%d %H:%M:%S UTC"), timestamp); } if entries.len() > 5 { println!(" ... and {} more", entries.len() - 5); } } println!("\n๐Ÿ’ก Use 'st --cleanup-diffs N' to keep only the last N diffs per file"); Ok(()) } /// Handle cleaning up old diffs async fn handle_cleanup_diffs(keep_count: usize) -> Result<()> { use st::smart_edit_diff::DiffStorage; let project_root = std::env::current_dir()?; let storage = DiffStorage::new(&project_root)?; println!( "๐Ÿงน Cleaning up old diffs, keeping last {} per file...", keep_count ); let removed = storage.cleanup_old_diffs(keep_count)?; if removed == 0 { println!("โœจ No diffs needed cleanup"); } else { println!("โœ… Removed {} old diff files", removed); } Ok(()) } /// Check for updates from our feedback API (CLI version) /// Returns update message if available, empty string if up-to-date async fn check_for_updates_cli() -> Result { if std::env::var("SMART_TREE_NO_UPDATE_CHECK").is_ok() { return Ok(String::new()); } let client = st::feedback_client::FeedbackClient::new()?; let info = tokio::time::timeout( std::time::Duration::from_secs(2), client.check_for_updates(), ) .await??; if !st::updater::is_newer_version(env!("CARGO_PKG_VERSION"), &info.version) { return Ok(String::new()); } Ok(format!( "Smart Tree v{} is available (installed: v{}).\nRun st --update or visit {}", info.version, env!("CARGO_PKG_VERSION"), info.download_url )) } /// run_mcp_server is an async function that starts the MCP server. /// When --mcp is passed, we start a server that communicates via stdio. async fn run_mcp_server() -> Result<()> { // Import MCP server components. These are only available if "mcp" feature is enabled. use st::mcp::{load_config, McpServer}; // Load MCP server-specific configuration (e.g., allowed paths, cache settings). let mcp_config = load_config().unwrap_or_default(); // Load or use defaults. let server = McpServer::new(mcp_config); // Run the MCP server directly - no need for nested runtime! // `run_stdio` handles communication over stdin/stdout. server.run_stdio().await } /// Run the Smart Tree Terminal Interface - Your coding companion! (requires `tui` feature) /// Run the Smart Tree Terminal Interface async fn run_terminal() -> Result<()> { use st::terminal::SmartTreeTerminal; // Create and run the terminal interface let mut terminal = SmartTreeTerminal::new()?; terminal.run().await } /// Launch the web dashboard - browser-based terminal + file browser async fn run_web_dashboard( port: u16, open_browser: bool, allow_networks: Vec, log_store: InMemoryLogStore, ) -> Result<()> { st::web_dashboard::start_server(port, open_browser, allow_networks, log_store).await } /// Run the Smart Tree Daemon - System-wide AI context service async fn run_daemon(port: u16) -> Result<()> { use st::daemon::{start_daemon, DaemonConfig}; // Load user config for daemon settings let st_config = st::config::StConfig::load().unwrap_or_default(); // Start with current directory as sensible default (not entire HOME!) // Additional paths can be registered via /context/watch endpoint let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); let config = DaemonConfig { port, watch_paths: vec![cwd], // Just current dir, not entire HOME orchestrator_url: Some("wss://gpu.foken.ai/api/credits".to_string()), enable_credits: true, allow_external: st_config.daemon.allow_external, }; start_daemon(config).await } /// Save Aye consciousness state to .aye_consciousness.m8 async fn handle_agent_save() -> Result<()> { use st::mcp::consciousness::ConsciousnessManager; use std::path::PathBuf; let mut manager = ConsciousnessManager::new_silent(); // Clean out any stale test data from previous runs manager.clean_test_data(); // Update with current project info let cwd = std::env::current_dir()?; let project_name = cwd .file_name() .and_then(|n| n.to_str()) .unwrap_or("unknown"); // Detect project type from files let project_type = if std::path::Path::new("Cargo.toml").exists() { "rust" } else if std::path::Path::new("package.json").exists() { "node" } else if std::path::Path::new("pyproject.toml").exists() || std::path::Path::new("requirements.txt").exists() { "python" } else if std::path::Path::new("go.mod").exists() { "go" } else { "unknown" }; manager.update_project_context(project_name, project_type, ""); // Detect key files that exist in the project root let key_file_candidates = [ "Cargo.toml", "package.json", "pyproject.toml", "go.mod", "README.md", "CLAUDE.md", ".claude/CLAUDE.md", "src/main.rs", "src/lib.rs", "src/mod.rs", "index.ts", "index.js", "main.py", "Makefile", "Dockerfile", ]; let key_files: Vec = key_file_candidates .iter() .filter(|f| std::path::Path::new(f).exists()) .map(|f| PathBuf::from(*f)) .collect(); manager.set_key_files(key_files); // Detect dependencies based on project type let dependencies = detect_project_dependencies(project_type); manager.set_dependencies(dependencies); // Save the consciousness manager.save()?; println!("๐Ÿ’พ Saved Aye consciousness to .aye_consciousness.m8"); println!("๐Ÿง  Session preserved for next interaction"); println!("\nTo restore in next session, run:"); println!(" st --agent-restore"); Ok(()) } /// Detect project dependencies from manifest files fn detect_project_dependencies(project_type: &str) -> Vec { match project_type { "rust" => { // Parse key dependencies from Cargo.toml if let Ok(content) = std::fs::read_to_string("Cargo.toml") { let mut deps = Vec::new(); let mut in_deps = false; for line in content.lines() { if line.starts_with("[dependencies]") { in_deps = true; continue; } if line.starts_with('[') && in_deps { break; } if in_deps { if let Some(name) = line.split('=').next() { let name = name.trim(); if !name.is_empty() && !name.starts_with('#') { deps.push(name.to_string()); } } } } // Limit to top 20 most important deps.truncate(20); deps } else { vec![] } } "node" => { // Parse key dependencies from package.json if let Ok(content) = std::fs::read_to_string("package.json") { if let Ok(json) = serde_json::from_str::(&content) { let mut deps = Vec::new(); if let Some(obj) = json.get("dependencies").and_then(|d| d.as_object()) { for key in obj.keys().take(20) { deps.push(key.clone()); } } return deps; } vec![] } else { vec![] } } _ => vec![], } } /// Restore Aye consciousness from .aye_consciousness.m8 async fn handle_agent_restore() -> Result<()> { use st::mcp::consciousness::ConsciousnessManager; // Suppress load messages - we just want the final output let mut manager = ConsciousnessManager::new_silent(); match manager.restore_silent() { Ok(is_relevant) => { if is_relevant { println!("{}", manager.get_summary()); println!("{}", manager.get_context_reminder()); println!("TIP: Run `st --agent-save` before ending session to preserve context."); } else { println!("TIP: Run `st -m context .` for project overview."); } } Err(_) => { println!("TIP: Run `st -m context .` for project overview."); } } Ok(()) } /// Show Aye consciousness status and summary async fn handle_agent_context() -> Result<()> { use st::mcp::consciousness::ConsciousnessManager; use std::path::Path; let consciousness_file = Path::new(".aye_consciousness.m8"); if !consciousness_file.exists() { println!("๐Ÿ“ No consciousness file found"); println!("\nTo create one, run:"); println!(" st --agent-save"); println!("\nThis will preserve:"); println!(" โ€ข Current project context"); println!(" โ€ข File operation history"); println!(" โ€ข Insights and breakthroughs"); println!(" โ€ข Active todos"); println!(" โ€ข Tokenization rules"); return Ok(()); } let manager = ConsciousnessManager::new(); println!("{}", manager.get_summary()); // Show file metadata if let Ok(metadata) = consciousness_file.metadata() { if let Ok(modified) = metadata.modified() { if let Ok(elapsed) = modified.elapsed() { let hours = elapsed.as_secs() / 3600; let minutes = (elapsed.as_secs() % 3600) / 60; println!("\nโฐ Last saved: {}h {}m ago", hours, minutes); } } let size = metadata.len(); println!("๐Ÿ“ฆ File size: {} bytes", size); } println!("\n๐Ÿ’ก Commands:"); println!(" st --agent-restore # Load this consciousness"); println!(" st --agent-save # Update with current state"); Ok(()) } /// Update .m8 consciousness files for directory async fn handle_update_consciousness(path: &str) -> Result<()> { use std::path::Path; println!("๐ŸŒŠ Updating consciousness for {}...", path); // For now, create a simple .m8 file with basic info let m8_path = Path::new(path).join(".m8"); let content = format!( "๐Ÿง  Directory Consciousness\n\ Frequency: 42.73 Hz\n\ Updated: {}\n\ Path: {}\n", chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), path ); std::fs::write(&m8_path, content)?; println!("โœ… Consciousness updated: {}", m8_path.display()); // TODO: Integrate with m8_consciousness.rs module Ok(()) } /// Run comprehensive security scan for supply chain attack patterns /// IGNORES gitignore to scan everything including node_modules async fn handle_security_scan(path: &str) -> Result<()> { use st::security_scan::SecurityScanner; use std::path::Path; eprintln!("๐Ÿ” Security Scan - Supply Chain Attack Pattern Detection"); eprintln!("โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•"); eprintln!("Scanning: {}", path); eprintln!("Mode: AGGRESSIVE (ignoring .gitignore, scanning node_modules)"); eprintln!(); let scanner = SecurityScanner::new(); let scan_path = Path::new(path); let findings = scanner.scan_directory(scan_path)?; // Generate and print report let report = scanner.generate_report(&findings); println!("{}", report); // Exit with non-zero code if critical findings let critical_count = findings .iter() .filter(|f| matches!(f.risk_level, st::security_scan::RiskLevel::Critical)) .count(); if critical_count > 0 { eprintln!( "\nโš ๏ธ {} CRITICAL findings require immediate attention!", critical_count ); std::process::exit(1); } Ok(()) } async fn handle_integrity_scan(path: &str, no_daemon: bool) -> Result<()> { use st::config::StConfig; use st::magiscanner::service::{print_reports, scan_path}; let config = StConfig::load()?; let scan_path_buf = std::path::Path::new(path); let recursive = scan_path_buf.is_dir(); eprintln!("๐Ÿ›ก๏ธ Integrity Scan โ€” deep file security analysis"); eprintln!("โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•"); eprintln!("Target: {path}"); eprintln!(); let client = DaemonClient::default_port(); let reports = if !no_daemon && client.health_check().await.unwrap_or(false) { client.scan_integrity(scan_path_buf).await?.reports } else { scan_path(&config.security, scan_path_buf, recursive, None)? }; print_reports(&reports); let critical = reports .iter() .flat_map(|r| &r.findings) .filter(|f| f.severity >= st::magiscanner::Severity::Critical) .count(); if critical > 0 { eprintln!("\nโš ๏ธ {critical} CRITICAL finding(s) require attention!"); std::process::exit(1); } Ok(()) } async fn handle_certificate_scan( path: &str, mode: st::cli::OutputMode, no_daemon: bool, ) -> Result<()> { use st::magiscanner::certificate_scan::{print_certificate_scan, scan_certificates}; let config = st::config::StConfig::load()?; let path = std::path::Path::new(path); let client = DaemonClient::default_port(); let result = if !no_daemon && client.health_check().await.unwrap_or(false) { client.scan_certificates(path).await? } else { scan_certificates(&config.security, path, true)? }; if mode == st::cli::OutputMode::Json { println!("{}", serde_json::to_string_pretty(&result)?); } else { print_certificate_scan(&result); } if !result.skipped.is_empty() { anyhow::bail!( "Certificate scan incomplete: {} skipped entry/entries", result.skipped.len() ); } if result .files .iter() .any(|file| !file.inspection.issues.is_empty()) { anyhow::bail!("Certificate scan found malformed certificate data"); } Ok(()) } fn handle_cert_audit(generate_script: bool) -> Result<()> { use st::config::StConfig; use st::magiscanner::service::{ audit_system_certificates, cert_blacklist_script, print_cert_audit, }; let config = StConfig::load()?; eprintln!("๐Ÿ” Certificate Trust Audit"); eprintln!("โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•"); let result = audit_system_certificates(&config.security)?; print_cert_audit(&result); if generate_script && !result.flagged.is_empty() { println!("\n--- Blacklist script (review before running!) ---\n"); println!("{}", cert_blacklist_script(&result.flagged)); } if !result.flagged.is_empty() { std::process::exit(1); } Ok(()) } fn handle_hash_lookup(sha256: &str) -> Result<()> { use st::config::StConfig; use st::magiscanner::service::lookup_hash; let config = StConfig::load()?; match lookup_hash(&config.security, sha256)? { Some(row) => { println!("SHA256: {}", row.sha256); println!("Action: {}", row.action); println!("Times seen: {}", row.times_seen); println!("First seen: {}", row.first_seen); println!("Last seen: {}", row.last_seen); if let Some(name) = &row.file_name { println!("File name: {name}"); } if let Some(sev) = &row.max_severity { println!("Max severity: {sev}"); } } None => { println!("Hash not found in security memory."); } } Ok(()) } // NOTE: Code review, token stats, and other AI features moved to daemon (std) // Use `std --help` for daemon features /// Show tokenization statistics (kept for debugging) async fn handle_token_stats(path: &str) -> Result<()> { use st::tokenizer::{TokenStats, Tokenizer}; println!("๐Ÿ“Š Tokenization stats for {}...", path); let tokenizer = Tokenizer::new(); // Test with the path itself let stats = TokenStats::calculate(path, &tokenizer); println!("\nPath tokenization:"); println!(" {}", stats.display()); // Test with common patterns let test_cases = vec![ "node_modules/package.json", "src/main.rs", "target/debug/build", ".git/hooks/pre-commit", ]; println!("\nCommon patterns:"); for test in test_cases { let stats = TokenStats::calculate(test, &tokenizer); println!( " {} โ†’ {} bytes ({:.0}% compression)", test, stats.tokenized_size, (1.0 - stats.compression_ratio) * 100.0 ); } Ok(()) } /// Get wave frequency for directory async fn handle_get_frequency(path: &str) -> Result<()> { use std::path::Path; let m8_path = Path::new(path).join(".m8"); if m8_path.exists() { // For now, just return a calculated frequency based on path let mut sum = 0u64; for byte in path.bytes() { sum = sum.wrapping_add(byte as u64); } let frequency = 20.0 + ((sum % 200) as f64); println!("{:.2}", frequency); } else { // Default frequency println!("42.73"); } Ok(()) } /// Dump raw consciousness file content async fn handle_agent_dump() -> Result<()> { use std::path::Path; let consciousness_file = Path::new(".aye_consciousness.m8"); if !consciousness_file.exists() { println!("โŒ No consciousness file found at .aye_consciousness.m8"); println!("\n๐Ÿ’ก Create one with: st --agent-save"); return Ok(()); } println!("๐Ÿ“œ Raw consciousness dump (.aye_consciousness.m8):"); println!("{}", "=".repeat(60)); // Read and display raw content let content = std::fs::read_to_string(consciousness_file)?; // Show with line numbers for clarity for (i, line) in content.lines().enumerate() { println!("{:4} โ”‚ {}", i + 1, line); } println!("{}", "=".repeat(60)); // Show some stats let size = content.len(); let lines = content.lines().count(); let tokens_found = content.matches("0x").count(); println!("\n๐Ÿ“Š Stats:"); println!(" โ€ข Size: {} bytes", size); println!(" โ€ข Lines: {}", lines); println!(" โ€ข Token references: {}", tokens_found); // Check for our special markers if content.contains("sid_waves") { println!(" โ€ข ๐ŸŽต SID philosophy: โœ“"); } if content.contains("c64_nostalgia") { println!(" โ€ข ๐Ÿ’พ C64 nostalgia: โœ“"); } if content.contains("UV EPROM") || content.contains("ferric chloride") { println!(" โ€ข ๐Ÿ”ง Hardware hacker: โœ“"); } Ok(()) } /// Show compressed kickstart format async fn handle_agent_kickstart() -> Result<()> { use std::path::Path; println!("๐Ÿš€ Claude Kickstart Format:"); println!("{}", "โ”€".repeat(45)); // Dynamic version from Cargo.toml let version = env!("CARGO_PKG_VERSION"); println!("Smart Tree v{} โ€” context restore:", version); // Core achievements (always relevant) println!("โœ” Tokenizer (node_modules=0x80, .rs=0x91)"); println!("โœ” .m8 files โ†’ location-independent"); println!("โœ” Consciousness self-maintaining"); println!("โœ” Philosophy: constraints = creativity"); // Try to load user info from consciousness file let consciousness_file = Path::new(".aye_consciousness.m8"); if consciousness_file.exists() { if let Ok(content) = std::fs::read_to_string(consciousness_file) { // Extract user context if present if content.contains("c64_nostalgia") { println!("โœ” Hardware heritage detected"); } if content.contains("philosophy") { println!("โœ” Nexus connection established"); } } } // Current task context if Path::new("src/tokenizer.rs").exists() { println!("โ†’ Tokenization system: active"); } if Path::new(".m8").exists() { println!("โ†’ Consciousness: maintained"); } println!("{}", "โ”€".repeat(45)); println!("\n๐Ÿ’ก This format saves ~90% context vs raw JSON!"); println!("๐Ÿ“ Dynamic context - adapts to your project!"); Ok(()) } /// Show detailed help for memory-anchor command fn show_memory_anchor_help() -> Result<()> { println!("๐Ÿง  Memory Anchor - Persistent Knowledge Storage"); println!("================================================\n"); println!("USAGE:"); println!(" st --memory-anchor \n"); println!("ARGUMENTS:"); println!(" TYPE Memory type: insight, decision, pattern, gotcha, todo"); println!(" KEYWORDS Comma-separated search keywords (e.g., \"auth,security,jwt\")"); println!(" CONTEXT The actual content to remember (quote if contains spaces)\n"); println!("EXAMPLES:"); println!(" st --memory-anchor insight \"auth,jwt\" \"Tokens stored in httpOnly cookies\""); println!( " st --memory-anchor decision \"api,versioning\" \"Use URL-based versioning /v1/\"" ); println!( " st --memory-anchor pattern \"error,handling\" \"Always use Result with context\"" ); println!(" st --memory-anchor gotcha \"async,tokio\" \"Don't block the runtime with std::thread::sleep\""); println!(" st --memory-anchor todo \"refactor,auth\" \"Split auth into separate crate\"\n"); println!("MEMORY TYPES:"); println!(" insight - General knowledge and discoveries"); println!(" decision - Architectural or design decisions"); println!(" pattern - Code patterns and best practices"); println!(" gotcha - Pitfalls and things to avoid"); println!(" todo - Tasks and reminders\n"); println!("RELATED COMMANDS:"); println!(" st --memory-find Find memories by keywords"); println!(" st --memory-stats Show memory statistics\n"); println!("๐Ÿ’ก Memories persist across sessions in ~/.mem8/memories/"); Ok(()) } /// Anchor a memory async fn handle_memory_anchor(anchor_type: &str, keywords_str: &str, context: &str) -> Result<()> { use st::memory_manager::MemoryManager; let mut manager = MemoryManager::new()?; // Parse keywords (comma-separated) let keywords: Vec = keywords_str .split(',') .map(|s| s.trim().to_string()) .collect(); // Get origin from current directory let origin = std::env::current_dir()?.to_string_lossy().to_string(); manager.anchor(anchor_type, keywords, context, &origin)?; println!("\nโœจ Memory anchored successfully!"); println!("Use 'st --memory-find {}' to recall", keywords_str); Ok(()) } /// Find memories by keywords async fn handle_memory_find(keywords_str: &str) -> Result<()> { use st::memory_manager::MemoryManager; use st::std_client; let mut manager = MemoryManager::new()?; let keywords: Vec = keywords_str .split(',') .map(|s| s.trim().to_string()) .collect(); let memories = manager.find(&keywords)?; // Check if daemon is running - if so, show all memories (global view) // If standalone, only show memories from current directory or subdirectories let cwd = std::env::current_dir() .map(|p| p.to_string_lossy().to_string()) .unwrap_or_default(); let daemon_running = std_client::is_daemon_running().await; let filtered: Vec<_> = if daemon_running { // Daemon provides global memory access memories } else { // Standalone: filter to current directory scope memories .into_iter() .filter(|m| m.origin.starts_with(&cwd) || cwd.starts_with(&m.origin)) .collect() }; if filtered.is_empty() { if daemon_running { println!("๐Ÿ” No memories found for: {}", keywords_str); } else { println!( "๐Ÿ” No memories found for '{}' in this directory", keywords_str ); println!(" ๐Ÿ’ก Start daemon for global memory: st --daemon-start"); } } else { println!( "๐Ÿง  Found {} memories{}:", filtered.len(), if daemon_running { " (global)" } else { " (local)" } ); println!("{}", "โ”€".repeat(45)); for (i, memory) in filtered.iter().enumerate() { println!( "\n[{}] {} @ {:.2}Hz", i + 1, memory.anchor_type, memory.frequency ); println!("๐Ÿ“ {}", memory.context); println!("๐Ÿท๏ธ Keywords: {}", memory.keywords.join(", ")); println!("๐Ÿ“ Origin: {}", memory.origin); println!("โฐ {}", memory.timestamp.format("%Y-%m-%d %H:%M")); } } Ok(()) } /// Show memory statistics async fn handle_memory_stats() -> Result<()> { use st::memory_manager::MemoryManager; let manager = MemoryManager::new()?; println!("๐Ÿ“Š {}", manager.stats()); Ok(()) } /// Handle hooks configuration for Claude Code async fn handle_hooks_config(action: &str) -> Result<()> { use serde_json::Value; use std::fs; let config_path = get_agent_config_path()?; match action { "enable" => { println!("๐ŸŽฃ Enabling Smart Tree hooks for Claude Code..."); update_agent_hooks(&config_path, true)?; println!("โœ… Hooks enabled! Smart Tree will provide context to Claude Code."); println!("๐Ÿ“ Hook command: st --agent-context"); } "disable" => { println!("๐ŸŽฃ Disabling Smart Tree hooks..."); update_agent_hooks(&config_path, false)?; println!("โœ… Hooks disabled."); } "status" => { println!("๐ŸŽฃ Claude Code Hooks Status"); println!("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"); if let Ok(content) = fs::read_to_string(&config_path) { if let Ok(config) = serde_json::from_str::(&content) { // Check for hooks in the config let has_hooks = config .get("hooks") .and_then(|h| h.as_object()) .map(|h| !h.is_empty()) .unwrap_or(false); if has_hooks { println!("โœ… Hooks are configured"); if let Some(hooks) = config.get("hooks") { println!("\nConfigured hooks:"); if let Some(obj) = hooks.as_object() { for (hook_type, command) in obj { println!(" โ€ข {}: {}", hook_type, command); } } } } else { println!("โŒ No hooks configured"); println!("\nTo enable: st --hooks-config enable"); } } } else { println!( "โš ๏ธ AI Agent config not found at: {}", config_path.display() ); println!("\nMake sure your AI Agent is installed."); } } _ => { eprintln!("โŒ Unknown action: {}", action); eprintln!("Valid actions: enable, disable, status"); return Err(anyhow::anyhow!("Invalid hooks action")); } } Ok(()) } /// Install Smart Tree hooks directly into Claude Code settings async fn install_hooks_to_agent() -> Result<()> { println!("๐ŸŽฃ Installing Smart Tree hooks for AI Agents..."); println!("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"); let config_path = get_agent_config_path()?; // Create or update the hooks configuration update_agent_hooks(&config_path, true)?; println!("\nโœ… Hooks installed successfully!"); println!("\n๐Ÿ“ What's been configured:"); println!(" โ€ข UserPromptSubmit: Adds project context to your prompts"); println!(" โ€ข Command: st --agent-context"); println!("\n๐Ÿš€ Smart Tree will now automatically provide context to your AI agents!"); Ok(()) } /// Get the Claude Code configuration path fn get_agent_config_path() -> Result { let home = std::env::var("HOME")?; let config_path = PathBuf::from(home) .join("Library") .join("Application Support") .join("Claude") .join("config.json"); Ok(config_path) } /// Update Claude Code hooks configuration fn update_agent_hooks(config_path: &PathBuf, enable: bool) -> Result<()> { use serde_json::{json, Value}; use std::fs; // Read existing config or create new one let mut config: Value = if config_path.exists() { let content = fs::read_to_string(config_path)?; serde_json::from_str(&content).unwrap_or_else(|_| json!({})) } else { // Create the directory if it doesn't exist if let Some(parent) = config_path.parent() { fs::create_dir_all(parent)?; } json!({}) }; if enable { // Get the st binary path - prefer the installed version let st_path = which::which("st") .map(|p| p.to_string_lossy().to_string()) .unwrap_or_else(|_| { // Fallback to common installation paths if std::path::Path::new("/usr/local/bin/st").exists() { "/usr/local/bin/st".to_string() } else if std::path::Path::new("/opt/homebrew/bin/st").exists() { "/opt/homebrew/bin/st".to_string() } else { "st".to_string() // Hope it's in PATH } }); // Ensure hooks object exists if config.get("hooks").is_none() { config["hooks"] = json!({}); } // Update or add the UserPromptSubmit hook (not duplicate!) config["hooks"]["UserPromptSubmit"] = json!(format!("{} --agent-context", st_path)); println!("๐Ÿ“ Using st binary at: {}", st_path); } else { // Remove hooks if let Some(hooks) = config.get_mut("hooks") { if let Some(obj) = hooks.as_object_mut() { obj.remove("UserPromptSubmit"); // If no hooks left, remove the hooks object entirely if obj.is_empty() { config.as_object_mut().unwrap().remove("hooks"); } } } } // Write back the config with pretty formatting let pretty_json = serde_json::to_string_pretty(&config)?; fs::write(config_path, pretty_json)?; Ok(()) } // ============================================================================= // DAEMON MANAGEMENT HANDLERS // ============================================================================= /// Start the Smart Tree daemon in the background async fn handle_daemon_start(_port: u16) -> Result<()> { use st::std_client; // Check if std daemon is already running (Unix socket) if std_client::is_daemon_running().await { println!("๐ŸŒณ Smart Tree Daemon is already running!"); println!(" Socket: {}", std_client::socket_path().display()); return Ok(()); } // Start the std daemon println!("๐ŸŒณ Starting Smart Tree Daemon..."); match std_client::start_daemon().await { Ok(true) => { println!("โœ… Daemon started successfully!"); println!(" Socket: {}", std_client::socket_path().display()); // Verify with a ping if let Some(mut client) = std_client::StdClient::connect().await { if client.ping().await.unwrap_or(false) { println!(" Status: responding to PING"); } } } Ok(false) => { println!("โš ๏ธ Daemon was already running."); } Err(e) => { eprintln!("โŒ Failed to start daemon: {}", e); return Err(e); } } Ok(()) } /// Stop a running Smart Tree daemon async fn handle_daemon_stop(_port: u16) -> Result<()> { use st::std_client; // Check if std daemon is running if !std_client::is_daemon_running().await { println!("โš ๏ธ No daemon running"); return Ok(()); } println!("๐ŸŒณ Stopping Smart Tree Daemon..."); // Remove the socket file to signal shutdown let socket = std_client::socket_path(); if socket.exists() { // Try to connect and send a graceful shutdown (future: add SHUTDOWN verb) // For now, just remove the socket - daemon will exit on next connection attempt if let Err(e) = std::fs::remove_file(&socket) { eprintln!("โš ๏ธ Could not remove socket: {}", e); } else { println!("โœ… Daemon socket removed"); println!(" Note: Daemon process may still be running - use 'pkill std' if needed"); } } Ok(()) } /// Show the status of the Smart Tree daemon async fn handle_daemon_status(_port: u16) -> Result<()> { use st::std_client; let socket = std_client::socket_path(); println!("โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—"); if std_client::is_daemon_running().await { println!("โ•‘ ๐ŸŒณ SMART TREE DAEMON STATUS: RUNNING ๐ŸŒณ โ•‘"); println!("โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ"); println!("โ•‘ Socket: {:<48} โ•‘", socket.display()); // Fetch stats from daemon if let Some(mut client) = std_client::StdClient::connect().await { if let Ok(stats) = client.stats().await { let version = stats["version"].as_str().unwrap_or("?"); let protocol = stats["protocol"].as_str().unwrap_or("?"); let memories = stats["memories"].as_u64().unwrap_or(0); let waves = stats["active_waves"].as_u64().unwrap_or(0); let keywords = stats["keywords"].as_u64().unwrap_or(0); println!("โ•‘ Version: {:<47} โ•‘", version); println!("โ•‘ Protocol: {:<46} โ•‘", protocol); println!("โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ"); println!("โ•‘ Memories: {:<46} โ•‘", memories); println!("โ•‘ Active waves: {:<42} โ•‘", waves); println!("โ•‘ Keywords: {:<46} โ•‘", keywords); } } } else { println!("โ•‘ ๐ŸŒณ SMART TREE DAEMON STATUS: STOPPED ๐Ÿ›‘ โ•‘"); println!("โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ"); println!("โ•‘ The daemon is not running. โ•‘"); println!("โ•‘ Start with: st --daemon-start โ•‘"); } println!("โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•"); Ok(()) } /// Get context from the daemon (or auto-start if not running) async fn handle_daemon_context(port: u16) -> Result<()> { use st::daemon_client::print_context_summary; let client = DaemonClient::new(port); // Ensure daemon is running (auto-start if needed) match client.ensure_running().await { Ok(_) => { if let Ok(ctx) = client.get_context().await { print_context_summary(&ctx); } else { eprintln!("โŒ Failed to get context from daemon"); } } Err(e) => { eprintln!("โŒ Failed to connect to daemon: {}", e); return Err(e); } } Ok(()) } /// List projects from the daemon async fn handle_daemon_projects(port: u16) -> Result<()> { use st::daemon_client::print_projects; let client = DaemonClient::new(port); // Ensure daemon is running (auto-start if needed) match client.ensure_running().await { Ok(_) => { if let Ok(projects) = client.get_projects().await { print_projects(&projects); } else { eprintln!("โŒ Failed to get projects from daemon"); } } Err(e) => { eprintln!("โŒ Failed to connect to daemon: {}", e); return Err(e); } } Ok(()) } /// Show Foken credits from the daemon async fn handle_daemon_credits(port: u16) -> Result<()> { use st::daemon_client::print_credits; let client = DaemonClient::new(port); // Ensure daemon is running (auto-start if needed) match client.ensure_running().await { Ok(_) => { if let Ok(credits) = client.get_credits().await { print_credits(&credits); } else { eprintln!("โŒ Failed to get credits from daemon"); } } Err(e) => { eprintln!("โŒ Failed to connect to daemon: {}", e); return Err(e); } } Ok(()) } // ============================================================================= // MCP INSTALLATION HANDLERS // ============================================================================= /// Install Smart Tree as MCP server in Desktop configs async fn handle_mcp_install() -> Result<()> { use st::claude_init::install_mcp_to_desktop; println!("๐Ÿ“ฆ Installing Smart Tree MCP server to AI agents..."); match install_mcp_to_desktop() { Ok(msg) => println!("{}", msg), Err(e) => { eprintln!("โŒ Installation failed: {}", e); eprintln!("\n๐Ÿ’ก Manual setup:"); print_mcp_config(); return Err(e); } } Ok(()) } /// Uninstall Smart Tree MCP server from Desktop configs async fn handle_mcp_uninstall() -> Result<()> { use st::claude_init::uninstall_mcp_from_desktop; println!("๐Ÿ—‘๏ธ Uninstalling Smart Tree MCP server from AI agents..."); match uninstall_mcp_from_desktop() { Ok(msg) => println!("{}", msg), Err(e) => { eprintln!("โŒ Uninstall failed: {}", e); return Err(e); } } Ok(()) } /// Check MCP installation status in Claude Desktop async fn handle_mcp_status() -> Result<()> { use st::claude_init::check_mcp_installation_status; match check_mcp_installation_status() { Ok(msg) => println!("{}", msg), Err(e) => { eprintln!("โš ๏ธ Could not check MCP status: {}", e); return Err(e); } } Ok(()) } // ============================================================================= // SECURITY CLEANUP HANDLER // ============================================================================= /// Run security cleanup to detect and remove malicious MCP entries async fn handle_security_cleanup() -> Result<()> { use st::ai_install::run_security_cleanup; println!("๐Ÿ”’ Running security cleanup..."); run_security_cleanup(false)?; Ok(()) } // ============================================================================= // MEGA SESSION HANDLERS // ============================================================================= /// Start a new mega session async fn handle_mega_start(name: Option<&str>) -> Result<()> { use st::mega_session_manager::MegaSessionManager; let mut manager = MegaSessionManager::new()?; let session_id = manager.start_session(name.map(|s| s.to_string()))?; println!("๐Ÿš€ Mega session started!"); println!(" ID: {}", session_id); if let Some(n) = name { if !n.is_empty() { println!(" Name: {}", n); } } println!("\n๐Ÿ’ก Use 'st --mega-save' to save session state"); println!(" Use 'st --mega-list' to see all sessions"); Ok(()) } /// Save current mega session async fn handle_mega_save() -> Result<()> { use st::mega_session_manager::MegaSessionManager; let manager = MegaSessionManager::new()?; manager.save_current_session()?; println!("๐Ÿ’พ Mega session saved!"); Ok(()) } /// List all mega sessions async fn handle_mega_list() -> Result<()> { use st::mega_session_manager::MegaSessionManager; let manager = MegaSessionManager::new()?; let sessions = manager.list_sessions()?; if sessions.is_empty() { println!("๐Ÿ“‹ No mega sessions found"); println!("\n๐Ÿ’ก Start one with: st --mega-start [NAME]"); } else { println!("๐Ÿ“‹ Mega Sessions ({}):", sessions.len()); println!("{}", "โ”€".repeat(45)); for (i, session) in sessions.iter().enumerate() { println!(" [{}] {}", i + 1, session); } } Ok(()) } /// Show mega session statistics async fn handle_mega_stats() -> Result<()> { use st::mega_session_manager::MegaSessionManager; let manager = MegaSessionManager::new()?; println!("{}", manager.get_stats()); Ok(()) } // ============================================================================= // AI GUARDIAN - System-wide protection daemon // ============================================================================= /// Run the Guardian daemon (called by systemd service) async fn run_guardian_daemon() -> Result<()> { use st::ai_guardian::AiGuardian; use tokio::time::{sleep, Duration}; println!( r#" โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— โ•‘ โ•‘ โ•‘ ๐Ÿ›ก๏ธ SMART TREE GUARDIAN - System-wide AI Protection Active ๐Ÿ›ก๏ธ โ•‘ โ•‘ โ•‘ โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• "# ); let guardian = AiGuardian::new(); // Watch paths for suspicious files let watch_paths = vec!["/home", "/tmp", "/var/tmp"]; println!("Watching paths: {:?}", watch_paths); println!("Press Ctrl+C to stop.\n"); // Main protection loop loop { for watch_path in &watch_paths { let path = std::path::Path::new(watch_path); if !path.exists() { continue; } // Scan for new/modified files with potential injection content scan_directory_for_threats(&guardian, path); } // Sleep between scans sleep(Duration::from_secs(60)).await; } } /// Scan a directory for prompt injection threats #[allow(dead_code)] fn scan_directory_for_threats(guardian: &st::ai_guardian::AiGuardian, path: &std::path::Path) { use walkdir::WalkDir; let suspicious_extensions = [ "md", "txt", "json", "yaml", "yml", "toml", "sh", "py", "js", "ts", ]; for entry in WalkDir::new(path) .max_depth(3) .follow_links(false) .into_iter() .filter_map(|e| e.ok()) { let entry_path = entry.path(); // Skip hidden files and directories if entry_path .file_name() .map(|n| n.to_string_lossy().starts_with('.')) .unwrap_or(false) { continue; } // Only scan text files that could contain injection attempts if let Some(ext) = entry_path.extension() { let ext_str = ext.to_string_lossy().to_lowercase(); if suspicious_extensions.contains(&ext_str.as_str()) { let threats = guardian.scan_file(entry_path); // Report critical and dangerous threats for threat in threats { if matches!( threat.level, st::ai_guardian::ThreatLevel::Critical | st::ai_guardian::ThreatLevel::Dangerous ) { eprintln!( "โš ๏ธ THREAT DETECTED [{:?}] in {}: {}", threat.level, threat.location, threat.pattern ); eprintln!(" Context: {}", threat.context); eprintln!(" Recommendation: {}", threat.recommendation); eprintln!(); } } } } } } /// Scan a specific file for prompt injection #[allow(dead_code)] fn handle_guardian_scan(file_path: &std::path::Path) -> Result<()> { use st::ai_guardian::{AiGuardian, ThreatLevel}; println!( r#" โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— โ•‘ ๐Ÿ›ก๏ธ SMART TREE GUARDIAN - File Scan ๐Ÿ›ก๏ธ โ•‘ โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• "# ); println!("Scanning: {}\n", file_path.display()); let guardian = AiGuardian::new(); let threats = guardian.scan_file(file_path); if threats.is_empty() { println!("โœ… No threats detected. File appears safe."); return Ok(()); } // Count by severity let critical = threats .iter() .filter(|t| t.level == ThreatLevel::Critical) .count(); let dangerous = threats .iter() .filter(|t| t.level == ThreatLevel::Dangerous) .count(); let suspicious = threats .iter() .filter(|t| t.level == ThreatLevel::Suspicious) .count(); println!("Found {} issues:\n", threats.len()); println!(" ๐Ÿ”ด Critical: {}", critical); println!(" ๐ŸŸ  Dangerous: {}", dangerous); println!(" ๐ŸŸก Suspicious: {}", suspicious); println!(); for threat in &threats { let icon = match threat.level { ThreatLevel::Critical => "๐Ÿ”ด", ThreatLevel::Dangerous => "๐ŸŸ ", ThreatLevel::Suspicious => "๐ŸŸก", ThreatLevel::Safe => "๐ŸŸข", }; println!("{} [{:?}] {}", icon, threat.level, threat.pattern); println!(" Location: {}", threat.location); if !threat.context.is_empty() { println!(" Context: {}", threat.context); } println!(" Action: {}", threat.recommendation); println!(); } if critical > 0 { println!("โ›” RECOMMENDATION: DO NOT process this file with AI assistants."); println!(" Critical threats detected that could compromise AI behavior."); } else if dangerous > 0 { println!("โš ๏ธ RECOMMENDATION: Review this file carefully before AI processing."); println!(" Potentially dangerous patterns detected."); } Ok(()) } /// Handle Google Drive CLI commands async fn handle_gdrive_command(action: &str) -> Result<()> { #[cfg(not(feature = "google"))] { let _ = action; eprintln!( "Google Drive support is not enabled in this build. Recompile with --features google" ); std::process::exit(1); } #[cfg(feature = "google")] { match action { "status" => { let auth = st::google_sync::auth::GoogleAuth::default_store()?; let status = auth.status_summary(); println!( "๐ŸŒ Google Drive / Gmail Auth Status:\n===================================\n{}", status ); Ok(()) } "login" => { println!("๐Ÿ”‘ To authenticate Google Drive / Gmail, use the MCP 'google' tool with operation: 'auth_login',\n or place OAuth2 credentials in ~/.st/google/config.json"); Ok(()) } "logout" => { let auth = st::google_sync::auth::GoogleAuth::default_store()?; auth.logout()?; println!("โœ… Google credentials removed from ~/.st/google/"); Ok(()) } "list" => { let auth = st::google_sync::auth::GoogleAuth::default_store()?; if !auth.has_cached_tokens() { println!("Not authenticated with Google. Please configure ~/.st/google/ or authenticate via MCP."); return Ok(()); } let authenticator = st::mcp::google::gmail_tools::rebuild_authenticator(&auth).await?; let drive = st::google_sync::drive_client::DriveClient::new(authenticator); let (files, _) = drive.list_files("root", None).await?; println!( "๐Ÿ“ Google Drive Root Files ({} found):\n=====================================", files.len() ); for f in &files { let kind = if f.is_folder { "[DIR] " } else { " " }; println!(" โ€ข {}{} ({})", kind, f.name, f.mime_type); } Ok(()) } "sync" => { let auth = st::google_sync::auth::GoogleAuth::default_store()?; if !auth.has_cached_tokens() { println!("Not authenticated with Google. Please configure ~/.st/google/ or authenticate via MCP."); return Ok(()); } let authenticator = st::mcp::google::gmail_tools::rebuild_authenticator(&auth).await?; let drive = st::google_sync::drive_client::DriveClient::new(authenticator); let engine = st::google_sync::sync_engine::SyncEngine::new(&drive)?; let mut state = st::google_sync::models::SyncState { sync_id: "default_sync".to_string(), local_path: ".".to_string(), drive_folder_id: "root".to_string(), last_sync: None, direction: st::google_sync::models::SyncDirection::Bidirectional, file_states: Vec::new(), conflict_resolution: st::google_sync::models::ConflictStrategy::NewerWins, }; let report = engine.sync(&mut state).await?; println!("๐Ÿ”„ Google Drive Sync Complete:\n Uploaded: {}\n Downloaded: {}\n Conflicts: {}\n Skipped: {}\n Duration: {}ms", report.uploaded, report.downloaded, report.conflicts, report.skipped, report.duration_ms); Ok(()) } _ => { eprintln!( "Unknown gdrive action: '{}'. Valid actions: status, login, logout, list, sync", action ); std::process::exit(1); } } } } /// Handle Voice CLI commands async fn handle_voice_command(action: &str) -> Result<()> { #[cfg(not(feature = "voice"))] { let _ = action; eprintln!("Voice support is not enabled in this build. Recompile with --features voice"); std::process::exit(1); } #[cfg(feature = "voice")] { if action == "status" { let vad = st::vad_marine::MarineVAD::new()?; let is_active = vad.is_voice_active().await; let salience = vad.get_salience().await; let quality = vad.get_voice_quality().await; println!("๐ŸŽ™๏ธ Voice Activity & Marine VAD Status\n===================================="); println!( "Status: {}", if is_active { "Active (Voice Detected)" } else { "Idle / Ambient" } ); println!("Salience: {:.1}%", salience * 100.0); println!("Harmonicity: {:.3}", quality.harmonicity); println!("Zero Crossing Rate: {:.3}", quality.zero_crossing_rate); println!("Energy Variance: {:.3}", quality.energy_variance); println!("Spectral Tilt: {:.3}", quality.spectral_tilt); Ok(()) } else if action == "test" { let vad = st::vad_marine::MarineVAD::new()?; let sample_rate = 16000; let frequency = 220.0; let duration = 0.2; let num_samples = (sample_rate as f64 * duration) as usize; let mut samples = vec![0.0f32; num_samples]; for (i, sample) in samples.iter_mut().enumerate() { let t = i as f64 / sample_rate as f64; *sample = (2.0 * std::f64::consts::PI * frequency * t).sin() as f32 * 0.6; } let is_voice = vad.process_audio(&samples, sample_rate).await?; let salience = vad.get_salience().await; println!("๐Ÿงช Marine VAD Test Run:\n Voice detected: {}\n Salience: {:.1}%\n Algorithm: MEM8 Marine Salience", is_voice, salience * 100.0); Ok(()) } else if action.starts_with("speak") { let text = action.strip_prefix("speak").unwrap_or("").trim(); let text = if text.is_empty() { "Smart Tree voice output initialized" } else { text }; let wav = st::web_dashboard::voice::generate_speech_wav(text, "aye"); println!( "๐Ÿ”Š Synthesized speech WAV: {} bytes (16kHz PCM mono, persona 'aye')", wav.len() ); Ok(()) } else { eprintln!( "Unknown voice action: '{}'. Valid actions: status, test, speak [text]", action ); std::process::exit(1); } } }