jira-commands 0.2.0

Jira CLI — terminal client for Atlassian Jira
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use jira_core::{config::JiraConfig, JiraClient};
use tracing_subscriber::{fmt, EnvFilter};

mod cli;
mod tui;

#[derive(Debug, Parser)]
#[command(
    name = "jira",
    about = "Jira CLI — terminal client for Atlassian Jira",
    long_about = "A fast Jira terminal client built in Rust.\n\nQuick start:\n  jira auth login                      Set up credentials\n  jira issue list                      List your assigned issues\n  jira issue list -p MYPROJ            List issues by project\n  jira issue view PROJ-123             View issue detail\n  jira issue create -p MYPROJ          Create an issue (interactive)\n  jira issue transition PROJ-123       Transition an issue (interactive)\n  jira issue attach PROJ-123 file.png  Upload attachment\n  jira issue worklog list PROJ-123     List worklogs on an issue\n  jira issue bulk-transition PROJ -q 'status = \"To Do\"'\n  jira plan list                       List Jira Plans (Premium)\n  jira api get /rest/api/3/serverInfo  Raw API passthrough\n  jira tui -p MYPROJ                   Launch interactive TUI\n\nConfig file: ~/.config/jira/config.toml\nEnv vars:    JIRA_URL, JIRA_EMAIL, JIRA_TOKEN",
    version
)]
struct Cli {
    #[command(subcommand)]
    command: Commands,

    /// Enable verbose logging (sets RUST_LOG=debug)
    #[arg(short, long, global = true)]
    verbose: bool,
}

#[derive(Debug, Subcommand)]
enum Commands {
    /// Create, list, view, update, delete, transition, and attach files to issues
    Issue {
        #[command(subcommand)]
        command: cli::issue::IssueCommand,
    },
    /// Manage credentials (login, logout, status, update)
    Auth {
        #[command(subcommand)]
        command: cli::auth::AuthCommand,
    },
    /// Launch the interactive TUI — browse and transition issues with keyboard shortcuts
    Tui {
        /// Filter by project key (e.g. MYPROJ). Defaults to assignee = currentUser()
        #[arg(short, long)]
        project: Option<String>,
    },
    /// Execute raw Jira REST API calls and print JSON response
    Api {
        #[command(subcommand)]
        command: cli::api::ApiCommand,
    },
    /// Manage Jira Plans (requires Jira Premium / Advanced Roadmaps)
    Plan {
        #[command(subcommand)]
        command: cli::plan::PlanCommand,
    },
}

#[tokio::main]
async fn main() -> Result<()> {
    let cli = Cli::parse();

    // Initialize tracing
    let filter = if cli.verbose {
        EnvFilter::new("debug")
    } else {
        EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("warn"))
    };

    fmt().with_env_filter(filter).with_target(false).init();

    match cli.command {
        Commands::Auth { command } => {
            cli::auth::handle(command).await?;
        }
        Commands::Issue { command } => {
            let client = build_client().context("Failed to initialize Jira client")?;
            let config = JiraConfig::load().unwrap_or_default();
            cli::issue::handle(command, client, config.project).await?;
        }
        Commands::Tui { project } => {
            let client = build_client().context("Failed to initialize Jira client")?;
            let config = JiraConfig::load().unwrap_or_default();
            let effective_project = project.or(config.project);
            tui::run_tui(client, effective_project)
                .await
                .context("TUI error")?;
        }
        Commands::Api { command } => {
            let client = build_client().context("Failed to initialize Jira client")?;
            cli::api::handle(command, client).await?;
        }
        Commands::Plan { command } => {
            let client = build_client().context("Failed to initialize Jira client")?;
            cli::plan::handle(command, client).await?;
        }
    }

    Ok(())
}

fn build_client() -> Result<JiraClient> {
    let config = JiraConfig::load().unwrap_or_default();

    if config.base_url.is_empty() {
        anyhow::bail!(
            "Jira URL not configured. Run `jira auth login` or set JIRA_URL environment variable."
        );
    }

    if config.email.is_empty() {
        anyhow::bail!(
            "Email not configured. Run `jira auth login` or set JIRA_EMAIL environment variable."
        );
    }

    if config.token.is_none() {
        anyhow::bail!(
            "API token not found. Run `jira auth login` or set JIRA_TOKEN environment variable."
        );
    }

    Ok(JiraClient::new(config))
}