Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ A cross-platform, AI-powered terminal assistant with custom commands, local API
- Terminal-like UI with command history and autocompletion
- AI-powered responses for natural language queries
- Custom shell command execution (with sudo/password support)
- **Built-in text editors: nano and vim with full modal editing support**
- Secure local storage for API keys
- Cross-platform: Linux, Windows, macOS (experimental)
- Colorful output and user-friendly design
Expand Down
11 changes: 8 additions & 3 deletions TODO
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ SCRAPED: change command to change the API key # Scraped as we have new settings
DONE: Add support to various AI models like gemini, groq, etc
DONE: add markdown support
DONE: clickable links
DONE: Add support for nano/vim
DONE: Persistent command history (saved to ~/.term_history)

TODO:
add proper updates to the command like sync/update (for progress)
handle nano/vim
write tests for each of above
Persistent history
API key in build
change model to change the model
A faster sudo command
focus on infutfield in password field each time and after closing on terminal
minimal ls command output
Implement context
Implement context (history knowledge)
Update Welcome screen when there isn't any API key set
Implement landing page with onboarding
handle failing commands
67 changes: 67 additions & 0 deletions src-tauri/src/commands/history.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use std::fs;
use std::path::PathBuf;

fn get_history_file_path() -> Result<PathBuf, String> {
let home_dir = dirs::home_dir()
.ok_or_else(|| "Failed to get home directory".to_string())?;

Ok(home_dir.join(".term_history"))
}

#[tauri::command]
pub fn save_command_to_history(command: String) -> Result<(), String> {
let history_file = get_history_file_path()?;

// Read existing history
let mut history = if history_file.exists() {
fs::read_to_string(&history_file)
.map_err(|e| format!("Failed to read history file: {}", e))?
} else {
String::new()
};

// Append new command with newline
if !history.is_empty() && !history.ends_with('\n') {
history.push('\n');
}
history.push_str(&command);
history.push('\n');

// Write back to file
fs::write(&history_file, history)
.map_err(|e| format!("Failed to write history file: {}", e))?;

Ok(())
}

#[tauri::command]
pub fn load_command_history() -> Result<Vec<String>, String> {
let history_file = get_history_file_path()?;

if !history_file.exists() {
return Ok(Vec::new());
}

let content = fs::read_to_string(&history_file)
.map_err(|e| format!("Failed to read history file: {}", e))?;

let history: Vec<String> = content
.lines()
.filter(|line| !line.trim().is_empty())
.map(|line| line.to_string())
.collect();

Ok(history)
}

#[tauri::command]
pub fn clear_command_history() -> Result<(), String> {
let history_file = get_history_file_path()?;

if history_file.exists() {
fs::remove_file(&history_file)
.map_err(|e| format!("Failed to clear history file: {}", e))?;
}

Ok(())
}
4 changes: 3 additions & 1 deletion src-tauri/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
pub mod ai;
pub mod api_key;
mod files;
pub mod files;
pub mod history;
pub mod settings;
pub mod shell;

pub use ai::*;
pub use api_key::*;
pub use files::*;
pub use history::*;
pub use settings::*;
pub use shell::*;
63 changes: 63 additions & 0 deletions src-tauri/src/commands/shell.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::process::Command;
use std::fs;
use std::path::Path;

#[tauri::command]
pub async fn run_shell(command: String) -> Result<String, String> {
Expand Down Expand Up @@ -281,3 +283,64 @@ pub fn change_directory(path: String) -> Result<String, String> {
Err(e) => Err(format!("Failed to change directory: {}", e))
}
}


#[tauri::command]
pub fn read_file_for_editor(path: String) -> Result<String, String> {
let expanded_path = if path.starts_with("~") {
if let Ok(home) = std::env::var("HOME") {
path.replacen("~", &home, 1)
} else {
path
}
} else {
path
};

// Make path absolute if it's relative
let absolute_path = if Path::new(&expanded_path).is_absolute() {
expanded_path
} else {
let current_dir = std::env::current_dir()
.map_err(|e| format!("Failed to get current directory: {}", e))?;
current_dir.join(&expanded_path)
.to_string_lossy()
.into_owned()
};

fs::read_to_string(&absolute_path)
.map_err(|e| format!("Failed to read file '{}': {}", absolute_path, e))
}

#[tauri::command]
pub fn write_file_from_editor(path: String, content: String) -> Result<(), String> {
let expanded_path = if path.starts_with("~") {
if let Ok(home) = std::env::var("HOME") {
path.replacen("~", &home, 1)
} else {
path
}
} else {
path
};

// Make path absolute if it's relative
let absolute_path = if Path::new(&expanded_path).is_absolute() {
expanded_path
} else {
let current_dir = std::env::current_dir()
.map_err(|e| format!("Failed to get current directory: {}", e))?;
current_dir.join(&expanded_path)
.to_string_lossy()
.into_owned()
};

// Create parent directories if they don't exist
if let Some(parent) = Path::new(&absolute_path).parent() {
fs::create_dir_all(parent)
.map_err(|e| format!("Failed to create parent directories: {}", e))?;
}

fs::write(&absolute_path, content)
.map_err(|e| format!("Failed to write file '{}': {}", absolute_path, e))
}
6 changes: 6 additions & 0 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ fn main() {
commands::shell::get_current_dir,
commands::shell::list_directory_contents,
commands::shell::change_directory,
commands::shell::read_file_for_editor,
commands::shell::write_file_from_editor,
commands::history::save_command_to_history,
commands::history::load_command_history,
commands::history::clear_command_history,
commands::files::read_file,
commands::ai::ask_llm,
commands::api_key::save_api_key,
commands::api_key::get_api_key,
Expand Down
Loading