Skip to main content

agentic_vision/cli/
repl.rs

1//! Interactive REPL for the avis CLI.
2
3use rustyline::error::ReadlineError;
4use rustyline::Editor;
5
6use super::repl_commands::ReplState;
7use super::repl_complete::{bind_keys, AvisHelper};
8use crate::types::VisionResult;
9
10fn history_path() -> std::path::PathBuf {
11    let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
12    std::path::PathBuf::from(home).join(".avis_history")
13}
14
15pub fn run() -> VisionResult<()> {
16    println!("avis -- AgenticVision interactive shell");
17    println!("Type /help for commands, /exit to quit.\n");
18
19    let helper = AvisHelper::new();
20    let mut rl = Editor::new()
21        .map_err(|e| crate::types::VisionError::Io(std::io::Error::other(e.to_string())))?;
22    rl.set_helper(Some(helper));
23    bind_keys(&mut rl);
24
25    let hist = history_path();
26    let _ = rl.load_history(&hist);
27
28    let mut state = ReplState::new();
29
30    loop {
31        let prompt = if let Some(ref p) = state.file_path {
32            format!(
33                "avis({})> ",
34                p.file_name().unwrap_or_default().to_string_lossy()
35            )
36        } else {
37            "avis> ".to_string()
38        };
39
40        match rl.readline(&prompt) {
41            Ok(line) => {
42                let line = line.trim().to_string();
43                if line.is_empty() {
44                    continue;
45                }
46                rl.add_history_entry(&line).ok();
47                if let Err(e) = state.dispatch(&line) {
48                    eprintln!("Error: {}", e);
49                }
50            }
51            Err(ReadlineError::Interrupted | ReadlineError::Eof) => {
52                println!("Goodbye.");
53                break;
54            }
55            Err(e) => {
56                eprintln!("Readline error: {}", e);
57                break;
58            }
59        }
60    }
61
62    let _ = rl.save_history(&hist);
63    Ok(())
64}