Skip to main content

agentic_codebase/cli/
repl.rs

1//! Interactive REPL for ACB — slash command interface.
2//!
3//! Launch with `acb` (no subcommand) to enter interactive mode.
4//! Type `/help` for available commands, Tab for completion.
5
6use crate::cli::output::Styled;
7use crate::cli::repl_commands;
8use crate::cli::repl_complete;
9use rustyline::config::CompletionType;
10use rustyline::error::ReadlineError;
11use rustyline::{Config, Editor};
12
13/// History file location.
14fn history_path() -> std::path::PathBuf {
15    let home = std::env::var("HOME")
16        .or_else(|_| std::env::var("USERPROFILE"))
17        .unwrap_or_else(|_| ".".to_string());
18    std::path::PathBuf::from(home).join(".acb_history")
19}
20
21/// Print the welcome banner.
22fn print_banner() {
23    let s = Styled::auto();
24
25    eprintln!();
26    eprintln!(
27        "  {} {} {}",
28        s.green("\u{25c9}"),
29        s.bold(&format!("acb v{}", env!("CARGO_PKG_VERSION"))),
30        s.dim("\u{2014} Semantic Code Compiler for AI Agents")
31    );
32    eprintln!();
33    eprintln!(
34        "    Press {} to browse commands, {} to complete, {} to quit.",
35        s.cyan("/"),
36        s.dim("Tab"),
37        s.dim("/exit")
38    );
39    eprintln!();
40}
41
42/// Run the interactive REPL.
43pub fn run() -> Result<(), Box<dyn std::error::Error>> {
44    print_banner();
45
46    // Configure rustyline with List completion
47    let config = Config::builder()
48        .history_ignore_space(true)
49        .auto_add_history(true)
50        .completion_type(CompletionType::List)
51        .completion_prompt_limit(20)
52        .build();
53
54    let helper = repl_complete::AcbHelper::new();
55    let mut rl: Editor<repl_complete::AcbHelper, rustyline::history::DefaultHistory> =
56        Editor::with_config(config)?;
57    rl.set_helper(Some(helper));
58
59    // Bind custom keys
60    repl_complete::bind_keys(&mut rl);
61
62    // Load history
63    let hist_path = history_path();
64    if hist_path.exists() {
65        let _ = rl.load_history(&hist_path);
66    }
67
68    // Session state
69    let mut state = repl_commands::ReplState::new();
70
71    // Prompt
72    let prompt = if Styled::auto().ok() == "OK" {
73        " acb> ".to_string()
74    } else {
75        " \x1b[36macb>\x1b[0m ".to_string()
76    };
77
78    // Main REPL loop
79    loop {
80        match rl.readline(&prompt) {
81            Ok(line) => {
82                let line = line.trim();
83                if line.is_empty() {
84                    continue;
85                }
86
87                match repl_commands::execute(line, &mut state) {
88                    Ok(true) => {
89                        let s = Styled::auto();
90                        eprintln!("  {} Goodbye!", s.dim("\u{2728}"));
91                        break;
92                    }
93                    Ok(false) => {}
94                    Err(e) => {
95                        let s = Styled::auto();
96                        eprintln!("  {} {e}", s.fail());
97                    }
98                }
99            }
100            Err(ReadlineError::Interrupted) => {
101                let s = Styled::auto();
102                eprintln!("  {} Type {} to quit.", s.dim("(Ctrl+C)"), s.bold("/exit"));
103            }
104            Err(ReadlineError::Eof) => {
105                let s = Styled::auto();
106                eprintln!("  {} Goodbye!", s.dim("\u{2728}"));
107                break;
108            }
109            Err(err) => {
110                eprintln!("  Error: {err}");
111                break;
112            }
113        }
114    }
115
116    // Save history
117    let _ = std::fs::create_dir_all(hist_path.parent().unwrap_or(std::path::Path::new(".")));
118    let _ = rl.save_history(&hist_path);
119
120    Ok(())
121}