use core::fmt::Write as FmtWrite;
use unilang::{ VerifiedCommand, ExecutionContext, OutputData, ErrorData, ErrorCode };
use super::storage::{ create_storage, parse_project_parameter, find_session_mut };
use super::format::format_entry_content;
#[ allow( clippy::struct_excessive_bools ) ]
struct SessionDisplayOptions
{
show_entries : bool,
metadata_only : bool,
show_stat : bool,
show_tokens : bool,
}
#[ allow( clippy::needless_pass_by_value ) ]
#[ inline ]
pub fn show_routine( cmd : VerifiedCommand, _ctx : ExecutionContext )
-> core::result::Result< OutputData, ErrorData >
{
let session_id_raw = cmd.get_string( "session_id" );
let session_id : Option< &str > = match session_id_raw
{
Some( s ) if s.trim().is_empty() =>
{
return Err
(
ErrorData::new( ErrorCode::InternalError, "session_id must be non-empty".to_string() )
);
}
Some( s ) => Some( s.trim() ),
None => None,
};
let project_param = cmd.get_string( "project" );
let metadata_only = cmd.get_boolean( "show_metadata" ).unwrap_or( false )
|| cmd.get_integer( "verbosity" ).unwrap_or( 1 ) == 0;
let opts = SessionDisplayOptions
{
show_entries : cmd.get_boolean( "show_entries" ).unwrap_or( false ),
metadata_only,
show_stat : cmd.get_boolean( "show_stat" ).unwrap_or( false ),
show_tokens : cmd.get_boolean( "show_tokens" ).unwrap_or( false ),
};
if opts.show_entries && session_id.is_none()
{
return Err
(
ErrorData::new
(
ErrorCode::InternalError,
"Parameter 'entries' requires 'session_id'. \
Use '.show session_id::<id> entries::1' to display session entries."
.to_string()
)
);
}
match ( session_id, project_param )
{
( None, None ) =>
{
show_project_for_cwd_impl()
}
( Some( sid ), None ) =>
{
show_session_in_cwd_impl( sid, opts )
}
( None, Some( proj ) ) =>
{
show_project_impl( proj )
}
( Some( sid ), Some( proj ) ) =>
{
show_session_in_project_impl( sid, proj, opts )
}
}
}
fn show_session_in_cwd_impl(
session_id : &str,
opts : SessionDisplayOptions,
) -> core::result::Result< OutputData, ErrorData >
{
let storage = create_storage()?;
let cwd = std::env::current_dir()
.map_err( | e | ErrorData::new( ErrorCode::InternalError, format!( "Failed to get current directory: {e}" ) ) )?;
let eb = claude_storage_core::encode_path( &cwd )
.map_err( | e | ErrorData::new( ErrorCode::InternalError, format!( "Failed to encode current directory: {e}" ) ) )?;
let topic_prefix = format!( "{eb}--" );
let all_projects = storage.list_projects()
.map_err( | e | ErrorData::new( ErrorCode::InternalError, format!( "Failed to list projects: {e}" ) ) )?;
for project in &all_projects
{
let dir_name = project.storage_dir()
.file_name()
.and_then( | n | n.to_str() )
.unwrap_or( "" );
if dir_name != eb && !dir_name.starts_with( &topic_prefix ) { continue; }
if let Ok( output ) = format_session_output( project, session_id, &opts )
{
return Ok( output );
}
}
Err( ErrorData::new( ErrorCode::InternalError, format!( "Session '{session_id}' not found in current directory projects" ) ) )
}
fn show_session_in_project_impl(
session_id : &str,
project_param : &str,
opts : SessionDisplayOptions,
) -> core::result::Result< OutputData, ErrorData >
{
let storage = create_storage()?;
let proj_id = parse_project_parameter( project_param )
.map_err( | e | ErrorData::new
(
ErrorCode::InternalError,
format!( "Invalid project parameter: {e}" )
))?;
let project = storage.load_project( &proj_id )
.map_err( | e | ErrorData::new
(
ErrorCode::InternalError,
format!( "Failed to load project {proj_id:?}: {e}" )
))?;
format_session_output( &project, session_id, &opts )
}
fn show_project_for_cwd_impl()
-> core::result::Result< OutputData, ErrorData >
{
let storage = create_storage()?;
let project = storage.load_project_for_cwd()
.map_err( | e | ErrorData::new
(
ErrorCode::InternalError,
format!( "Failed to load project from current directory: {e}" )
))?;
format_project_output( &project )
}
fn show_project_impl( project_param : &str )
-> core::result::Result< OutputData, ErrorData >
{
let storage = create_storage()?;
let proj_id = parse_project_parameter( project_param )
.map_err( | e | ErrorData::new
(
ErrorCode::InternalError,
format!( "Invalid project parameter: {e}" )
))?;
let project = storage.load_project( &proj_id )
.map_err( | e | ErrorData::new
(
ErrorCode::InternalError,
format!( "Failed to load project {proj_id:?}: {e}" )
))?;
format_project_output( &project )
}
fn format_session_output(
project : &claude_storage_core::Project,
session_id : &str,
opts : &SessionDisplayOptions,
) -> core::result::Result< OutputData, ErrorData >
{
let mut sessions = project.all_sessions()
.map_err( | e | ErrorData::new( ErrorCode::InternalError, format!( "Failed to list sessions: {e}" ) ) )?;
let session = find_session_mut( &mut sessions, session_id )?;
let stats = session.stats()
.map_err( | e | ErrorData::new( ErrorCode::InternalError, format!( "Failed to get session stats: {e}" ) ) )?;
let mut output = String::new();
let entry_noun = if stats.total_entries == 1 { "entry" } else { "entries" };
writeln!( output, "Session: {} ({} {entry_noun})", session_id, stats.total_entries ).unwrap();
if opts.metadata_only
{
writeln!( output, "Path: {}", session.storage_path().display() ).unwrap();
writeln!( output, "Agent Session: {}", stats.is_agent_session ).unwrap();
writeln!( output, "Total Entries: {}", stats.total_entries ).unwrap();
writeln!( output, "User Entries: {}", stats.user_entries ).unwrap();
writeln!( output, "Assistant Entries: {}", stats.assistant_entries ).unwrap();
if let Some( first ) = &stats.first_timestamp
{
writeln!( output, "First Entry: {first}" ).unwrap();
}
if let Some( last ) = &stats.last_timestamp
{
writeln!( output, "Last Entry: {last}" ).unwrap();
}
if opts.show_tokens
{
output.push_str( "\nToken Usage:\n" );
writeln!( output, "- Input: {}", stats.total_input_tokens ).unwrap();
writeln!( output, "- Output: {}", stats.total_output_tokens ).unwrap();
writeln!( output, "- Cache Read: {}", stats.total_cache_read_tokens ).unwrap();
writeln!( output, "- Cache Creation: {}", stats.total_cache_creation_tokens ).unwrap();
}
if opts.show_entries
{
let entries = session.entries()
.map_err( | e | ErrorData::new( ErrorCode::InternalError, format!( "Failed to load entries: {e}" ) ) )?;
output.push_str( "\nEntries:\n" );
for ( idx, entry ) in entries.iter().enumerate()
{
writeln!
(
output,
"{}. [{:?}] {} ({})",
idx + 1,
entry.entry_type,
entry.uuid,
entry.timestamp
).unwrap();
}
}
}
else
{
let entries = session.entries()
.map_err( | e | ErrorData::new( ErrorCode::InternalError, format!( "Failed to load entries: {e}" ) ) )?;
output.push_str( "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" );
output.push( '\n' );
for entry in entries
{
let formatted = format_entry_content( entry, None );
output.push_str( &formatted );
output.push_str( "\n\n" );
}
output.push_str( "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" );
if opts.show_stat
{
output.push( '\n' );
output.push_str( "Session Metadata:\n" );
writeln!( output, "- Path: {}", session.storage_path().display() ).unwrap();
writeln!( output, "- Total Entries: {}", stats.total_entries ).unwrap();
writeln!( output, "- User/Assistant: {}/{}", stats.user_entries, stats.assistant_entries ).unwrap();
}
if opts.show_tokens
{
output.push_str( "\nToken Usage:\n" );
writeln!( output, "- Input: {}", stats.total_input_tokens ).unwrap();
writeln!( output, "- Output: {}", stats.total_output_tokens ).unwrap();
writeln!( output, "- Cache Read: {}", stats.total_cache_read_tokens ).unwrap();
writeln!( output, "- Cache Creation: {}", stats.total_cache_creation_tokens ).unwrap();
}
}
Ok( OutputData::new( output, "text" ) )
}
fn format_project_output(
project : &claude_storage_core::Project,
) -> core::result::Result< OutputData, ErrorData >
{
let stats = project.project_stats()
.map_err( | e | ErrorData::new
(
ErrorCode::InternalError,
format!( "Failed to get project stats: {e}" )
))?;
let mut sessions = project.sessions()
.map_err( | e | ErrorData::new
(
ErrorCode::InternalError,
format!( "Failed to list sessions: {e}" )
))?;
let mut output = String::new();
writeln!( output, "Project: {:?}", project.id() ).unwrap();
writeln!( output, "Storage: {}", project.storage_dir().display() ).unwrap();
output.push( '\n' );
writeln!( output, "Sessions: {} (Main: {}, Agent: {})",
stats.session_count,
stats.main_session_count,
stats.agent_session_count
).unwrap();
writeln!( output, "Total Entries: {}", stats.total_entries ).unwrap();
output.push( '\n' );
if sessions.is_empty()
{
output.push_str( "No sessions found in this project.\n" );
}
else
{
output.push_str( "Sessions:\n" );
for session in &mut sessions
{
let session_stats = session.stats()
.map_err( | e | ErrorData::new
(
ErrorCode::InternalError,
format!( "Failed to get session stats: {e}" )
))?;
let last = session_stats.last_timestamp
.unwrap_or_else( || "unknown".to_string() );
let e_noun = if session_stats.total_entries == 1 { "entry" } else { "entries" };
writeln!( output, " - {} ({} {e_noun}, last: {})",
session.id(),
session_stats.total_entries,
last
).unwrap();
}
}
Ok( OutputData::new( output, "text" ) )
}