use unilang::{ VerifiedCommand, ExecutionContext, OutputData, ErrorData, ErrorCode };
use super::storage::resolve_path_parameter;
const DEFAULT_TOPIC : &str = "default_topic";
fn resolve_cmd_path( cmd : &VerifiedCommand ) -> core::result::Result< std::path::PathBuf, ErrorData >
{
let resolved = cmd.get_string( "path" )
.map( | p | resolve_path_parameter( p )
.map_err( | e | ErrorData::new( ErrorCode::InternalError, e ) )
)
.transpose()?;
match resolved
{
Some( s ) => Ok( std::path::PathBuf::from( s ) ),
None =>
{
std::env::current_dir()
.map_err( | e | ErrorData::new(
ErrorCode::InternalError,
format!( "Failed to get current directory: {e}" ),
) )
}
}
}
fn validate_topic( topic : &str ) -> core::result::Result< (), ErrorData >
{
if topic.is_empty()
{
return Err( ErrorData::new(
ErrorCode::InternalError,
"topic must be non-empty".to_string(),
) );
}
if topic.contains( '/' )
{
return Err( ErrorData::new(
ErrorCode::InternalError,
"topic must not contain path separators".to_string(),
) );
}
Ok( () )
}
#[ allow( clippy::needless_pass_by_value ) ]
#[ inline ]
pub fn project_path_routine( cmd : VerifiedCommand, _ctx : ExecutionContext )
-> core::result::Result< OutputData, ErrorData >
{
let base = resolve_cmd_path( &cmd )?;
let session_dir = if let Some( topic ) = cmd.get_string( "topic" )
{
validate_topic( topic )?;
base.join( format!( "-{topic}" ) )
}
else
{
base
};
let storage_path = claude_storage_core::continuation::to_storage_path_for( &session_dir )
.ok_or_else( || ErrorData::new(
ErrorCode::InternalError,
"Failed to compute storage path (HOME not set or invalid path)".to_string(),
) )?;
Ok( OutputData::new( format!( "{}/", storage_path.display() ), "text" ) )
}
#[ allow( clippy::needless_pass_by_value ) ]
#[ inline ]
pub fn project_exists_routine( cmd : VerifiedCommand, _ctx : ExecutionContext )
-> core::result::Result< OutputData, ErrorData >
{
let base = resolve_cmd_path( &cmd )?;
let session_dir = if let Some( topic ) = cmd.get_string( "topic" )
{
validate_topic( topic )?;
base.join( format!( "-{topic}" ) )
}
else
{
base
};
if claude_storage_core::continuation::check_continuation( &session_dir )
{
Ok( OutputData::new( "sessions exist".to_string(), "text" ) )
}
else
{
Err( ErrorData::new( ErrorCode::InternalError, "no sessions".to_string() ) )
}
}
fn resolve_session_dir(
cmd : &VerifiedCommand,
) -> core::result::Result< std::path::PathBuf, ErrorData >
{
let base = resolve_cmd_path( cmd )?;
let topic = cmd.get_string( "topic" ).unwrap_or( DEFAULT_TOPIC );
validate_topic( topic )?;
Ok( base.join( format!( "-{topic}" ) ) )
}
#[ allow( clippy::needless_pass_by_value ) ]
#[ inline ]
pub fn session_dir_routine( cmd : VerifiedCommand, _ctx : ExecutionContext )
-> core::result::Result< OutputData, ErrorData >
{
let session_dir = resolve_session_dir( &cmd )?;
Ok( OutputData::new( format!( "{}", session_dir.display() ), "text" ) )
}
#[ allow( clippy::needless_pass_by_value ) ]
#[ inline ]
pub fn session_ensure_routine( cmd : VerifiedCommand, _ctx : ExecutionContext )
-> core::result::Result< OutputData, ErrorData >
{
let session_dir = resolve_session_dir( &cmd )?;
let forced_strategy = if let Some( s ) = cmd.get_string( "strategy" )
{
match s.to_lowercase().as_str()
{
"resume" => Some( true ),
"fresh" => Some( false ),
other => return Err( ErrorData::new(
ErrorCode::InternalError,
format!( "strategy must be resume|fresh, got {other}" ),
) ),
}
}
else
{
None
};
std::fs::create_dir_all( &session_dir ).map_err( | e | ErrorData::new(
ErrorCode::InternalError,
format!( "Failed to create session directory: {e}" ),
) )?;
let is_resume = forced_strategy.unwrap_or_else(
|| claude_storage_core::continuation::check_continuation( &session_dir )
);
let strategy = if is_resume { "resume" } else { "fresh" };
Ok( OutputData::new( format!( "{}\n{strategy}", session_dir.display() ), "text" ) )
}