@ -1,4 +1,4 @@
//! CLI types: output format, include list, global options, and command structure.
//! CLI types: output format, include list, limit, global options, and command structure.
/// Output format for all commands.
/// Output format for all commands.
#[ derive(Debug, Clone, Default, clap::ValueEnum) ]
#[ derive(Debug, Clone, Default, clap::ValueEnum) ]
@ -51,6 +51,46 @@ impl std::str::FromStr for IncludeList {
}
}
}
}
/// How many entries to show in list commands.
///
/// Accepts an integer (e.g. `10`) or the keyword `all` to show every entry.
#[ derive(Debug, Clone) ]
pub enum Limit {
/// Show at most N entries.
Count ( usize ) ,
/// Show all entries without truncation.
All ,
}
impl Default for Limit {
fn default ( ) -> Self {
Limit ::Count ( 10 )
}
}
impl std ::str ::FromStr for Limit {
type Err = crate ::error ::AppError ;
fn from_str ( s : & str ) -> crate ::error ::Result < Self > {
if s . eq_ignore_ascii_case ( "all" ) {
return Ok ( Limit ::All ) ;
}
s . parse ::< usize > ( )
. map ( Limit ::Count )
. map_err ( | _ | crate ::error ::AppError ::InvalidArg ( format! ( "invalid limit: {s}" ) ) )
}
}
impl Limit {
/// Apply this limit to a vector, returning a truncated or full slice.
pub fn apply < T > ( & self , items : Vec < T > ) -> Vec < T > {
match self {
Limit ::All = > items ,
Limit ::Count ( n ) = > items . into_iter ( ) . take ( * n ) . collect ( ) ,
}
}
}
/// Top-level CLI entry point for claudbg.
/// Top-level CLI entry point for claudbg.
#[ derive(Debug, clap::Parser) ]
#[ derive(Debug, clap::Parser) ]
#[ command(name = " claudbg " , about = " Claude Code session inspector " ) ]
#[ command(name = " claudbg " , about = " Claude Code session inspector " ) ]
@ -110,7 +150,11 @@ pub enum Commands {
#[ derive(Debug, clap::Subcommand) ]
#[ derive(Debug, clap::Subcommand) ]
pub enum SessionsCmd {
pub enum SessionsCmd {
/// List all sessions, most recent first.
/// List all sessions, most recent first.
List ,
List {
/// Maximum number of sessions to show, or "all" for no limit.
#[ arg(long, default_value = " 10 " ) ]
limit : Limit ,
} ,
/// Dump raw messages from a session.
/// Dump raw messages from a session.
Dump {
Dump {
/// Session ID or 8-char prefix.
/// Session ID or 8-char prefix.
@ -136,6 +180,9 @@ pub enum AgentsCmd {
List {
List {
/// Parent session ID or 8-char prefix.
/// Parent session ID or 8-char prefix.
session_id : String ,
session_id : String ,
/// Maximum number of agents to show, or "all" for no limit.
#[ arg(long, default_value = " 10 " ) ]
limit : Limit ,
} ,
} ,
/// Dump raw messages from an agent run.
/// Dump raw messages from an agent run.
Dump {
Dump {
@ -219,4 +266,63 @@ mod tests {
let fmt = OutputFormat ::default ( ) ;
let fmt = OutputFormat ::default ( ) ;
assert! ( matches! ( fmt , OutputFormat ::Table ) ) ;
assert! ( matches! ( fmt , OutputFormat ::Table ) ) ;
}
}
/// Parsing a number produces `Limit::Count`.
#[ test ]
fn limit_parse_count ( ) {
let limit = Limit ::from_str ( "5" ) . unwrap ( ) ;
assert! ( matches! ( limit , Limit ::Count ( 5 ) ) ) ;
}
/// Parsing `"all"` produces `Limit::All`.
#[ test ]
fn limit_parse_all ( ) {
let limit = Limit ::from_str ( "all" ) . unwrap ( ) ;
assert! ( matches! ( limit , Limit ::All ) ) ;
}
/// Parsing `"ALL"` (case-insensitive) also produces `Limit::All`.
#[ test ]
fn limit_parse_all_uppercase ( ) {
let limit = Limit ::from_str ( "ALL" ) . unwrap ( ) ;
assert! ( matches! ( limit , Limit ::All ) ) ;
}
/// Parsing an invalid string returns an error.
#[ test ]
fn limit_parse_invalid ( ) {
let result = Limit ::from_str ( "banana" ) ;
assert! ( result . is_err ( ) ) ;
}
/// Default limit is `Count(10)`.
#[ test ]
fn limit_default_is_ten ( ) {
let limit = Limit ::default ( ) ;
assert! ( matches! ( limit , Limit ::Count ( 10 ) ) ) ;
}
/// `Limit::Count(3).apply` truncates a longer vec.
#[ test ]
fn limit_apply_count_truncates ( ) {
let v = vec! [ 1 , 2 , 3 , 4 , 5 ] ;
let result = Limit ::Count ( 3 ) . apply ( v ) ;
assert_eq! ( result , vec! [ 1 , 2 , 3 ] ) ;
}
/// `Limit::All.apply` returns the full vec unchanged.
#[ test ]
fn limit_apply_all_returns_all ( ) {
let v = vec! [ 1 , 2 , 3 , 4 , 5 ] ;
let result = Limit ::All . apply ( v ) ;
assert_eq! ( result , vec! [ 1 , 2 , 3 , 4 , 5 ] ) ;
}
/// `Limit::Count(N).apply` on a shorter vec returns the full vec.
#[ test ]
fn limit_apply_count_shorter_vec_unchanged ( ) {
let v = vec! [ 1 , 2 ] ;
let result = Limit ::Count ( 10 ) . apply ( v ) ;
assert_eq! ( result , vec! [ 1 , 2 ] ) ;
}
}
}