Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions apps/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ log = "0.4"
chrono = { version = "0.4", features = ["serde"] }
async-trait = "0.1"
simplelog = "0.12"
unicode-width = "0.1"
60 changes: 46 additions & 14 deletions apps/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,16 @@ pub enum Commands {
mod ui;

use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture},
event::{EnableBracketedPaste, DisableBracketedPaste},
execute,
style::Print,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{backend::CrosstermBackend, Terminal};
use routecode_sdk::core::AgentOrchestrator;
use routecode_sdk::tools::bash::BashTool;
use routecode_sdk::tools::file_ops::{FileEditTool, FileReadTool, FileWriteTool};
use routecode_sdk::tools::navigation::{GrepTool, LsTool};
use routecode_sdk::tools::navigation::{GrepTool, LsTool, TreeTool};
use routecode_sdk::tools::ToolRegistry;
use std::io;
use std::process::Command;
Expand All @@ -72,35 +73,53 @@ async fn main() -> anyhow::Result<()> {

let log_level = if cli.debug { LevelFilter::Debug } else { LevelFilter::Info };

CombinedLogger::init(vec![
let loggers: Vec<Box<dyn SharedLogger>> = vec![
WriteLogger::new(
log_level,
ConfigBuilder::new().set_time_format_rfc3339().build(),
File::create(&log_path)?,
),
])?;
];

// Only use TermLogger if we are NOT about to enter TUI mode immediately,
// or if it's a headless run. But for now, let's just use it for the very beginning.
// Actually, simplelog doesn't support easy removal, so we'll just use WriteLogger
// and manual printlns for the very early stages if needed.

CombinedLogger::init(loggers)?;

log::info!("Starting RouteCode v{}", env!("CARGO_PKG_VERSION"));

if cli.debug {
log::debug!("Debug mode active. Spawning log window...");
// Spawn a new terminal window to tail the log file
#[cfg(target_os = "windows")]
{
let _ = Command::new("cmd")
.args(["/C", "start", "powershell", "-NoExit", "-Command", &format!("Get-Content -Path '{}' -Wait", log_path.display())])
.spawn();
if let Err(e) = Command::new("cmd")
.args(["/C", "start", "powershell", "-NoExit", "-Command", &format!("Get-Content -Path \"{}\" -Wait", log_path.display())])
.spawn()
{
log::warn!("Failed to spawn debug log window: {}", e);
}
}
#[cfg(target_os = "macos")]
{
let _ = Command::new("osascript")
if let Err(e) = Command::new("osascript")
.args(["-e", &format!("tell application \"Terminal\" to do script \"tail -f '{}'\"", log_path.display())])
.spawn();
.spawn()
{
log::warn!("Failed to spawn debug log window: {}", e);
}
}
#[cfg(target_os = "linux")]
{
// Try common terminal emulators
let _ = Command::new("x-terminal-emulator")
if let Err(e) = Command::new("x-terminal-emulator")
.args(["-e", "tail", "-f", &log_path.display().to_string()])
.spawn();
.spawn()
{
log::warn!("Failed to spawn debug log window: {}", e);
}
}
}

Expand Down Expand Up @@ -148,6 +167,7 @@ async fn main() -> anyhow::Result<()> {
tool_registry.register(Arc::new(FileEditTool));
tool_registry.register(Arc::new(BashTool));
tool_registry.register(Arc::new(LsTool));
tool_registry.register(Arc::new(TreeTool));
tool_registry.register(Arc::new(GrepTool));
let tool_registry = Arc::new(tool_registry);

Expand All @@ -161,7 +181,7 @@ async fn main() -> anyhow::Result<()> {
// Setup terminal
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
execute!(stdout, EnterAlternateScreen, Print("\x1b[?1003h\x1b[?1006h"), EnableBracketedPaste)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;

Expand All @@ -177,8 +197,19 @@ async fn main() -> anyhow::Result<()> {
app.current_model = session.model;
let mut u = app.orchestrator.usage.lock().await;
*u = session.usage;
app.session_id = resume_name.clone();
if let Ok(config) = routecode_sdk::utils::storage::load_session_config(&resume_name) {
app.orchestrator.allow_session_commands.store(config.allow_all_commands, std::sync::atomic::Ordering::SeqCst);
app.orchestrator.allow_session_outside_access.store(config.allow_all_outside_access, std::sync::atomic::Ordering::SeqCst);
}
}
Err(e) => eprintln!("Failed to resume session: {}", e),
Err(e) => app.history.push(routecode_sdk::core::Message::system(format!("Failed to resume session '{}': {}", resume_name, e))),
}
}

if let Ok(workspace_config) = routecode_sdk::utils::storage::load_workspace_config() {
if workspace_config.allow_all_outside_access {
app.orchestrator.allow_session_outside_access.store(true, std::sync::atomic::Ordering::SeqCst);
}
}

Expand All @@ -189,7 +220,8 @@ async fn main() -> anyhow::Result<()> {
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
Print("\x1b[?1003l\x1b[?1006l"),
DisableBracketedPaste
)?;
terminal.show_cursor()?;

Expand Down
76 changes: 76 additions & 0 deletions apps/cli/src/ui/components.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Clear};
use ratatui::Frame;

// --- Theme ---
pub const COLOR_PRIMARY: Color = Color::Rgb(0, 150, 255); // Ocean Blue
pub const COLOR_BG: Color = Color::Rgb(25, 25, 25); // Midnight Charcoal
pub const COLOR_INPUT_BG: Color = Color::Rgb(35, 35, 35);// Soft Obsidian
pub const COLOR_SECONDARY: Color = Color::DarkGray; // Slate Gray
pub const COLOR_SYSTEM: Color = Color::Yellow; // Amber Yellow
pub const COLOR_SUCCESS: Color = Color::Green; // Emerald Green
pub const COLOR_TEXT: Color = Color::White; // Primary Text
pub const COLOR_DIM: Color = Color::Rgb(50, 50, 50); // Very Dim Text/Lines

pub fn clean_model_name(name: &str, provider_id: &str) -> String {
if provider_id.starts_with("cloudflare") && name.starts_with("@cf/") {
name.split('/').last().unwrap_or(name).to_string()
} else if (provider_id == "openrouter" || provider_id == "nvidia") && name.contains('/') {
name.split('/').last().unwrap_or(name).to_string()
} else {
name.to_string()
}
}

pub fn draw_modal(f: &mut Frame, title: &str, width: u16, height: u16, mouse_col: Option<u16>, mouse_row: Option<u16>, footer: Vec<Span>) -> Rect {
let area = f.size();
let modal_area = Rect::new(
(area.width.saturating_sub(width)) / 2,
(area.height.saturating_sub(height)) / 2,
width,
height,
);
f.render_widget(Clear, modal_area);
f.render_widget(Block::default().style(Style::default().bg(COLOR_BG)), modal_area);

let main_layout = ratatui::layout::Layout::default()
.direction(ratatui::layout::Direction::Vertical)
.constraints([
ratatui::layout::Constraint::Length(1), // Header
ratatui::layout::Constraint::Min(0), // Content
ratatui::layout::Constraint::Length(1), // Footer Spacer
ratatui::layout::Constraint::Length(1), // Footer
])
.margin(1)
.split(modal_area);

let header_layout = ratatui::layout::Layout::default()
.direction(ratatui::layout::Direction::Horizontal)
.constraints([
ratatui::layout::Constraint::Min(0),
ratatui::layout::Constraint::Length(5), // "esc"
])
.split(main_layout[0]);

f.render_widget(
ratatui::widgets::Paragraph::new(Span::styled(title, Style::default().add_modifier(Modifier::BOLD))),
header_layout[0],
);
let mut esc_style = Style::default().fg(COLOR_SECONDARY);
if let (Some(col), Some(row)) = (mouse_col, mouse_row) {
if row <= modal_area.y + 2 && col >= modal_area.x + width.saturating_sub(10) && col <= modal_area.x + width {
esc_style = Style::default().fg(Color::Red).add_modifier(Modifier::BOLD);
}
}

f.render_widget(
ratatui::widgets::Paragraph::new(Span::styled("esc", esc_style)),
header_layout[1],
);

f.render_widget(ratatui::widgets::Paragraph::new(Line::from(footer)), main_layout[3]);

main_layout[1]
}
Loading
Loading