22
33use ash:: { Tool , ToolResult } ;
44use ash:: daemon;
5+ use ash:: style;
56use ash:: tools;
67use clap:: { Parser , Subcommand } ;
78use serde_json:: Value ;
@@ -49,16 +50,7 @@ enum OutputFormat { Text, Json }
4950#[ derive( Subcommand ) ]
5051enum Commands {
5152 // ==================== File Operations ====================
52-
53- /// Read file with line numbers
54- View {
55- file_path : String ,
56- #[ arg( short = 'n' , long, default_value = "1" ) ]
57- offset : usize ,
58- #[ arg( short, long, default_value = "100" ) ]
59- limit : usize ,
60- } ,
61-
53+
6254 /// Search for pattern in files (ripgrep)
6355 Grep {
6456 pattern : String ,
@@ -144,10 +136,9 @@ enum Commands {
144136
145137 // ==================== Filesystem ====================
146138
147- /// Filesystem operations
148- Fs {
149- #[ command( subcommand) ]
150- op : FsOp ,
139+ /// List directory contents
140+ Ls {
141+ path : String ,
151142 } ,
152143
153144 // ==================== Buffer ====================
@@ -409,42 +400,6 @@ enum SessionOp {
409400 } ,
410401}
411402
412- #[ derive( Subcommand ) ]
413- enum FsOp {
414- /// List directory contents
415- Ls { path : String } ,
416- /// Get file/directory metadata
417- Stat { path : String } ,
418- /// Write content to file
419- Write {
420- path : String ,
421- content : String ,
422- } ,
423- /// Create directory
424- Mkdir {
425- path : String ,
426- #[ arg( long, default_value = "true" ) ]
427- recursive : bool ,
428- } ,
429- /// Remove file or directory
430- Rm {
431- path : String ,
432- #[ arg( short, long) ]
433- recursive : bool ,
434- } ,
435- /// Move/rename file or directory
436- Mv {
437- from : String ,
438- to : String ,
439- } ,
440- /// Copy file or directory
441- Cp {
442- from : String ,
443- to : String ,
444- #[ arg( short, long) ]
445- recursive : bool ,
446- } ,
447- }
448403
449404#[ derive( Subcommand ) ]
450405enum BufferOp {
@@ -601,12 +556,6 @@ async fn main() -> anyhow::Result<()> {
601556 let result = match cli. command {
602557 // ==================== File Operations ====================
603558
604- Commands :: View { file_path, offset, limit } => {
605- exec_tool ( & tools:: ViewTool , serde_json:: json!( {
606- "file_path" : file_path, "offset" : offset, "limit" : limit
607- } ) , & session_id) . await
608- }
609-
610559 Commands :: Grep { pattern, path, include, limit } => {
611560 exec_tool ( & tools:: GrepTool , serde_json:: json!( {
612561 "pattern" : pattern, "path" : path, "include" : include, "limit" : limit
@@ -675,30 +624,8 @@ async fn main() -> anyhow::Result<()> {
675624
676625 // ==================== Filesystem ====================
677626
678- Commands :: Fs { op } => {
679- match op {
680- FsOp :: Ls { path } => {
681- exec_tool ( & tools:: FsListDirTool , serde_json:: json!( { "path" : path} ) , & session_id) . await
682- }
683- FsOp :: Stat { path } => {
684- exec_tool ( & tools:: FsStatTool , serde_json:: json!( { "path" : path} ) , & session_id) . await
685- }
686- FsOp :: Write { path, content } => {
687- exec_tool ( & tools:: FsWriteTool , serde_json:: json!( { "path" : path, "content" : content} ) , & session_id) . await
688- }
689- FsOp :: Mkdir { path, recursive } => {
690- exec_tool ( & tools:: FsMkdirTool , serde_json:: json!( { "path" : path, "recursive" : recursive} ) , & session_id) . await
691- }
692- FsOp :: Rm { path, recursive } => {
693- exec_tool ( & tools:: FsRemoveTool , serde_json:: json!( { "path" : path, "recursive" : recursive} ) , & session_id) . await
694- }
695- FsOp :: Mv { from, to } => {
696- exec_tool ( & tools:: FsMoveTool , serde_json:: json!( { "from" : from, "to" : to} ) , & session_id) . await
697- }
698- FsOp :: Cp { from, to, recursive } => {
699- exec_tool ( & tools:: FsCopyTool , serde_json:: json!( { "from" : from, "to" : to, "recursive" : recursive} ) , & session_id) . await
700- }
701- }
627+ Commands :: Ls { path } => {
628+ exec_tool ( & tools:: FsListDirTool , serde_json:: json!( { "path" : path} ) , & session_id) . await
702629 }
703630
704631 // ==================== Buffer ====================
@@ -973,7 +900,8 @@ async fn main() -> anyhow::Result<()> {
973900 match op {
974901 GatewayOp :: Start { foreground } => {
975902 if daemon:: is_gateway_running ( ) {
976- println ! ( "Gateway is already running" ) ;
903+ println ! ( "{} Gateway is already running" ,
904+ style:: color( style:: check( ) , style:: GREEN ) ) ;
977905 return Ok ( ( ) ) ;
978906 }
979907
@@ -988,7 +916,9 @@ async fn main() -> anyhow::Result<()> {
988916 . stdout ( std:: process:: Stdio :: null ( ) )
989917 . stderr ( std:: process:: Stdio :: null ( ) )
990918 . spawn ( ) ?;
991- println ! ( "Gateway started (pid {})" , child. id( ) ) ;
919+ println ! ( "{} Gateway started {}" ,
920+ style:: color( style:: check( ) , style:: GREEN ) ,
921+ style:: dim( & format!( "(pid {})" , child. id( ) ) ) ) ;
992922 }
993923 }
994924 GatewayOp :: Stop => {
@@ -1004,13 +934,16 @@ async fn main() -> anyhow::Result<()> {
1004934 tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 300 ) ) . await ;
1005935 let _ = std:: fs:: remove_file ( daemon:: pid_path ( ) ) ;
1006936 let _ = std:: fs:: remove_file ( daemon:: socket_path ( ) ) ;
1007- println ! ( "Gateway stopped (pid {})" , pid) ;
937+ println ! ( "{} Gateway stopped {}" ,
938+ style:: color( style:: cross( ) , style:: GRAY ) ,
939+ style:: dim( & format!( "(pid {})" , pid) ) ) ;
1008940 } else {
1009941 eprintln ! ( "Invalid PID file" ) ;
1010942 }
1011943 }
1012944 Err ( _) => {
1013- eprintln ! ( "Gateway is not running" ) ;
945+ eprintln ! ( "{} Gateway is not running" ,
946+ style:: ecolor( style:: cross( ) , style:: GRAY ) ) ;
1014947 }
1015948 }
1016949 }
@@ -1021,10 +954,15 @@ async fn main() -> anyhow::Result<()> {
1021954 . and_then ( |r| r. get ( "uptime_secs" ) )
1022955 . and_then ( |u| u. as_u64 ( ) )
1023956 . unwrap_or ( 0 ) ;
1024- println ! ( "Gateway running (uptime: {}s)" , uptime) ;
957+ println ! ( "{} Gateway {} {}" ,
958+ style:: color( style:: check( ) , style:: GREEN ) ,
959+ style:: color( "running" , style:: GREEN ) ,
960+ style:: dim( & format!( "(uptime: {})" , style:: format_uptime( uptime) ) ) ) ;
1025961 }
1026962 None => {
1027- println ! ( "Gateway is not running" ) ;
963+ println ! ( "{} Gateway {}" ,
964+ style:: color( style:: cross( ) , style:: GRAY ) ,
965+ style:: dim( "not running" ) ) ;
1028966 }
1029967 }
1030968 }
@@ -1034,80 +972,96 @@ async fn main() -> anyhow::Result<()> {
1034972
1035973 Commands :: Info => {
1036974 let mut out = String :: new ( ) ;
975+ let ver = env ! ( "CARGO_PKG_VERSION" ) ;
1037976
1038- // Version
1039- out. push_str ( & format ! ( "ash {}\n \n " , env! ( "CARGO_PKG_VERSION" ) ) ) ;
977+ // Banner
978+ out. push_str ( & format ! ( "\n {}\n " , style :: banner_line ( ver ) ) ) ;
1040979
1041980 // Try gateway for info
1042981 if let Some ( resp) = daemon:: gateway_call ( "gateway/info" , serde_json:: json!( { } ) ) . await {
1043982 if let Some ( info) = resp. get ( "result" ) {
1044983 let uptime = info. get ( "uptime_secs" ) . and_then ( |u| u. as_u64 ( ) ) . unwrap_or ( 0 ) ;
1045- out. push_str ( & format ! ( "Gateway: running (uptime: {}s)\n " , uptime) ) ;
984+ out. push_str ( & format ! ( "\n {}\n " , style:: section( "Gateway" ) ) ) ;
985+ out. push_str ( & format ! ( " {} {} {}\n " ,
986+ style:: color( style:: check( ) , style:: GREEN ) ,
987+ style:: color( "running" , style:: GREEN ) ,
988+ style:: dim( & format!( "uptime {}" , style:: format_uptime( uptime) ) ) ) ) ;
1046989
1047990 let local_mcp_port = info. get ( "local_mcp_port" ) . and_then ( |p| p. as_u64 ( ) ) ;
1048991 if let Some ( port) = local_mcp_port {
1049- out. push_str ( & format ! ( "Local ash-mcp: port {}\n " , port) ) ;
992+ out. push_str ( & format ! ( " {} ash-mcp on port {}\n " ,
993+ style:: color( style:: check( ) , style:: GREEN ) ,
994+ style:: color( & port. to_string( ) , style:: CYAN ) ) ) ;
1050995 }
1051- out. push ( '\n' ) ;
1052996
1053997 let docker_ok = info. pointer ( "/backends/docker" ) . and_then ( |v| v. as_bool ( ) ) . unwrap_or ( false ) ;
1054998 let k8s_ok = info. pointer ( "/backends/k8s" ) . and_then ( |v| v. as_bool ( ) ) . unwrap_or ( false ) ;
1055999 let default_backend = info. get ( "default_backend" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "local" ) ;
10561000
1057- out. push_str ( "Backends:\n " ) ;
1058- let docker_mark = if docker_ok { "ok" } else { "--" } ;
1059- let k8s_mark = if k8s_ok { "ok" } else { "--" } ;
1001+ out. push_str ( & format ! ( "\n {}\n " , style:: section( "Backends" ) ) ) ;
10601002 let docker_default = if default_backend == "docker" { " (default)" } else { "" } ;
10611003 let k8s_default = if default_backend == "k8s" { " (default)" } else { "" } ;
1062- out. push_str ( & format ! ( " docker {}{}\n " , docker_mark, docker_default) ) ;
1063- out. push_str ( & format ! ( " k8s {}{}\n " , k8s_mark, k8s_default) ) ;
1004+ out. push_str ( & format ! ( "{}\n " , style:: status_line(
1005+ & format!( "docker{}" , docker_default) , if docker_ok { "available" } else { "unavailable" } , docker_ok) ) ) ;
1006+ out. push_str ( & format ! ( "{}\n " , style:: status_line(
1007+ & format!( "k8s{}" , k8s_default) , if k8s_ok { "available" } else { "unavailable" } , k8s_ok) ) ) ;
10641008
10651009 let sessions = info. get ( "sessions" ) . and_then ( |s| s. as_u64 ( ) ) . unwrap_or ( 0 ) ;
10661010 let routes = info. get ( "routes" ) . and_then ( |r| r. as_u64 ( ) ) . unwrap_or ( 0 ) ;
1067- out. push_str ( & format ! ( "\n Sessions: {} ({} routes)\n " , sessions, routes) ) ;
1068-
10691011 let tool_count = tools:: all_tools ( ) . len ( ) ;
1070- out. push_str ( & format ! ( "Tools: {}\n " , tool_count) ) ;
10711012
1013+ out. push_str ( & format ! ( "\n {}\n " , style:: section( "Stats" ) ) ) ;
1014+ out. push_str ( & format ! ( "{}\n " , style:: kv( "sessions" , & format!( "{} {}" , sessions, style:: dim( & format!( "({} routes)" , routes) ) ) ) ) ) ;
1015+ out. push_str ( & format ! ( "{}\n " , style:: kv( "tools " , & tool_count. to_string( ) ) ) ) ;
1016+
1017+ out. push ( '\n' ) ;
10721018 print ! ( "{}" , out) ;
10731019 return Ok ( ( ) ) ;
10741020 }
10751021 }
10761022
10771023 // Fallback: no gateway running
1078- out. push_str ( "Gateway: not running\n \n " ) ;
1024+ out. push_str ( & format ! ( "\n {}\n " , style:: section( "Gateway" ) ) ) ;
1025+ out. push_str ( & format ! ( " {} {}\n " ,
1026+ style:: color( style:: cross( ) , style:: GRAY ) ,
1027+ style:: dim( "not running" ) ) ) ;
10791028
10801029 use ash:: backend:: BackendType ;
10811030
1082- out. push_str ( "Backends: \n " ) ;
1031+ out. push_str ( & format ! ( " \n {} \n " , style :: section ( "Backends" ) ) ) ;
10831032 let manager = tools:: session:: BACKEND_MANAGER . read ( ) . await ;
10841033 let default_backend = manager. default_backend ( ) ;
10851034 let local_ok = manager. health_check ( BackendType :: Local ) . await . is_ok ( ) ;
10861035 let docker_ok = manager. health_check ( BackendType :: Docker ) . await . is_ok ( ) ;
10871036 let k8s_ok = manager. health_check ( BackendType :: K8s ) . await . is_ok ( ) ;
10881037 drop ( manager) ;
10891038
1090- let local_mark = if local_ok { "ok" } else { "--" } ;
1091- let docker_mark = if docker_ok { "ok" } else { "--" } ;
1092- let k8s_mark = if k8s_ok { "ok" } else { "--" } ;
10931039 let local_default = if default_backend == BackendType :: Local { " (default)" } else { "" } ;
10941040 let docker_default = if default_backend == BackendType :: Docker { " (default)" } else { "" } ;
10951041 let k8s_default = if default_backend == BackendType :: K8s { " (default)" } else { "" } ;
1096- out. push_str ( & format ! ( " local {}{}\n " , local_mark, local_default) ) ;
1097- out. push_str ( & format ! ( " docker {}{}\n " , docker_mark, docker_default) ) ;
1098- out. push_str ( & format ! ( " k8s {}{}\n " , k8s_mark, k8s_default) ) ;
1042+ out. push_str ( & format ! ( "{}\n " , style:: status_line(
1043+ & format!( "local{}" , local_default) , if local_ok { "available" } else { "unavailable" } , local_ok) ) ) ;
1044+ out. push_str ( & format ! ( "{}\n " , style:: status_line(
1045+ & format!( "docker{}" , docker_default) , if docker_ok { "available" } else { "unavailable" } , docker_ok) ) ) ;
1046+ out. push_str ( & format ! ( "{}\n " , style:: status_line(
1047+ & format!( "k8s{}" , k8s_default) , if k8s_ok { "available" } else { "unavailable" } , k8s_ok) ) ) ;
10991048
11001049 let tool_count = tools:: all_tools ( ) . len ( ) ;
1101- out. push_str ( & format ! ( "\n Tools: {}\n " , tool_count) ) ;
1050+ out. push_str ( & format ! ( "\n {}\n " , style:: section( "Stats" ) ) ) ;
1051+ out. push_str ( & format ! ( "{}\n " , style:: kv( "tools" , & tool_count. to_string( ) ) ) ) ;
11021052
1053+ out. push ( '\n' ) ;
11031054 print ! ( "{}" , out) ;
11041055 return Ok ( ( ) ) ;
11051056 }
11061057
11071058 Commands :: Tools => {
1108- for tool in tools:: all_tools ( ) {
1109- println ! ( "{}: {}" , tool. name( ) , tool. description( ) ) ;
1059+ let all = tools:: all_tools ( ) ;
1060+ println ! ( "\n {}" , style:: section( & format!( "Tools ({})" , all. len( ) ) ) ) ;
1061+ for tool in all {
1062+ println ! ( "{}" , style:: tool_entry( tool. name( ) , tool. description( ) ) ) ;
11101063 }
1064+ println ! ( ) ;
11111065 return Ok ( ( ) ) ;
11121066 }
11131067 } ;
@@ -1119,7 +1073,8 @@ async fn main() -> anyhow::Result<()> {
11191073 print ! ( "{}" , result. output) ;
11201074 if !result. output . ends_with ( '\n' ) { println ! ( ) ; }
11211075 } else {
1122- eprintln ! ( "Error: {}" , result. error. unwrap_or_default( ) ) ;
1076+ let msg = result. error . unwrap_or_default ( ) ;
1077+ eprintln ! ( "{} {}" , style:: ecolor( "error:" , style:: BRIGHT_RED ) , msg) ;
11231078 std:: process:: exit ( 1 ) ;
11241079 }
11251080 }
0 commit comments