2025-06-09 14:38:31 +03:00
|
|
|
package ui
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"time"
|
|
|
|
|
|
2026-02-25 17:07:09 +03:00
|
|
|
"charm.land/lipgloss/v2"
|
2025-06-09 14:38:31 +03:00
|
|
|
"golang.org/x/term"
|
2026-04-01 13:54:10 +03:00
|
|
|
|
|
|
|
|
"github.com/mark3labs/kit/internal/ui/style"
|
2025-06-09 14:38:31 +03:00
|
|
|
)
|
|
|
|
|
|
2026-02-26 16:59:59 +03:00
|
|
|
// CLI manages the command-line interface for KIT, providing message rendering,
|
2026-03-31 13:01:30 +03:00
|
|
|
// user input handling, and display management. It handles streaming responses,
|
|
|
|
|
// tracks token usage, and manages the overall conversation flow between the
|
|
|
|
|
// user and AI assistants.
|
2025-06-09 14:38:31 +03:00
|
|
|
type CLI struct {
|
2026-02-28 01:01:12 +03:00
|
|
|
renderer Renderer
|
|
|
|
|
usageTracker *UsageTracker
|
|
|
|
|
width int
|
|
|
|
|
debug bool
|
|
|
|
|
modelName string
|
2025-06-09 14:38:31 +03:00
|
|
|
}
|
|
|
|
|
|
2026-03-31 13:01:30 +03:00
|
|
|
// NewCLI creates and initializes a new CLI instance. The debug parameter enables
|
|
|
|
|
// debug message rendering. Returns an initialized CLI ready for interaction or an
|
2025-11-12 16:48:46 +03:00
|
|
|
// error if initialization fails.
|
2026-03-31 13:01:30 +03:00
|
|
|
func NewCLI(debug bool) (*CLI, error) {
|
2025-06-27 15:56:09 +03:00
|
|
|
cli := &CLI{
|
2026-03-31 13:01:30 +03:00
|
|
|
debug: debug,
|
2025-06-27 15:56:09 +03:00
|
|
|
}
|
2025-06-09 14:38:31 +03:00
|
|
|
cli.updateSize()
|
2026-03-31 13:01:30 +03:00
|
|
|
cli.renderer = newMessageRenderer(cli.width, debug)
|
2025-06-10 01:21:17 +03:00
|
|
|
|
2025-06-09 14:38:31 +03:00
|
|
|
return cli, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 16:48:46 +03:00
|
|
|
// SetUsageTracker attaches a usage tracker to the CLI for monitoring token
|
|
|
|
|
// consumption and costs. The tracker will be automatically updated with the
|
|
|
|
|
// current display width for proper rendering.
|
2025-06-18 14:05:30 +03:00
|
|
|
func (c *CLI) SetUsageTracker(tracker *UsageTracker) {
|
|
|
|
|
c.usageTracker = tracker
|
|
|
|
|
if c.usageTracker != nil {
|
|
|
|
|
c.usageTracker.SetWidth(c.width)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-26 01:45:24 +03:00
|
|
|
// GetUsageTracker returns the usage tracker attached to this CLI, or nil if no
|
|
|
|
|
// tracker has been configured. Callers that need a usage-tracker-agnostic handle
|
|
|
|
|
// can assign the returned *UsageTracker wherever an app.UsageUpdater is expected —
|
|
|
|
|
// *UsageTracker satisfies that interface.
|
|
|
|
|
func (c *CLI) GetUsageTracker() *UsageTracker {
|
|
|
|
|
return c.usageTracker
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 16:48:46 +03:00
|
|
|
// SetModelName updates the current AI model name being used in the conversation.
|
|
|
|
|
// This name is displayed in message headers to indicate which model is responding.
|
2025-06-27 15:56:09 +03:00
|
|
|
func (c *CLI) SetModelName(modelName string) {
|
|
|
|
|
c.modelName = modelName
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-26 12:24:17 +03:00
|
|
|
// ShowSpinner displays an animated spinner while executing the provided action
|
|
|
|
|
// function. The spinner automatically stops when the action completes. Returns
|
|
|
|
|
// any error returned by the action function.
|
|
|
|
|
func (c *CLI) ShowSpinner(action func() error) error {
|
|
|
|
|
spinner := NewSpinner()
|
2025-06-09 14:38:31 +03:00
|
|
|
spinner.Start()
|
2025-06-10 01:21:17 +03:00
|
|
|
|
2025-06-09 14:38:31 +03:00
|
|
|
err := action()
|
2025-06-10 01:21:17 +03:00
|
|
|
|
2025-06-09 14:38:31 +03:00
|
|
|
spinner.Stop()
|
2025-06-10 01:21:17 +03:00
|
|
|
|
2025-06-09 14:38:31 +03:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 16:48:46 +03:00
|
|
|
// DisplayUserMessage renders and displays a user's message with appropriate
|
|
|
|
|
// formatting based on the current display mode (standard or compact). The message
|
|
|
|
|
// is timestamped and styled according to the active theme.
|
2025-06-09 14:38:31 +03:00
|
|
|
func (c *CLI) DisplayUserMessage(message string) {
|
2026-02-28 01:01:12 +03:00
|
|
|
fmt.Println(c.renderer.RenderUserMessage(message, time.Now()).Content)
|
2025-06-09 14:38:31 +03:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 16:48:46 +03:00
|
|
|
// DisplayAssistantMessageWithModel renders and displays an AI assistant's response
|
|
|
|
|
// with the specified model name shown in the message header. The message is
|
|
|
|
|
// formatted according to the current display mode and includes timestamp information.
|
2025-06-09 14:38:31 +03:00
|
|
|
func (c *CLI) DisplayAssistantMessageWithModel(message, modelName string) error {
|
2026-02-28 01:01:12 +03:00
|
|
|
fmt.Println(c.renderer.RenderAssistantMessage(message, time.Now(), modelName).Content)
|
2025-06-09 14:38:31 +03:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 16:48:46 +03:00
|
|
|
// DisplayToolMessage renders and displays the complete result of a tool execution,
|
|
|
|
|
// including the tool name, arguments, and result. The isError parameter determines
|
|
|
|
|
// whether the result should be displayed as an error or success message.
|
2025-06-09 14:38:31 +03:00
|
|
|
func (c *CLI) DisplayToolMessage(toolName, toolArgs, toolResult string, isError bool) {
|
2026-02-28 01:01:12 +03:00
|
|
|
fmt.Println(c.renderer.RenderToolMessage(toolName, toolArgs, toolResult, isError).Content)
|
2025-06-09 14:38:31 +03:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 16:48:46 +03:00
|
|
|
// DisplayError renders and displays an error message with distinctive formatting
|
|
|
|
|
// to ensure visibility. The error is timestamped and styled according to the
|
|
|
|
|
// current display mode's error theme.
|
2025-06-09 14:38:31 +03:00
|
|
|
func (c *CLI) DisplayError(err error) {
|
2026-02-28 01:01:12 +03:00
|
|
|
fmt.Println(c.renderer.RenderErrorMessage(err.Error(), time.Now()).Content)
|
2025-06-09 14:38:31 +03:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 16:48:46 +03:00
|
|
|
// DisplayInfo renders and displays an informational system message. These messages
|
|
|
|
|
// are typically used for status updates, notifications, or other non-error system
|
|
|
|
|
// communications to the user.
|
2025-06-09 14:38:31 +03:00
|
|
|
func (c *CLI) DisplayInfo(message string) {
|
2026-02-28 01:01:12 +03:00
|
|
|
fmt.Println(c.renderer.RenderSystemMessage(message, time.Now()).Content)
|
2025-06-09 14:38:31 +03:00
|
|
|
}
|
|
|
|
|
|
2026-02-27 00:08:48 +03:00
|
|
|
// DisplayExtensionBlock renders a custom styled block with the given border
|
|
|
|
|
// color and optional subtitle. Used by extensions via ctx.PrintBlock.
|
|
|
|
|
func (c *CLI) DisplayExtensionBlock(text, borderColor, subtitle string) {
|
2026-04-01 13:54:10 +03:00
|
|
|
theme := style.GetTheme()
|
2026-02-27 00:08:48 +03:00
|
|
|
|
2026-03-19 18:04:56 +03:00
|
|
|
borderClr := theme.Info
|
2026-02-27 00:08:48 +03:00
|
|
|
if borderColor != "" {
|
|
|
|
|
borderClr = lipgloss.Color(borderColor)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
content := text
|
|
|
|
|
if subtitle != "" {
|
|
|
|
|
sub := lipgloss.NewStyle().Foreground(theme.VeryMuted).Render(" " + subtitle)
|
|
|
|
|
content = content + "\n" + sub
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rendered := renderContentBlock(
|
|
|
|
|
content,
|
2026-02-28 01:01:12 +03:00
|
|
|
c.width,
|
2026-02-27 00:08:48 +03:00
|
|
|
WithAlign(lipgloss.Left),
|
|
|
|
|
WithBorderColor(borderClr),
|
|
|
|
|
WithMarginBottom(1),
|
|
|
|
|
)
|
|
|
|
|
fmt.Println(rendered)
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 16:48:46 +03:00
|
|
|
// DisplayDebugMessage renders and displays a debug message if debug mode is enabled.
|
|
|
|
|
// Debug messages are formatted distinctively and only shown when the CLI is
|
|
|
|
|
// initialized with debug=true.
|
2025-08-08 09:32:04 +03:00
|
|
|
func (c *CLI) DisplayDebugMessage(message string) {
|
|
|
|
|
if !c.debug {
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-02-28 01:01:12 +03:00
|
|
|
fmt.Println(c.renderer.RenderDebugMessage(message, time.Now()).Content)
|
2025-06-18 10:29:47 +03:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 16:48:46 +03:00
|
|
|
// DisplayDebugConfig renders and displays configuration settings in a formatted
|
|
|
|
|
// debug message. The config parameter should contain key-value pairs representing
|
|
|
|
|
// configuration options that will be displayed for debugging purposes.
|
2025-06-11 11:45:55 +03:00
|
|
|
func (c *CLI) DisplayDebugConfig(config map[string]any) {
|
2026-02-28 01:01:12 +03:00
|
|
|
fmt.Println(c.renderer.RenderDebugConfigMessage(config, time.Now()).Content)
|
2025-06-09 14:38:31 +03:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 16:48:46 +03:00
|
|
|
// DisplayUsageAfterResponse renders and displays token usage information immediately
|
|
|
|
|
// following an AI response. This provides real-time feedback about the cost and
|
|
|
|
|
// token consumption of each interaction.
|
2025-06-27 11:33:44 +03:00
|
|
|
func (c *CLI) DisplayUsageAfterResponse() {
|
|
|
|
|
if c.usageTracker == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
usageInfo := c.usageTracker.RenderUsageInfo()
|
|
|
|
|
if usageInfo != "" {
|
|
|
|
|
paddedUsage := lipgloss.NewStyle().
|
|
|
|
|
PaddingLeft(2).
|
|
|
|
|
PaddingTop(1).
|
|
|
|
|
Render(usageInfo)
|
2026-02-26 00:07:27 +03:00
|
|
|
fmt.Println(paddedUsage)
|
2025-06-27 11:33:44 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-09 14:38:31 +03:00
|
|
|
// updateSize updates the CLI size based on terminal dimensions
|
|
|
|
|
func (c *CLI) updateSize() {
|
2026-02-28 01:01:12 +03:00
|
|
|
width, _, err := term.GetSize(int(os.Stdout.Fd()))
|
2025-06-09 14:38:31 +03:00
|
|
|
if err != nil {
|
2026-02-28 01:01:12 +03:00
|
|
|
c.width = 80 // Fallback width
|
2025-06-09 14:38:31 +03:00
|
|
|
return
|
|
|
|
|
}
|
2025-06-10 01:21:17 +03:00
|
|
|
|
2025-06-25 17:24:37 +03:00
|
|
|
// Add left and right padding (4 characters total: 2 on each side)
|
|
|
|
|
paddingTotal := 4
|
|
|
|
|
c.width = width - paddingTotal
|
2025-06-10 01:21:17 +03:00
|
|
|
|
2026-02-28 01:01:12 +03:00
|
|
|
// Update renderer if it exists
|
|
|
|
|
if c.renderer != nil {
|
|
|
|
|
c.renderer.SetWidth(c.width)
|
2025-06-09 14:38:31 +03:00
|
|
|
}
|
2025-06-18 14:05:30 +03:00
|
|
|
if c.usageTracker != nil {
|
|
|
|
|
c.usageTracker.SetWidth(c.width)
|
|
|
|
|
}
|
2025-06-09 14:38:31 +03:00
|
|
|
}
|