@@ -12,6 +12,7 @@ use std::process::Command;
1212#[ derive( Debug , Clone , Copy ) ]
1313pub enum ContainerCmd {
1414 DockerPs ,
15+ DockerPsAll ,
1516 DockerImages ,
1617 DockerLogs ,
1718 KubectlPods ,
@@ -22,6 +23,7 @@ pub enum ContainerCmd {
2223pub fn run ( cmd : ContainerCmd , args : & [ String ] , verbose : u8 ) -> Result < i32 > {
2324 match cmd {
2425 ContainerCmd :: DockerPs => docker_ps ( verbose) ,
26+ ContainerCmd :: DockerPsAll => docker_ps_all ( verbose) ,
2527 ContainerCmd :: DockerImages => docker_images ( verbose) ,
2628 ContainerCmd :: DockerLogs => docker_logs ( args, verbose) ,
2729 ContainerCmd :: KubectlPods => kubectl_pods ( args, verbose) ,
@@ -81,40 +83,136 @@ fn docker_ps(_verbose: u8) -> Result<i32> {
8183 return Ok ( 0 ) ;
8284 }
8385
84- let count = stdout. lines ( ) . count ( ) ;
85- rtk. push_str ( & format ! ( "[docker] {} containers:\n " , count) ) ;
86+ const MAX_CONTAINERS : usize = 20 ;
87+ let lines: Vec < String > = stdout
88+ . lines ( )
89+ . filter ( |l| !l. trim ( ) . is_empty ( ) )
90+ . filter_map ( |line| format_container_line ( line, true ) )
91+ . collect ( ) ;
92+
93+ rtk. push_str ( & format ! ( "[docker] {} containers:\n " , lines. len( ) ) ) ;
94+ for entry in lines. iter ( ) . take ( MAX_CONTAINERS ) {
95+ rtk. push_str ( entry) ;
96+ }
97+ if lines. len ( ) > MAX_CONTAINERS {
98+ rtk. push_str ( & format ! ( " … +{} more\n " , lines. len( ) - MAX_CONTAINERS ) ) ;
99+ let full: String = lines. concat ( ) ;
100+ if let Some ( hint) = crate :: core:: tee:: force_tee_hint ( & full, "docker-ps" ) {
101+ rtk. push_str ( & format ! ( "{}\n " , hint) ) ;
102+ }
103+ }
104+
105+ print ! ( "{}" , rtk) ;
106+ timer. track ( "docker ps" , "rtk docker ps" , & raw , & rtk) ;
107+ Ok ( 0 )
108+ }
109+
110+ fn docker_ps_all ( _verbose : u8 ) -> Result < i32 > {
111+ let timer = tracking:: TimedExecution :: start ( ) ;
112+
113+ let raw = exec_capture ( resolved_command ( "docker" ) . args ( [ "ps" , "-a" ] ) )
114+ . map ( |r| r. stdout )
115+ . unwrap_or_default ( ) ;
86116
87- for line in stdout. lines ( ) . take ( 15 ) {
117+ let result = exec_capture ( resolved_command ( "docker" ) . args ( [
118+ "ps" ,
119+ "-a" ,
120+ "--format" ,
121+ "{{.State}}\t {{.ID}}\t {{.Names}}\t {{.Status}}\t {{.Image}}\t {{.Ports}}" ,
122+ ] ) )
123+ . context ( "Failed to run docker ps -a" ) ?;
124+
125+ if !result. success ( ) {
126+ eprint ! ( "{}" , result. stderr) ;
127+ timer. track ( "docker ps -a" , "rtk docker ps -a" , & raw , & raw ) ;
128+ return Ok ( result. exit_code ) ;
129+ }
130+
131+ let mut running_lines: Vec < String > = Vec :: new ( ) ;
132+ let mut stopped_lines: Vec < String > = Vec :: new ( ) ;
133+ for line in result. stdout . lines ( ) . filter ( |l| !l. trim ( ) . is_empty ( ) ) {
88134 let parts: Vec < & str > = line. split ( '\t' ) . collect ( ) ;
89- if parts. len ( ) >= 4 {
90- let id = & parts[ 0 ] [ ..12 . min ( parts[ 0 ] . len ( ) ) ] ;
91- let name = parts[ 1 ] ;
92- let short_image = parts
93- . get ( 3 )
94- . unwrap_or ( & "" )
95- . split ( '/' )
96- . next_back ( )
97- . unwrap_or ( "" ) ;
98- let ports = compact_ports ( parts. get ( 4 ) . unwrap_or ( & "" ) ) ;
99- if ports == "-" {
100- rtk. push_str ( & format ! ( " {} {} ({})\n " , id, name, short_image) ) ;
135+ let state = parts. first ( ) . copied ( ) . unwrap_or ( "" ) ;
136+ let is_running = matches ! ( state, "running" | "restarting" ) ;
137+ if let Some ( entry) = format_container_line_from_parts ( & parts[ 1 ..] , is_running) {
138+ if is_running {
139+ running_lines. push ( entry) ;
101140 } else {
102- rtk. push_str ( & format ! (
103- " {} {} ({}) [{}]\n " ,
104- id, name, short_image, ports
105- ) ) ;
141+ stopped_lines. push ( entry) ;
106142 }
107143 }
108144 }
109- if count > 15 {
110- rtk. push_str ( & format ! ( " ... +{} more" , count - 15 ) ) ;
145+
146+ const MAX_CONTAINERS : usize = 20 ;
147+ let truncated = running_lines. len ( ) > MAX_CONTAINERS || stopped_lines. len ( ) > MAX_CONTAINERS ;
148+
149+ let mut rtk = String :: new ( ) ;
150+ rtk. push_str ( & format ! ( "[docker] {} running:\n " , running_lines. len( ) ) ) ;
151+ for l in running_lines. iter ( ) . take ( MAX_CONTAINERS ) {
152+ rtk. push_str ( l) ;
153+ }
154+ if running_lines. len ( ) > MAX_CONTAINERS {
155+ rtk. push_str ( & format ! (
156+ " … +{} more\n " ,
157+ running_lines. len( ) - MAX_CONTAINERS
158+ ) ) ;
159+ }
160+ if !stopped_lines. is_empty ( ) {
161+ rtk. push_str ( & format ! (
162+ "[docker] {} stopped/exited:\n " ,
163+ stopped_lines. len( )
164+ ) ) ;
165+ for l in stopped_lines. iter ( ) . take ( MAX_CONTAINERS ) {
166+ rtk. push_str ( l) ;
167+ }
168+ if stopped_lines. len ( ) > MAX_CONTAINERS {
169+ rtk. push_str ( & format ! (
170+ " … +{} more\n " ,
171+ stopped_lines. len( ) - MAX_CONTAINERS
172+ ) ) ;
173+ }
174+ }
175+ if truncated {
176+ let full: String = running_lines. iter ( ) . chain ( stopped_lines. iter ( ) ) . cloned ( ) . collect ( ) ;
177+ if let Some ( hint) = crate :: core:: tee:: force_tee_hint ( & full, "docker-ps-a" ) {
178+ rtk. push_str ( & format ! ( "{}\n " , hint) ) ;
179+ }
111180 }
112181
113182 print ! ( "{}" , rtk) ;
114- timer. track ( "docker ps" , "rtk docker ps" , & raw , & rtk) ;
183+ timer. track ( "docker ps -a " , "rtk docker ps -a " , & raw , & rtk) ;
115184 Ok ( 0 )
116185}
117186
187+ fn format_container_line ( line : & str , with_ports : bool ) -> Option < String > {
188+ let parts: Vec < & str > = line. split ( '\t' ) . collect ( ) ;
189+ format_container_line_from_parts ( & parts, with_ports)
190+ }
191+
192+ fn format_container_line_from_parts ( parts : & [ & str ] , with_ports : bool ) -> Option < String > {
193+ if parts. len ( ) < 4 {
194+ return None ;
195+ }
196+ let id = & parts[ 0 ] [ ..12 . min ( parts[ 0 ] . len ( ) ) ] ;
197+ let name = parts[ 1 ] ;
198+ let status = parts[ 2 ] . trim ( ) ;
199+ let short_image = parts[ 3 ] . split ( '/' ) . next_back ( ) . unwrap_or ( "" ) ;
200+ let port_suffix = if with_ports {
201+ let ports = compact_ports ( parts. get ( 4 ) . unwrap_or ( & "" ) ) ;
202+ if ports == "-" {
203+ String :: new ( )
204+ } else {
205+ format ! ( " [{}]" , ports)
206+ }
207+ } else {
208+ String :: new ( )
209+ } ;
210+ Some ( format ! (
211+ " {} {} ({}) {}{}\n " ,
212+ id, name, short_image, status, port_suffix
213+ ) )
214+ }
215+
118216fn docker_images ( _verbose : u8 ) -> Result < i32 > {
119217 let timer = tracking:: TimedExecution :: start ( ) ;
120218
@@ -173,21 +271,35 @@ fn docker_images(_verbose: u8) -> Result<i32> {
173271 total_display
174272 ) ) ;
175273
176- for line in lines. iter ( ) . take ( 15 ) {
177- let parts: Vec < & str > = line. split ( '\t' ) . collect ( ) ;
178- if !parts. is_empty ( ) {
179- let image = parts[ 0 ] ;
180- let size = parts. get ( 1 ) . unwrap_or ( & "" ) ;
181- let short = if image. len ( ) > 40 {
182- format ! ( "...{}" , & image[ image. len( ) - 37 ..] )
183- } else {
184- image. to_string ( )
185- } ;
186- rtk. push_str ( & format ! ( " {} [{}]\n " , short, size) ) ;
187- }
274+ // Show images with their full `repository:tag` name — truncating the
275+ // registry/user prefix to "..." breaks exact-match lookups against
276+ // deployment manifests and CI configs. The list is generously capped (a
277+ // higher bound than before, and only the count, never the names, is
278+ // abbreviated) so token savings still hold on machines with many images.
279+ const MAX_IMAGES : usize = 60 ;
280+ let image_lines: Vec < String > = lines
281+ . iter ( )
282+ . map ( |line| {
283+ let parts: Vec < & str > = line. split ( '\t' ) . collect ( ) ;
284+ let image = parts. first ( ) . copied ( ) . unwrap_or ( "" ) ;
285+ let size = parts. get ( 1 ) . copied ( ) . unwrap_or ( "" ) ;
286+ format ! ( " {} [{}]\n " , image, size)
287+ } )
288+ . collect ( ) ;
289+
290+ let mut full_rtk = rtk. clone ( ) ;
291+ for l in & image_lines {
292+ full_rtk. push_str ( l) ;
293+ }
294+
295+ for l in image_lines. iter ( ) . take ( MAX_IMAGES ) {
296+ rtk. push_str ( l) ;
188297 }
189- if lines. len ( ) > 15 {
190- rtk. push_str ( & format ! ( " ... +{} more" , lines. len( ) - 15 ) ) ;
298+ if image_lines. len ( ) > MAX_IMAGES {
299+ rtk. push_str ( & format ! ( " … +{} more\n " , image_lines. len( ) - MAX_IMAGES ) ) ;
300+ if let Some ( hint) = crate :: core:: tee:: force_tee_tail_hint ( & full_rtk, "docker-images" , MAX_IMAGES + 2 ) {
301+ rtk. push_str ( & format ! ( "{}\n " , hint) ) ;
302+ }
191303 }
192304
193305 print ! ( "{}" , rtk) ;
@@ -294,7 +406,7 @@ fn format_kubectl_pods(json: &Value) -> String {
294406 out. push_str ( & format ! ( " {}\n " , issue) ) ;
295407 }
296408 if issues. len ( ) > 10 {
297- out. push_str ( & format ! ( " ... +{} more" , issues. len( ) - 10 ) ) ;
409+ out. push_str ( & format ! ( " … +{} more" , issues. len( ) - 10 ) ) ;
298410 }
299411 }
300412 out
@@ -347,7 +459,7 @@ fn format_kubectl_services(json: &Value) -> String {
347459 ) ) ;
348460 }
349461 if services. len ( ) > 15 {
350- out. push_str ( & format ! ( " ... +{} more" , services. len( ) - 15 ) ) ;
462+ out. push_str ( & format ! ( " … +{} more" , services. len( ) - 15 ) ) ;
351463 }
352464 out
353465}
@@ -421,7 +533,7 @@ pub fn format_compose_ps(raw: &str) -> String {
421533 }
422534 }
423535 if lines. len ( ) > 20 {
424- result. push_str ( & format ! ( " ... +{} more\n " , lines. len( ) - 20 ) ) ;
536+ result. push_str ( & format ! ( " … +{} more\n " , lines. len( ) - 20 ) ) ;
425537 }
426538
427539 result. trim_end ( ) . to_string ( )
@@ -511,7 +623,7 @@ fn compact_ports(ports: &str) -> String {
511623 port_nums. join ( ", " )
512624 } else {
513625 format ! (
514- "{}, ... +{}" ,
626+ "{}, … +{}" ,
515627 port_nums[ ..2 ] . join( ", " ) ,
516628 port_nums. len( ) - 2
517629 )
@@ -522,12 +634,15 @@ pub fn run_docker_passthrough(args: &[OsString], verbose: u8) -> Result<i32> {
522634 crate :: core:: runner:: run_passthrough ( "docker" , args, verbose)
523635}
524636
525- /// Run `docker compose ps` with compact output
526- pub fn run_compose_ps ( verbose : u8 ) -> Result < i32 > {
637+ /// Run `docker compose ps` (or `docker compose ps -a`) with compact output
638+ pub fn run_compose_ps ( all : bool , verbose : u8 ) -> Result < i32 > {
527639 let timer = tracking:: TimedExecution :: start ( ) ;
528640
529- // Raw output for token tracking
530- let raw_result = exec_capture ( resolved_command ( "docker" ) . args ( [ "compose" , "ps" ] ) )
641+ let mut raw_args: Vec < & str > = vec ! [ "compose" , "ps" ] ;
642+ if all {
643+ raw_args. push ( "-a" ) ;
644+ }
645+ let raw_result = exec_capture ( resolved_command ( "docker" ) . args ( & raw_args) )
531646 . context ( "Failed to run docker compose ps" ) ?;
532647
533648 if !raw_result. success ( ) {
@@ -536,14 +651,13 @@ pub fn run_compose_ps(verbose: u8) -> Result<i32> {
536651 }
537652 let raw = raw_result. stdout ;
538653
539- // Structured output for parsing (same pattern as docker_ps)
540- let result = exec_capture ( resolved_command ( "docker" ) . args ( [
541- "compose" ,
542- "ps" ,
543- "--format" ,
544- "{{.Name}}\t {{.Image}}\t {{.Status}}\t {{.Ports}}" ,
545- ] ) )
546- . context ( "Failed to run docker compose ps --format" ) ?;
654+ let mut format_args: Vec < & str > = vec ! [ "compose" , "ps" ] ;
655+ if all {
656+ format_args. push ( "-a" ) ;
657+ }
658+ format_args. extend ( [ "--format" , "{{.Name}}\t {{.Image}}\t {{.Status}}\t {{.Ports}}" ] ) ;
659+ let result = exec_capture ( resolved_command ( "docker" ) . args ( & format_args) )
660+ . context ( "Failed to run docker compose ps --format" ) ?;
547661
548662 if !result. success ( ) {
549663 eprintln ! ( "{}" , result. stderr) ;
@@ -557,7 +671,9 @@ pub fn run_compose_ps(verbose: u8) -> Result<i32> {
557671
558672 let rtk = format_compose_ps ( & structured) ;
559673 println ! ( "{}" , rtk) ;
560- timer. track ( "docker compose ps" , "rtk docker compose ps" , & raw , & rtk) ;
674+ let label = if all { "docker compose ps -a" } else { "docker compose ps" } ;
675+ let rtk_label = if all { "rtk docker compose ps -a" } else { "rtk docker compose ps" } ;
676+ timer. track ( label, rtk_label, & raw , & rtk) ;
561677 Ok ( 0 )
562678}
563679
@@ -789,7 +905,7 @@ api-1 | Connected to database";
789905 #[ test]
790906 fn test_compact_ports_many ( ) {
791907 let result = compact_ports ( "0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:8080->8080/tcp, 0.0.0.0:9090->9090/tcp" ) ;
792- assert ! ( result. contains( "... " ) , "should truncate for >3 ports" ) ;
908+ assert ! ( result. contains( "… " ) , "should truncate for >3 ports" ) ;
793909 }
794910
795911 #[ test]
0 commit comments