Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var (
noExitFlag bool
maxSteps int
streamFlag bool // Enable streaming output
compactMode bool // Enable compact output mode
scriptMCPConfig *config.Config // Used to override config in script mode

// Session management
Expand Down Expand Up @@ -166,6 +167,8 @@ func init() {
IntVar(&maxSteps, "max-steps", 0, "maximum number of agent steps (0 for unlimited)")
rootCmd.PersistentFlags().
BoolVar(&streamFlag, "stream", true, "enable streaming output for faster response display")
rootCmd.PersistentFlags().
BoolVar(&compactMode, "compact", false, "enable compact output mode without fancy styling")

// Session management flags
rootCmd.PersistentFlags().
Expand Down Expand Up @@ -196,6 +199,7 @@ func init() {
viper.BindPFlag("prompt", rootCmd.PersistentFlags().Lookup("prompt"))
viper.BindPFlag("max-steps", rootCmd.PersistentFlags().Lookup("max-steps"))
viper.BindPFlag("stream", rootCmd.PersistentFlags().Lookup("stream"))
viper.BindPFlag("compact", rootCmd.PersistentFlags().Lookup("compact"))
viper.BindPFlag("provider-url", rootCmd.PersistentFlags().Lookup("provider-url"))
viper.BindPFlag("provider-api-key", rootCmd.PersistentFlags().Lookup("provider-api-key"))
viper.BindPFlag("max-tokens", rootCmd.PersistentFlags().Lookup("max-tokens"))
Expand Down Expand Up @@ -301,7 +305,7 @@ func runNormalMode(ctx context.Context) error {

if strings.HasPrefix(viper.GetString("model"), "ollama:") && !quietFlag {
// Create a temporary CLI for the spinner
tempCli, tempErr := ui.NewCLI(viper.GetBool("debug"))
tempCli, tempErr := ui.NewCLI(viper.GetBool("debug"), viper.GetBool("compact"))
if tempErr == nil {
err = tempCli.ShowSpinner("Loading Ollama model...", func() error {
var agentErr error
Expand Down Expand Up @@ -336,10 +340,13 @@ func runNormalMode(ctx context.Context) error {
// Create CLI interface (skip if quiet mode)
var cli *ui.CLI
if !quietFlag {
cli, err = ui.NewCLI(viper.GetBool("debug"))
cli, err = ui.NewCLI(viper.GetBool("debug"), viper.GetBool("compact"))
if err != nil {
return fmt.Errorf("failed to create CLI: %v", err)
}

// Set the model name for consistent display
cli.SetModelName(modelName)

// Set up usage tracking for supported providers
if len(parts) == 2 {
Expand Down Expand Up @@ -660,6 +667,7 @@ func runAgenticStep(ctx context.Context, mcpAgent *agent.Agent, cli *ui.CLI, mes
if !streamingStarted {
cli.StartStreamingMessage(config.ModelName)
streamingStarted = true
streamingContent.Reset() // Reset content for new streaming session
}

// Accumulate content and update message
Expand Down Expand Up @@ -734,6 +742,9 @@ func runAgenticStep(ctx context.Context, mcpAgent *agent.Agent, cli *ui.CLI, mes
}

cli.DisplayToolMessage(toolName, toolArgs, resultContent, isError)
// Reset streaming state for next LLM call
responseWasStreamed = false
streamingStarted = false
// Start spinner again for next LLM call
currentSpinner = ui.NewSpinner("Thinking...")
currentSpinner.Start()
Expand Down
3 changes: 2 additions & 1 deletion cmd/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ func runScriptMode(ctx context.Context, mcpConfig *config.Config, prompt string,
}

finalDebug := viper.GetBool("debug") || mcpConfig.Debug
finalCompact := viper.GetBool("compact")
finalMaxSteps := viper.GetInt("max-steps")
if finalMaxSteps == 0 && mcpConfig.MaxSteps != 0 {
finalMaxSteps = mcpConfig.MaxSteps
Expand Down Expand Up @@ -582,7 +583,7 @@ func runScriptMode(ctx context.Context, mcpConfig *config.Config, prompt string,
// Create CLI interface (skip if quiet mode)
var cli *ui.CLI
if !quietFlag {
cli, err = ui.NewCLI(finalDebug)
cli, err = ui.NewCLI(finalDebug, finalCompact)
if err != nil {
return fmt.Errorf("failed to create CLI: %v", err)
}
Expand Down
117 changes: 97 additions & 20 deletions internal/ui/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,24 @@ var (
// CLI handles the command line interface with improved message rendering
type CLI struct {
messageRenderer *MessageRenderer
compactRenderer *CompactRenderer // Add compact renderer
messageContainer *MessageContainer
usageTracker *UsageTracker
width int
height int
compactMode bool // Add compact mode flag
modelName string // Store current model name
}

// NewCLI creates a new CLI instance with message container
func NewCLI(debug bool) (*CLI, error) {
cli := &CLI{}
func NewCLI(debug bool, compact bool) (*CLI, error) {
cli := &CLI{
compactMode: compact,
}
cli.updateSize()
cli.messageRenderer = NewMessageRenderer(cli.width, debug)
cli.messageContainer = NewMessageContainer(cli.width, cli.height-4) // Reserve space for input and help
cli.compactRenderer = NewCompactRenderer(cli.width, debug)
cli.messageContainer = NewMessageContainer(cli.width, cli.height-4, compact) // Pass compact mode

return cli, nil
}
Expand All @@ -45,6 +51,14 @@ func (c *CLI) SetUsageTracker(tracker *UsageTracker) {
}
}

// SetModelName sets the current model name for the CLI
func (c *CLI) SetModelName(modelName string) {
c.modelName = modelName
if c.messageContainer != nil {
c.messageContainer.SetModelName(modelName)
}
}

// GetPrompt gets user input using the huh library with divider and padding
func (c *CLI) GetPrompt() (string, error) {
// Usage info is now displayed immediately after responses via DisplayUsageAfterResponse()
Expand Down Expand Up @@ -97,9 +111,14 @@ func (c *CLI) ShowSpinner(message string, action func() error) error {
return err
}

// DisplayUserMessage displays the user's message using the new renderer
// DisplayUserMessage displays the user's message using the appropriate renderer
func (c *CLI) DisplayUserMessage(message string) {
msg := c.messageRenderer.RenderUserMessage(message, time.Now())
var msg UIMessage
if c.compactMode {
msg = c.compactRenderer.RenderUserMessage(message, time.Now())
} else {
msg = c.messageRenderer.RenderUserMessage(message, time.Now())
}
c.messageContainer.AddMessage(msg)
c.displayContainer()
}
Expand All @@ -111,15 +130,25 @@ func (c *CLI) DisplayAssistantMessage(message string) error {

// DisplayAssistantMessageWithModel displays the assistant's message with model info
func (c *CLI) DisplayAssistantMessageWithModel(message, modelName string) error {
msg := c.messageRenderer.RenderAssistantMessage(message, time.Now(), modelName)
var msg UIMessage
if c.compactMode {
msg = c.compactRenderer.RenderAssistantMessage(message, time.Now(), modelName)
} else {
msg = c.messageRenderer.RenderAssistantMessage(message, time.Now(), modelName)
}
c.messageContainer.AddMessage(msg)
c.displayContainer()
return nil
}

// DisplayToolCallMessage displays a tool call in progress
func (c *CLI) DisplayToolCallMessage(toolName, toolArgs string) {
msg := c.messageRenderer.RenderToolCallMessage(toolName, toolArgs, time.Now())
var msg UIMessage
if c.compactMode {
msg = c.compactRenderer.RenderToolCallMessage(toolName, toolArgs, time.Now())
} else {
msg = c.messageRenderer.RenderToolCallMessage(toolName, toolArgs, time.Now())
}

// Always display immediately - spinner management is handled externally
c.messageContainer.AddMessage(msg)
Expand All @@ -128,7 +157,12 @@ func (c *CLI) DisplayToolCallMessage(toolName, toolArgs string) {

// DisplayToolMessage displays a tool call message
func (c *CLI) DisplayToolMessage(toolName, toolArgs, toolResult string, isError bool) {
msg := c.messageRenderer.RenderToolMessage(toolName, toolArgs, toolResult, isError)
var msg UIMessage
if c.compactMode {
msg = c.compactRenderer.RenderToolMessage(toolName, toolArgs, toolResult, isError)
} else {
msg = c.messageRenderer.RenderToolMessage(toolName, toolArgs, toolResult, isError)
}

// Always display immediately - spinner management is handled externally
c.messageContainer.AddMessage(msg)
Expand All @@ -138,7 +172,12 @@ func (c *CLI) DisplayToolMessage(toolName, toolArgs, toolResult string, isError
// StartStreamingMessage starts a streaming assistant message
func (c *CLI) StartStreamingMessage(modelName string) {
// Add an empty assistant message that we'll update during streaming
msg := c.messageRenderer.RenderAssistantMessage("", time.Now(), modelName)
var msg UIMessage
if c.compactMode {
msg = c.compactRenderer.RenderAssistantMessage("", time.Now(), modelName)
} else {
msg = c.messageRenderer.RenderAssistantMessage("", time.Now(), modelName)
}
c.messageContainer.AddMessage(msg)
c.displayContainer()
}
Expand All @@ -150,30 +189,50 @@ func (c *CLI) UpdateStreamingMessage(content string) {
c.displayContainer()
}

// DisplayError displays an error message using the message component
// DisplayError displays an error message using the appropriate renderer
func (c *CLI) DisplayError(err error) {
msg := c.messageRenderer.RenderErrorMessage(err.Error(), time.Now())
var msg UIMessage
if c.compactMode {
msg = c.compactRenderer.RenderErrorMessage(err.Error(), time.Now())
} else {
msg = c.messageRenderer.RenderErrorMessage(err.Error(), time.Now())
}
c.messageContainer.AddMessage(msg)
c.displayContainer()
}

// DisplayInfo displays an informational message using the system message component
// DisplayInfo displays an informational message using the appropriate renderer
func (c *CLI) DisplayInfo(message string) {
msg := c.messageRenderer.RenderSystemMessage(message, time.Now())
var msg UIMessage
if c.compactMode {
msg = c.compactRenderer.RenderSystemMessage(message, time.Now())
} else {
msg = c.messageRenderer.RenderSystemMessage(message, time.Now())
}
c.messageContainer.AddMessage(msg)
c.displayContainer()
}

// DisplayCancellation displays a cancellation message
func (c *CLI) DisplayCancellation() {
msg := c.messageRenderer.RenderSystemMessage("Generation cancelled by user (ESC pressed)", time.Now())
var msg UIMessage
if c.compactMode {
msg = c.compactRenderer.RenderSystemMessage("Generation cancelled by user (ESC pressed)", time.Now())
} else {
msg = c.messageRenderer.RenderSystemMessage("Generation cancelled by user (ESC pressed)", time.Now())
}
c.messageContainer.AddMessage(msg)
c.displayContainer()
}

// DisplayDebugConfig displays configuration settings in debug mode using tool response block styling
// DisplayDebugConfig displays configuration settings using the appropriate renderer
func (c *CLI) DisplayDebugConfig(config map[string]any) {
msg := c.messageRenderer.RenderDebugConfigMessage(config, time.Now())
var msg UIMessage
if c.compactMode {
msg = c.compactRenderer.RenderDebugConfigMessage(config, time.Now())
} else {
msg = c.messageRenderer.RenderDebugConfigMessage(config, time.Now())
}
c.messageContainer.AddMessage(msg)
c.displayContainer()
}
Expand Down Expand Up @@ -242,15 +301,25 @@ func (c *CLI) DisplayServers(servers []string) {
// DisplayHistory displays conversation history using the message container
func (c *CLI) DisplayHistory(messages []*schema.Message) {
// Create a temporary container for history
historyContainer := NewMessageContainer(c.width, c.height-4)
historyContainer := NewMessageContainer(c.width, c.height-4, c.compactMode)

for _, msg := range messages {
switch msg.Role {
case schema.User:
uiMsg := c.messageRenderer.RenderUserMessage(msg.Content, time.Now())
var uiMsg UIMessage
if c.compactMode {
uiMsg = c.compactRenderer.RenderUserMessage(msg.Content, time.Now())
} else {
uiMsg = c.messageRenderer.RenderUserMessage(msg.Content, time.Now())
}
historyContainer.AddMessage(uiMsg)
case schema.Assistant:
uiMsg := c.messageRenderer.RenderAssistantMessage(msg.Content, time.Now(), "")
var uiMsg UIMessage
if c.compactMode {
uiMsg = c.compactRenderer.RenderAssistantMessage(msg.Content, time.Now(), c.modelName)
} else {
uiMsg = c.messageRenderer.RenderAssistantMessage(msg.Content, time.Now(), c.modelName)
}
historyContainer.AddMessage(uiMsg)
}
}
Expand Down Expand Up @@ -384,7 +453,12 @@ func (c *CLI) DisplayUsageStats() {
content.WriteString(fmt.Sprintf("**Session Total:** %d input + %d output tokens = $%.6f (%d requests)\n",
sessionStats.TotalInputTokens, sessionStats.TotalOutputTokens, sessionStats.TotalCost, sessionStats.RequestCount))

msg := c.messageRenderer.RenderSystemMessage(content.String(), time.Now())
var msg UIMessage
if c.compactMode {
msg = c.compactRenderer.RenderSystemMessage(content.String(), time.Now())
} else {
msg = c.messageRenderer.RenderSystemMessage(content.String(), time.Now())
}
c.messageContainer.AddMessage(msg)
c.displayContainer()
}
Expand Down Expand Up @@ -434,6 +508,9 @@ func (c *CLI) updateSize() {
if c.messageRenderer != nil {
c.messageRenderer.SetWidth(c.width)
}
if c.compactRenderer != nil {
c.compactRenderer.SetWidth(c.width)
}
if c.messageContainer != nil {
c.messageContainer.SetSize(c.width, c.height-4)
}
Expand Down
Loading