@@ -29,6 +29,7 @@ var statusJSON bool
2929var statusFast bool
3030var statusWatch bool
3131var statusInterval int
32+ var statusVerbose bool
3233
3334var statusCmd = & cobra.Command {
3435 Use : "status" ,
@@ -49,6 +50,7 @@ func init() {
4950 statusCmd .Flags ().BoolVar (& statusFast , "fast" , false , "Skip mail lookups for faster execution" )
5051 statusCmd .Flags ().BoolVarP (& statusWatch , "watch" , "w" , false , "Watch mode: refresh status continuously" )
5152 statusCmd .Flags ().IntVarP (& statusInterval , "interval" , "n" , 2 , "Refresh interval in seconds" )
53+ statusCmd .Flags ().BoolVarP (& statusVerbose , "verbose" , "v" , false , "Show detailed multi-line output per agent" )
5254 rootCmd .AddCommand (statusCmd )
5355}
5456
@@ -456,8 +458,16 @@ func outputStatusText(status TownStatus) error {
456458 if icon == "" {
457459 icon = roleIcons [agent .Name ]
458460 }
459- fmt .Printf ("%s %s\n " , icon , style .Bold .Render (capitalizeFirst (agent .Name )))
460- renderAgentDetails (agent , " " , nil , status .Location )
461+ if statusVerbose {
462+ fmt .Printf ("%s %s\n " , icon , style .Bold .Render (capitalizeFirst (agent .Name )))
463+ renderAgentDetails (agent , " " , nil , status .Location )
464+ fmt .Println ()
465+ } else {
466+ // Compact: icon + name on one line
467+ renderAgentCompact (agent , icon + " " , nil , status .Location )
468+ }
469+ }
470+ if ! statusVerbose && len (status .Agents ) > 0 {
461471 fmt .Println ()
462472 }
463473
@@ -488,73 +498,86 @@ func outputStatusText(status TownStatus) error {
488498
489499 // Witness
490500 if len (witnesses ) > 0 {
491- fmt .Printf ("%s %s\n " , roleIcons ["witness" ], style .Bold .Render ("Witness" ))
492- for _ , agent := range witnesses {
493- renderAgentDetails (agent , " " , r .Hooks , status .Location )
501+ if statusVerbose {
502+ fmt .Printf ("%s %s\n " , roleIcons ["witness" ], style .Bold .Render ("Witness" ))
503+ for _ , agent := range witnesses {
504+ renderAgentDetails (agent , " " , r .Hooks , status .Location )
505+ }
506+ fmt .Println ()
507+ } else {
508+ for _ , agent := range witnesses {
509+ renderAgentCompact (agent , roleIcons ["witness" ]+ " " , r .Hooks , status .Location )
510+ }
494511 }
495- fmt .Println ()
496512 }
497513
498514 // Refinery
499515 if len (refineries ) > 0 {
500- fmt .Printf ("%s %s\n " , roleIcons ["refinery" ], style .Bold .Render ("Refinery" ))
501- for _ , agent := range refineries {
502- renderAgentDetails (agent , " " , r .Hooks , status .Location )
503- }
504- // MQ summary (shown under refinery)
505- if r .MQ != nil {
506- mqParts := []string {}
507- if r .MQ .Pending > 0 {
508- mqParts = append (mqParts , fmt .Sprintf ("%d pending" , r .MQ .Pending ))
509- }
510- if r .MQ .InFlight > 0 {
511- mqParts = append (mqParts , style .Warning .Render (fmt .Sprintf ("%d in-flight" , r .MQ .InFlight )))
516+ if statusVerbose {
517+ fmt .Printf ("%s %s\n " , roleIcons ["refinery" ], style .Bold .Render ("Refinery" ))
518+ for _ , agent := range refineries {
519+ renderAgentDetails (agent , " " , r .Hooks , status .Location )
512520 }
513- if r .MQ .Blocked > 0 {
514- mqParts = append (mqParts , style .Dim .Render (fmt .Sprintf ("%d blocked" , r .MQ .Blocked )))
515- }
516- if len (mqParts ) > 0 {
517- // Add state indicator
518- stateIcon := "○" // idle
519- switch r .MQ .State {
520- case "processing" :
521- stateIcon = style .Success .Render ("●" )
522- case "blocked" :
523- stateIcon = style .Error .Render ("○" )
521+ // MQ summary (shown under refinery)
522+ if r .MQ != nil {
523+ mqStr := formatMQSummary (r .MQ )
524+ if mqStr != "" {
525+ fmt .Printf (" MQ: %s\n " , mqStr )
524526 }
525- // Add health warning if stale
526- healthSuffix := ""
527- if r .MQ .Health == "stale" {
528- healthSuffix = style .Error .Render (" [stale]" )
527+ }
528+ fmt .Println ()
529+ } else {
530+ for _ , agent := range refineries {
531+ // Compact: include MQ on same line if present
532+ mqSuffix := ""
533+ if r .MQ != nil {
534+ mqStr := formatMQSummaryCompact (r .MQ )
535+ if mqStr != "" {
536+ mqSuffix = " " + mqStr
537+ }
529538 }
530- fmt . Printf ( " MQ: %s %s%s \n " , stateIcon , strings . Join ( mqParts , ", " ), healthSuffix )
539+ renderAgentCompactWithSuffix ( agent , roleIcons [ "refinery" ] + " " , r . Hooks , status . Location , mqSuffix )
531540 }
532541 }
533- fmt .Println ()
534542 }
535543
536544 // Crew
537545 if len (crews ) > 0 {
538- fmt .Printf ("%s %s (%d)\n " , roleIcons ["crew" ], style .Bold .Render ("Crew" ), len (crews ))
539- for _ , agent := range crews {
540- renderAgentDetails (agent , " " , r .Hooks , status .Location )
546+ if statusVerbose {
547+ fmt .Printf ("%s %s (%d)\n " , roleIcons ["crew" ], style .Bold .Render ("Crew" ), len (crews ))
548+ for _ , agent := range crews {
549+ renderAgentDetails (agent , " " , r .Hooks , status .Location )
550+ }
551+ fmt .Println ()
552+ } else {
553+ fmt .Printf ("%s %s (%d)\n " , roleIcons ["crew" ], style .Bold .Render ("Crew" ), len (crews ))
554+ for _ , agent := range crews {
555+ renderAgentCompact (agent , " " , r .Hooks , status .Location )
556+ }
541557 }
542- fmt .Println ()
543558 }
544559
545560 // Polecats
546561 if len (polecats ) > 0 {
547- fmt .Printf ("%s %s (%d)\n " , roleIcons ["polecat" ], style .Bold .Render ("Polecats" ), len (polecats ))
548- for _ , agent := range polecats {
549- renderAgentDetails (agent , " " , r .Hooks , status .Location )
562+ if statusVerbose {
563+ fmt .Printf ("%s %s (%d)\n " , roleIcons ["polecat" ], style .Bold .Render ("Polecats" ), len (polecats ))
564+ for _ , agent := range polecats {
565+ renderAgentDetails (agent , " " , r .Hooks , status .Location )
566+ }
567+ fmt .Println ()
568+ } else {
569+ fmt .Printf ("%s %s (%d)\n " , roleIcons ["polecat" ], style .Bold .Render ("Polecats" ), len (polecats ))
570+ for _ , agent := range polecats {
571+ renderAgentCompact (agent , " " , r .Hooks , status .Location )
572+ }
550573 }
551- fmt .Println ()
552574 }
553575
554576 // No agents
555577 if len (witnesses ) == 0 && len (refineries ) == 0 && len (crews ) == 0 && len (polecats ) == 0 {
556- fmt .Printf (" %s\n \n " , style .Dim .Render ("(no agents)" ))
578+ fmt .Printf (" %s\n " , style .Dim .Render ("(no agents)" ))
557579 }
580+ fmt .Println ()
558581 }
559582
560583 return nil
@@ -665,6 +688,165 @@ func renderAgentDetails(agent AgentRuntime, indent string, hooks []AgentHookInfo
665688 }
666689}
667690
691+ // formatMQSummary formats the MQ status for verbose display
692+ func formatMQSummary (mq * MQSummary ) string {
693+ if mq == nil {
694+ return ""
695+ }
696+ mqParts := []string {}
697+ if mq .Pending > 0 {
698+ mqParts = append (mqParts , fmt .Sprintf ("%d pending" , mq .Pending ))
699+ }
700+ if mq .InFlight > 0 {
701+ mqParts = append (mqParts , style .Warning .Render (fmt .Sprintf ("%d in-flight" , mq .InFlight )))
702+ }
703+ if mq .Blocked > 0 {
704+ mqParts = append (mqParts , style .Dim .Render (fmt .Sprintf ("%d blocked" , mq .Blocked )))
705+ }
706+ if len (mqParts ) == 0 {
707+ return ""
708+ }
709+ // Add state indicator
710+ stateIcon := "○" // idle
711+ switch mq .State {
712+ case "processing" :
713+ stateIcon = style .Success .Render ("●" )
714+ case "blocked" :
715+ stateIcon = style .Error .Render ("○" )
716+ }
717+ // Add health warning if stale
718+ healthSuffix := ""
719+ if mq .Health == "stale" {
720+ healthSuffix = style .Error .Render (" [stale]" )
721+ }
722+ return fmt .Sprintf ("%s %s%s" , stateIcon , strings .Join (mqParts , ", " ), healthSuffix )
723+ }
724+
725+ // formatMQSummaryCompact formats MQ status for compact single-line display
726+ func formatMQSummaryCompact (mq * MQSummary ) string {
727+ if mq == nil {
728+ return ""
729+ }
730+ // Very compact: "MQ:12" or "MQ:12 [stale]"
731+ total := mq .Pending + mq .InFlight + mq .Blocked
732+ if total == 0 {
733+ return ""
734+ }
735+ healthSuffix := ""
736+ if mq .Health == "stale" {
737+ healthSuffix = style .Error .Render ("[stale]" )
738+ }
739+ return fmt .Sprintf ("MQ:%d%s" , total , healthSuffix )
740+ }
741+
742+ // renderAgentCompactWithSuffix renders a single-line agent status with an extra suffix
743+ func renderAgentCompactWithSuffix (agent AgentRuntime , indent string , hooks []AgentHookInfo , townRoot string , suffix string ) {
744+ // Build status indicator
745+ var statusIndicator string
746+ beadState := agent .State
747+ sessionExists := agent .Running
748+ beadSaysRunning := beadState == "running" || beadState == "idle" || beadState == ""
749+
750+ switch {
751+ case beadSaysRunning && sessionExists :
752+ statusIndicator = style .Success .Render ("●" )
753+ case beadSaysRunning && ! sessionExists :
754+ statusIndicator = style .Error .Render ("●" ) + style .Warning .Render (" dead" )
755+ case ! beadSaysRunning && sessionExists :
756+ statusIndicator = style .Success .Render ("●" ) + style .Warning .Render (" [" + beadState + "]" )
757+ default :
758+ statusIndicator = style .Error .Render ("○" )
759+ }
760+
761+ // Get hook info
762+ hookBead := agent .HookBead
763+ hookTitle := agent .WorkTitle
764+ if hookBead == "" && hooks != nil {
765+ for _ , h := range hooks {
766+ if h .Agent == agent .Address && h .HasWork {
767+ hookBead = h .Molecule
768+ hookTitle = h .Title
769+ break
770+ }
771+ }
772+ }
773+
774+ // Build hook suffix
775+ hookSuffix := ""
776+ if hookBead != "" {
777+ if hookTitle != "" {
778+ hookSuffix = style .Dim .Render (" → " ) + truncateWithEllipsis (hookTitle , 30 )
779+ } else {
780+ hookSuffix = style .Dim .Render (" → " ) + hookBead
781+ }
782+ } else if hookTitle != "" {
783+ hookSuffix = style .Dim .Render (" → " ) + truncateWithEllipsis (hookTitle , 30 )
784+ }
785+
786+ // Mail indicator
787+ mailSuffix := ""
788+ if agent .UnreadMail > 0 {
789+ mailSuffix = fmt .Sprintf (" 📬%d" , agent .UnreadMail )
790+ }
791+
792+ // Print single line: name + status + hook + mail + suffix
793+ fmt .Printf ("%s%-12s %s%s%s%s\n " , indent , agent .Name , statusIndicator , hookSuffix , mailSuffix , suffix )
794+ }
795+
796+ // renderAgentCompact renders a single-line agent status
797+ func renderAgentCompact (agent AgentRuntime , indent string , hooks []AgentHookInfo , townRoot string ) {
798+ // Build status indicator
799+ var statusIndicator string
800+ beadState := agent .State
801+ sessionExists := agent .Running
802+ beadSaysRunning := beadState == "running" || beadState == "idle" || beadState == ""
803+
804+ switch {
805+ case beadSaysRunning && sessionExists :
806+ statusIndicator = style .Success .Render ("●" )
807+ case beadSaysRunning && ! sessionExists :
808+ statusIndicator = style .Error .Render ("●" ) + style .Warning .Render (" dead" )
809+ case ! beadSaysRunning && sessionExists :
810+ statusIndicator = style .Success .Render ("●" ) + style .Warning .Render (" [" + beadState + "]" )
811+ default :
812+ statusIndicator = style .Error .Render ("○" )
813+ }
814+
815+ // Get hook info
816+ hookBead := agent .HookBead
817+ hookTitle := agent .WorkTitle
818+ if hookBead == "" && hooks != nil {
819+ for _ , h := range hooks {
820+ if h .Agent == agent .Address && h .HasWork {
821+ hookBead = h .Molecule
822+ hookTitle = h .Title
823+ break
824+ }
825+ }
826+ }
827+
828+ // Build hook suffix
829+ hookSuffix := ""
830+ if hookBead != "" {
831+ if hookTitle != "" {
832+ hookSuffix = style .Dim .Render (" → " ) + truncateWithEllipsis (hookTitle , 30 )
833+ } else {
834+ hookSuffix = style .Dim .Render (" → " ) + hookBead
835+ }
836+ } else if hookTitle != "" {
837+ hookSuffix = style .Dim .Render (" → " ) + truncateWithEllipsis (hookTitle , 30 )
838+ }
839+
840+ // Mail indicator
841+ mailSuffix := ""
842+ if agent .UnreadMail > 0 {
843+ mailSuffix = fmt .Sprintf (" 📬%d" , agent .UnreadMail )
844+ }
845+
846+ // Print single line: name + status + hook + mail
847+ fmt .Printf ("%s%-12s %s%s%s\n " , indent , agent .Name , statusIndicator , hookSuffix , mailSuffix )
848+ }
849+
668850// formatHookInfo formats the hook bead and title for display
669851func formatHookInfo (hookBead , title string , maxLen int ) string {
670852 if hookBead == "" {
0 commit comments