use clap::Parser;
use indodax_cli::errors::IndodaxError;
use indodax_cli::mcp;
use indodax_cli::output::{CommandOutput, OutputFormat};
use indodax_cli::{
client::IndodaxClient,
commands::utility::{execute as utility_execute, UtilityCommand},
config::IndodaxConfig,
dispatch, map_anyhow_error, Cli, Command,
};
use std::io::BufRead;
use std::process;
#[tokio::main]
async fn main() {
std::panic::set_hook(Box::new(|info| {
let message = if let Some(s) = info.payload().downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = info.payload().downcast_ref::<String>() {
s.clone()
} else {
"unexpected internal error".to_string()
};
let location = info
.location()
.map(|l| format!(" at {}:{}", l.file(), l.line()))
.unwrap_or_default();
eprintln!("Internal error: {}{}", message, location);
std::process::exit(1);
}));
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "indodax_cli=info".into()),
)
.with_target(true)
.init();
let cli = Cli::parse();
let output_format = cli.output;
let mut config = match IndodaxConfig::load() {
Ok(c) => c,
Err(e) => {
report_error(&IndodaxError::Other(e.to_string()), output_format);
process::exit(1);
}
};
let api_secret = if cli.api_secret_stdin {
let mut secret = String::new();
if let Err(e) = std::io::stdin().lock().read_line(&mut secret) {
report_error(
&IndodaxError::Other(format!("Failed to read API secret from stdin: {}", e)),
output_format,
);
process::exit(1);
}
let trimmed = secret.trim().to_string();
if trimmed.is_empty() {
eprintln!("Warning: --api-secret-stdin used but no input received (empty line).");
cli.api_secret.clone()
} else {
Some(trimmed)
}
} else {
cli.api_secret.clone()
};
let creds = match config.resolve_credentials(cli.api_key.clone(), api_secret) {
Ok(c) => c,
Err(e) => {
report_error(&IndodaxError::Other(e.to_string()), output_format);
process::exit(1);
}
};
let signer = creds
.as_ref()
.map(|c| indodax_cli::auth::Signer::new(c.api_key.as_str(), c.api_secret.as_str()));
let client = match IndodaxClient::new(signer) {
Ok(c) => c.with_ws_token(config.ws_token.as_ref().map(|t| t.as_str().to_string())),
Err(e) => {
report_error(&e, output_format);
process::exit(1);
}
};
if let Command::Mcp {
groups,
allow_dangerous,
port,
http,
} = &cli.command
{
#[cfg(feature = "mcp")]
{
if *http {
#[cfg(feature = "server")]
{
match mcp::run_http(*port, groups, *allow_dangerous).await {
Ok(()) => process::exit(0),
Err(e) => {
report_error(&e, output_format);
process::exit(1);
}
}
}
#[cfg(not(feature = "server"))]
{
report_error(
&IndodaxError::Other("HTTP server feature not enabled. Rebuild with --features server".into()),
output_format,
);
process::exit(1);
}
} else {
match mcp::run(groups, *allow_dangerous, client, config).await {
Ok(()) => process::exit(0),
Err(e) => {
report_error(&e, output_format);
process::exit(1);
}
}
}
}
#[cfg(not(feature = "mcp"))]
{
let _ = (groups, allow_dangerous, port, http);
report_error(
&IndodaxError::Other("MCP feature not enabled".into()),
output_format,
);
process::exit(1);
}
}
let result: Result<CommandOutput, IndodaxError> = match &cli.command {
Command::Setup => utility_execute(&client, &creds, &UtilityCommand::Setup)
.await
.map_err(map_anyhow_error),
Command::Shell => utility_execute(&client, &creds, &UtilityCommand::Shell)
.await
.map_err(map_anyhow_error),
_ => dispatch(cli, &client, &mut config).await,
};
match result {
Ok(output) => {
if !output.suppress_final_output || output_format == OutputFormat::Table {
println!("{}", output.render());
}
}
Err(e) => {
report_error(&e, output_format);
process::exit(1);
}
}
}
fn report_error(err: &IndodaxError, format: OutputFormat) {
if format == OutputFormat::Json {
let envelope = serde_json::json!({
"success": false,
"data": null,
"error": true,
"message": err.to_string(),
"error_type": err.category(),
"retryable": err.is_retryable(),
});
match serde_json::to_string_pretty(&envelope) {
Ok(s) => println!("{}", s),
Err(_) => eprintln!("Error: {}", err),
}
} else {
eprintln!("Error: {}", err);
}
}