1- use crate :: sandbox:: profile:: { ProfileBuilder , SandboxRule } ;
2- use crate :: sandbox:: executor:: { SerializedProfile , SerializedOperation } ;
1+ use crate :: sandbox:: profile:: ProfileBuilder ;
32use anyhow:: Result ;
43use chrono;
54use log:: { debug, error, info, warn} ;
@@ -8,112 +7,15 @@ use serde::{Deserialize, Serialize};
87use serde_json:: Value as JsonValue ;
98use std:: path:: PathBuf ;
109use std:: process:: Stdio ;
11- use std:: sync:: { Arc , Mutex } ;
10+ use std:: sync:: Mutex ;
1211use tauri:: { AppHandle , Manager , State , Emitter } ;
1312use tokio:: io:: { AsyncBufReadExt , BufReader } ;
1413use tokio:: process:: Command ;
1514
1615/// Finds the full path to the claude binary
1716/// This is necessary because macOS apps have a limited PATH environment
1817fn find_claude_binary ( app_handle : & AppHandle ) -> Result < String , String > {
19- log:: info!( "Searching for claude binary..." ) ;
20-
21- // First check if we have a stored path in the database
22- if let Ok ( app_data_dir) = app_handle. path ( ) . app_data_dir ( ) {
23- let db_path = app_data_dir. join ( "agents.db" ) ;
24- if db_path. exists ( ) {
25- if let Ok ( conn) = rusqlite:: Connection :: open ( & db_path) {
26- if let Ok ( stored_path) = conn. query_row (
27- "SELECT value FROM app_settings WHERE key = 'claude_binary_path'" ,
28- [ ] ,
29- |row| row. get :: < _ , String > ( 0 ) ,
30- ) {
31- log:: info!( "Found stored claude path in database: {}" , stored_path) ;
32- let path_buf = std:: path:: PathBuf :: from ( & stored_path) ;
33- if path_buf. exists ( ) && path_buf. is_file ( ) {
34- return Ok ( stored_path) ;
35- } else {
36- log:: warn!( "Stored claude path no longer exists: {}" , stored_path) ;
37- }
38- }
39- }
40- }
41- }
42-
43- // Common installation paths for claude
44- let mut paths_to_check: Vec < String > = vec ! [
45- "/usr/local/bin/claude" . to_string( ) ,
46- "/opt/homebrew/bin/claude" . to_string( ) ,
47- "/usr/bin/claude" . to_string( ) ,
48- "/bin/claude" . to_string( ) ,
49- ] ;
50-
51- // Also check user-specific paths
52- if let Ok ( home) = std:: env:: var ( "HOME" ) {
53- paths_to_check. extend ( vec ! [
54- format!( "{}/.claude/local/claude" , home) ,
55- format!( "{}/.local/bin/claude" , home) ,
56- format!( "{}/.npm-global/bin/claude" , home) ,
57- format!( "{}/.yarn/bin/claude" , home) ,
58- format!( "{}/.bun/bin/claude" , home) ,
59- format!( "{}/bin/claude" , home) ,
60- // Check common node_modules locations
61- format!( "{}/node_modules/.bin/claude" , home) ,
62- format!( "{}/.config/yarn/global/node_modules/.bin/claude" , home) ,
63- ] ) ;
64- }
65-
66- // Check each path
67- for path in paths_to_check {
68- let path_buf = std:: path:: PathBuf :: from ( & path) ;
69- if path_buf. exists ( ) && path_buf. is_file ( ) {
70- log:: info!( "Found claude at: {}" , path) ;
71- return Ok ( path) ;
72- }
73- }
74-
75- // In production builds, skip the 'which' command as it's blocked by Tauri
76- #[ cfg( not( debug_assertions) ) ]
77- {
78- log:: warn!( "Cannot use 'which' command in production build, checking if claude is in PATH" ) ;
79- // In production, just return "claude" and let the execution fail with a proper error
80- // if it's not actually available. The user can then set the path manually.
81- return Ok ( "claude" . to_string ( ) ) ;
82- }
83-
84- // Only try 'which' in development builds
85- #[ cfg( debug_assertions) ]
86- {
87- // Fallback: try using 'which' command
88- log:: info!( "Trying 'which claude' to find binary..." ) ;
89- if let Ok ( output) = std:: process:: Command :: new ( "which" )
90- . arg ( "claude" )
91- . output ( )
92- {
93- if output. status . success ( ) {
94- let path = String :: from_utf8_lossy ( & output. stdout ) . trim ( ) . to_string ( ) ;
95- if !path. is_empty ( ) {
96- log:: info!( "'which' found claude at: {}" , path) ;
97- return Ok ( path) ;
98- }
99- }
100- }
101-
102- // Additional fallback: check if claude is in the current PATH
103- // This might work in dev mode
104- if let Ok ( output) = std:: process:: Command :: new ( "claude" )
105- . arg ( "--version" )
106- . output ( )
107- {
108- if output. status . success ( ) {
109- log:: info!( "claude is available in PATH (dev mode?)" ) ;
110- return Ok ( "claude" . to_string ( ) ) ;
111- }
112- }
113- }
114-
115- log:: error!( "Could not find claude binary in any common location" ) ;
116- 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 ( ) )
18+ crate :: claude_binary:: find_claude_binary ( app_handle)
11719}
11820
11921/// 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
18961798/// Helper function to create a tokio Command with proper environment variables
18971799/// This ensures commands like Claude can find Node.js and other dependencies
18981800fn create_command_with_env ( program : & str ) -> Command {
1899- let mut cmd = Command :: new ( program) ;
1900-
1901- // Inherit essential environment variables from parent process
1801+ // Convert std::process::Command to tokio::process::Command
1802+ let _std_cmd = crate :: claude_binary:: create_command_with_env ( program) ;
1803+
1804+ // Create a new tokio Command from the program path
1805+ let mut tokio_cmd = Command :: new ( program) ;
1806+
1807+ // Copy over all environment variables from the std::process::Command
1808+ // This is a workaround since we can't directly convert between the two types
19021809 for ( key, value) in std:: env:: vars ( ) {
19031810 if key == "PATH" || key == "HOME" || key == "USER"
19041811 || key == "SHELL" || key == "LANG" || key == "LC_ALL" || key. starts_with ( "LC_" )
19051812 || key == "NODE_PATH" || key == "NVM_DIR" || key == "NVM_BIN"
19061813 || key == "HOMEBREW_PREFIX" || key == "HOMEBREW_CELLAR" {
1907- cmd. env ( & key, & value) ;
1814+ tokio_cmd. env ( & key, & value) ;
1815+ }
1816+ }
1817+
1818+ // Add NVM support if the program is in an NVM directory
1819+ if program. contains ( "/.nvm/versions/node/" ) {
1820+ if let Some ( node_bin_dir) = std:: path:: Path :: new ( program) . parent ( ) {
1821+ let current_path = std:: env:: var ( "PATH" ) . unwrap_or_default ( ) ;
1822+ let node_bin_str = node_bin_dir. to_string_lossy ( ) ;
1823+ if !current_path. contains ( & node_bin_str. as_ref ( ) ) {
1824+ let new_path = format ! ( "{}:{}" , node_bin_str, current_path) ;
1825+ tokio_cmd. env ( "PATH" , new_path) ;
1826+ }
19081827 }
19091828 }
19101829
1911- // Ensure PATH contains common Homebrew locations so that `/usr/bin/env node` resolves
1912- // when the application is launched from the macOS GUI (PATH is very minimal there).
1830+ // Ensure PATH contains common Homebrew locations
19131831 if let Ok ( existing_path) = std:: env:: var ( "PATH" ) {
19141832 let mut paths: Vec < & str > = existing_path. split ( ':' ) . collect ( ) ;
19151833 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 {
19181836 }
19191837 }
19201838 let joined = paths. join ( ":" ) ;
1921- cmd . env ( "PATH" , joined) ;
1839+ tokio_cmd . env ( "PATH" , joined) ;
19221840 } else {
1923- // Fallback: set a reasonable default PATH
1924- cmd. env ( "PATH" , "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin" ) ;
1841+ tokio_cmd. env ( "PATH" , "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin" ) ;
19251842 }
19261843
1927- cmd
1844+ tokio_cmd
19281845}
19291846
19301847/// Import an agent from JSON data
0 commit comments