use clap::Parser;
use intent_engine::cli::{Cli, Commands, DashboardCommands};
use intent_engine::cli_handlers::{
handle_config_command, handle_dashboard_command, handle_doctor_command, handle_init_command,
handle_log, handle_search_command, handle_status, handle_task_command, print_plan_result,
read_stdin,
};
use intent_engine::error::{IntentError, Result};
use intent_engine::events::EventManager;
use intent_engine::logging::LoggingConfig;
use intent_engine::plan::{
cleanup_included_files, process_file_includes, PlanExecutor, PlanRequest,
};
use intent_engine::project::ProjectContext;
use intent_engine::tasks::TaskManager;
use intent_engine::workspace::WorkspaceManager;
use std::io::IsTerminal;
#[tokio::main]
async fn main() {
#[cfg(windows)]
if let Err(e) = intent_engine::windows_console::setup_windows_console() {
eprintln!("Warning: Failed to setup Windows console UTF-8: {}", e);
eprintln!(
"Chinese characters may not display correctly. Consider running 'chcp 65001' first."
);
}
let cli = Cli::parse();
let mut log_config = LoggingConfig::from_args(cli.quiet, cli.verbose > 0, cli.json);
if matches!(
cli.command,
Commands::Dashboard(DashboardCommands::Start { .. })
) {
let force_file_log = std::env::var("IE_DASHBOARD_LOG_FILE").is_ok();
if force_file_log || !std::io::stdout().is_terminal() {
use intent_engine::logging::{log_file_path, ApplicationMode};
log_config = LoggingConfig::for_mode(ApplicationMode::Dashboard);
log_config.file_output = Some(log_file_path(ApplicationMode::Dashboard));
}
}
if let Err(e) = intent_engine::logging::init_logging(log_config) {
eprintln!("Failed to initialize logging: {}", e);
std::process::exit(1);
}
if matches!(
cli.command,
Commands::Dashboard(DashboardCommands::Start { .. })
) {
use intent_engine::logging::cleanup_old_logs;
let log_dir = dirs::home_dir().map(|h| h.join(".intent-engine").join("logs"));
if let Some(dir) = log_dir {
let retention_days = std::env::var("IE_LOG_RETENTION_DAYS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(7);
cleanup_old_logs(&dir, retention_days).ok();
}
}
if let Err(e) = run(&cli).await {
let error_response = e.to_error_response();
eprintln!("{}", serde_json::to_string_pretty(&error_response).unwrap());
std::process::exit(1);
}
}
async fn run(cli: &Cli) -> Result<()> {
match cli.command.clone() {
Commands::Plan { format } => {
let json_input = read_stdin()?;
let mut request: PlanRequest = serde_json::from_str(&json_input)
.map_err(|e| IntentError::InvalidInput(format!("Invalid JSON: {}", e)))?;
let file_include_result =
process_file_includes(&mut request).map_err(IntentError::InvalidInput)?;
let ctx = ProjectContext::load_or_init().await?;
let project_path = ctx.root.to_string_lossy().to_string();
let workspace_mgr = WorkspaceManager::new(&ctx.pool);
let current = workspace_mgr.get_current_task(None).await?;
let mut executor = PlanExecutor::with_project_path(&ctx.pool, project_path);
if let Some(current_task_id) = current.current_task_id {
executor = executor.with_default_parent(current_task_id);
}
let result = executor.execute(&request).await?;
if result.success && !file_include_result.files_to_delete.is_empty() {
cleanup_included_files(&file_include_result.files_to_delete);
}
print_plan_result(&result, &format)?;
},
Commands::Log {
event_type,
message,
task,
format,
} => {
let ctx = ProjectContext::load_or_init().await?;
let project_path = ctx.root.to_string_lossy().to_string();
let event_mgr = EventManager::with_project_path(&ctx.pool, project_path);
let workspace_mgr = WorkspaceManager::new(&ctx.pool);
handle_log(
&event_mgr,
&workspace_mgr,
event_type,
&message,
task,
&format,
)
.await?;
},
Commands::Search {
query,
tasks,
events,
limit,
offset,
since,
until,
format,
} => {
let ctx = ProjectContext::load_or_init().await?;
let project_path = ctx.root.to_string_lossy().to_string();
let task_mgr = TaskManager::with_project_path(&ctx.pool, project_path);
handle_search_command(
&task_mgr, &query, tasks, events, limit, offset, since, until, &format,
)
.await?
},
Commands::Init { at, force } => handle_init_command(at, force).await?,
Commands::Dashboard(dashboard_cmd) => handle_dashboard_command(dashboard_cmd).await?,
Commands::Doctor => handle_doctor_command().await?,
Commands::Task(task_cmd) => {
let ctx = ProjectContext::load_or_init().await?;
let project_path = ctx.root.to_string_lossy().to_string();
let task_mgr = TaskManager::with_project_path(&ctx.pool, project_path);
let workspace_mgr = WorkspaceManager::new(&ctx.pool);
handle_task_command(&task_mgr, &workspace_mgr, task_cmd).await?
},
Commands::Suggestions(suggestions_cmd) => {
use intent_engine::cli::SuggestionsCommands;
use intent_engine::cli_handlers::suggestions_commands;
match suggestions_cmd {
SuggestionsCommands::List { format } => {
suggestions_commands::handle_list(&format).await?
},
SuggestionsCommands::Dismiss { id, all, format } => {
suggestions_commands::handle_dismiss(id, all, &format).await?
},
SuggestionsCommands::Clear { format } => {
suggestions_commands::handle_clear(&format).await?
},
}
},
Commands::Config(config_cmd) => handle_config_command(config_cmd).await?,
Commands::Status {
task_id,
with_events,
format,
} => {
let ctx = ProjectContext::load_or_init().await?;
let task_mgr = TaskManager::new(&ctx.pool);
let workspace_mgr = WorkspaceManager::new(&ctx.pool);
intent_engine::llm::analyze_task_structure_background(ctx.pool.clone());
handle_status(&task_mgr, &workspace_mgr, task_id, with_events, &format).await?;
if format != "json" {
intent_engine::llm::display_suggestions(&ctx.pool).await?;
}
},
}
Ok(())
}