never executed always true always false
    1 module PureClaw.Channels.CLI
    2   ( -- * CLI channel
    3     mkCLIChannelHandle
    4   ) where
    5 
    6 import Control.Exception (bracket, bracket_)
    7 import Data.IORef
    8 import Data.Text (Text)
    9 import Data.Text qualified as T
   10 import Data.Text.IO qualified as TIO
   11 import System.IO
   12 
   13 import PureClaw.Core.Types
   14 import PureClaw.Handles.Channel
   15 
   16 -- | Create a channel handle that reads from stdin and writes to stdout.
   17 -- Receive prints a @>@ prompt and reads a line. EOF (Ctrl-D) causes
   18 -- an 'IOError' which the agent loop catches to exit cleanly.
   19 mkCLIChannelHandle :: ChannelHandle
   20 mkCLIChannelHandle = ChannelHandle
   21   { _ch_receive = do
   22       putStr "> "
   23       hFlush stdout
   24       line <- readUnbufferedLine
   25       pure IncomingMessage
   26         { _im_userId  = UserId "cli-user"
   27         , _im_content = line
   28         }
   29   , _ch_send = \msg -> do
   30       TIO.putStrLn ""
   31       TIO.putStrLn (_om_content msg)
   32       TIO.putStrLn ""
   33   , _ch_sendError = \err ->
   34       TIO.hPutStrLn stderr $ "Error: " <> T.pack (show err)
   35   , _ch_sendChunk = \case
   36       ChunkText t -> do
   37         TIO.putStr t
   38         hFlush stdout
   39       ChunkDone -> TIO.putStrLn ""
   40   , _ch_streaming = True
   41   , _ch_readSecret = bracket_
   42       (hSetEcho stdin False)
   43       (hSetEcho stdin True)
   44       readUnbufferedLine
   45   , _ch_prompt = \promptText -> do
   46       TIO.putStr promptText
   47       hFlush stdout
   48       readUnbufferedLine
   49   , _ch_promptSecret = \promptText -> do
   50       TIO.putStr promptText
   51       hFlush stdout
   52       bracket_ (hSetEcho stdin False) (hSetEcho stdin True) $ do
   53         line <- readUnbufferedLine
   54         TIO.putStrLn ""  -- newline after hidden input
   55         pure line
   56   }
   57 
   58 -- | Read a line from stdin bypassing the terminal's canonical mode line
   59 -- buffer (which is limited to ~1024 bytes on macOS). Temporarily switches
   60 -- stdin to 'NoBuffering' and reads character-by-character until newline.
   61 readUnbufferedLine :: IO Text
   62 readUnbufferedLine = do
   63   origBuf <- hGetBuffering stdin
   64   bracket
   65     (hSetBuffering stdin NoBuffering)
   66     (\_ -> hSetBuffering stdin origBuf)
   67     (\_ -> do
   68       ref <- newIORef []
   69       let go = do
   70             c <- getChar
   71             if c == '\n'
   72               then pure ()
   73               else modifyIORef ref (c :) >> go
   74       go
   75       chars <- readIORef ref
   76       pure (T.pack (reverse chars))
   77     )