Skip to main content

agentic_memory/cli/
repl.rs

1//! Interactive REPL for amem — slash command interface.
2//!
3//! Launch with `amem` (no subcommand) to enter interactive mode.
4//! Type `/help` for available commands, Tab for completion.
5
6use crate::cli::repl_commands;
7use crate::cli::repl_complete;
8use rustyline::config::CompletionType;
9use rustyline::error::ReadlineError;
10use rustyline::{Config, Editor};
11
12/// History file location.
13fn history_path() -> std::path::PathBuf {
14    let home = std::env::var("HOME")
15        .or_else(|_| std::env::var("USERPROFILE"))
16        .unwrap_or_else(|_| ".".to_string());
17    std::path::PathBuf::from(home).join(".amem_history")
18}
19
20/// Print the welcome banner.
21fn print_banner() {
22    eprintln!();
23    eprintln!(
24        "  \x1b[32m\u{25c9}\x1b[0m \x1b[1mamem v{}\x1b[0m \x1b[90m\u{2014} Binary Graph Memory for AI Agents\x1b[0m",
25        env!("CARGO_PKG_VERSION")
26    );
27    eprintln!();
28    eprintln!(
29        "    Press \x1b[36m/\x1b[0m to browse commands, \x1b[90mTab\x1b[0m to complete, \x1b[90m/exit\x1b[0m to quit."
30    );
31    eprintln!();
32}
33
34/// Run the interactive REPL.
35pub fn run() -> Result<(), Box<dyn std::error::Error>> {
36    print_banner();
37
38    let config = Config::builder()
39        .history_ignore_space(true)
40        .auto_add_history(true)
41        .completion_type(CompletionType::List)
42        .completion_prompt_limit(20)
43        .build();
44
45    let helper = repl_complete::AmemHelper::new();
46    let mut rl: Editor<repl_complete::AmemHelper, rustyline::history::DefaultHistory> =
47        Editor::with_config(config)?;
48    rl.set_helper(Some(helper));
49    repl_complete::bind_keys(&mut rl);
50
51    let hist_path = history_path();
52    if hist_path.exists() {
53        let _ = rl.load_history(&hist_path);
54    }
55
56    let mut state = repl_commands::ReplState::new();
57    let prompt = " \x1b[36mamem>\x1b[0m ";
58
59    loop {
60        match rl.readline(prompt) {
61            Ok(line) => {
62                let line = line.trim();
63                if line.is_empty() {
64                    continue;
65                }
66                match repl_commands::execute(line, &mut state) {
67                    Ok(true) => {
68                        eprintln!("  \x1b[90m\u{2728}\x1b[0m Goodbye!");
69                        break;
70                    }
71                    Ok(false) => {}
72                    Err(e) => {
73                        eprintln!("  Error: {e}");
74                    }
75                }
76            }
77            Err(ReadlineError::Interrupted) => {
78                eprintln!("  \x1b[90m(Ctrl+C)\x1b[0m Type \x1b[1m/exit\x1b[0m to quit.");
79            }
80            Err(ReadlineError::Eof) => {
81                eprintln!("  \x1b[90m\u{2728}\x1b[0m Goodbye!");
82                break;
83            }
84            Err(err) => {
85                eprintln!("  Error: {err}");
86                break;
87            }
88        }
89    }
90
91    let _ = std::fs::create_dir_all(hist_path.parent().unwrap_or(std::path::Path::new(".")));
92    let _ = rl.save_history(&hist_path);
93
94    Ok(())
95}