#![ allow( missing_docs ) ]
#![ allow( clippy::unreadable_literal ) ]
use std::{ env, io::{ self, Write }, process };
use crate::cli;
use unilang::prelude::*;
include!( concat!( env!( "OUT_DIR" ), "/static_commands.rs" ) );
fn build_command_registry() -> CommandRegistry
{
type RoutineFn = fn( VerifiedCommand, ExecutionContext ) -> Result< OutputData, ErrorData >;
let routines : phf::Map< &'static str, RoutineFn > = phf::phf_map!
{
".status" => cli::status_routine,
".list" => cli::list_routine,
".show" => cli::show_routine,
".count" => cli::count_routine,
".search" => cli::search_routine,
".export" => cli::export_routine,
".projects" => cli::projects_routine,
".project.path" => cli::project_path_routine,
".project.exists" => cli::project_exists_routine,
".session.dir" => cli::session_dir_routine,
".session.ensure" => cli::session_ensure_routine,
};
let mut registry = CommandRegistry::new();
for ( name, static_cmd ) in AGGREGATED_COMMANDS.entries()
{
if let Some( &routine ) = routines.get( *name )
{
let cmd : CommandDefinition = ( *static_cmd ).into();
if let Err( e ) = registry.register_with_routine( &cmd, Box::new( routine ) )
{
eprintln!( "WARNING: Failed to register routine for {name}: {e}" );
}
}
}
registry
}
fn print_usage( binary : &str )
{
use cli_fmt::help::*;
let data = CliHelpData
{
binary : binary.to_string(),
tagline : "Claude Code storage explorer: query conversations, sessions, and projects.".to_string(),
groups : vec!
[
CommandGroup
{
name : "Status".to_string(),
entries : vec!
[
CommandEntry { name : ".status".to_string(), desc : "Show storage summary (projects, sessions, entries)".to_string() },
],
},
CommandGroup
{
name : "Session".to_string(),
entries : vec!
[
CommandEntry { name : ".session.dir".to_string(), desc : "Print the filesystem path of a session directory".to_string() },
CommandEntry { name : ".session.ensure".to_string(), desc : "Ensure a session directory exists (create if missing)".to_string() },
],
},
CommandGroup
{
name : "Project".to_string(),
entries : vec!
[
CommandEntry { name : ".projects".to_string(), desc : "List all known projects with session counts".to_string() },
CommandEntry { name : ".project.path".to_string(), desc : "Print the filesystem path of a project directory".to_string() },
CommandEntry { name : ".project.exists".to_string(), desc : "Check whether a project has any sessions".to_string() },
],
},
CommandGroup
{
name : "Query".to_string(),
entries : vec!
[
CommandEntry { name : ".list".to_string(), desc : "List sessions with filtering and sorting".to_string() },
CommandEntry { name : ".show".to_string(), desc : "Display entries from a specific session".to_string() },
CommandEntry { name : ".count".to_string(), desc : "Count sessions or entries matching criteria".to_string() },
CommandEntry { name : ".search".to_string(), desc : "Search conversation content across sessions".to_string() },
CommandEntry { name : ".export".to_string(), desc : "Export session data in various formats".to_string() },
],
},
],
options : vec!
[
OptionEntry { name : "project::ID".to_string(), desc : "Filter by project identifier".to_string() },
OptionEntry { name : "session::ID".to_string(), desc : "Target a specific session".to_string() },
OptionEntry { name : "scope::VALUE".to_string(), desc : "Scope filter (all, cli, web, ide)".to_string() },
OptionEntry { name : "format::FMT".to_string(), desc : "Output format (text, json, markdown)".to_string() },
OptionEntry { name : "limit::N".to_string(), desc : "Maximum entries to return".to_string() },
OptionEntry { name : "query::TEXT".to_string(), desc : "Search query string".to_string() },
],
examples : vec!
[
ExampleEntry { invocation : format!( "{binary} .status" ), desc : None },
ExampleEntry { invocation : format!( "{binary} .list scope::cli limit::10" ), desc : None },
ExampleEntry { invocation : format!( "{binary} .search query::\"error handling\"" ), desc : None },
ExampleEntry { invocation : format!( "{binary} --repl" ), desc : Some( "Enter interactive REPL mode".to_string() ) },
],
};
print!( "{}", CliHelpTemplate::new( CliHelpStyle::default(), data ).render() );
}
fn run_repl( registry : CommandRegistry )
{
println!( "Claude Code Storage CLI" );
println!( "Type 'help' for available commands, 'exit' to quit.\n" );
let pipeline = Pipeline::new( registry );
let mut command_buffer = String::new();
loop
{
print!( "> " );
io::stdout().flush().unwrap();
command_buffer.clear();
if let Err( e ) = io::stdin().read_line( &mut command_buffer )
{
eprintln!( "Error reading input: {e}" );
continue;
}
let input = command_buffer.trim();
if input.is_empty() { continue; }
if input == "exit" || input == "quit" || input == "q"
{
println!( "Goodbye!" );
break;
}
let result = pipeline.process_command_simple( input );
if result.success
{
if let Some( output ) = result.outputs.first()
{
println!( "{}", output.content );
}
}
else if let Some( error ) = result.error
{
eprintln!( "Error: {error}" );
}
}
}
fn extract_user_message( error : &str ) -> String
{
let trimmed = error.trim_end();
for prefix in &[
"Execution error: Execution Error: ",
"Semantic analysis error: Execution Error: ",
]
{
if let Some( rest ) = trimmed.strip_prefix( prefix )
{
return rest.trim().to_string();
}
}
trimmed.to_string()
}
#[ allow( clippy::needless_pass_by_value ) ]
fn execute_oneshot( registry : CommandRegistry, args : Vec< String > ) -> !
{
let pipeline = Pipeline::new( registry );
let command_line = args[ 1.. ]
.iter()
.map( | arg |
{
if let Some( sep ) = arg.find( "::" )
{
let key = &arg[ ..sep + 2 ];
let value = &arg[ sep + 2.. ];
if value.contains( ' ' )
{
return format!( "{}\"{}\"", key, value.replace( '"', "\\\"" ) );
}
}
arg.clone()
} )
.collect::< Vec< _ > >()
.join( " " );
let result = pipeline.process_command_simple( &command_line );
if result.success
{
if let Some( output ) = result.outputs.first()
{
println!( "{}", output.content );
}
process::exit( 0 );
}
else
{
if let Some( error ) = result.error
{
eprintln!( "{}", extract_user_message( &error ) );
}
process::exit( 1 );
}
}
#[ inline ]
pub fn run()
{
let args : Vec< String > = env::args().collect();
let binary = args.first()
.and_then( | p | std::path::Path::new( p ).file_name() )
.and_then( | n | n.to_str() )
.unwrap_or( "clg" )
.to_owned();
if args.len() == 1
{
print_usage( &binary );
process::exit( 0 );
}
let first = &args[ 1 ];
if first == ".help" || first == "--help" || first == "-h" || first == "help"
{
print_usage( &binary );
process::exit( 0 );
}
if first == "--repl"
{
let registry = build_command_registry();
run_repl( registry );
return;
}
let registry = build_command_registry();
execute_oneshot( registry, args );
}