package shell import ( "io" "time" ) // EventCallback is called with a copy of data whenever the channel is read or written. // direction is 0 for input (client→server) and 1 for output (server→client). type EventCallback func(ts time.Time, direction int, data []byte) // RecordingChannel wraps an io.ReadWriteCloser and optionally invokes a callback // on every Read (input) and Write (output). Phase 2.3 will add byte-level // keystroke recording here without changing any shell code. type RecordingChannel struct { inner io.ReadWriteCloser callback EventCallback } // NewRecordingChannel returns a RecordingChannel wrapping rw. func NewRecordingChannel(rw io.ReadWriteCloser) *RecordingChannel { return &RecordingChannel{inner: rw} } // WithCallback sets the event callback and returns the RecordingChannel for chaining. func (r *RecordingChannel) WithCallback(cb EventCallback) *RecordingChannel { r.callback = cb return r } func (r *RecordingChannel) Read(p []byte) (int, error) { n, err := r.inner.Read(p) if n > 0 && r.callback != nil { cp := make([]byte, n) copy(cp, p[:n]) r.callback(time.Now(), 0, cp) } return n, err } func (r *RecordingChannel) Write(p []byte) (int, error) { n, err := r.inner.Write(p) if n > 0 && r.callback != nil { cp := make([]byte, n) copy(cp, p[:n]) r.callback(time.Now(), 1, cp) } return n, err } func (r *RecordingChannel) Close() error { return r.inner.Close() }