mod config;
mod events;
mod jira;
mod sources;
mod subcommands;
mod tui;
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "do-next", about = "Pick your next Jira task")]
struct Cli {
#[arg(long, value_name = "FILE")]
log: Option<std::path::PathBuf>,
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
Comment {
issue_key: Option<String>,
#[arg(long)]
no_history: bool,
},
Fields {
issue_key: String,
#[arg(long, value_name = "FIELD_ID")]
field: Option<String>,
#[arg(long, requires = "field")]
raw: bool,
},
Auth,
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
if let Some(ref log_path) = cli.log {
use simplelog::{Config, LevelFilter, WriteLogger};
let file = std::fs::File::create(log_path)
.with_context(|| format!("Failed to create log file: {}", log_path.display()))?;
WriteLogger::init(LevelFilter::Debug, Config::default(), file)
.context("Failed to initialise logger")?;
log::info!("do-next starting, logging to {}", log_path.display());
}
let mut loaded = config::load().context("Failed to load configuration")?;
if matches!(&cli.command, Some(Commands::Auth)) {
tui::onboarding::run_auth_reset(&mut loaded.config).context("Auth reset failed")?;
return Ok(());
}
if loaded.config.jira.base_url.is_empty() && loaded.teams.is_empty() {
loaded = tui::onboarding::run_onboarding().context("Onboarding failed")?;
}
if loaded.teams.is_empty() {
loaded =
tui::onboarding::run_team_setup(&mut loaded.config).context("Team setup failed")?;
}
let mut clients: std::collections::HashMap<String, jira::JiraClient> =
std::collections::HashMap::new();
for team in &loaded.teams {
let url = &team.jira.base_url;
if !clients.contains_key(url) {
let auth = config::credentials::resolve_auth(&team.jira)
.with_context(|| format!("Failed to resolve auth for team '{}'", team.id))?;
let client = jira::JiraClient::new(url.clone(), auth)
.with_context(|| format!("Failed to create Jira client for team '{}'", team.id))?;
clients.insert(url.clone(), client);
}
}
let default_client = if let Some(first_team) = loaded.teams.first() {
clients
.get(&first_team.jira.base_url)
.cloned()
.context("No Jira client available")?
} else {
let auth = config::credentials::resolve_auth(&loaded.config.jira)
.context("Failed to resolve Jira authentication")?;
jira::JiraClient::new(loaded.config.jira.base_url.clone(), auth)
.context("Failed to create Jira client")?
};
match cli.command {
Some(Commands::Comment {
issue_key,
no_history,
}) => {
subcommands::comment::run(&default_client, issue_key.as_deref(), no_history).await?;
}
Some(Commands::Fields {
issue_key,
field,
raw,
}) => {
subcommands::fields::run(&default_client, &issue_key, field.as_deref(), raw).await?;
}
Some(Commands::Auth) => {
unreachable!("handled before credential resolution")
}
None => {
tui::run(loaded, clients).await?;
}
}
Ok(())
}