Skip to content
Open
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
34 changes: 26 additions & 8 deletions pkg/tui/components/statusbar/statusbar.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import (

"github.com/docker/docker-agent/pkg/tui/core"
"github.com/docker/docker-agent/pkg/tui/styles"
"github.com/docker/docker-agent/pkg/version"
)

// StatusBar displays key-binding help on the left and version info on the right.
// When the tab bar is hidden (single tab), it also shows a clickable "+ new tab" button.
type StatusBar struct {
width int
help core.KeyMapHelp
title string

showNewTab bool
newTabStartX int
Expand All @@ -25,12 +25,31 @@ type StatusBar struct {
cacheDirty bool
}

// Option is a functional option for configuring a StatusBar.
type Option func(*StatusBar)

// WithTitle sets a custom title for the status bar.
//
// If not provided, defaults to "docker agent".
func WithTitle(title string) Option {
return func(s *StatusBar) {
s.title = title
}
}

// New creates a new StatusBar instance
func New(help core.KeyMapHelp) StatusBar {
return StatusBar{
func New(help core.KeyMapHelp, opts ...Option) StatusBar {
s := StatusBar{
help: help,
title: "docker agent",
cacheDirty: true,
}

for _, opt := range opts {
opt(&s)
}

return s
}

// SetWidth sets the width of the status bar
Expand Down Expand Up @@ -76,19 +95,18 @@ func (s *StatusBar) rebuild() {
s.newTabStartX = 0
s.newTabEndX = 0

// Build the styled right side: optional new-tab button + version.
var right string
// Build the styled right side: optional new-tab button + title.
var rightW, newTabW int
ver := styles.MutedStyle.Render("docker agent " + version.Version)
right := styles.MutedStyle.Render(s.title)

if s.showNewTab {
newTab := styles.MutedStyle.Render(" \u2502 ") +
styles.HighlightWhiteStyle.Render("+") +
styles.SecondaryStyle.Render(" new tab")
newTabW = lipgloss.Width(newTab)
right = newTab + " " + ver
right = newTab + " " + right
rightW = lipgloss.Width(right)
} else {
right = ver
rightW = lipgloss.Width(right)
}

Expand Down
10 changes: 6 additions & 4 deletions pkg/tui/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -595,23 +595,25 @@ func (m *appModel) handleElicitationResponse(action tools.ElicitationAction, con
}

func (m *appModel) startShell() (tea.Model, tea.Cmd) {
exitMsg := "Type 'exit' to return to " + m.appName

var cmd *exec.Cmd
if goruntime.GOOS == "windows" {
if path, err := exec.LookPath("pwsh.exe"); err == nil {
cmd = exec.Command(path, "-NoLogo", "-NoExit", "-Command",
`Write-Host ""; Write-Host "Type 'exit' to return to docker agent 🐳"`)
`Write-Host ""; Write-Host "`+exitMsg+`"`)
} else if path, err := exec.LookPath("powershell.exe"); err == nil {
cmd = exec.Command(path, "-NoLogo", "-NoExit", "-Command",
`Write-Host ""; Write-Host "Type 'exit' to return to docker agent 🐳"`)
`Write-Host ""; Write-Host "`+exitMsg+`"`)
} else {
// Use absolute path to cmd.exe to prevent PATH hijacking (CWE-426).
shell := shellpath.WindowsCmdExe()
cmd = exec.Command(shell, "/K", `echo. & echo Type 'exit' to return to docker agent`)
cmd = exec.Command(shell, "/K", "echo. & echo "+exitMsg)
}
} else {
shell := shellpath.DetectUnixShell()
cmd = exec.Command(shell, "-i", "-c",
`echo -e "\nType 'exit' to return to docker agent 🐳"; exec `+shell)
`echo -e "\n`+exitMsg+`"; exec `+shell)
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
Expand Down
30 changes: 27 additions & 3 deletions pkg/tui/tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
"github.com/docker/docker-agent/pkg/tui/service/tuistate"
"github.com/docker/docker-agent/pkg/tui/styles"
"github.com/docker/docker-agent/pkg/userconfig"
"github.com/docker/docker-agent/pkg/version"
)

// SessionSpawner creates new sessions with their own runtime.
Expand Down Expand Up @@ -163,6 +164,9 @@ type appModel struct {

// buildCommandCategories is a function that returns the list of command categories.
buildCommandCategories func(context.Context, tea.Model) []commands.Category

appName string
appVersion string
}

// Option configures the TUI.
Expand All @@ -176,6 +180,24 @@ func WithLeanMode() Option {
}
}

// WithAppName sets the application name.
//
// If not provided, defaults to "docker agent".
func WithAppName(name string) Option {
return func(m *appModel) {
m.appName = name
}
}

// WithVersion sets the application version.
//
// If not provided, defaults to version.Version.
func WithVersion(v string) Option {
return func(m *appModel) {
m.appVersion = v
}
}

// WithCommandBuilder builds the command categories shown in the command
// palette from the given function. It overrides the default command category
// builder. To include the default commands, the given function should call
Expand Down Expand Up @@ -242,6 +264,8 @@ func New(ctx context.Context, spawner SessionSpawner, initialApp *app.App, initi
focusedPanel: PanelEditor,
editorLines: 3,
dockerDesktop: os.Getenv("TERM_PROGRAM") == "docker_desktop",
appName: "docker agent",
appVersion: version.Version,
}

// Apply options
Expand All @@ -260,7 +284,7 @@ func New(ctx context.Context, spawner SessionSpawner, initialApp *app.App, initi
m.chatPage = initialChatPage

// Initialize status bar (pass m as help provider)
m.statusBar = statusbar.New(m)
m.statusBar = statusbar.New(m, statusbar.WithTitle(m.appName+" "+m.appVersion))

// Add the initial session to the supervisor
sv.AddSession(ctx, initialApp, initialApp.Session(), initialWorkingDir, cleanup)
Expand Down Expand Up @@ -2320,9 +2344,9 @@ func (m *appModel) View() tea.View {
// When the agent is working, a rotating spinner character is prepended so that
// terminal multiplexers (tmux) can detect activity in the pane.
func (m *appModel) windowTitle() string {
title := "docker agent"
title := m.appName
if sessionTitle := m.sessionState.SessionTitle(); sessionTitle != "" {
title = sessionTitle + " - docker agent"
title = sessionTitle + " - " + m.appName
}
if m.chatPage.IsWorking() {
title = spinner.Frame(m.animFrame) + " " + title
Expand Down
Loading