tmux-tango 2.7.3

A CLI tool for managing tmux sessions - dance between your sessions!
Documentation
use anyhow::Result;
use clap::{Parser, Subcommand};

mod error;
mod hooks;
mod status;
mod tmux;
mod interactive;

use error::TmuxFzfError;
use tmux::TmuxClient;
use interactive::InteractiveSelector;

#[derive(Parser)]
#[command(name = "tango")]
#[command(about = "A CLI tool for managing tmux sessions - dance between your sessions!")]
#[command(version)]
struct Cli {
    #[command(subcommand)]
    command: Option<Commands>,
}

#[derive(Subcommand)]
enum Commands {
    /// List all tmux sessions
    List,
    /// Attach to a session
    Attach {
        /// Session name to attach to
        session: String,
    },
    /// Kill a session
    Kill {
        /// Session name to kill
        session: String,
    },
    /// Create a new session
    New {
        /// Optional session name
        name: Option<String>,
    },
    /// Rename a session
    Rename {
        /// Current session name
        old_name: String,
        /// New session name
        new_name: String,
    },
    /// Manage Claude Code hooks for status monitoring
    Hooks {
        #[command(subcommand)]
        action: HooksAction,
    },
}

#[derive(Subcommand)]
enum HooksAction {
    /// Install Claude Code hooks for pane status detection
    Install,
    /// Remove TmuxTango hooks from Claude Code settings
    Uninstall,
    /// Show hook installation status
    Status,
}

fn check_dependencies() -> Result<(), TmuxFzfError> {
    which::which("tmux")
        .map_err(|_| TmuxFzfError::DependencyMissing("tmux".to_string()))?;

    Ok(())
}

fn main() -> Result<()> {
    let cli = Cli::parse();

    check_dependencies()?;

    if let Err(e) = run_cli(&cli) {
        match e {
            TmuxFzfError::UserCancelled => {
                std::process::exit(0);
            }
            _ => {
                eprintln!("Error: {}", e);
                std::process::exit(1);
            }
        }
    }

    Ok(())
}

fn run_cli(cli: &Cli) -> Result<(), TmuxFzfError> {
    let mut tmux_client = TmuxClient::new();

    match &cli.command {
        Some(Commands::List) => {
            let sessions = tmux_client.list_sessions()?;
            if sessions.is_empty() {
                println!("No tmux sessions found");
            } else {
                for session in sessions {
                    let status = if session.is_attached() { " (attached)" } else { "" };
                    println!("{}: {} windows{}", session.name, session.windows, status);
                }
            }
        },
        Some(Commands::Attach { session }) => {
            tmux_client.attach_session(session)?;
        },
        Some(Commands::Kill { session }) => {
            tmux_client.kill_session(session)?;
            println!("Killed session '{}'", session);
        },
        Some(Commands::New { name }) => {
            let session_name = tmux_client.new_session(name.as_deref())?;
            println!("Created session '{}'", session_name);
        },
        Some(Commands::Rename { old_name, new_name }) => {
            tmux_client.rename_session(old_name, new_name)?;
            println!("Renamed session '{}' to '{}'", old_name, new_name);
        },
        Some(Commands::Hooks { action }) => {
            let result = match action {
                HooksAction::Install => hooks::install(),
                HooksAction::Uninstall => hooks::uninstall(),
                HooksAction::Status => hooks::status(),
            };
            result.map_err(|e| TmuxFzfError::TmuxError(e.to_string()))?;
        },
        None => {
            hooks::ensure_installed();
            let mut selector = InteractiveSelector::new();
            selector.run()?;
        }
    }

    Ok(())
}