use crate::core::event::EventLogger;
use crate::tui::app::App;
use crate::tui::event::{TuiEvent, spawn_event_listener};
use crate::tui::ui;
use crossterm::event::KeyEventKind;
use crossterm::execute;
use crossterm::terminal::{
EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
};
use ratatui::Terminal;
use ratatui::backend::CrosstermBackend;
use std::io::stdout;
use std::path::Path;
pub async fn execute(
project_root: &Path,
parallel_limit: usize,
watch_mode: bool,
pty_flag: bool,
) -> anyhow::Result<()> {
let forge_dir = project_root.join(".forge");
if !forge_dir.exists() {
anyhow::bail!("Forge is not initialized. Run `forge init` first.");
}
let pty_mode = if pty_flag {
true
} else {
let state_mgr = crate::core::state::StateManager::new(&forge_dir);
state_mgr
.load()
.map(|s| s.dashboard_mode == "pty")
.unwrap_or(false)
};
let original_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info| {
let _ = disable_raw_mode();
let _ = execute!(stdout(), LeaveAlternateScreen);
original_hook(panic_info);
}));
enable_raw_mode()?;
let mut stdout = stdout();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let (mut app, mut agent_rx, agent_tx) = App::new(
forge_dir.clone(),
project_root.to_path_buf(),
parallel_limit,
watch_mode,
);
app.pty_mode = pty_mode;
app.reload_tasks()?;
app.reset_orphaned_tasks()?;
app.reload_tasks()?;
let event_logger = EventLogger::new(&forge_dir);
if let Ok(recent) = event_logger.read_recent(20) {
for ev in recent {
let ts = ev.timestamp.format("%H:%M:%S");
app.events.push_back(format!("[{}] {}", ts, ev.message));
}
}
app.schedule_unblocked_tasks(&agent_tx);
app.spawn_idle_tuis(&agent_tx);
let (tui_tx, mut tui_rx) = tokio::sync::mpsc::unbounded_channel();
spawn_event_listener(tui_tx);
let mut interval = tokio::time::interval(tokio::time::Duration::from_millis(50));
loop {
terminal.draw(|f| ui::render(f, &app))?;
while let Ok(tui_event) = tui_rx.try_recv() {
match tui_event {
TuiEvent::Key(key) => {
if key.kind == KeyEventKind::Press {
app.handle_key(key, &agent_tx);
if app.should_quit {
break;
}
}
}
TuiEvent::Tick => {
app.handle_tick(&agent_tx)?;
}
TuiEvent::Resize(cols, rows) => {
app.handle_resize(cols, rows);
}
}
}
if app.should_quit {
break;
}
let mut agent_count = 0;
while let Ok(agent_event) = agent_rx.try_recv() {
app.handle_agent_event(agent_event, &agent_tx)?;
agent_count += 1;
if agent_count >= 50 {
break;
}
}
if agent_count == 0 {
tokio::select! {
Some(tui_event) = tui_rx.recv() => {
match tui_event {
TuiEvent::Key(key) => {
if key.kind == KeyEventKind::Press {
app.handle_key(key, &agent_tx);
}
}
TuiEvent::Tick => {
app.handle_tick(&agent_tx)?;
}
TuiEvent::Resize(cols, rows) => {
app.handle_resize(cols, rows);
}
}
}
Some(agent_event) = agent_rx.recv() => {
app.handle_agent_event(agent_event, &agent_tx)?;
}
_ = interval.tick() => {
}
}
}
}
app.cleanup_running_tasks();
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
terminal.show_cursor()?;
let completed = app
.tasks
.iter()
.filter(|t| t.status == crate::core::task::TaskStatus::Completed)
.count();
let failed = app
.tasks
.iter()
.filter(|t| t.status == crate::core::task::TaskStatus::Failed)
.count();
println!(
"Dashboard complete. {} completed, {} failed.",
completed, failed
);
Ok(())
}