@@ -5,16 +5,21 @@ import (
55 "errors"
66 "fmt"
77 "io"
8+ "os"
9+ "os/signal"
810 "path/filepath"
911 "strings"
12+ "syscall"
1013 "time"
1114
1215 "github.com/chainreactors/IoM-go/client"
1316 "github.com/chainreactors/IoM-go/consts"
17+ "github.com/chainreactors/logs"
1418 "github.com/chainreactors/malice-network/client/repl"
1519 "github.com/reeflective/console"
1620 "github.com/spf13/cobra"
1721 "golang.org/x/exp/slices"
22+ "golang.org/x/term"
1823 "google.golang.org/grpc/metadata"
1924
2025 "github.com/chainreactors/malice-network/client/assets"
@@ -123,16 +128,27 @@ func (c *Console) Start(bindCmds ...BindCmds) error {
123128 c .InitLocalRPCServer ()
124129 }
125130
131+ // Initialize active menu BEFORE headless check.
132+ // MCP/LocalRPC depend on ActiveMenu() returning the correct menu
133+ // (RunCommand calls con.App.Execute(ctx, con.App.ActiveMenu(), args, false)).
126134 if c .Session == nil {
127135 c .App .SwitchMenu (consts .ClientMenu )
128136 } else {
129137 c .SwitchImplant (c .GetInteractive (), consts .CalleeCMD )
130138 }
131- err := c .App .Start ()
132- if err != nil {
133- return err
139+
140+ // Headless mode: stdin is not a terminal (e.g., launched by GUI with /dev/null).
141+ // Skip readline loop to avoid busy-spin on MakeRaw(ENOTTY), block on signal instead.
142+ if ! term .IsTerminal (int (os .Stdin .Fd ())) {
143+ logs .Log .Importantf ("running in headless mode (no terminal detected), waiting for signal..." )
144+ sig := make (chan os.Signal , 1 )
145+ signal .Notify (sig , syscall .SIGINT , syscall .SIGTERM )
146+ <- sig
147+ logs .Log .Importantf ("received exit signal, shutting down" )
148+ return nil
134149 }
135- return nil
150+
151+ return c .App .Start ()
136152}
137153
138154func (c * Console ) Context () context.Context {
0 commit comments