66 "log/slog"
77 "os"
88 "os/exec"
9+ "path/filepath"
10+ "strings"
911)
1012
1113func startTtyd (ctx context.Context ,
@@ -26,8 +28,21 @@ func startTtyd(ctx context.Context,
2628 return fmt .Errorf ("fetch available startup command: %w" , err )
2729 }
2830
29- // execute ttyd <options> <startupCommand>
30- cmd := exec .CommandContext (ctx , ttydBinary , "--writable" , "--port" , fmt .Sprintf ("%d" , listenPort ), "--credential" , fmt .Sprintf ("%s:%s" , username , password ), startupCommand )
31+ // Get asciinema binary and setup recording (best-effort)
32+ command , args , _ := prepareAsciinemaCommand (ctx , startupCommand )
33+
34+ // execute ttyd <options> <command> [args]
35+ var cmd * exec.Cmd
36+ if args == "" {
37+ // No recording, just use the original command
38+ cmd = exec .CommandContext (ctx , ttydBinary , "--writable" , "--port" , fmt .Sprintf ("%d" , listenPort ), "--credential" , fmt .Sprintf ("%s:%s" , username , password ), command )
39+ } else {
40+ // Recording with asciinema - split args properly
41+ argsList := strings .Fields (args )
42+ ttydArgs := []string {"--writable" , "--port" , fmt .Sprintf ("%d" , listenPort ), "--credential" , fmt .Sprintf ("%s:%s" , username , password ), command }
43+ ttydArgs = append (ttydArgs , argsList ... )
44+ cmd = exec .CommandContext (ctx , ttydBinary , ttydArgs ... )
45+ }
3146
3247 if os .Getenv ("DEBUG" ) != "" {
3348 cmd .Stdout = os .Stdout
@@ -39,7 +54,7 @@ func startTtyd(ctx context.Context,
3954
4055func fetchAvailableStartupCommand (ctx context.Context ) (string , error ) {
4156 // test commands in PATH,
42- // zsh, fish, bash, sh, login
57+ // zsh, fish, bash, sh, login (login as lowest choice)
4358 commands := []string {"zsh" , "fish" , "bash" , "sh" , "login" }
4459 for _ , command := range commands {
4560 if _ , err := exec .LookPath (command ); err == nil {
@@ -48,3 +63,29 @@ func fetchAvailableStartupCommand(ctx context.Context) (string, error) {
4863 }
4964 return "" , fmt .Errorf ("no available startup command found, auto detect failed with zsh, fish, bash, sh, login" )
5065}
66+
67+ func prepareAsciinemaCommand (ctx context.Context , originalCommand string ) (string , string , error ) {
68+ // Lookup asciinema binary
69+ asciinema , err := lookupBinary (ctx , "asciinema" )
70+ if err != nil {
71+ // Best-effort: if asciinema is not available, just use the original command
72+ slog .Debug ("asciinema not available, proceeding without recording" , "error" , err )
73+ return originalCommand , "" , nil
74+ }
75+
76+ // Ensure recordings directory exists
77+ recordingsDir , err := ensureRecordingsDirectory ()
78+ if err != nil {
79+ slog .Warn ("failed to create recordings directory, proceeding without recording" , "error" , err )
80+ return originalCommand , "" , nil
81+ }
82+
83+ // Generate recording filename
84+ recordingFile := filepath .Join (recordingsDir , generateRecordingFilename ())
85+
86+ slog .Info ("recording session" , "file" , recordingFile )
87+
88+ // Use asciinema as the main command with -c flag to specify shell to record
89+ // Format: asciinema rec filename.cast -c shell_command
90+ return asciinema , fmt .Sprintf ("rec %s -c %s" , recordingFile , originalCommand ), nil
91+ }
0 commit comments