diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go
index 187bfa0d5..0caf8d58e 100644
--- a/pkg/cmdrunner/cmdrunner.go
+++ b/pkg/cmdrunner/cmdrunner.go
@@ -565,7 +565,6 @@ func RemoteShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s
 	return sstore.ModelUpdate{
 		Info: &sstore.InfoMsgType{
 			InfoTitle:   fmt.Sprintf("show remote [%s] info", ids.Remote.DisplayName),
-			InfoLines:   splitLinesForInfo(buf.String()),
 			PtyRemoteId: state.RemoteId,
 		},
 	}, nil
diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go
index 38b8b3772..bc2aad142 100644
--- a/pkg/remote/remote.go
+++ b/pkg/remote/remote.go
@@ -23,6 +23,7 @@ import (
 	"github.com/scripthaus-dev/mshell/pkg/base"
 	"github.com/scripthaus-dev/mshell/pkg/packet"
 	"github.com/scripthaus-dev/mshell/pkg/shexec"
+	"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
 	"github.com/scripthaus-dev/sh2-server/pkg/sstore"
 )
 
@@ -30,7 +31,7 @@ const RemoteTypeMShell = "mshell"
 const DefaultTerm = "xterm-256color"
 const DefaultMaxPtySize = 1024 * 1024
 const CircBufSize = 64 * 1024
-const RemoteTermRows = 10
+const RemoteTermRows = 8
 const RemoteTermCols = 80
 const PtyReadBufSize = 100
 
@@ -440,6 +441,29 @@ func MakeMShell(r *sstore.RemoteType) *MShellProc {
 	return rtn
 }
 
+func SendRemoteInput(pk *scpacket.RemoteInputPacketType) error {
+	data, err := base64.StdEncoding.DecodeString(pk.InputData64)
+	if err != nil {
+		return fmt.Errorf("cannot decode base64: %v\n", err)
+	}
+	msh := GetRemoteById(pk.RemoteId)
+	if msh == nil {
+		return fmt.Errorf("remote not found")
+	}
+	var cmdPty *os.File
+	msh.WithLock(func() {
+		cmdPty = msh.ControllingPty
+	})
+	if cmdPty == nil {
+		return fmt.Errorf("remote has no attached pty")
+	}
+	_, err = cmdPty.Write(data)
+	if err != nil {
+		return fmt.Errorf("writing to pty: %v", err)
+	}
+	return nil
+}
+
 func convertSSHOpts(opts *sstore.SSHOpts) shexec.SSHOpts {
 	if opts == nil || opts.Local {
 		opts = &sstore.SSHOpts{}
@@ -521,9 +545,9 @@ func (msh *MShellProc) writeToPtyBuffer_nolock(strFmt string, args ...interface{
 			realStr = realStr + "\r\n"
 		}
 		if strings.HasPrefix(realStr, "*") {
-			realStr = "\033[0m\033[31m[scripthaus]\033[0m " + realStr[1:]
+			realStr = "\033[0m\033[31mscripthaus>\033[0m " + realStr[1:]
 		} else {
-			realStr = "\033[0m\033[32m[scripthaus]\033[0m " + realStr
+			realStr = "\033[0m\033[32mscripthaus>\033[0m " + realStr
 		}
 		barr := msh.PtyBuffer.Bytes()
 		if len(barr) > 0 && barr[len(barr)-1] != '\n' {
@@ -592,6 +616,8 @@ func (msh *MShellProc) Launch() {
 	}()
 	if remoteName == "test2" {
 		go func() {
+			return
+
 			time.Sleep(2 * time.Second)
 			cmdPty.Write([]byte(Test2Pw))
 			msh.WriteToPtyBuffer("~[password sent]\r\n")
diff --git a/pkg/scpacket/scpacket.go b/pkg/scpacket/scpacket.go
index 73c056971..aab8a9e31 100644
--- a/pkg/scpacket/scpacket.go
+++ b/pkg/scpacket/scpacket.go
@@ -11,6 +11,7 @@ import (
 const FeCommandPacketStr = "fecmd"
 const WatchScreenPacketStr = "watchscreen"
 const FeInputPacketStr = "feinput"
+const RemoteInputPacketStr = "remoteinput"
 
 type FeCommandPacketType struct {
 	Type        string            `json:"type"`
@@ -39,6 +40,12 @@ type FeInputPacketType struct {
 	WinSize     *packet.WinSize      `json:"winsize,omitempty"`
 }
 
+type RemoteInputPacketType struct {
+	Type        string `json:"type"`
+	RemoteId    string `json:"remoteid"`
+	InputData64 string `json:"inputdata64"`
+}
+
 type WatchScreenPacketType struct {
 	Type      string `json:"type"`
 	SessionId string `json:"sessionid"`
@@ -50,6 +57,7 @@ func init() {
 	packet.RegisterPacketType(FeCommandPacketStr, reflect.TypeOf(FeCommandPacketType{}))
 	packet.RegisterPacketType(WatchScreenPacketStr, reflect.TypeOf(WatchScreenPacketType{}))
 	packet.RegisterPacketType(FeInputPacketStr, reflect.TypeOf(FeInputPacketType{}))
+	packet.RegisterPacketType(RemoteInputPacketStr, reflect.TypeOf(RemoteInputPacketType{}))
 }
 
 func (*FeCommandPacketType) GetType() string {
@@ -75,3 +83,11 @@ func (*WatchScreenPacketType) GetType() string {
 func MakeWatchScreenPacket() *WatchScreenPacketType {
 	return &WatchScreenPacketType{Type: WatchScreenPacketStr}
 }
+
+func MakeRemoteInputPacket() *RemoteInputPacketType {
+	return &RemoteInputPacketType{Type: RemoteInputPacketStr}
+}
+
+func (*RemoteInputPacketType) GetType() string {
+	return RemoteInputPacketStr
+}
diff --git a/pkg/scws/scws.go b/pkg/scws/scws.go
index 7326aa4b4..71e91c92a 100644
--- a/pkg/scws/scws.go
+++ b/pkg/scws/scws.go
@@ -178,7 +178,7 @@ func (ws *WSState) RunWSRead() {
 			fmt.Printf("error unmarshalling ws message: %v\n", err)
 			continue
 		}
-		if pk.GetType() == "feinput" {
+		if pk.GetType() == scpacket.FeInputPacketStr {
 			feInputPk := pk.(*scpacket.FeInputPacketType)
 			if feInputPk.Remote.OwnerId != "" {
 				fmt.Printf("[error] cannot send input to remote with ownerid\n")
@@ -189,6 +189,7 @@ func (ws *WSState) RunWSRead() {
 				continue
 			}
 			go func() {
+				// TODO enforce a strong ordering (channel with list)
 				err = sendCmdInput(feInputPk)
 				if err != nil {
 					fmt.Printf("[error] sending command input: %v\n", err)
@@ -196,7 +197,7 @@ func (ws *WSState) RunWSRead() {
 			}()
 			continue
 		}
-		if pk.GetType() == "watchscreen" {
+		if pk.GetType() == scpacket.WatchScreenPacketStr {
 			wsPk := pk.(*scpacket.WatchScreenPacketType)
 			err := ws.handleWatchScreen(wsPk)
 			if err != nil {
@@ -205,6 +206,20 @@ func (ws *WSState) RunWSRead() {
 			}
 			continue
 		}
+		if pk.GetType() == scpacket.RemoteInputPacketStr {
+			inputPk := pk.(*scpacket.RemoteInputPacketType)
+			if inputPk.RemoteId == "" {
+				fmt.Printf("[error] invalid remoteinput packet, remoteid is not set\n")
+				continue
+			}
+			go func() {
+				err = remote.SendRemoteInput(inputPk)
+				if err != nil {
+					fmt.Printf("[error] processing remote input: %v\n", err)
+				}
+			}()
+			continue
+		}
 		fmt.Printf("got ws bad message: %v\n", pk.GetType())
 	}
 }