@@ -67,6 +67,7 @@ struct NodeSession {
6767 channel : Channel < Msg > ,
6868 working_dir : String ,
6969 is_connected : bool ,
70+ is_active : bool , // Whether this node is currently active for commands
7071}
7172
7273impl NodeSession {
@@ -244,6 +245,7 @@ impl InteractiveCommand {
244245 channel,
245246 working_dir,
246247 is_connected : true ,
248+ is_active : true , // All nodes start as active
247249 } )
248250 }
249251
@@ -386,6 +388,108 @@ impl InteractiveCommand {
386388 Ok ( commands_executed)
387389 }
388390
391+ /// Parse and handle special commands (starting with !)
392+ fn handle_special_command ( command : & str , sessions : & mut [ NodeSession ] ) -> Result < bool > {
393+ if !command. starts_with ( '!' ) {
394+ return Ok ( false ) ; // Not a special command
395+ }
396+
397+ let cmd = command. trim_start_matches ( '!' ) . to_lowercase ( ) ;
398+
399+ match cmd. as_str ( ) {
400+ "all" => {
401+ // Activate all nodes
402+ for session in sessions. iter_mut ( ) {
403+ if session. is_connected {
404+ session. is_active = true ;
405+ }
406+ }
407+ println ! ( "All nodes activated" ) ;
408+ Ok ( true )
409+ }
410+ "list" | "nodes" | "ls" => {
411+ // List all nodes with their status
412+ println ! ( "\n Nodes status:" ) ;
413+ for ( i, session) in sessions. iter ( ) . enumerate ( ) {
414+ let status = if !session. is_connected {
415+ "disconnected"
416+ } else if session. is_active {
417+ "active"
418+ } else {
419+ "inactive"
420+ } ;
421+ println ! ( " [{}] {} - {}" , i + 1 , session. node, status) ;
422+ }
423+ println ! ( ) ;
424+ Ok ( true )
425+ }
426+ "status" => {
427+ // Show current active nodes
428+ let active_nodes: Vec < String > = sessions
429+ . iter ( )
430+ . filter ( |s| s. is_active && s. is_connected )
431+ . map ( |s| s. node . to_string ( ) )
432+ . collect ( ) ;
433+
434+ if active_nodes. is_empty ( ) {
435+ println ! ( "No active nodes" ) ;
436+ } else {
437+ println ! ( "Active nodes: {}" , active_nodes. join( ", " ) ) ;
438+ }
439+ Ok ( true )
440+ }
441+ "help" | "?" => {
442+ println ! ( "\n Special commands:" ) ;
443+ println ! ( " !all - Activate all nodes" ) ;
444+ println ! ( " !node<N> - Switch to node N (e.g., !node1)" ) ;
445+ println ! ( " !n<N> - Shorthand for !node<N>" ) ;
446+ println ! ( " !list, !nodes - List all nodes with status" ) ;
447+ println ! ( " !status - Show active nodes" ) ;
448+ println ! ( " !help - Show this help" ) ;
449+ println ! ( " exit - Exit interactive mode" ) ;
450+ println ! ( ) ;
451+ Ok ( true )
452+ }
453+ _ => {
454+ // Check for node selection commands
455+ if let Some ( node_num) = cmd. strip_prefix ( "node" ) {
456+ Self :: switch_to_node ( node_num, sessions)
457+ } else if let Some ( node_num) = cmd. strip_prefix ( 'n' ) {
458+ Self :: switch_to_node ( node_num, sessions)
459+ } else {
460+ println ! ( "Unknown command: !{cmd}. Type !help for available commands." ) ;
461+ Ok ( true )
462+ }
463+ }
464+ }
465+ }
466+
467+ /// Switch to a specific node by number
468+ fn switch_to_node ( node_num : & str , sessions : & mut [ NodeSession ] ) -> Result < bool > {
469+ match node_num. parse :: < usize > ( ) {
470+ Ok ( num) if num > 0 && num <= sessions. len ( ) => {
471+ // Deactivate all nodes first
472+ for session in sessions. iter_mut ( ) {
473+ session. is_active = false ;
474+ }
475+
476+ // Activate the selected node
477+ let index = num - 1 ;
478+ if sessions[ index] . is_connected {
479+ sessions[ index] . is_active = true ;
480+ println ! ( "Switched to node {}: {}" , num, sessions[ index] . node) ;
481+ } else {
482+ println ! ( "Node {num} is disconnected" ) ;
483+ }
484+ Ok ( true )
485+ }
486+ _ => {
487+ println ! ( "Invalid node number. Use 1-{}" , sessions. len( ) ) ;
488+ Ok ( true )
489+ }
490+ }
491+ }
492+
389493 /// Run interactive mode with multiple nodes (multiplex)
390494 async fn run_multiplex_mode ( & self , mut sessions : Vec < NodeSession > ) -> Result < usize > {
391495 let mut commands_executed = 0 ;
@@ -404,7 +508,7 @@ impl InteractiveCommand {
404508 "Interactive multiplex mode started. Commands will be sent to all {} nodes." ,
405509 sessions. len( )
406510 ) ;
407- println ! ( "Type 'exit' or press Ctrl+D to quit." ) ;
511+ println ! ( "Type 'exit' or press Ctrl+D to quit. Type '!help' for special commands. " ) ;
408512 println ! ( ) ;
409513
410514 // Main interactive loop
@@ -414,51 +518,94 @@ impl InteractiveCommand {
414518 println ! ( "\n Interrupted by user. Exiting..." ) ;
415519 break ;
416520 }
417- // Show node status
418- print ! ( "[" ) ;
419- for ( i, session) in sessions. iter ( ) . enumerate ( ) {
420- if i > 0 {
421- print ! ( " " ) ;
521+ // Build prompt with node status
522+ let active_count = sessions
523+ . iter ( )
524+ . filter ( |s| s. is_active && s. is_connected )
525+ . count ( ) ;
526+ let total_connected = sessions. iter ( ) . filter ( |s| s. is_connected ) . count ( ) ;
527+
528+ let prompt = if active_count == total_connected {
529+ // All nodes active - show simple status
530+ let mut status = String :: from ( "[" ) ;
531+ for ( i, session) in sessions. iter ( ) . enumerate ( ) {
532+ if i > 0 {
533+ status. push ( ' ' ) ;
534+ }
535+ if session. is_connected {
536+ status. push_str ( & "●" . green ( ) . to_string ( ) ) ;
537+ } else {
538+ status. push_str ( & "○" . red ( ) . to_string ( ) ) ;
539+ }
422540 }
423- if session. is_connected {
424- print ! ( "{}" , "●" . green( ) ) ;
425- } else {
426- print ! ( "{}" , "○" . red( ) ) ;
541+ status. push_str ( "] bssh> " ) ;
542+ status
543+ } else {
544+ // Some nodes inactive - show which are active
545+ let mut status = String :: from ( "[" ) ;
546+ for ( i, session) in sessions. iter ( ) . enumerate ( ) {
547+ if i > 0 {
548+ status. push ( ' ' ) ;
549+ }
550+ if !session. is_connected {
551+ status. push_str ( & "○" . red ( ) . to_string ( ) ) ;
552+ } else if session. is_active {
553+ status. push_str ( & format ! ( "{}" , ( i + 1 ) . to_string( ) . green( ) ) ) ;
554+ } else {
555+ status. push_str ( & "·" . yellow ( ) . to_string ( ) ) ;
556+ }
427557 }
428- }
429- print ! ( "] " ) ;
430- io :: stdout ( ) . flush ( ) ? ;
558+ status . push_str ( & format ! ( "] ({active_count}/{total_connected}) bssh> " ) ) ;
559+ status
560+ } ;
431561
432562 // Read input
433- let prompt = "bssh> " ;
434- match rl. readline ( prompt) {
563+ match rl. readline ( & prompt) {
435564 Ok ( line) => {
436565 if line. trim ( ) == "exit" {
437566 break ;
438567 }
439568
569+ // Check for special commands first
570+ if line. trim ( ) . starts_with ( '!' )
571+ && Self :: handle_special_command ( & line, & mut sessions) ?
572+ {
573+ continue ; // Command was handled, continue to next iteration
574+ }
575+
440576 rl. add_history_entry ( & line) ?;
441577
442- // Send command to all connected nodes
578+ // Send command only to active nodes
579+ let mut command_sent = false ;
443580 for session in & mut sessions {
444- if session. is_connected {
581+ if session. is_connected && session . is_active {
445582 if let Err ( e) = session. send_command ( & line) . await {
446583 eprintln ! (
447584 "Failed to send command to {}: {}" ,
448585 session. node. to_string( ) . red( ) ,
449586 e
450587 ) ;
451588 session. is_connected = false ;
589+ } else {
590+ command_sent = true ;
452591 }
453592 }
454593 }
455- commands_executed += 1 ;
594+
595+ if command_sent {
596+ commands_executed += 1 ;
597+ } else {
598+ eprintln ! (
599+ "No active nodes to send command to. Use !list to see nodes or !all to activate all."
600+ ) ;
601+ continue ;
602+ }
456603
457604 // Wait a bit for output and collect from all nodes
458605 tokio:: time:: sleep ( Duration :: from_millis ( 500 ) ) . await ;
459606
460607 for session in & mut sessions {
461- if session. is_connected {
608+ if session. is_connected && session . is_active {
462609 while let Ok ( Some ( output) ) = session. read_output ( ) . await {
463610 // Print output with node prefix
464611 for line in output. lines ( ) {
0 commit comments