#[cfg(feature = "tui")]
mod app;
#[cfg(feature = "tui")]
mod cache;
#[cfg(feature = "tui")]
pub mod chat_agent;
#[cfg(feature = "tui")]
pub mod command;
#[cfg(feature = "tui")]
pub mod config;
#[cfg(feature = "tui")]
mod cosmic_theme;
#[cfg(feature = "tui")]
pub mod diagnostics;
#[cfg(feature = "tui")]
mod edit_history;
#[cfg(feature = "tui")]
pub mod file_resolve;
#[cfg(feature = "tui")]
mod focus;
#[cfg(feature = "tui")]
pub mod git; #[cfg(feature = "tui")]
pub mod highlight;
#[cfg(feature = "tui")]
pub mod icons;
#[cfg(feature = "tui")]
mod keybindings;
#[cfg(feature = "tui")]
mod layout;
#[cfg(feature = "tui")]
mod mode;
#[cfg(feature = "tui")]
pub mod providers;
#[cfg(feature = "tui")]
pub mod selection;
#[cfg(feature = "tui")]
pub mod session;
#[cfg(feature = "tui")]
mod standalone;
#[cfg(feature = "tui")]
pub mod startup;
#[cfg(feature = "tui")]
mod state;
#[cfg(feature = "tui")]
mod theme;
#[cfg(feature = "tui")]
pub mod tokens;
#[cfg(feature = "tui")]
mod unicode;
#[cfg(feature = "tui")]
mod utils;
#[cfg(feature = "tui")]
mod verification;
#[cfg(feature = "tui")]
mod views;
#[cfg(feature = "tui")]
pub mod widgets;
#[cfg(feature = "tui")]
pub mod wizard;
#[cfg(feature = "tui")]
pub use app::App;
#[cfg(feature = "tui")]
pub use cache::RenderCache;
#[cfg(feature = "tui")]
pub use chat_agent::{ChatAgent, ChatMessage, ChatRole, StreamingState};
#[cfg(feature = "tui")]
pub use command::{Command, HELP_TEXT};
#[cfg(feature = "tui")]
pub use config::{
ChatSettings, ConfigError, PathSettings, StudioSettings, ThemeName, TuiConfig, TuiSettings,
};
#[cfg(feature = "tui")]
pub use cosmic_theme::CosmicTheme;
#[cfg(feature = "tui")]
pub use edit_history::EditHistory;
#[cfg(feature = "tui")]
pub use file_resolve::FileResolver;
#[cfg(feature = "tui")]
pub use focus::{FocusState, PanelId as NavPanelId};
#[cfg(feature = "tui")]
pub use highlight::{
HighlightCapture, HighlightTheme, Highlighter, SolarizedTheme, TreeSitterHighlighter,
};
#[cfg(feature = "tui")]
pub use icons::{IconMode, IconSet};
#[cfg(feature = "tui")]
pub use keybindings::{format_key, keybindings_for_context, KeyCategory, Keybinding};
#[cfg(feature = "tui")]
pub use layout::{LayoutMode, ResponsiveLayout};
#[cfg(feature = "tui")]
pub use mode::InputMode;
#[cfg(feature = "tui")]
pub use selection::{Position, Selection, SelectionMode, SelectionSet};
#[cfg(feature = "tui")]
pub use session::{
delete_session, get_latest_session, list_sessions, load_session, save_session, ChatSession,
SessionMeta,
};
#[cfg(feature = "tui")]
pub use standalone::{BrowserEntry, HistoryEntry, StandalonePanel, StandaloneState};
#[cfg(feature = "tui")]
pub use state::{
AgentTurnState,
PanelId,
PanelScrollState,
TuiMode,
TuiState,
FRAME_CYCLE,
FRAME_DIV_GLACIAL,
FRAME_DIV_NORMAL,
};
#[cfg(feature = "tui")]
pub use theme::{ColorMode, MissionPhase, TaskStatus, Theme, VerbColor};
#[cfg(feature = "tui")]
pub use tokens::{ColorPalette, CosmicVariant, SemanticColors, TokenResolver};
#[cfg(feature = "tui")]
pub use unicode::{display_width, truncate_to_width};
#[cfg(feature = "tui")]
pub use utils::{format_number, format_number_compact, format_number_u64};
#[cfg(feature = "tui")]
pub use verification::{VerificationCache, VerificationEntry};
#[cfg(feature = "tui")]
pub use views::{
ChatView, DagTab, HomeView, MissionTab, MonitorView, NovanetTab, ReasoningTab, SettingsView,
StudioView, TuiView, View, ViewAction, YamlEditorPanel,
};
#[cfg(feature = "tui")]
pub use wizard::{WizardConfig, WizardState, WizardStep};
#[cfg(feature = "tui")]
fn install_panic_hook() {
use std::io::Write;
use std::panic;
use std::sync::Once;
use crossterm::{execute, terminal::LeaveAlternateScreen};
static HOOK_INSTALLED: Once = Once::new();
HOOK_INSTALLED.call_once(|| {
let original_hook = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
let _ = crossterm::terminal::disable_raw_mode();
let _ = execute!(std::io::stdout(), LeaveAlternateScreen);
let crash_log_path = dirs::config_dir()
.map(|d| d.join("nika").join("crash.log"))
.unwrap_or_else(|| std::path::PathBuf::from("/tmp/nika-crash.log"));
if let Some(parent) = crash_log_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
if let Ok(mut f) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&crash_log_path)
{
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
let _ = writeln!(
f,
"\n══════════════════════════════════════════════════════════════"
);
let _ = writeln!(f, "NIKA CRASH: {}", timestamp);
let _ = writeln!(
f,
"══════════════════════════════════════════════════════════════"
);
let _ = writeln!(f, "{}", panic_info);
let backtrace = std::backtrace::Backtrace::capture();
let _ = writeln!(f, "\nBacktrace:\n{}", backtrace);
}
eprintln!(
"\n\x1b[31m╔══════════════════════════════════════════════════════════════╗\x1b[0m"
);
eprintln!(
"\x1b[31m║ NIKA CRASHED - Terminal has been restored ║\x1b[0m"
);
eprintln!(
"\x1b[31m╠══════════════════════════════════════════════════════════════╣\x1b[0m"
);
eprintln!(
"\x1b[31m║ Crash log: {:49} ║\x1b[0m",
crash_log_path.display()
);
eprintln!(
"\x1b[31m║ Please report: https://github.com/SuperNovae-studio/nika ║\x1b[0m"
);
eprintln!(
"\x1b[31m╚══════════════════════════════════════════════════════════════╝\x1b[0m"
);
original_hook(panic_info);
}));
});
}
#[cfg(feature = "tui")]
pub async fn run_tui(workflow_path: &std::path::Path) -> crate::error::Result<()> {
use crate::ast::parse_analyzed;
use crate::event::EventLog;
use crate::runtime::Runner;
install_panic_hook();
let yaml_content = tokio::fs::read_to_string(workflow_path)
.await
.map_err(|e| crate::error::NikaError::WorkflowNotFound {
path: format!("{}: {}", workflow_path.display(), e),
})?;
let workflow = parse_analyzed(&yaml_content)?;
let (event_log, event_rx) = EventLog::new_with_broadcast();
let mut runner = Runner::with_event_log(workflow, event_log)?.quiet();
let runner_handle = tokio::spawn(async move {
match runner.run().await {
Ok(output) => {
tracing::info!("Workflow completed: {} bytes output", output.len());
}
Err(e) => {
tracing::error!("Workflow failed: {}", e);
}
}
});
let app = App::new(workflow_path)?.with_broadcast_receiver(event_rx);
let tui_result = app.run_unified().await.map(|_| ());
runner_handle.abort();
tui_result
}
#[cfg(feature = "tui")]
pub async fn run_tui_standalone() -> crate::error::Result<()> {
use views::WizardView;
if !WizardView::was_completed() {
use std::io::IsTerminal;
if std::io::stdin().is_terminal() {
use colored::Colorize;
use std::io::{self, Write};
println!();
println!(
"{}",
"╔═══════════════════════════════════════════════════════════════╗".cyan()
);
println!(
"{}",
"║ 🦋 Welcome to Nika! ║".cyan()
);
println!(
"{}",
"║ ║".cyan()
);
println!(
"{}",
"║ It looks like this is your first time running Nika. ║".cyan()
);
println!(
"{}",
"║ Would you like to run the setup wizard? ║".cyan()
);
println!(
"{}",
"╚═══════════════════════════════════════════════════════════════╝".cyan()
);
println!();
print!("{}", "Run setup wizard? [Y/n]: ".bold());
io::stdout().flush().ok();
let mut input = String::new();
if io::stdin().read_line(&mut input).is_ok() {
let input = input.trim().to_lowercase();
if input.is_empty() || input == "y" || input == "yes" {
println!();
println!("{}", "Starting setup wizard...".dimmed());
println!();
run_tui_wizard().await?;
println!();
println!("{}", "Setup complete! Starting Nika...".green());
println!();
}
}
}
}
install_panic_hook();
let root = find_project_root().unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
let state = StandaloneState::new(root);
let app = App::new_standalone(state)?;
let launch_wizard = app.run_unified().await?;
if launch_wizard {
println!();
run_tui_wizard().await?;
}
Ok(())
}
#[cfg(feature = "tui")]
pub async fn run_tui_chat(
provider: Option<String>,
model: Option<String>,
) -> crate::error::Result<()> {
use views::TuiView;
install_panic_hook();
let root = find_project_root().unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
let state = StandaloneState::new(root);
let app = App::new_standalone(state)?
.with_initial_view(TuiView::Command)
.with_chat_overrides(provider, model);
app.run_unified().await.map(|_| ())
}
#[cfg(feature = "tui")]
pub async fn run_tui_studio(workflow: Option<std::path::PathBuf>) -> crate::error::Result<()> {
use views::TuiView;
install_panic_hook();
let root = find_project_root().unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
let state = StandaloneState::new(root.clone());
let mut app = App::new_standalone(state)?.with_initial_view(TuiView::Studio);
if let Some(path) = workflow {
let full_path = if path.is_absolute() {
path
} else {
root.join(path)
};
app = app.with_studio_file(full_path);
}
app.run_unified().await.map(|_| ())
}
#[cfg(feature = "tui")]
pub async fn run_tui_with_options(
workflow: Option<std::path::PathBuf>,
initial_view: Option<views::TuiView>,
) -> crate::error::Result<()> {
install_panic_hook();
let root = find_project_root().unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
let state = StandaloneState::new(root.clone());
let mut app = App::new_standalone(state)?;
if let Some(view) = initial_view {
app = app.with_initial_view(view);
}
if let Some(path) = workflow {
let full_path = if path.is_absolute() {
path
} else {
root.join(path)
};
app = app.with_studio_file(full_path);
}
app.run_unified().await.map(|_| ())
}
#[cfg(feature = "tui")]
fn find_project_root() -> Option<std::path::PathBuf> {
let mut current = std::env::current_dir().ok()?;
loop {
if current.join("Cargo.toml").exists() || current.join(".git").exists() {
return Some(current);
}
if !current.pop() {
return None;
}
}
}
#[cfg(not(feature = "tui"))]
pub async fn run_tui(_workflow_path: &std::path::Path) -> crate::error::Result<()> {
Err(crate::error::NikaError::ValidationError {
reason: "TUI feature not enabled. Rebuild with --features tui".to_string(),
})
}
#[cfg(not(feature = "tui"))]
pub async fn run_tui_standalone() -> crate::error::Result<()> {
Err(crate::error::NikaError::ValidationError {
reason: "TUI feature not enabled. Rebuild with --features tui".to_string(),
})
}
#[cfg(not(feature = "tui"))]
pub async fn run_tui_chat(
_provider: Option<String>,
_model: Option<String>,
) -> crate::error::Result<()> {
Err(crate::error::NikaError::ValidationError {
reason: "TUI feature not enabled. Rebuild with --features tui".to_string(),
})
}
#[cfg(not(feature = "tui"))]
pub async fn run_tui_studio(_workflow: Option<std::path::PathBuf>) -> crate::error::Result<()> {
Err(crate::error::NikaError::ValidationError {
reason: "TUI feature not enabled. Rebuild with --features tui".to_string(),
})
}
#[cfg(not(feature = "tui"))]
pub async fn run_tui_with_options(
_workflow: Option<std::path::PathBuf>,
_initial_view: Option<views::TuiView>,
) -> crate::error::Result<()> {
Err(crate::error::NikaError::ValidationError {
reason: "TUI feature not enabled. Rebuild with --features tui".to_string(),
})
}
#[cfg(feature = "tui")]
pub async fn run_tui_wizard() -> crate::error::Result<()> {
use crossterm::{
event::{self, Event, KeyEventKind},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{backend::CrosstermBackend, Terminal};
use std::io::stdout;
use crate::tui::state::TuiState;
use crate::tui::theme::Theme;
use crate::tui::views::{View, ViewAction, WizardView};
install_panic_hook();
enable_raw_mode().map_err(crate::error::NikaError::IoError)?;
let mut stdout = stdout();
execute!(stdout, EnterAlternateScreen).map_err(crate::error::NikaError::IoError)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend).map_err(crate::error::NikaError::IoError)?;
let mut wizard = WizardView::new();
let mut tui_state = TuiState::new("wizard");
let theme = Theme::default();
let result = loop {
terminal
.draw(|frame| {
wizard.render(frame, frame.area(), &tui_state, &theme);
})
.map_err(crate::error::NikaError::IoError)?;
if event::poll(std::time::Duration::from_millis(100))
.map_err(crate::error::NikaError::IoError)?
{
if let Event::Key(key) = event::read().map_err(crate::error::NikaError::IoError)? {
if key.kind == KeyEventKind::Press {
match wizard.handle_key(key, &mut tui_state) {
ViewAction::Quit => break Ok(()),
ViewAction::Error(msg) => {
break Err(crate::error::NikaError::ValidationError { reason: msg })
}
_ => {}
}
}
}
}
};
disable_raw_mode().ok();
execute!(terminal.backend_mut(), LeaveAlternateScreen).ok();
terminal.show_cursor().ok();
result
}
#[cfg(not(feature = "tui"))]
pub async fn run_tui_wizard() -> crate::error::Result<()> {
Err(crate::error::NikaError::ValidationError {
reason: "TUI feature not enabled. Rebuild with --features tui".to_string(),
})
}