mielin_cli/
repl.rs

1//! Interactive REPL (Read-Eval-Print Loop) for MielinCTL
2
3use crate::history::History;
4use anyhow::Result;
5use rustyline::completion::{Completer, Pair};
6use rustyline::error::ReadlineError;
7use rustyline::highlight::Highlighter;
8use rustyline::hint::Hinter;
9use rustyline::history::DefaultHistory;
10use rustyline::validate::Validator;
11use rustyline::{CompletionType, Config, Editor, Helper};
12use std::borrow::Cow;
13use std::time::Instant;
14
15/// REPL helper for command completion and highlighting
16struct MielinHelper {
17    commands: Vec<String>,
18}
19
20impl Helper for MielinHelper {}
21
22impl MielinHelper {
23    fn new() -> Self {
24        Self {
25            commands: vec![
26                "node".to_string(),
27                "node list".to_string(),
28                "node info".to_string(),
29                "node start".to_string(),
30                "node stop".to_string(),
31                "node create".to_string(),
32                "node join".to_string(),
33                "node leave".to_string(),
34                "node config".to_string(),
35                "agent".to_string(),
36                "agent list".to_string(),
37                "agent deploy".to_string(),
38                "agent migrate".to_string(),
39                "agent stop".to_string(),
40                "agent create".to_string(),
41                "agent inspect".to_string(),
42                "agent logs".to_string(),
43                "agent exec".to_string(),
44                "mesh".to_string(),
45                "mesh status".to_string(),
46                "mesh peers".to_string(),
47                "cluster".to_string(),
48                "cluster init".to_string(),
49                "cluster status".to_string(),
50                "cluster health".to_string(),
51                "cluster upgrade".to_string(),
52                "migrate".to_string(),
53                "migrate status".to_string(),
54                "migrate cancel".to_string(),
55                "migrate history".to_string(),
56                "registry".to_string(),
57                "registry list".to_string(),
58                "registry query".to_string(),
59                "registry stats".to_string(),
60                "gossip".to_string(),
61                "gossip status".to_string(),
62                "gossip members".to_string(),
63                "gossip sync".to_string(),
64                "wasm".to_string(),
65                "wasm build".to_string(),
66                "wasm validate".to_string(),
67                "wasm test".to_string(),
68                "wasm optimize".to_string(),
69                "debug".to_string(),
70                "debug attach".to_string(),
71                "debug trace".to_string(),
72                "debug dump".to_string(),
73                "debug profile".to_string(),
74                "history".to_string(),
75                "history show".to_string(),
76                "history search".to_string(),
77                "history stats".to_string(),
78                "history clear".to_string(),
79                "history export".to_string(),
80                "version".to_string(),
81                "help".to_string(),
82                "exit".to_string(),
83                "quit".to_string(),
84            ],
85        }
86    }
87}
88
89impl Completer for MielinHelper {
90    type Candidate = Pair;
91
92    fn complete(
93        &self,
94        line: &str,
95        pos: usize,
96        _ctx: &rustyline::Context<'_>,
97    ) -> Result<(usize, Vec<Pair>), ReadlineError> {
98        let mut matches = Vec::new();
99
100        for cmd in &self.commands {
101            if cmd.starts_with(&line[..pos]) {
102                matches.push(Pair {
103                    display: cmd.clone(),
104                    replacement: cmd.clone(),
105                });
106            }
107        }
108
109        Ok((0, matches))
110    }
111}
112
113impl Hinter for MielinHelper {
114    type Hint = String;
115
116    fn hint(&self, line: &str, pos: usize, _ctx: &rustyline::Context<'_>) -> Option<String> {
117        if line.is_empty() || pos < line.len() {
118            return None;
119        }
120
121        for cmd in &self.commands {
122            if cmd.starts_with(line) && cmd != line {
123                return Some(cmd[line.len()..].to_string());
124            }
125        }
126
127        None
128    }
129}
130
131impl Highlighter for MielinHelper {
132    fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
133        // Simple highlighting: no color changes for now
134        // Could be extended with ANSI colors in the future
135        Cow::Borrowed(line)
136    }
137}
138
139impl Validator for MielinHelper {}
140
141/// Interactive REPL for MielinCTL
142pub struct Repl {
143    editor: Editor<MielinHelper, DefaultHistory>,
144    history: History,
145}
146
147impl Repl {
148    /// Create a new REPL instance
149    pub fn new() -> Result<Self> {
150        let config = Config::builder()
151            .completion_type(CompletionType::List)
152            .build();
153
154        let helper = MielinHelper::new();
155        let mut editor = Editor::with_config(config)?;
156        editor.set_helper(Some(helper));
157
158        // Load command history
159        let history = History::load()?;
160
161        Ok(Self { editor, history })
162    }
163
164    /// Run the REPL
165    pub async fn run(&mut self) -> Result<()> {
166        println!("MielinOS CLI - Interactive Mode");
167        println!("Type 'help' for commands, 'exit' or 'quit' to exit");
168        println!();
169
170        loop {
171            let readline = self.editor.readline("mielin> ");
172
173            match readline {
174                Ok(line) => {
175                    let line = line.trim();
176
177                    if line.is_empty() {
178                        continue;
179                    }
180
181                    // Add to readline history
182                    let _ = self.editor.add_history_entry(line);
183
184                    // Handle exit commands
185                    if line == "exit" || line == "quit" {
186                        println!("Goodbye!");
187                        break;
188                    }
189
190                    // Handle help command
191                    if line == "help" {
192                        self.show_help();
193                        continue;
194                    }
195
196                    // Execute command
197                    let start = Instant::now();
198                    let result = self.execute_command(line).await;
199                    let duration = start.elapsed();
200
201                    let exit_code = if result.is_ok() { 0 } else { 1 };
202
203                    // Add to command history
204                    self.history
205                        .add(line.to_string(), exit_code, duration.as_millis() as u64);
206
207                    if let Err(e) = result {
208                        eprintln!("Error: {}", crate::format_error(&e));
209                    }
210                }
211                Err(ReadlineError::Interrupted) => {
212                    println!("Interrupted (Ctrl+C). Type 'exit' or 'quit' to exit.");
213                }
214                Err(ReadlineError::Eof) => {
215                    println!("EOF (Ctrl+D). Exiting...");
216                    break;
217                }
218                Err(err) => {
219                    eprintln!("Error: {:?}", err);
220                    break;
221                }
222            }
223        }
224
225        // Save command history
226        self.history.save()?;
227
228        Ok(())
229    }
230
231    /// Execute a command
232    async fn execute_command(&self, line: &str) -> Result<()> {
233        // Parse the command line and execute it
234        // This is a simplified version - in a real implementation,
235        // you would parse the command and execute it properly
236
237        let parts: Vec<&str> = line.split_whitespace().collect();
238        if parts.is_empty() {
239            return Ok(());
240        }
241
242        // For now, just print a message indicating the command would be executed
243        println!("Would execute: {}", line);
244        println!("(Command execution in REPL mode is a stub - full integration pending)");
245
246        Ok(())
247    }
248
249    /// Show help message
250    fn show_help(&self) {
251        println!("Available commands:");
252        println!();
253        println!("  Node Management:");
254        println!("    node list                 - List all nodes");
255        println!("    node info <id>            - Show node details");
256        println!("    node start <id>           - Start a node");
257        println!("    node stop <id>            - Stop a node");
258        println!();
259        println!("  Agent Management:");
260        println!("    agent list                - List agents");
261        println!("    agent deploy <wasm>       - Deploy WASM agent");
262        println!("    agent migrate <id> <node> - Migrate agent");
263        println!("    agent stop <id>           - Stop agent");
264        println!();
265        println!("  Mesh Network:");
266        println!("    mesh status               - Mesh status");
267        println!("    mesh peers                - List peers");
268        println!();
269        println!("  Cluster:");
270        println!("    cluster init              - Initialize cluster");
271        println!("    cluster status            - Cluster status");
272        println!("    cluster health            - Health check");
273        println!();
274        println!("  Other:");
275        println!("    history show              - Show command history");
276        println!("    history stats             - History statistics");
277        println!("    version                   - Show version");
278        println!("    help                      - Show this help");
279        println!("    exit, quit                - Exit interactive mode");
280        println!();
281    }
282}
283
284impl Default for Repl {
285    fn default() -> Self {
286        Self::new().expect("Failed to create REPL")
287    }
288}