1212use crate :: styled_output:: { print_info, print_warning} ;
1313use anyhow:: { Context , Result , bail} ;
1414use clap:: Parser ;
15+ use cortex_common:: dirs:: { AppDirs , HOME_DIR_NAME , LEGACY_XDG_NAME } ;
1516use std:: collections:: HashMap ;
1617use std:: fs;
1718use std:: path:: { Path , PathBuf } ;
@@ -388,12 +389,22 @@ fn collect_binary_locations(home_dir: &Path) -> Result<Vec<RemovalItem>> {
388389
389390/// Collect items from the ~/.cortex directory.
390391fn collect_cortex_home_items ( home_dir : & Path ) -> Result < Vec < RemovalItem > > {
391- let mut items = Vec :: new ( ) ;
392- let cortex_home = home_dir. join ( ".cortex" ) ;
392+ let app_dirs = AppDirs :: new ( ) . unwrap_or_else ( || {
393+ let cortex_home = home_dir. join ( HOME_DIR_NAME ) ;
394+ AppDirs {
395+ config_dir : cortex_home. clone ( ) ,
396+ data_dir : cortex_home. clone ( ) ,
397+ cache_dir : cortex_home. join ( "cache" ) ,
398+ legacy_xdg_home : home_dir. join ( LEGACY_XDG_NAME ) ,
399+ }
400+ } ) ;
393401
394- if !cortex_home. exists ( ) {
395- return Ok ( items) ;
396- }
402+ collect_cortex_home_items_from_dirs ( & app_dirs)
403+ }
404+
405+ fn collect_cortex_home_items_from_dirs ( app_dirs : & AppDirs ) -> Result < Vec < RemovalItem > > {
406+ let mut items = Vec :: new ( ) ;
407+ let cortex_home = & app_dirs. config_dir ;
397408
398409 // Configuration files
399410 let config_files = [
@@ -402,33 +413,83 @@ fn collect_cortex_home_items(home_dir: &Path) -> Result<Vec<RemovalItem>> {
402413 ( "auth.json" , "OAuth tokens" ) ,
403414 ] ;
404415
405- for ( file, desc) in config_files {
406- let path = cortex_home. join ( file) ;
407- if path. exists ( ) {
416+ if cortex_home. exists ( ) {
417+ for ( file, desc) in config_files {
418+ let path = cortex_home. join ( file) ;
419+ if path. exists ( ) {
420+ items. push ( RemovalItem {
421+ path : path. clone ( ) ,
422+ description : desc. to_string ( ) ,
423+ size : get_file_size ( & path) ,
424+ requires_sudo : false ,
425+ category : RemovalCategory :: Config ,
426+ } ) ;
427+ }
428+ }
429+
430+ // Session data directory
431+ let sessions_dir = cortex_home. join ( "sessions" ) ;
432+ if sessions_dir. exists ( ) {
408433 items. push ( RemovalItem {
409- path : path. clone ( ) ,
410- description : desc. to_string ( ) ,
411- size : get_file_size ( & path) ,
434+ path : sessions_dir. clone ( ) ,
435+ description : "Session history and data" . to_string ( ) ,
436+ size : get_dir_size ( & sessions_dir) ,
437+ requires_sudo : false ,
438+ category : RemovalCategory :: Data ,
439+ } ) ;
440+ }
441+
442+ // Plugins directory
443+ let plugins_dir = cortex_home. join ( "plugins" ) ;
444+ if plugins_dir. exists ( ) {
445+ items. push ( RemovalItem {
446+ path : plugins_dir. clone ( ) ,
447+ description : "Installed plugins" . to_string ( ) ,
448+ size : get_dir_size ( & plugins_dir) ,
449+ requires_sudo : false ,
450+ category : RemovalCategory :: Plugins ,
451+ } ) ;
452+ }
453+
454+ // Skills directory
455+ let skills_dir = cortex_home. join ( "skills" ) ;
456+ if skills_dir. exists ( ) {
457+ items. push ( RemovalItem {
458+ path : skills_dir. clone ( ) ,
459+ description : "Custom skills" . to_string ( ) ,
460+ size : get_dir_size ( & skills_dir) ,
461+ requires_sudo : false ,
462+ category : RemovalCategory :: Plugins ,
463+ } ) ;
464+ }
465+
466+ // MCP servers directory
467+ let mcp_dir = cortex_home. join ( "mcp" ) ;
468+ if mcp_dir. exists ( ) {
469+ items. push ( RemovalItem {
470+ path : mcp_dir. clone ( ) ,
471+ description : "MCP server configurations" . to_string ( ) ,
472+ size : get_dir_size ( & mcp_dir) ,
412473 requires_sudo : false ,
413474 category : RemovalCategory :: Config ,
414475 } ) ;
415476 }
416- }
417477
418- // Session data directory
419- let sessions_dir = cortex_home. join ( "sessions" ) ;
420- if sessions_dir. exists ( ) {
421- items. push ( RemovalItem {
422- path : sessions_dir. clone ( ) ,
423- description : "Session history and data" . to_string ( ) ,
424- size : get_dir_size ( & sessions_dir) ,
425- requires_sudo : false ,
426- category : RemovalCategory :: Data ,
427- } ) ;
478+ // Agents directory
479+ let agents_dir = cortex_home. join ( "agents" ) ;
480+ if agents_dir. exists ( ) {
481+ items. push ( RemovalItem {
482+ path : agents_dir. clone ( ) ,
483+ description : "Custom agents" . to_string ( ) ,
484+ size : get_dir_size ( & agents_dir) ,
485+ requires_sudo : false ,
486+ category : RemovalCategory :: Plugins ,
487+ } ) ;
488+ }
428489 }
429490
430- // Logs directory
431- let logs_dir = cortex_home . join ( "logs" ) ;
491+ // Actual logs directory used by `cortex logs`.
492+ let logs_dir = app_dirs . logs_dir ( ) ;
432493 if logs_dir. exists ( ) {
433494 items. push ( RemovalItem {
434495 path : logs_dir. clone ( ) ,
@@ -439,56 +500,8 @@ fn collect_cortex_home_items(home_dir: &Path) -> Result<Vec<RemovalItem>> {
439500 } ) ;
440501 }
441502
442- // Plugins directory
443- let plugins_dir = cortex_home. join ( "plugins" ) ;
444- if plugins_dir. exists ( ) {
445- items. push ( RemovalItem {
446- path : plugins_dir. clone ( ) ,
447- description : "Installed plugins" . to_string ( ) ,
448- size : get_dir_size ( & plugins_dir) ,
449- requires_sudo : false ,
450- category : RemovalCategory :: Plugins ,
451- } ) ;
452- }
453-
454- // Skills directory
455- let skills_dir = cortex_home. join ( "skills" ) ;
456- if skills_dir. exists ( ) {
457- items. push ( RemovalItem {
458- path : skills_dir. clone ( ) ,
459- description : "Custom skills" . to_string ( ) ,
460- size : get_dir_size ( & skills_dir) ,
461- requires_sudo : false ,
462- category : RemovalCategory :: Plugins ,
463- } ) ;
464- }
465-
466- // MCP servers directory
467- let mcp_dir = cortex_home. join ( "mcp" ) ;
468- if mcp_dir. exists ( ) {
469- items. push ( RemovalItem {
470- path : mcp_dir. clone ( ) ,
471- description : "MCP server configurations" . to_string ( ) ,
472- size : get_dir_size ( & mcp_dir) ,
473- requires_sudo : false ,
474- category : RemovalCategory :: Config ,
475- } ) ;
476- }
477-
478- // Agents directory
479- let agents_dir = cortex_home. join ( "agents" ) ;
480- if agents_dir. exists ( ) {
481- items. push ( RemovalItem {
482- path : agents_dir. clone ( ) ,
483- description : "Custom agents" . to_string ( ) ,
484- size : get_dir_size ( & agents_dir) ,
485- requires_sudo : false ,
486- category : RemovalCategory :: Plugins ,
487- } ) ;
488- }
489-
490503 // Cache directory
491- let cache_dir = cortex_home . join ( "cache" ) ;
504+ let cache_dir = app_dirs . cache_dir . clone ( ) ;
492505 if cache_dir. exists ( ) {
493506 items. push ( RemovalItem {
494507 path : cache_dir. clone ( ) ,
@@ -517,7 +530,7 @@ fn collect_cortex_home_items(home_dir: &Path) -> Result<Vec<RemovalItem>> {
517530 } else {
518531 // Add the parent directory itself at the end (to be removed after contents)
519532 items. push ( RemovalItem {
520- path : cortex_home,
533+ path : cortex_home. clone ( ) ,
521534 description : "Cortex home directory (if empty)" . to_string ( ) ,
522535 size : 0 ,
523536 requires_sudo : false ,
@@ -926,6 +939,7 @@ fn clean_rc_file(path: &Path, patterns: &[&str]) -> Result<()> {
926939#[ cfg( test) ]
927940mod tests {
928941 use super :: * ;
942+ use tempfile:: TempDir ;
929943
930944 #[ test]
931945 fn test_format_size ( ) {
@@ -976,4 +990,61 @@ mod tests {
976990 | InstallMethod :: Unknown => { }
977991 }
978992 }
993+
994+ #[ test]
995+ fn test_collect_cortex_home_items_uses_actual_cache_logs_dir ( ) {
996+ let temp = TempDir :: new ( ) . unwrap ( ) ;
997+ let config_dir = temp. path ( ) . join ( "config" ) ;
998+ let cache_dir = temp. path ( ) . join ( "cache" ) ;
999+ let logs_dir = cache_dir. join ( "logs" ) ;
1000+
1001+ fs:: create_dir_all ( & config_dir) . unwrap ( ) ;
1002+ fs:: create_dir_all ( & logs_dir) . unwrap ( ) ;
1003+ fs:: write ( logs_dir. join ( "debug.log" ) , "test log" ) . unwrap ( ) ;
1004+
1005+ let app_dirs = AppDirs {
1006+ config_dir : config_dir. clone ( ) ,
1007+ data_dir : config_dir. clone ( ) ,
1008+ cache_dir : cache_dir. clone ( ) ,
1009+ legacy_xdg_home : temp. path ( ) . join ( "legacy" ) ,
1010+ } ;
1011+
1012+ let items = collect_cortex_home_items_from_dirs ( & app_dirs) . unwrap ( ) ;
1013+
1014+ assert ! (
1015+ items. iter( ) . any( |item| item. path == logs_dir) ,
1016+ "expected uninstall items to include the real cache logs directory"
1017+ ) ;
1018+ assert ! (
1019+ !items
1020+ . iter( )
1021+ . any( |item| item. path == config_dir. join( "logs" ) ) ,
1022+ "uninstall should not look for logs under the config directory"
1023+ ) ;
1024+ }
1025+
1026+ #[ test]
1027+ fn test_collect_cortex_home_items_keeps_cache_logs_without_config_dir ( ) {
1028+ let temp = TempDir :: new ( ) . unwrap ( ) ;
1029+ let config_dir = temp. path ( ) . join ( "config" ) ;
1030+ let cache_dir = temp. path ( ) . join ( "cache" ) ;
1031+ let logs_dir = cache_dir. join ( "logs" ) ;
1032+
1033+ fs:: create_dir_all ( & logs_dir) . unwrap ( ) ;
1034+ fs:: write ( logs_dir. join ( "debug.log" ) , "test log" ) . unwrap ( ) ;
1035+
1036+ let app_dirs = AppDirs {
1037+ config_dir,
1038+ data_dir : temp. path ( ) . join ( "data" ) ,
1039+ cache_dir : cache_dir. clone ( ) ,
1040+ legacy_xdg_home : temp. path ( ) . join ( "legacy" ) ,
1041+ } ;
1042+
1043+ let items = collect_cortex_home_items_from_dirs ( & app_dirs) . unwrap ( ) ;
1044+
1045+ assert ! (
1046+ items. iter( ) . any( |item| item. path == logs_dir) ,
1047+ "cache logs should still be removable even if the config dir is absent"
1048+ ) ;
1049+ }
9791050}
0 commit comments