use super::config::ConfigManager;
use super::plugins::PluginManager;
use miette::{IntoDiagnostic, Result, WrapErr};
use reasonkit::thinktool::{ExecutorConfig, ProtocolExecutor, ProtocolInput};
use rustyline::config::Configurer;
use rustyline::history::{FileHistory, History};
use rustyline::Context;
use rustyline::{
completion::Completer, error::ReadlineError, highlight::Highlighter, hint::Hinter,
validate::Validator, Editor, Helper,
};
use std::borrow::Cow;
use tokio::runtime::Runtime;
#[derive(Default)]
struct ReplHelper {
commands: Vec<&'static str>,
protocols: Vec<&'static str>,
profiles: Vec<&'static str>,
plugin_names: Vec<String>,
}
impl ReplHelper {
fn update_plugins(&mut self, plugins: &[String]) {
self.plugin_names = plugins.to_vec();
}
}
impl Completer for ReplHelper {
type Candidate = String;
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &Context<'_>,
) -> rustyline::Result<(usize, Vec<String>)> {
let line_start = &line[..pos];
if line_start.is_empty() || line_start.split_whitespace().count() <= 1 {
let mut commands: Vec<String> = self
.commands
.iter()
.filter(|cmd| cmd.starts_with(line_start))
.map(|s| s.to_string())
.collect();
let plugins: Vec<String> = self
.plugin_names
.iter()
.filter(|p| p.starts_with(line_start))
.cloned()
.collect();
commands.extend(plugins);
if !commands.is_empty() {
return Ok((0, commands));
}
}
if line_start.contains("--protocol") || line_start.contains("-p") {
let last_part = line_start.split_whitespace().last().unwrap_or("");
let protocols: Vec<String> = self
.protocols
.iter()
.filter(|proto| proto.starts_with(last_part))
.map(|s| s.to_string())
.collect();
if !protocols.is_empty() {
let start_pos = line_start.rfind(last_part).unwrap_or(pos);
return Ok((start_pos, protocols));
}
}
if line_start.contains("--profile") {
let last_part = line_start.split_whitespace().last().unwrap_or("");
let profiles: Vec<String> = self
.profiles
.iter()
.filter(|profile| profile.starts_with(last_part))
.map(|s| s.to_string())
.collect();
if !profiles.is_empty() {
let start_pos = line_start.rfind(last_part).unwrap_or(pos);
return Ok((start_pos, profiles));
}
}
Ok((pos, vec![]))
}
}
impl Highlighter for ReplHelper {
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> std::borrow::Cow<'l, str> {
if line.starts_with("think ") || line.starts_with("t ") {
Cow::Owned(format!("\x1b[1;36m{}\x1b[0m", line))
} else if line.starts_with("--") || line.starts_with('-') {
Cow::Owned(format!("\x1b[1;33m{}\x1b[0m", line))
} else if line.contains("gigathink")
|| line.contains("laserlogic")
|| line.contains("bedrock")
|| line.contains("proofguard")
|| line.contains("brutalhonesty")
{
Cow::Owned(format!("\x1b[1;35m{}\x1b[0m", line))
} else {
Cow::Borrowed(line)
}
}
}
impl Hinter for ReplHelper {
type Hint = String;
fn hint(&self, line: &str, _pos: usize, _ctx: &Context<'_>) -> Option<Self::Hint> {
if line.trim().is_empty() {
return Some(
"Type 'help' for commands or 'think <query>' to start reasoning.".to_string(),
);
}
if line.contains("--protocol") && !line.contains("--profile") {
return Some(" [--profile <quick|balanced|deep|paranoid>]".to_string());
}
None
}
}
impl Validator for ReplHelper {}
impl Helper for ReplHelper {}
pub struct ReplContext {
rt: Runtime,
editor: Editor<ReplHelper, FileHistory>,
config_manager: ConfigManager,
plugin_manager: PluginManager,
}
impl ReplContext {
pub fn new(banner: bool) -> Result<Self> {
let rt = Runtime::new()
.into_diagnostic()
.context("Failed to create async runtime")?;
let config_manager = ConfigManager::new()
.into_diagnostic()
.context("Failed to load configuration")?;
let mut plugin_manager =
PluginManager::new(config_manager.config().plugins.plugin_dir.clone());
if let Err(e) = plugin_manager.scan() {
eprintln!("Warning: Failed to scan plugins: {}", e);
}
let mut editor = Editor::<ReplHelper, FileHistory>::new().into_diagnostic()?;
let mut helper = ReplHelper {
commands: vec![
"think",
"t",
"help",
"exit",
"quit",
"clear",
"history",
"protocols",
"profiles",
"config",
"mode",
"examples",
"plugins",
],
protocols: vec![
"gigathink",
"laserlogic",
"bedrock",
"proofguard",
"brutalhonesty",
"got",
],
profiles: vec![
"quick",
"balanced",
"deep",
"paranoid",
"decide",
"scientific",
"graph",
"consistent",
"verified",
],
plugin_names: vec![],
};
let plugin_names: Vec<String> = plugin_manager
.list()
.iter()
.map(|p| p.name.clone())
.collect();
helper.update_plugins(&plugin_names);
editor.set_helper(Some(helper));
editor.set_max_history_size(1000).into_diagnostic()?;
editor.set_auto_add_history(true);
if banner {
Self::show_welcome_banner(&config_manager, &plugin_manager);
}
Ok(Self {
rt,
editor,
config_manager,
plugin_manager,
})
}
fn show_welcome_banner(_config_manager: &ConfigManager, plugin_manager: &PluginManager) {
println!(
"\n\x1b[1;36m╭─────────────────────────────────────────────────────────────╮\x1b[0m"
);
println!(
"\x1b[1;36m│ ReasonKit Interactive Shell │\x1b[0m"
);
println!(
"\x1b[1;36m╰─────────────────────────────────────────────────────────────╯\x1b[0m\n"
);
println!("📚 \x1b[1mCommands:\x1b[0m");
println!(" \x1b[34mthink <query>\x1b[0m - Run structured reasoning");
println!(" \x1b[34mhelp\x1b[0m - Show this help message");
println!(" \x1b[34mconfig\x1b[0m - Manage configuration");
println!(" \x1b[34mplugins\x1b[0m - List available plugins");
println!(" \x1b[34mexit/quit\x1b[0m - Exit the shell\n");
let plugin_count = plugin_manager.list().len();
if plugin_count > 0 {
println!("🔌 \x1b[1mPlugins Loaded: {}\x1b[0m", plugin_count);
}
println!("🔧 \x1b[1mTry:\x1b[0m");
println!(" \x1b[32mthink \"Should I invest in AI startups?\"\x1b[0m");
println!(
" \x1b[32mthink --protocol gigathink --profile paranoid \"Is this safe?\"\x1b[0m\n"
);
}
fn show_help() {
println!("\n\x1b[1;34mReasonKit REPL Help\x1b[0m\n");
println!("📖 \x1b[1mBasic Usage:\x1b[0m");
println!(" think [OPTIONS] <query> - Execute structured reasoning");
println!(" think --protocol <name> - Use specific ThinkTool");
println!(" think --profile <name> - Use reasoning profile\n");
println!("🎯 \x1b[1mThinkTools:\x1b[0m");
println!(" \x1b[35mgigathink\x1b[0m - Expansive creative thinking");
println!(" \x1b[35mlaserlogic\x1b[0m - Precision deductive reasoning");
println!(" \x1b[35mbedrock\x1b[0m - First principles decomposition");
println!(" \x1b[35mproofguard\x1b[0m - Multi-source verification");
println!(" \x1b[35mbrutalhonesty\x1b[0m - Adversarial self-critique");
println!(" \x1b[35mgot\x1b[0m - Graph-of-Thoughts reasoning\n");
println!("⚙️ \x1b[1mSystem:\x1b[0m");
println!(" config show - Show current configuration");
println!(" config edit - Edit configuration file");
println!(" plugins - List installed plugins");
println!(" plugins scan - Rescan for new plugins");
println!("\n\x1b[2mType 'exit' or 'quit' to leave the shell.\x1b[0m\n");
}
pub fn run(&mut self) -> Result<()> {
println!("\n\x1b[2mType 'help' for commands. Press Ctrl+C to exit.\x1b[0m\n");
loop {
let readline = self.editor.readline("\x1b[1;36mreasonkit>\x1b[0m ");
match readline {
Ok(line) => {
let line = line.trim();
if line.is_empty() {
continue;
}
match line {
"help" => Self::show_help(),
"protocols" => println!("\nRun 'help' to see protocols.\n"), "profiles" => println!("\nRun 'help' to see profiles.\n"), "examples" => println!("\nRun 'help' to see examples.\n"), "history" => {
let history_len = self.editor.history().len();
println!("Command history: {} entries", history_len);
}
"clear" => {
print!("\x1b[2J\x1b[H");
let _ = std::io::Write::flush(&mut std::io::stdout());
}
"exit" | "quit" => {
println!("Goodbye!");
break;
}
"config" => {
self.handle_config()
.unwrap_or_else(|e| eprintln!("Config error: {}", e));
}
"plugins" => {
self.list_plugins();
}
"plugins scan" => match self.plugin_manager.scan() {
Ok(count) => println!("Scanned {} plugins.", count),
Err(e) => eprintln!("Scan error: {}", e),
},
line if line.starts_with("config ") => {
self.handle_config_args(line)
.unwrap_or_else(|e| eprintln!("Config error: {}", e));
}
line if line.starts_with("think ") || line.starts_with("t ") => {
if let Err(e) = self.handle_think_command(line) {
eprintln!("\x1b[1;31mError executing ThinkTool: {}\x1b[0m", e);
}
}
line => {
let parts: Vec<&str> = line.split_whitespace().collect();
if let Some(cmd) = parts.first() {
if self.plugin_manager.get(cmd).is_some() {
let args: Vec<String> =
parts.iter().skip(1).map(|s| s.to_string()).collect();
if let Err(e) = self.plugin_manager.execute(cmd, &args) {
eprintln!("Plugin execution failed: {}", e);
}
continue;
}
}
println!("\x1b[1;31mUnknown command: '{}'\x1b[0m", line);
println!("Type 'help' for available commands.");
}
}
}
Err(ReadlineError::Interrupted) => {
println!("Interrupted. Type 'exit' to quit.");
}
Err(ReadlineError::Eof) => {
println!("Goodbye!");
break;
}
Err(err) => {
eprintln!("Error: {:?}", err);
break;
}
}
}
Ok(())
}
fn handle_think_command(&mut self, line: &str) -> Result<()> {
println!("\n\x1b[1;34mExecuting ThinkTool...\x1b[0m\n");
let parts: Vec<&str> = line.split_whitespace().collect();
let mut query = String::new();
let mut protocol = None;
let mut profile = None;
let mut i = 1; while i < parts.len() {
match parts[i] {
"--protocol" | "-p" if i + 1 < parts.len() => {
protocol = Some(parts[i + 1]);
i += 2;
}
"--profile" if i + 1 < parts.len() => {
profile = Some(parts[i + 1]);
i += 2;
}
_ => {
query.push_str(parts[i]);
query.push(' ');
i += 1;
}
}
}
let query = query.trim();
if query.is_empty() {
println!("\x1b[1;31mError: No query provided\x1b[0m");
return Ok(());
}
let config = ExecutorConfig {
verbose: true,
..Default::default()
};
let executor_result = self.rt.block_on(async {
let executor = ProtocolExecutor::with_config(config).into_diagnostic()?;
let input = ProtocolInput::query(query.to_string());
if let Some(proto) = protocol {
executor.execute(proto, input).await.into_diagnostic()
} else {
let prof = profile.unwrap_or("balanced");
executor
.execute_profile(prof, input)
.await
.into_diagnostic()
}
});
match executor_result {
Ok(output) => {
println!("Thinking Process:");
for step in &output.steps {
println!("\n[{}] {}", step.step_id, step.as_text().unwrap_or(""));
}
println!("\nConfidence: {:.2}", output.confidence);
}
Err(e) => {
if e.to_string().contains("API key") {
eprintln!("\n\x1b[1;33m⚠️ Missing API Key\x1b[0m");
eprintln!("To use this feature, please set your API key in the environment or config.");
eprintln!("Example: export ANTHROPIC_API_KEY=sk-...");
}
return Err(e);
}
}
Ok(())
}
fn handle_config(&self) -> Result<()> {
self.config_manager.show();
println!("{}", self.config_manager.show());
Ok(())
}
fn handle_config_args(&self, line: &str) -> Result<()> {
let parts: Vec<&str> = line.split_whitespace().collect();
match parts.get(1).copied() {
Some("show") => {
println!("{}", self.config_manager.show());
}
Some("edit") => {
let config_file = self.config_manager.config_file();
if let Ok(editor) = std::env::var("EDITOR") {
std::process::Command::new(editor)
.arg(config_file)
.status()
.into_diagnostic()?;
} else {
println!("EDITOR environment variable not set.");
println!("Config file is at: {}", config_file.display());
}
}
Some("reset") => {
println!("Reset not implemented in this session (requires restart to take effect fully).");
}
_ => {
println!("Usage: config [show|edit|reset]");
}
}
Ok(())
}
fn list_plugins(&self) {
let plugins = self.plugin_manager.list();
if plugins.is_empty() {
println!(
"No plugins found in {}",
self.config_manager.config().plugins.plugin_dir.display()
);
} else {
println!("\n\x1b[1;35mAvailable Plugins:\x1b[0m");
for plugin in plugins {
println!(" \x1b[1m{}\x1b[0m - {}", plugin.name, plugin.description);
}
}
}
}
pub fn run_repl(banner: bool) -> anyhow::Result<()> {
let mut repl = ReplContext::new(banner).map_err(|e| anyhow::anyhow!("{}", e))?;
repl.run().map_err(|e| anyhow::anyhow!("{}", e))
}