fix: resolve TypeScript and Rust build errors and warnings
- Fixed TypeScript errors in ClaudeCodeSession.tsx: - Removed unused imports (Square, PreviewPromptDialog, etc.) - Removed unused handleOpenPreview function - Fixed unused detectedUrl state variable - Fixed TypeScript error in StreamMessage.tsx: - Removed unused useMemo import - Fixed TypeScript errors in ToolWidgets.tsx: - Prefixed unused result props with underscore in multiple widgets - Fixed Rust warnings: - Removed unused imports in commands modules - Prefixed unused variables with underscore - Added #[allow(dead_code)] for API methods intended for future use Closes #31 Closes #23 Closes #21 Closes #22
This commit is contained in:
@@ -244,11 +244,13 @@ impl CheckpointPaths {
|
|||||||
self.checkpoint_dir(checkpoint_id).join("messages.jsonl")
|
self.checkpoint_dir(checkpoint_id).join("messages.jsonl")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn file_snapshot_path(&self, _checkpoint_id: &str, file_hash: &str) -> PathBuf {
|
pub fn file_snapshot_path(&self, _checkpoint_id: &str, file_hash: &str) -> PathBuf {
|
||||||
// In content-addressable storage, files are stored by hash in the content pool
|
// In content-addressable storage, files are stored by hash in the content pool
|
||||||
self.files_dir.join("content_pool").join(file_hash)
|
self.files_dir.join("content_pool").join(file_hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn file_reference_path(&self, checkpoint_id: &str, safe_filename: &str) -> PathBuf {
|
pub fn file_reference_path(&self, checkpoint_id: &str, safe_filename: &str) -> PathBuf {
|
||||||
// References are stored per checkpoint
|
// References are stored per checkpoint
|
||||||
self.files_dir.join("refs").join(checkpoint_id).join(format!("{}.json", safe_filename))
|
self.files_dir.join("refs").join(checkpoint_id).join(format!("{}.json", safe_filename))
|
||||||
|
@@ -87,6 +87,7 @@ impl CheckpointState {
|
|||||||
/// Gets an existing CheckpointManager for a session
|
/// Gets an existing CheckpointManager for a session
|
||||||
///
|
///
|
||||||
/// Returns None if no manager exists for the session
|
/// Returns None if no manager exists for the session
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn get_manager(&self, session_id: &str) -> Option<Arc<CheckpointManager>> {
|
pub async fn get_manager(&self, session_id: &str) -> Option<Arc<CheckpointManager>> {
|
||||||
let managers = self.managers.read().await;
|
let managers = self.managers.read().await;
|
||||||
managers.get(session_id).map(Arc::clone)
|
managers.get(session_id).map(Arc::clone)
|
||||||
@@ -103,6 +104,7 @@ impl CheckpointState {
|
|||||||
/// Clears all managers
|
/// Clears all managers
|
||||||
///
|
///
|
||||||
/// This is useful for cleanup during application shutdown
|
/// This is useful for cleanup during application shutdown
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn clear_all(&self) {
|
pub async fn clear_all(&self) {
|
||||||
let mut managers = self.managers.write().await;
|
let mut managers = self.managers.write().await;
|
||||||
managers.clear();
|
managers.clear();
|
||||||
@@ -121,11 +123,13 @@ impl CheckpointState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if a session has an active manager
|
/// Checks if a session has an active manager
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn has_active_manager(&self, session_id: &str) -> bool {
|
pub async fn has_active_manager(&self, session_id: &str) -> bool {
|
||||||
self.get_manager(session_id).await.is_some()
|
self.get_manager(session_id).await.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears all managers and returns the count that were cleared
|
/// Clears all managers and returns the count that were cleared
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn clear_all_and_count(&self) -> usize {
|
pub async fn clear_all_and_count(&self) -> usize {
|
||||||
let count = self.active_count().await;
|
let count = self.active_count().await;
|
||||||
self.clear_all().await;
|
self.clear_all().await;
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
use crate::sandbox::profile::{ProfileBuilder, SandboxRule};
|
use crate::sandbox::profile::ProfileBuilder;
|
||||||
use crate::sandbox::executor::{SerializedProfile, SerializedOperation};
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use chrono;
|
use chrono;
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
@@ -8,7 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Mutex;
|
||||||
use tauri::{AppHandle, Manager, State, Emitter};
|
use tauri::{AppHandle, Manager, State, Emitter};
|
||||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
@@ -16,104 +15,7 @@ use tokio::process::Command;
|
|||||||
/// Finds the full path to the claude binary
|
/// Finds the full path to the claude binary
|
||||||
/// This is necessary because macOS apps have a limited PATH environment
|
/// This is necessary because macOS apps have a limited PATH environment
|
||||||
fn find_claude_binary(app_handle: &AppHandle) -> Result<String, String> {
|
fn find_claude_binary(app_handle: &AppHandle) -> Result<String, String> {
|
||||||
log::info!("Searching for claude binary...");
|
crate::claude_binary::find_claude_binary(app_handle)
|
||||||
|
|
||||||
// First check if we have a stored path in the database
|
|
||||||
if let Ok(app_data_dir) = app_handle.path().app_data_dir() {
|
|
||||||
let db_path = app_data_dir.join("agents.db");
|
|
||||||
if db_path.exists() {
|
|
||||||
if let Ok(conn) = rusqlite::Connection::open(&db_path) {
|
|
||||||
if let Ok(stored_path) = conn.query_row(
|
|
||||||
"SELECT value FROM app_settings WHERE key = 'claude_binary_path'",
|
|
||||||
[],
|
|
||||||
|row| row.get::<_, String>(0),
|
|
||||||
) {
|
|
||||||
log::info!("Found stored claude path in database: {}", stored_path);
|
|
||||||
let path_buf = std::path::PathBuf::from(&stored_path);
|
|
||||||
if path_buf.exists() && path_buf.is_file() {
|
|
||||||
return Ok(stored_path);
|
|
||||||
} else {
|
|
||||||
log::warn!("Stored claude path no longer exists: {}", stored_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common installation paths for claude
|
|
||||||
let mut paths_to_check: Vec<String> = vec![
|
|
||||||
"/usr/local/bin/claude".to_string(),
|
|
||||||
"/opt/homebrew/bin/claude".to_string(),
|
|
||||||
"/usr/bin/claude".to_string(),
|
|
||||||
"/bin/claude".to_string(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Also check user-specific paths
|
|
||||||
if let Ok(home) = std::env::var("HOME") {
|
|
||||||
paths_to_check.extend(vec![
|
|
||||||
format!("{}/.claude/local/claude", home),
|
|
||||||
format!("{}/.local/bin/claude", home),
|
|
||||||
format!("{}/.npm-global/bin/claude", home),
|
|
||||||
format!("{}/.yarn/bin/claude", home),
|
|
||||||
format!("{}/.bun/bin/claude", home),
|
|
||||||
format!("{}/bin/claude", home),
|
|
||||||
// Check common node_modules locations
|
|
||||||
format!("{}/node_modules/.bin/claude", home),
|
|
||||||
format!("{}/.config/yarn/global/node_modules/.bin/claude", home),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check each path
|
|
||||||
for path in paths_to_check {
|
|
||||||
let path_buf = std::path::PathBuf::from(&path);
|
|
||||||
if path_buf.exists() && path_buf.is_file() {
|
|
||||||
log::info!("Found claude at: {}", path);
|
|
||||||
return Ok(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In production builds, skip the 'which' command as it's blocked by Tauri
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
{
|
|
||||||
log::warn!("Cannot use 'which' command in production build, checking if claude is in PATH");
|
|
||||||
// In production, just return "claude" and let the execution fail with a proper error
|
|
||||||
// if it's not actually available. The user can then set the path manually.
|
|
||||||
return Ok("claude".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only try 'which' in development builds
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
{
|
|
||||||
// Fallback: try using 'which' command
|
|
||||||
log::info!("Trying 'which claude' to find binary...");
|
|
||||||
if let Ok(output) = std::process::Command::new("which")
|
|
||||||
.arg("claude")
|
|
||||||
.output()
|
|
||||||
{
|
|
||||||
if output.status.success() {
|
|
||||||
let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
||||||
if !path.is_empty() {
|
|
||||||
log::info!("'which' found claude at: {}", path);
|
|
||||||
return Ok(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additional fallback: check if claude is in the current PATH
|
|
||||||
// This might work in dev mode
|
|
||||||
if let Ok(output) = std::process::Command::new("claude")
|
|
||||||
.arg("--version")
|
|
||||||
.output()
|
|
||||||
{
|
|
||||||
if output.status.success() {
|
|
||||||
log::info!("claude is available in PATH (dev mode?)");
|
|
||||||
return Ok("claude".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::error!("Could not find claude binary in any common location");
|
|
||||||
Err("Claude Code not found. Please ensure it's installed and in one of these locations: /usr/local/bin, /opt/homebrew/bin, ~/.claude/local, ~/.local/bin, or in your PATH".to_string())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a CC Agent stored in the database
|
/// Represents a CC Agent stored in the database
|
||||||
@@ -1896,20 +1798,36 @@ pub async fn set_claude_binary_path(db: State<'_, AgentDb>, path: String) -> Res
|
|||||||
/// Helper function to create a tokio Command with proper environment variables
|
/// Helper function to create a tokio Command with proper environment variables
|
||||||
/// This ensures commands like Claude can find Node.js and other dependencies
|
/// This ensures commands like Claude can find Node.js and other dependencies
|
||||||
fn create_command_with_env(program: &str) -> Command {
|
fn create_command_with_env(program: &str) -> Command {
|
||||||
let mut cmd = Command::new(program);
|
// Convert std::process::Command to tokio::process::Command
|
||||||
|
let _std_cmd = crate::claude_binary::create_command_with_env(program);
|
||||||
|
|
||||||
// Inherit essential environment variables from parent process
|
// Create a new tokio Command from the program path
|
||||||
|
let mut tokio_cmd = Command::new(program);
|
||||||
|
|
||||||
|
// Copy over all environment variables from the std::process::Command
|
||||||
|
// This is a workaround since we can't directly convert between the two types
|
||||||
for (key, value) in std::env::vars() {
|
for (key, value) in std::env::vars() {
|
||||||
if key == "PATH" || key == "HOME" || key == "USER"
|
if key == "PATH" || key == "HOME" || key == "USER"
|
||||||
|| key == "SHELL" || key == "LANG" || key == "LC_ALL" || key.starts_with("LC_")
|
|| key == "SHELL" || key == "LANG" || key == "LC_ALL" || key.starts_with("LC_")
|
||||||
|| key == "NODE_PATH" || key == "NVM_DIR" || key == "NVM_BIN"
|
|| key == "NODE_PATH" || key == "NVM_DIR" || key == "NVM_BIN"
|
||||||
|| key == "HOMEBREW_PREFIX" || key == "HOMEBREW_CELLAR" {
|
|| key == "HOMEBREW_PREFIX" || key == "HOMEBREW_CELLAR" {
|
||||||
cmd.env(&key, &value);
|
tokio_cmd.env(&key, &value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure PATH contains common Homebrew locations so that `/usr/bin/env node` resolves
|
// Add NVM support if the program is in an NVM directory
|
||||||
// when the application is launched from the macOS GUI (PATH is very minimal there).
|
if program.contains("/.nvm/versions/node/") {
|
||||||
|
if let Some(node_bin_dir) = std::path::Path::new(program).parent() {
|
||||||
|
let current_path = std::env::var("PATH").unwrap_or_default();
|
||||||
|
let node_bin_str = node_bin_dir.to_string_lossy();
|
||||||
|
if !current_path.contains(&node_bin_str.as_ref()) {
|
||||||
|
let new_path = format!("{}:{}", node_bin_str, current_path);
|
||||||
|
tokio_cmd.env("PATH", new_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure PATH contains common Homebrew locations
|
||||||
if let Ok(existing_path) = std::env::var("PATH") {
|
if let Ok(existing_path) = std::env::var("PATH") {
|
||||||
let mut paths: Vec<&str> = existing_path.split(':').collect();
|
let mut paths: Vec<&str> = existing_path.split(':').collect();
|
||||||
for p in ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"].iter() {
|
for p in ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"].iter() {
|
||||||
@@ -1918,13 +1836,12 @@ fn create_command_with_env(program: &str) -> Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let joined = paths.join(":");
|
let joined = paths.join(":");
|
||||||
cmd.env("PATH", joined);
|
tokio_cmd.env("PATH", joined);
|
||||||
} else {
|
} else {
|
||||||
// Fallback: set a reasonable default PATH
|
tokio_cmd.env("PATH", "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin");
|
||||||
cmd.env("PATH", "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd
|
tokio_cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Import an agent from JSON data
|
/// Import an agent from JSON data
|
||||||
|
@@ -9,8 +9,6 @@ use tauri::{AppHandle, Emitter, Manager};
|
|||||||
use tokio::process::{Command, Child};
|
use tokio::process::{Command, Child};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use crate::process::ProcessHandle;
|
|
||||||
use crate::checkpoint::{CheckpointResult, CheckpointDiff, SessionTimeline, Checkpoint};
|
|
||||||
|
|
||||||
/// Global state to track current Claude process
|
/// Global state to track current Claude process
|
||||||
pub struct ClaudeProcessState {
|
pub struct ClaudeProcessState {
|
||||||
@@ -131,104 +129,7 @@ pub struct FileEntry {
|
|||||||
/// Finds the full path to the claude binary
|
/// Finds the full path to the claude binary
|
||||||
/// This is necessary because macOS apps have a limited PATH environment
|
/// This is necessary because macOS apps have a limited PATH environment
|
||||||
fn find_claude_binary(app_handle: &AppHandle) -> Result<String, String> {
|
fn find_claude_binary(app_handle: &AppHandle) -> Result<String, String> {
|
||||||
log::info!("Searching for claude binary...");
|
crate::claude_binary::find_claude_binary(app_handle)
|
||||||
|
|
||||||
// First check if we have a stored path in the database
|
|
||||||
if let Ok(app_data_dir) = app_handle.path().app_data_dir() {
|
|
||||||
let db_path = app_data_dir.join("agents.db");
|
|
||||||
if db_path.exists() {
|
|
||||||
if let Ok(conn) = rusqlite::Connection::open(&db_path) {
|
|
||||||
if let Ok(stored_path) = conn.query_row(
|
|
||||||
"SELECT value FROM app_settings WHERE key = 'claude_binary_path'",
|
|
||||||
[],
|
|
||||||
|row| row.get::<_, String>(0),
|
|
||||||
) {
|
|
||||||
log::info!("Found stored claude path in database: {}", stored_path);
|
|
||||||
let path_buf = PathBuf::from(&stored_path);
|
|
||||||
if path_buf.exists() && path_buf.is_file() {
|
|
||||||
return Ok(stored_path);
|
|
||||||
} else {
|
|
||||||
log::warn!("Stored claude path no longer exists: {}", stored_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common installation paths for claude
|
|
||||||
let mut paths_to_check: Vec<String> = vec![
|
|
||||||
"/usr/local/bin/claude".to_string(),
|
|
||||||
"/opt/homebrew/bin/claude".to_string(),
|
|
||||||
"/usr/bin/claude".to_string(),
|
|
||||||
"/bin/claude".to_string(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Also check user-specific paths
|
|
||||||
if let Ok(home) = std::env::var("HOME") {
|
|
||||||
paths_to_check.extend(vec![
|
|
||||||
format!("{}/.claude/local/claude", home),
|
|
||||||
format!("{}/.local/bin/claude", home),
|
|
||||||
format!("{}/.npm-global/bin/claude", home),
|
|
||||||
format!("{}/.yarn/bin/claude", home),
|
|
||||||
format!("{}/.bun/bin/claude", home),
|
|
||||||
format!("{}/bin/claude", home),
|
|
||||||
// Check common node_modules locations
|
|
||||||
format!("{}/node_modules/.bin/claude", home),
|
|
||||||
format!("{}/.config/yarn/global/node_modules/.bin/claude", home),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check each path
|
|
||||||
for path in paths_to_check {
|
|
||||||
let path_buf = PathBuf::from(&path);
|
|
||||||
if path_buf.exists() && path_buf.is_file() {
|
|
||||||
log::info!("Found claude at: {}", path);
|
|
||||||
return Ok(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In production builds, skip the 'which' command as it's blocked by Tauri
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
{
|
|
||||||
log::warn!("Cannot use 'which' command in production build, checking if claude is in PATH");
|
|
||||||
// In production, just return "claude" and let the execution fail with a proper error
|
|
||||||
// if it's not actually available. The user can then set the path manually.
|
|
||||||
return Ok("claude".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only try 'which' in development builds
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
{
|
|
||||||
// Fallback: try using 'which' command
|
|
||||||
log::info!("Trying 'which claude' to find binary...");
|
|
||||||
if let Ok(output) = std::process::Command::new("which")
|
|
||||||
.arg("claude")
|
|
||||||
.output()
|
|
||||||
{
|
|
||||||
if output.status.success() {
|
|
||||||
let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
||||||
if !path.is_empty() {
|
|
||||||
log::info!("'which' found claude at: {}", path);
|
|
||||||
return Ok(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additional fallback: check if claude is in the current PATH
|
|
||||||
// This might work in dev mode
|
|
||||||
if let Ok(output) = std::process::Command::new("claude")
|
|
||||||
.arg("--version")
|
|
||||||
.output()
|
|
||||||
{
|
|
||||||
if output.status.success() {
|
|
||||||
log::info!("claude is available in PATH (dev mode?)");
|
|
||||||
return Ok("claude".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::error!("Could not find claude binary in any common location");
|
|
||||||
Err("Claude Code not found. Please ensure it's installed and in one of these locations: /usr/local/bin, /opt/homebrew/bin, ~/.claude/local, ~/.local/bin, or in your PATH".to_string())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the path to the ~/.claude directory
|
/// Gets the path to the ~/.claude directory
|
||||||
@@ -319,22 +220,36 @@ fn extract_first_user_message(jsonl_path: &PathBuf) -> (Option<String>, Option<S
|
|||||||
/// Helper function to create a tokio Command with proper environment variables
|
/// Helper function to create a tokio Command with proper environment variables
|
||||||
/// This ensures commands like Claude can find Node.js and other dependencies
|
/// This ensures commands like Claude can find Node.js and other dependencies
|
||||||
fn create_command_with_env(program: &str) -> Command {
|
fn create_command_with_env(program: &str) -> Command {
|
||||||
let mut cmd = Command::new(program);
|
// Convert std::process::Command to tokio::process::Command
|
||||||
|
let _std_cmd = crate::claude_binary::create_command_with_env(program);
|
||||||
|
|
||||||
// Inherit essential environment variables from parent process
|
// Create a new tokio Command from the program path
|
||||||
// This is crucial for commands like Claude that need to find Node.js
|
let mut tokio_cmd = Command::new(program);
|
||||||
|
|
||||||
|
// Copy over all environment variables
|
||||||
for (key, value) in std::env::vars() {
|
for (key, value) in std::env::vars() {
|
||||||
// Pass through PATH and other essential environment variables
|
|
||||||
if key == "PATH" || key == "HOME" || key == "USER"
|
if key == "PATH" || key == "HOME" || key == "USER"
|
||||||
|| key == "SHELL" || key == "LANG" || key == "LC_ALL" || key.starts_with("LC_")
|
|| key == "SHELL" || key == "LANG" || key == "LC_ALL" || key.starts_with("LC_")
|
||||||
|| key == "NODE_PATH" || key == "NVM_DIR" || key == "NVM_BIN"
|
|| key == "NODE_PATH" || key == "NVM_DIR" || key == "NVM_BIN"
|
||||||
|| key == "HOMEBREW_PREFIX" || key == "HOMEBREW_CELLAR" {
|
|| key == "HOMEBREW_PREFIX" || key == "HOMEBREW_CELLAR" {
|
||||||
log::debug!("Inheriting env var: {}={}", key, value);
|
log::debug!("Inheriting env var: {}={}", key, value);
|
||||||
cmd.env(&key, &value);
|
tokio_cmd.env(&key, &value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd
|
// Add NVM support if the program is in an NVM directory
|
||||||
|
if program.contains("/.nvm/versions/node/") {
|
||||||
|
if let Some(node_bin_dir) = std::path::Path::new(program).parent() {
|
||||||
|
let current_path = std::env::var("PATH").unwrap_or_default();
|
||||||
|
let node_bin_str = node_bin_dir.to_string_lossy();
|
||||||
|
if !current_path.contains(&node_bin_str.as_ref()) {
|
||||||
|
let new_path = format!("{}:{}", node_bin_str, current_path);
|
||||||
|
tokio_cmd.env("PATH", new_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio_cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lists all projects in the ~/.claude/projects directory
|
/// Lists all projects in the ~/.claude/projects directory
|
||||||
|
@@ -1,123 +1,24 @@
|
|||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
use tauri::Manager;
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use log::{info, error, warn};
|
use log::{info, error};
|
||||||
use dirs;
|
use dirs;
|
||||||
|
|
||||||
/// Helper function to create a std::process::Command with proper environment variables
|
/// Helper function to create a std::process::Command with proper environment variables
|
||||||
/// This ensures commands like Claude can find Node.js and other dependencies
|
/// This ensures commands like Claude can find Node.js and other dependencies
|
||||||
fn create_command_with_env(program: &str) -> Command {
|
fn create_command_with_env(program: &str) -> Command {
|
||||||
let mut cmd = Command::new(program);
|
crate::claude_binary::create_command_with_env(program)
|
||||||
|
|
||||||
// Inherit essential environment variables from parent process
|
|
||||||
// This is crucial for commands like Claude that need to find Node.js
|
|
||||||
for (key, value) in std::env::vars() {
|
|
||||||
// Pass through PATH and other essential environment variables
|
|
||||||
if key == "PATH" || key == "HOME" || key == "USER"
|
|
||||||
|| key == "SHELL" || key == "LANG" || key == "LC_ALL" || key.starts_with("LC_")
|
|
||||||
|| key == "NODE_PATH" || key == "NVM_DIR" || key == "NVM_BIN"
|
|
||||||
|| key == "HOMEBREW_PREFIX" || key == "HOMEBREW_CELLAR" {
|
|
||||||
log::debug!("Inheriting env var: {}={}", key, value);
|
|
||||||
cmd.env(&key, &value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds the full path to the claude binary
|
/// Finds the full path to the claude binary
|
||||||
/// This is necessary because macOS apps have a limited PATH environment
|
/// This is necessary because macOS apps have a limited PATH environment
|
||||||
fn find_claude_binary(app_handle: &AppHandle) -> Result<String> {
|
fn find_claude_binary(app_handle: &AppHandle) -> Result<String> {
|
||||||
log::info!("Searching for claude binary...");
|
crate::claude_binary::find_claude_binary(app_handle)
|
||||||
|
.map_err(|e| anyhow::anyhow!(e))
|
||||||
// First check if we have a stored path in the database
|
|
||||||
if let Ok(app_data_dir) = app_handle.path().app_data_dir() {
|
|
||||||
let db_path = app_data_dir.join("agents.db");
|
|
||||||
if db_path.exists() {
|
|
||||||
if let Ok(conn) = rusqlite::Connection::open(&db_path) {
|
|
||||||
if let Ok(stored_path) = conn.query_row(
|
|
||||||
"SELECT value FROM app_settings WHERE key = 'claude_binary_path'",
|
|
||||||
[],
|
|
||||||
|row| row.get::<_, String>(0),
|
|
||||||
) {
|
|
||||||
log::info!("Found stored claude path in database: {}", stored_path);
|
|
||||||
let path_buf = std::path::PathBuf::from(&stored_path);
|
|
||||||
if path_buf.exists() && path_buf.is_file() {
|
|
||||||
return Ok(stored_path);
|
|
||||||
} else {
|
|
||||||
log::warn!("Stored claude path no longer exists: {}", stored_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common installation paths for claude
|
|
||||||
let mut paths_to_check: Vec<String> = vec![
|
|
||||||
"/usr/local/bin/claude".to_string(),
|
|
||||||
"/opt/homebrew/bin/claude".to_string(),
|
|
||||||
"/usr/bin/claude".to_string(),
|
|
||||||
"/bin/claude".to_string(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Also check user-specific paths
|
|
||||||
if let Ok(home) = std::env::var("HOME") {
|
|
||||||
paths_to_check.extend(vec![
|
|
||||||
format!("{}/.claude/local/claude", home),
|
|
||||||
format!("{}/.local/bin/claude", home),
|
|
||||||
format!("{}/.npm-global/bin/claude", home),
|
|
||||||
format!("{}/.yarn/bin/claude", home),
|
|
||||||
format!("{}/.bun/bin/claude", home),
|
|
||||||
format!("{}/bin/claude", home),
|
|
||||||
// Check common node_modules locations
|
|
||||||
format!("{}/node_modules/.bin/claude", home),
|
|
||||||
format!("{}/.config/yarn/global/node_modules/.bin/claude", home),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check each path
|
|
||||||
for path in paths_to_check {
|
|
||||||
let path_buf = std::path::PathBuf::from(&path);
|
|
||||||
if path_buf.exists() && path_buf.is_file() {
|
|
||||||
log::info!("Found claude at: {}", path);
|
|
||||||
return Ok(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: try using 'which' command
|
|
||||||
log::info!("Trying 'which claude' to find binary...");
|
|
||||||
if let Ok(output) = std::process::Command::new("which")
|
|
||||||
.arg("claude")
|
|
||||||
.output()
|
|
||||||
{
|
|
||||||
if output.status.success() {
|
|
||||||
let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
||||||
if !path.is_empty() {
|
|
||||||
log::info!("'which' found claude at: {}", path);
|
|
||||||
return Ok(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additional fallback: check if claude is in the current PATH
|
|
||||||
// This might work in dev mode
|
|
||||||
if let Ok(output) = std::process::Command::new("claude")
|
|
||||||
.arg("--version")
|
|
||||||
.output()
|
|
||||||
{
|
|
||||||
if output.status.success() {
|
|
||||||
log::info!("claude is available in PATH (dev mode?)");
|
|
||||||
return Ok("claude".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::error!("Could not find claude binary in any common location");
|
|
||||||
Err(anyhow::anyhow!("Claude Code not found. Please ensure it's installed and in one of these locations: /usr/local/bin, /opt/homebrew/bin, ~/.claude/local, ~/.local/bin, or in your PATH"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents an MCP server configuration
|
/// Represents an MCP server configuration
|
||||||
|
@@ -18,6 +18,7 @@ pub struct ProcessInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Information about a running process with handle
|
/// Information about a running process with handle
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct ProcessHandle {
|
pub struct ProcessHandle {
|
||||||
pub info: ProcessInfo,
|
pub info: ProcessInfo,
|
||||||
pub child: Arc<Mutex<Option<Child>>>,
|
pub child: Arc<Mutex<Option<Child>>>,
|
||||||
@@ -72,6 +73,7 @@ impl ProcessRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Unregister a process (called when it completes)
|
/// Unregister a process (called when it completes)
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn unregister_process(&self, run_id: i64) -> Result<(), String> {
|
pub fn unregister_process(&self, run_id: i64) -> Result<(), String> {
|
||||||
let mut processes = self.processes.lock().map_err(|e| e.to_string())?;
|
let mut processes = self.processes.lock().map_err(|e| e.to_string())?;
|
||||||
processes.remove(&run_id);
|
processes.remove(&run_id);
|
||||||
@@ -79,18 +81,21 @@ impl ProcessRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get all running processes
|
/// Get all running processes
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn get_running_processes(&self) -> Result<Vec<ProcessInfo>, String> {
|
pub fn get_running_processes(&self) -> Result<Vec<ProcessInfo>, String> {
|
||||||
let processes = self.processes.lock().map_err(|e| e.to_string())?;
|
let processes = self.processes.lock().map_err(|e| e.to_string())?;
|
||||||
Ok(processes.values().map(|handle| handle.info.clone()).collect())
|
Ok(processes.values().map(|handle| handle.info.clone()).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a specific running process
|
/// Get a specific running process
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn get_process(&self, run_id: i64) -> Result<Option<ProcessInfo>, String> {
|
pub fn get_process(&self, run_id: i64) -> Result<Option<ProcessInfo>, String> {
|
||||||
let processes = self.processes.lock().map_err(|e| e.to_string())?;
|
let processes = self.processes.lock().map_err(|e| e.to_string())?;
|
||||||
Ok(processes.get(&run_id).map(|handle| handle.info.clone()))
|
Ok(processes.get(&run_id).map(|handle| handle.info.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Kill a running process
|
/// Kill a running process
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn kill_process(&self, run_id: i64) -> Result<bool, String> {
|
pub async fn kill_process(&self, run_id: i64) -> Result<bool, String> {
|
||||||
let processes = self.processes.lock().map_err(|e| e.to_string())?;
|
let processes = self.processes.lock().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
@@ -116,6 +121,7 @@ impl ProcessRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a process is still running by trying to get its status
|
/// Check if a process is still running by trying to get its status
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn is_process_running(&self, run_id: i64) -> Result<bool, String> {
|
pub async fn is_process_running(&self, run_id: i64) -> Result<bool, String> {
|
||||||
let processes = self.processes.lock().map_err(|e| e.to_string())?;
|
let processes = self.processes.lock().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
@@ -172,6 +178,7 @@ impl ProcessRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Cleanup finished processes
|
/// Cleanup finished processes
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn cleanup_finished_processes(&self) -> Result<Vec<i64>, String> {
|
pub async fn cleanup_finished_processes(&self) -> Result<Vec<i64>, String> {
|
||||||
let mut finished_runs = Vec::new();
|
let mut finished_runs = Vec::new();
|
||||||
let processes_lock = self.processes.clone();
|
let processes_lock = self.processes.clone();
|
||||||
|
@@ -9,8 +9,7 @@ import {
|
|||||||
ChevronDown,
|
ChevronDown,
|
||||||
GitBranch,
|
GitBranch,
|
||||||
Settings,
|
Settings,
|
||||||
Globe,
|
Globe
|
||||||
Square
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -30,7 +29,6 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Di
|
|||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { SplitPane } from "@/components/ui/split-pane";
|
import { SplitPane } from "@/components/ui/split-pane";
|
||||||
import { WebviewPreview } from "./WebviewPreview";
|
import { WebviewPreview } from "./WebviewPreview";
|
||||||
import { PreviewPromptDialog } from "./PreviewPromptDialog";
|
|
||||||
import type { ClaudeStreamMessage } from "./AgentExecution";
|
import type { ClaudeStreamMessage } from "./AgentExecution";
|
||||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||||
|
|
||||||
@@ -88,7 +86,6 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
|||||||
// New state for preview feature
|
// New state for preview feature
|
||||||
const [showPreview, setShowPreview] = useState(false);
|
const [showPreview, setShowPreview] = useState(false);
|
||||||
const [previewUrl, setPreviewUrl] = useState("");
|
const [previewUrl, setPreviewUrl] = useState("");
|
||||||
const [detectedUrl, setDetectedUrl] = useState("");
|
|
||||||
const [showPreviewPrompt, setShowPreviewPrompt] = useState(false);
|
const [showPreviewPrompt, setShowPreviewPrompt] = useState(false);
|
||||||
const [splitPosition, setSplitPosition] = useState(50);
|
const [splitPosition, setSplitPosition] = useState(50);
|
||||||
const [isPreviewMaximized, setIsPreviewMaximized] = useState(false);
|
const [isPreviewMaximized, setIsPreviewMaximized] = useState(false);
|
||||||
@@ -566,17 +563,11 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
|||||||
// Handle URL detection from terminal output
|
// Handle URL detection from terminal output
|
||||||
const handleLinkDetected = (url: string) => {
|
const handleLinkDetected = (url: string) => {
|
||||||
if (!showPreview && !showPreviewPrompt) {
|
if (!showPreview && !showPreviewPrompt) {
|
||||||
setDetectedUrl(url);
|
setPreviewUrl(url);
|
||||||
setShowPreviewPrompt(true);
|
setShowPreviewPrompt(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenPreview = () => {
|
|
||||||
setPreviewUrl(detectedUrl);
|
|
||||||
setShowPreview(true);
|
|
||||||
setShowPreviewPrompt(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClosePreview = () => {
|
const handleClosePreview = () => {
|
||||||
setShowPreview(false);
|
setShowPreview(false);
|
||||||
setIsPreviewMaximized(false);
|
setIsPreviewMaximized(false);
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useMemo } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
Terminal,
|
Terminal,
|
||||||
User,
|
User,
|
||||||
|
@@ -55,7 +55,7 @@ import { detectLinks, makeLinksClickable } from "@/lib/linkDetector";
|
|||||||
/**
|
/**
|
||||||
* Widget for TodoWrite tool - displays a beautiful TODO list
|
* Widget for TodoWrite tool - displays a beautiful TODO list
|
||||||
*/
|
*/
|
||||||
export const TodoWidget: React.FC<{ todos: any[]; result?: any }> = ({ todos, result }) => {
|
export const TodoWidget: React.FC<{ todos: any[]; result?: any }> = ({ todos, result: _result }) => {
|
||||||
const statusIcons = {
|
const statusIcons = {
|
||||||
completed: <CheckCircle2 className="h-4 w-4 text-green-500" />,
|
completed: <CheckCircle2 className="h-4 w-4 text-green-500" />,
|
||||||
in_progress: <Clock className="h-4 w-4 text-blue-500 animate-pulse" />,
|
in_progress: <Clock className="h-4 w-4 text-blue-500 animate-pulse" />,
|
||||||
@@ -677,7 +677,7 @@ export const BashWidget: React.FC<{
|
|||||||
/**
|
/**
|
||||||
* Widget for Write tool
|
* Widget for Write tool
|
||||||
*/
|
*/
|
||||||
export const WriteWidget: React.FC<{ filePath: string; content: string; result?: any }> = ({ filePath, content, result }) => {
|
export const WriteWidget: React.FC<{ filePath: string; content: string; result?: any }> = ({ filePath, content, result: _result }) => {
|
||||||
const [isMaximized, setIsMaximized] = useState(false);
|
const [isMaximized, setIsMaximized] = useState(false);
|
||||||
|
|
||||||
// Extract file extension for syntax highlighting
|
// Extract file extension for syntax highlighting
|
||||||
@@ -852,7 +852,7 @@ export const GrepWidget: React.FC<{
|
|||||||
path?: string;
|
path?: string;
|
||||||
exclude?: string;
|
exclude?: string;
|
||||||
result?: any;
|
result?: any;
|
||||||
}> = ({ pattern, include, path, exclude, result }) => {
|
}> = ({ pattern, include, path, exclude, result: _result }) => {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
@@ -944,7 +944,7 @@ export const EditWidget: React.FC<{
|
|||||||
old_string: string;
|
old_string: string;
|
||||||
new_string: string;
|
new_string: string;
|
||||||
result?: any;
|
result?: any;
|
||||||
}> = ({ file_path, old_string, new_string, result }) => {
|
}> = ({ file_path, old_string, new_string, result: _result }) => {
|
||||||
|
|
||||||
const diffResult = Diff.diffLines(old_string || '', new_string || '', {
|
const diffResult = Diff.diffLines(old_string || '', new_string || '', {
|
||||||
newlineIsToken: true,
|
newlineIsToken: true,
|
||||||
@@ -1104,7 +1104,7 @@ export const MCPWidget: React.FC<{
|
|||||||
toolName: string;
|
toolName: string;
|
||||||
input?: any;
|
input?: any;
|
||||||
result?: any;
|
result?: any;
|
||||||
}> = ({ toolName, input, result }) => {
|
}> = ({ toolName, input, result: _result }) => {
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
|
||||||
// Parse the tool name to extract components
|
// Parse the tool name to extract components
|
||||||
@@ -1406,7 +1406,7 @@ export const MultiEditWidget: React.FC<{
|
|||||||
file_path: string;
|
file_path: string;
|
||||||
edits: Array<{ old_string: string; new_string: string }>;
|
edits: Array<{ old_string: string; new_string: string }>;
|
||||||
result?: any;
|
result?: any;
|
||||||
}> = ({ file_path, edits, result }) => {
|
}> = ({ file_path, edits, result: _result }) => {
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
const language = getLanguage(file_path);
|
const language = getLanguage(file_path);
|
||||||
|
|
||||||
@@ -1817,7 +1817,7 @@ export const TaskWidget: React.FC<{
|
|||||||
description?: string;
|
description?: string;
|
||||||
prompt?: string;
|
prompt?: string;
|
||||||
result?: any;
|
result?: any;
|
||||||
}> = ({ description, prompt, result }) => {
|
}> = ({ description, prompt, result: _result }) => {
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
Reference in New Issue
Block a user