From 6f2803295f198d1d186c554ec41af424d3efe900 Mon Sep 17 00:00:00 2001 From: Ian Date: Wed, 7 Oct 2026 13:36:13 -0400 Subject: [PATCH] feat(tui): set the terminal title to the scan target --- strix/interface/tui/internal/app/model.go | 1 + strix/interface/tui/internal/app/wire.go | 27 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/strix/interface/tui/internal/app/model.go b/strix/interface/tui/internal/app/model.go index ee1e9100..bae3cb7e 100644 --- a/strix/interface/tui/internal/app/model.go +++ b/strix/interface/tui/internal/app/model.go @@ -90,6 +90,7 @@ type Model struct { client *Client width, height int snapshot protocol.Snapshot + windowTitle string input textarea.Model viewport viewport.Model viewportContent string diff --git a/strix/interface/tui/internal/app/wire.go b/strix/interface/tui/internal/app/wire.go index 1536110b..72f02a38 100644 --- a/strix/interface/tui/internal/app/wire.go +++ b/strix/interface/tui/internal/app/wire.go @@ -2,10 +2,13 @@ package app import ( "encoding/json" + "fmt" + "os" "strings" "time" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/x/ansi" "github.com/usestrix/strix/tui/internal/protocol" "github.com/usestrix/strix/tui/internal/render" ) @@ -56,6 +59,10 @@ func (m *Model) handleEnvelope(envelope protocol.Envelope) tea.Cmd { // resize (not just refresh): status-row visibility changes the chat height. m.resizeViewport() m.resizeVulnerabilityViewport() + if title := windowTitle(m.snapshot.Targets); title != m.windowTitle { + m.windowTitle = title + return setTerminalTitle(title) + } case "collection_bootstrap": return m.handleCollectionBootstrap(envelope.Payload) case "collection_delta": @@ -118,6 +125,26 @@ func (m *Model) handleEnvelope(envelope protocol.Envelope) tea.Cmd { return nil } +// tea.SetWindowTitle only sends OSC 2 (window title); iTerm tabs show the OSC 1 +// icon name, which shells usually set, so send OSC 0 to set both. +func setTerminalTitle(title string) tea.Cmd { + return func() tea.Msg { + _, _ = os.Stdout.WriteString(ansi.SetIconNameWindowTitle(title)) + return nil + } +} + +func windowTitle(targets []string) string { + switch len(targets) { + case 0: + return "strix" + case 1: + return targets[0] + default: + return fmt.Sprintf("%s +%d", targets[0], len(targets)-1) + } +} + func (m *Model) consumeMessages(messages []protocol.Message, setupMode bool) { if m.seenMessages == nil { m.seenMessages = map[string]bool{}