Backport: Agentic Shell Command Execution (v1.0.0 → v1.0.8) #66

Merged
sarman merged 16 commits from feature/agentic-shell-commands into master 2026-06-05 15:30:28 +00:00
5 changed files with 226 additions and 2 deletions
Showing only changes of commit 1400f43d7a - Show all commits

38
scripts/download-kubectl.sh Executable file
View File

@ -0,0 +1,38 @@
#!/bin/bash
set -e
KUBECTL_VERSION="v1.30.0"
BINARIES_DIR="src-tauri/binaries"
echo "Downloading kubectl binaries version ${KUBECTL_VERSION}..."
mkdir -p "$BINARIES_DIR"
# Download for all platforms
# Tauri uses this structure: binaries/kubectl-{target-triple} or kubectl-{target-triple}.exe
echo "Downloading kubectl for Linux x86_64..."
curl -L -o "$BINARIES_DIR/kubectl-x86_64-unknown-linux-gnu" \
"https://dl.k8s.io/release/$KUBECTL_VERSION/bin/linux/amd64/kubectl"
echo "Downloading kubectl for Linux aarch64..."
curl -L -o "$BINARIES_DIR/kubectl-aarch64-unknown-linux-gnu" \
"https://dl.k8s.io/release/$KUBECTL_VERSION/bin/linux/arm64/kubectl"
echo "Downloading kubectl for macOS x86_64..."
curl -L -o "$BINARIES_DIR/kubectl-x86_64-apple-darwin" \
"https://dl.k8s.io/release/$KUBECTL_VERSION/bin/darwin/amd64/kubectl"
echo "Downloading kubectl for macOS aarch64..."
curl -L -o "$BINARIES_DIR/kubectl-aarch64-apple-darwin" \
"https://dl.k8s.io/release/$KUBECTL_VERSION/bin/darwin/arm64/kubectl"
echo "Downloading kubectl for Windows x86_64..."
curl -L -o "$BINARIES_DIR/kubectl-x86_64-pc-windows-gnu.exe" \
"https://dl.k8s.io/release/$KUBECTL_VERSION/bin/windows/amd64/kubectl.exe"
# Make binaries executable (not needed for Windows .exe)
chmod +x "$BINARIES_DIR"/kubectl-*-linux-* "$BINARIES_DIR"/kubectl-*-darwin
echo "kubectl binaries downloaded successfully to $BINARIES_DIR"
echo "Total size:"
du -sh "$BINARIES_DIR"

3
src-tauri/Cargo.lock generated
View File

@ -6370,6 +6370,7 @@ dependencies = [
"flate2", "flate2",
"futures", "futures",
"hex", "hex",
"http 1.4.0",
"infer 0.15.0", "infer 0.15.0",
"lazy_static", "lazy_static",
"lopdf", "lopdf",
@ -6391,7 +6392,7 @@ dependencies = [
"tauri-plugin-http", "tauri-plugin-http",
"tauri-plugin-shell", "tauri-plugin-shell",
"tauri-plugin-stronghold", "tauri-plugin-stronghold",
"thiserror 1.0.69", "thiserror 2.0.18",
"tokio", "tokio",
"tokio-test", "tokio-test",
"tracing", "tracing",

View File

@ -30,7 +30,7 @@ docx-rs = "0.4"
sha2 = { version = "0.10", features = ["std"] } sha2 = { version = "0.10", features = ["std"] }
hex = "0.4" hex = "0.4"
anyhow = "1" anyhow = "1"
thiserror = "1" thiserror = "2"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
@ -53,6 +53,7 @@ rmcp = { version = "1.7.0", features = [
"transport-child-process", "transport-child-process",
"transport-streamable-http-client-reqwest", "transport-streamable-http-client-reqwest",
] } ] }
http = "1.4"
flate2 = { version = "1", features = ["rust_backend"] } flate2 = { version = "1", features = ["rust_backend"] }
[dev-dependencies] [dev-dependencies]

View File

@ -333,6 +333,7 @@ pub async fn initiate_oauth(
app_data_dir, app_data_dir,
integration_webviews, integration_webviews,
mcp_connections, mcp_connections,
pending_approvals: Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())),
}; };
while let Some(callback) = callback_rx.recv().await { while let Some(callback) = callback_rx.recv().await {
tracing::info!("Received OAuth callback for state: {}", callback.state); tracing::info!("Received OAuth callback for state: {}", callback.state);

View File

@ -95,6 +95,189 @@ pub fn get_install_instructions(platform: &str) -> InstallGuide {
} }
} }
/// Helper to find Ollama binary in common locations
fn find_ollama_binary() -> Option<std::path::PathBuf> {
let common_paths = [
"/usr/local/bin/ollama",
"/opt/homebrew/bin/ollama",
"/usr/bin/ollama",
"/home/linuxbrew/.linuxbrew/bin/ollama",
];
for path in &common_paths {
let p = std::path::Path::new(path);
if p.exists() {
return Some(p.to_path_buf());
}
}
// Fallback to which/where command
let which_cmd = if cfg!(target_os = "windows") {
"where"
} else {
"which"
};
std::process::Command::new(which_cmd)
.arg("ollama")
.output()
.ok()
.and_then(|output| {
if output.status.success() {
String::from_utf8(output.stdout)
.ok()
.map(|s| std::path::PathBuf::from(s.trim()))
} else {
None
}
})
}
/// Attempt to start Ollama service if installed but not running
pub async fn start_ollama_service() -> anyhow::Result<bool> {
let status = check_ollama().await?;
// If already running, nothing to do
if status.running {
tracing::info!("Ollama is already running");
return Ok(true);
}
// If not installed, can't start it
if !status.installed {
tracing::warn!("Ollama is not installed, cannot auto-start");
return Ok(false);
}
tracing::info!("Ollama is installed but not running, attempting to start...");
// Platform-specific start logic
#[cfg(target_os = "macos")]
{
// On macOS, try to launch Ollama.app which manages the service
let ollama_app = "/Applications/Ollama.app";
if std::path::Path::new(ollama_app).exists() {
tracing::info!("Launching Ollama.app...");
let result = std::process::Command::new("open").arg(ollama_app).spawn();
match result {
Ok(_) => {
// Wait a few seconds for Ollama to start
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
// Check if it's now running
let new_status = check_ollama().await?;
if new_status.running {
tracing::info!("Ollama started successfully via Ollama.app");
return Ok(true);
} else {
tracing::warn!("Ollama.app launched but service not responding yet");
return Ok(false);
}
}
Err(e) => {
tracing::error!("Failed to launch Ollama.app: {}", e);
}
}
}
// Fallback: try direct ollama serve with full path
if let Some(ollama_bin) = find_ollama_binary() {
tracing::info!(
"Attempting to start ollama serve directly at {:?}...",
ollama_bin
);
let result = std::process::Command::new(&ollama_bin)
.arg("serve")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn();
match result {
Ok(_) => {
// Wait for service to become available
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
let new_status = check_ollama().await?;
Ok(new_status.running)
}
Err(e) => {
tracing::error!("Failed to start ollama serve: {}", e);
Ok(false)
}
}
} else {
tracing::error!("Ollama binary not found in PATH or common locations");
Ok(false)
}
}
#[cfg(target_os = "linux")]
{
// On Linux, start ollama serve in background using full path
if let Some(ollama_bin) = find_ollama_binary() {
tracing::info!("Starting ollama serve at {:?}...", ollama_bin);
let result = std::process::Command::new(&ollama_bin)
.arg("serve")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn();
match result {
Ok(_) => {
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
let new_status = check_ollama().await?;
if new_status.running {
tracing::info!("Ollama started successfully");
Ok(true)
} else {
tracing::warn!("ollama serve started but not responding yet");
Ok(false)
}
}
Err(e) => {
tracing::error!("Failed to start ollama serve: {}", e);
Ok(false)
}
}
} else {
tracing::error!("Ollama binary not found");
Ok(false)
}
}
#[cfg(target_os = "windows")]
{
// On Windows, Ollama runs as a service, check if we can start it
tracing::info!("Attempting to start Ollama on Windows...");
if let Some(ollama_bin) = find_ollama_binary() {
let result = std::process::Command::new(&ollama_bin)
.arg("serve")
.spawn();
match result {
Ok(_) => {
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
let new_status = check_ollama().await?;
Ok(new_status.running)
}
Err(e) => {
tracing::error!("Failed to start Ollama: {}", e);
Ok(false)
}
}
} else {
tracing::error!("Ollama binary not found");
Ok(false)
}
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
{
tracing::warn!("Auto-start not supported on this platform");
Ok(false)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;