added a remove project function, and layed the framework for prompting

for user interaction.
This commit is contained in:
2026-05-21 13:15:44 -05:00
parent 2bdf100ed6
commit 9a43f4c378
2 changed files with 379 additions and 121 deletions
+181 -88
View File
@@ -31,7 +31,7 @@ pub fn run_tui(
cursor::Hide,
EnableMouseCapture
)?;
let backend = CrosstermBackend::new(stdout);
let backend = CrosstermBackend::new(&stdout);
let mut terminal = Terminal::new(backend)?;
let (event_tx, event_rx) = channel::<AppEvent>();
let input_tx = event_tx.clone();
@@ -142,114 +142,207 @@ pub fn run_tui(
state.output.push(txt);
state.output_scroll = u16::MAX;
}
ToolMessage::UpdateProject(index, project) => {
if index < state.projects.len() {
state.projects[index] = project;
}
}
ToolMessage::RebuildDB => {
let project = state.projects[state.selected_project].clone();
if let Some(db) = project.db {
disable_raw_mode()?;
execute!(
&stdout,
LeaveAlternateScreen,
cursor::Show,
DisableMouseCapture
)?;
std::io::stdout().flush()?;
let mut status = Command::new("distrobox");
let template_box = state.config.get("template_box").unwrap();
status
.arg("create")
.arg("--root")
.arg("--clone")
.arg(&template_box)
.arg("--name")
.arg(format!(
"{}-{}-{}",
template_box, project.org_name, project.name
));
for volume in db.volumes {
let mut dir_name = volume.split("/").last().unwrap();
if volume.contains("files") {
dir_name = "/pentest";
}
if volume.contains("/notes") {
dir_name = "notes";
}
status
.arg("--volume")
.arg(format!("{}:{}:rw", volume, dir_name));
}
status
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
match status.status() {
Ok(status) if !status.success() => {
println!(
"\nCommand failed with exit code {status}, press enter to return to tetanus."
);
let mut discard = String::new();
let _ = std::io::stdin().read_line(&mut discard);
}
Err(e) => {
println!(
"\nFailed to execure distrobox: {e}, press enter to return to tetanus."
);
let mut discard = String::new();
let _ = std::io::stdin().read_line(&mut discard);
}
_ => {}
}
enable_raw_mode()?;
execute!(
&stdout,
EnterAlternateScreen,
DisableMouseCapture,
cursor::Hide
)?;
terminal.clear()?;
}
}
_ => {}
},
AppEvent::Key(key) => match key.code {
KeyCode::Esc => break,
KeyCode::Enter => {
let trimmed = state.curent_intput.trim().to_string();
if !trimmed.is_empty() {
state.history.push(trimmed.clone());
state.output.push("\n".to_string());
state.output.push(format!("[user input] > {}", trimmed));
state.output.push("\n".to_string());
let (command, args) = trimmed.split_once(' ').unwrap_or((&trimmed, ""));
match command {
"exit" | "quit" => break,
"reload-modules" => {
state.initialize_modules();
state.output.push("Reloading module paths...".into());
}
"help" => {
state.output.push("Available Modules:".into());
for name in state.module_loader.commands.keys() {
state.output.push(format!(" - {}", name));
AppEvent::Key(key) => {
match key.code {
KeyCode::Esc => break,
KeyCode::Enter => {
let trimmed = state.curent_intput.trim().to_string();
if !trimmed.is_empty() {
state.history.push(trimmed.clone());
state.output.push("\n".to_string());
state.output.push(format!("[user input] > {}", trimmed));
state.output.push("\n".to_string());
if let Some(action) = state.prompt.action.clone() {
match action {
ToolMessage::RemoveProject => {
if trimmed.to_lowercase().contains("y") {
state.execute_command(
"remove_project_confirm",
None,
)?;
}
}
_ => {}
}
}
"new_project" | "np" => {
if args.split_once(' ').is_some() {
let _ =
state.execute_command(command, Some(args.to_string()));
} else {
state.output.push("Error: USAGE -> np <org> <name>".into());
let (command, args) =
trimmed.split_once(' ').unwrap_or((&trimmed, ""));
match command {
"exit" | "quit" => break,
"reload-modules" => {
state.initialize_modules();
state.output.push("Reloading module paths...".into());
}
}
"current_project" | "cp" => {
let _ = state.execute_command(command, None);
}
command_name => {
if state.module_loader.commands.contains_key(command_name) {
state.output.push(format!(
"[Worker] Executing script '{}'...",
command_name
));
if let Err(e) = state.execute_command(command_name, None) {
"help" => {
state.output.push("Available Modules:".into());
for name in state.module_loader.commands.keys() {
state.output.push(format!(" - {}", name));
}
}
"new_project" | "np" => {
if args.split_once(' ').is_some() {
let _ = state
.execute_command(command, Some(args.to_string()));
} else {
state
.output
.push(format!("[Error] Pipeline fail: {}", e));
.push("Error: USAGE -> np <org> <name>".into());
}
}
command_name => {
if state.module_loader.commands.contains_key(command_name) {
state.output.push(format!(
"[Worker] Executing script '{}'...",
command_name
));
if let Err(e) =
state.execute_command(command_name, None)
{
state
.output
.push(format!("[Error] Pipeline fail: {}", e));
}
} else {
if args == "" {
let _ = state.execute_command(command, None);
} else {
let _ = state.execute_command(
command,
Some(args.to_string()),
);
}
}
} else {
state.output.push(format!(
"[Error] Command '{}' unknown.",
command_name
));
}
}
state.curent_intput.clear();
}
state.curent_intput.clear();
history_index = state.history.len();
}
history_index = state.history.len();
}
KeyCode::Char(c) => {
state.curent_intput.push(c);
}
KeyCode::Backspace => {
state.curent_intput.pop();
}
KeyCode::Up if key.modifiers.is_empty() => {
if !state.history.is_empty() && history_index > 0 {
history_index -= 1;
state.curent_intput = state.history[history_index].clone();
KeyCode::Char(c) => {
state.curent_intput.push(c);
}
}
KeyCode::Down if key.modifiers.is_empty() => {
if history_index < state.history.len() {
history_index += 1;
if history_index == state.history.len() {
state.curent_intput.clear(); // Clear back to a fresh prompt
} else {
KeyCode::Backspace => {
state.curent_intput.pop();
}
KeyCode::Up if key.modifiers.is_empty() => {
if !state.history.is_empty() && history_index > 0 {
history_index -= 1;
state.curent_intput = state.history[history_index].clone();
}
}
}
KeyCode::Up
if key
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL) =>
{
if let Some(selected) = project_list_state.selected() {
if selected > 0 {
let new_index = selected - 1;
project_list_state.select(Some(new_index));
state.selected_project = new_index;
KeyCode::Down if key.modifiers.is_empty() => {
if history_index < state.history.len() {
history_index += 1;
if history_index == state.history.len() {
state.curent_intput.clear(); // Clear back to a fresh prompt
} else {
state.curent_intput = state.history[history_index].clone();
}
}
}
}
KeyCode::Down
if key
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL) =>
{
if let Some(selected) = project_list_state.selected() {
if selected + 1 < state.projects.len() {
let new_index = selected + 1;
project_list_state.select(Some(new_index));
state.selected_project = new_index;
KeyCode::Up
if key
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL) =>
{
if let Some(selected) = project_list_state.selected() {
if selected > 0 {
let new_index = selected - 1;
project_list_state.select(Some(new_index));
state.selected_project = new_index;
}
}
}
KeyCode::Down
if key
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL) =>
{
if let Some(selected) = project_list_state.selected() {
if selected + 1 < state.projects.len() {
let new_index = selected + 1;
project_list_state.select(Some(new_index));
state.selected_project = new_index;
}
}
}
_ => {}
}
_ => {}
},
}
AppEvent::Mouse(mouse) => {
if mouse.kind == crossterm::event::MouseEventKind::ScrollUp {
state.output_scroll = state.output_scroll.saturating_sub(1);