@@ -22,6 +22,7 @@ import {
2222 createTunnel ,
2323 createSSHKey ,
2424} from "../services/devboxService.js" ;
25+ import { parseLogEntry , formatTimestamp } from "../utils/logFormatter.js" ;
2526
2627type Operation =
2728 | "exec"
@@ -419,20 +420,15 @@ export const DevboxActionsMenu = ({
419420 typeof operationResult === "object" &&
420421 ( operationResult as any ) . __customRender === "logs"
421422 ) {
422- // Copy logs to clipboard
423+ // Copy logs to clipboard using shared formatter
423424 const logs = ( operationResult as any ) . __logs || [ ] ;
424425 const logsText = logs
425426 . map ( ( log : any ) => {
426- const time = new Date ( log . timestamp_ms ) . toLocaleString ( ) ;
427- const level = log . level || "INFO" ;
428- const source = log . source || "exec" ;
429- const message = log . message || "" ;
430- const cmd = log . cmd ? `[${ log . cmd } ] ` : "" ;
431- const exitCode =
432- log . exit_code !== null && log . exit_code !== undefined
433- ? `(${ log . exit_code } ) `
434- : "" ;
435- return `${ time } ${ level } /${ source } ${ exitCode } ${ cmd } ${ message } ` ;
427+ const parts = parseLogEntry ( log ) ;
428+ const cmd = parts . cmd ? `$ ${ parts . cmd } ` : "" ;
429+ const exitCode = parts . exitCode !== null ? `exit=${ parts . exitCode } ` : "" ;
430+ const shell = parts . shellName ? `(${ parts . shellName } ) ` : "" ;
431+ return `${ parts . timestamp } ${ parts . level } [${ parts . source } ] ${ shell } ${ cmd } ${ parts . message } ${ exitCode } ` . trim ( ) ;
436432 } )
437433 . join ( "\n" ) ;
438434
@@ -846,94 +842,123 @@ export const DevboxActionsMenu = ({
846842 paddingX = { 1 }
847843 >
848844 { visibleLogs . map ( ( log : any , index : number ) => {
849- const time = new Date ( log . timestamp_ms ) . toLocaleTimeString ( ) ;
850- const level = log . level ? log . level [ 0 ] . toUpperCase ( ) : "I" ;
851- const source = log . source ? log . source . substring ( 0 , 8 ) : "exec" ;
852- // Sanitize message: escape special chars to prevent layout breaks while preserving visibility
853- const rawMessage = log . message || "" ;
854- const escapedMessage = rawMessage
855- . replace ( / \r \n / g, "\\n" ) // Windows line endings
856- . replace ( / \n / g, "\\n" ) // Unix line endings
857- . replace ( / \r / g, "\\r" ) // Old Mac line endings
858- . replace ( / \t / g, "\\t" ) ; // Tabs
845+ const parts = parseLogEntry ( log ) ;
846+
847+ // Sanitize message: escape special chars to prevent layout breaks
848+ const escapedMessage = parts . message
849+ . replace ( / \r \n / g, "\\n" )
850+ . replace ( / \n / g, "\\n" )
851+ . replace ( / \r / g, "\\r" )
852+ . replace ( / \t / g, "\\t" ) ;
853+
859854 // Limit message length to prevent Yoga layout engine errors
860855 const MAX_MESSAGE_LENGTH = 1000 ;
861856 const fullMessage =
862857 escapedMessage . length > MAX_MESSAGE_LENGTH
863858 ? escapedMessage . substring ( 0 , MAX_MESSAGE_LENGTH ) + "..."
864859 : escapedMessage ;
865- const cmd = log . cmd
866- ? `[${ log . cmd . substring ( 0 , 40 ) } ${ log . cmd . length > 40 ? "..." : "" } ] `
860+
861+ const cmd = parts . cmd
862+ ? `$ ${ parts . cmd . substring ( 0 , 40 ) } ${ parts . cmd . length > 40 ? "..." : "" } `
867863 : "" ;
868864 const exitCode =
869- log . exit_code !== null && log . exit_code !== undefined
870- ? `(${ log . exit_code } ) `
871- : "" ;
872-
873- let levelColor : string = colors . textDim ;
874- if ( level === "E" ) levelColor = colors . error ;
875- else if ( level === "W" ) levelColor = colors . warning ;
876- else if ( level === "I" ) levelColor = colors . primary ;
865+ parts . exitCode !== null ? `exit=${ parts . exitCode } ` : "" ;
866+
867+ // Map color names to theme colors
868+ const levelColorMap : Record < string , string > = {
869+ red : colors . error ,
870+ yellow : colors . warning ,
871+ blue : colors . primary ,
872+ gray : colors . textDim ,
873+ } ;
874+ const sourceColorMap : Record < string , string > = {
875+ magenta : "#d33682" ,
876+ cyan : colors . info ,
877+ green : colors . success ,
878+ yellow : colors . warning ,
879+ gray : colors . textDim ,
880+ white : colors . text ,
881+ } ;
882+ const levelColor = levelColorMap [ parts . levelColor ] || colors . textDim ;
883+ const sourceColor = sourceColorMap [ parts . sourceColor ] || colors . textDim ;
877884
878885 if ( logsWrapMode ) {
879886 return (
880887 < Box key = { index } >
881888 < Text color = { colors . textDim } dimColor >
882- { time }
889+ { parts . timestamp }
883890 </ Text >
884891 < Text > </ Text >
885- < Text color = { levelColor } bold >
886- { level }
887- </ Text >
888- < Text color = { colors . textDim } dimColor >
889- /{ source }
892+ < Text color = { levelColor } bold = { parts . levelColor === "red" } >
893+ { parts . level }
890894 </ Text >
891895 < Text > </ Text >
892- { exitCode && < Text color = { colors . warning } > { exitCode } </ Text > }
896+ < Text color = { sourceColor } > [{ parts . source } ]</ Text >
897+ < Text > </ Text >
898+ { parts . shellName && (
899+ < Text color = { colors . textDim } dimColor >
900+ ({ parts . shellName } ){ " " }
901+ </ Text >
902+ ) }
893903 { cmd && (
894- < Text color = { colors . info } dimColor >
904+ < Text color = { colors . info } >
895905 { cmd }
896906 </ Text >
897907 ) }
898908 < Text > { fullMessage } </ Text >
909+ { exitCode && (
910+ < Text color = { parts . exitCode === 0 ? colors . success : colors . error } >
911+ { " " } { exitCode }
912+ </ Text >
913+ ) }
899914 </ Box >
900915 ) ;
901916 } else {
902- // CRITICAL: Validate all lengths and ensure positive values for Yoga
903- const exitCodeLen = typeof exitCode === 'string' ? exitCode . length : 0 ;
904- const cmdLen = typeof cmd === 'string' ? cmd . length : 0 ;
905- const metadataWidth = 11 + 1 + 1 + 1 + 8 + 1 + exitCodeLen + cmdLen + 6 ;
906- // Ensure terminalWidth is valid and availableMessageWidth is always positive
917+ // Calculate available width for message truncation
918+ const timestampLen = parts . timestamp . length ;
919+ const levelLen = parts . level . length ;
920+ const sourceLen = parts . source . length + 2 ; // brackets
921+ const shellLen = parts . shellName ? parts . shellName . length + 3 : 0 ;
922+ const cmdLen = cmd . length ;
923+ const exitLen = exitCode . length ;
924+ const spacesLen = 5 ; // spaces between elements
925+ const metadataWidth = timestampLen + levelLen + sourceLen + shellLen + cmdLen + exitLen + spacesLen ;
926+
907927 const safeTerminalWidth = Math . max ( 80 , terminalWidth ) ;
908- const availableMessageWidth = Math . max (
909- 20 ,
910- Math . floor ( safeTerminalWidth - metadataWidth ) ,
911- ) ;
928+ const availableMessageWidth = Math . max ( 20 , safeTerminalWidth - metadataWidth ) ;
912929 const truncatedMessage =
913930 fullMessage . length > availableMessageWidth
914- ? fullMessage . substring ( 0 , Math . max ( 1 , availableMessageWidth - 3 ) ) +
915- "..."
931+ ? fullMessage . substring ( 0 , Math . max ( 1 , availableMessageWidth - 3 ) ) + "..."
916932 : fullMessage ;
933+
917934 return (
918935 < Box key = { index } >
919936 < Text color = { colors . textDim } dimColor >
920- { time }
937+ { parts . timestamp }
921938 </ Text >
922939 < Text > </ Text >
923- < Text color = { levelColor } bold >
924- { level }
925- </ Text >
926- < Text color = { colors . textDim } dimColor >
927- /{ source }
940+ < Text color = { levelColor } bold = { parts . levelColor === "red" } >
941+ { parts . level }
928942 </ Text >
929943 < Text > </ Text >
930- { exitCode && < Text color = { colors . warning } > { exitCode } </ Text > }
944+ < Text color = { sourceColor } > [{ parts . source } ]</ Text >
945+ < Text > </ Text >
946+ { parts . shellName && (
947+ < Text color = { colors . textDim } dimColor >
948+ ({ parts . shellName } ){ " " }
949+ </ Text >
950+ ) }
931951 { cmd && (
932- < Text color = { colors . info } dimColor >
952+ < Text color = { colors . info } >
933953 { cmd }
934954 </ Text >
935955 ) }
936956 < Text > { truncatedMessage } </ Text >
957+ { exitCode && (
958+ < Text color = { parts . exitCode === 0 ? colors . success : colors . error } >
959+ { " " } { exitCode }
960+ </ Text >
961+ ) }
937962 </ Box >
938963 ) ;
939964 }
0 commit comments