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
39 changes: 29 additions & 10 deletions cmd/claws/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ func main() {
}
cfg.SetReadOnly(opts.readOnly)

var compactHeader bool
if opts.compactHeader != nil {
compactHeader = *opts.compactHeader
} else {
compactHeader = fileCfg.GetCompactHeader()
}
cfg.SetCompactHeader(compactHeader)

for _, p := range opts.profiles {
if !config.IsValidProfileName(p) {
fmt.Fprintf(os.Stderr, "Error: invalid profile name: %s\n", p)
Expand Down Expand Up @@ -116,16 +124,17 @@ func main() {
}

type cliOptions struct {
profiles []string
regions []string
readOnly bool
envCreds bool
autosave *bool
logFile string
configFile string
service string
resourceID string
theme string
profiles []string
regions []string
readOnly bool
envCreds bool
autosave *bool
logFile string
configFile string
service string
resourceID string
theme string
compactHeader *bool
}

// parseFlags parses command line flags and returns options
Expand Down Expand Up @@ -194,6 +203,12 @@ func parseFlagsFromArgs(args []string) cliOptions {
i++
opts.theme = args[i]
}
case "--compact":
t := true
opts.compactHeader = &t
case "--no-compact":
f := false
opts.compactHeader = &f
case "-h", "--help":
showHelp = true
case "-v", "--version":
Expand Down Expand Up @@ -245,6 +260,10 @@ func printUsage() {
fmt.Println(" Enable debug logging to specified file")
fmt.Println(" -t, --theme <name>")
fmt.Println(" Color theme: dark, light, nord, dracula, gruvbox, catppuccin")
fmt.Println(" --compact")
fmt.Println(" Start with compact header mode (toggle with Ctrl+E)")
fmt.Println(" --no-compact")
fmt.Println(" Disable compact header (overrides config file)")
fmt.Println(" -v, --version")
fmt.Println(" Show version")
fmt.Println(" -h, --help")
Expand Down
6 changes: 4 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ cloudwatch:
window: 15m # Metrics data window period (default: 15m)

autosave:
enabled: true # Save region/profile/theme on change (default: false)
enabled: true # Save region/profile/theme/compact_header on change (default: false)

compact_header: false # Use single-line compact header (default: false)

startup: # Applied on launch if present
view: services # Startup view: "dashboard", "services", or "service/resource" (e.g., "ec2", "rds/snapshots")
Expand Down Expand Up @@ -84,7 +86,7 @@ theme: nord # Preset: dark, light, nord, dracula, gruvbox, catppuc

The config file is **not created automatically**. Create it manually if needed.

CLI flags (`-p`, `-r`, `-t`, `--autosave`, `--no-autosave`) override config file settings.
CLI flags (`-p`, `-r`, `-t`, `--compact`, `--no-compact`, `--autosave`, `--no-autosave`) override config file settings.
Multiple values supported: `-p dev,prod` or `-p dev -p prod`.

### Special Profile IDs
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Complete reference for all keyboard shortcuts in claws.
| `:services` | Go to service browser |
| `/` | Filter mode (fuzzy search) |
| `A` | AI Chat (Bedrock) |
| `Ctrl+E` | Toggle compact header |
| `?` | Show help |

## Resource Browser
Expand Down Expand Up @@ -63,6 +64,7 @@ Complete reference for all keyboard shortcuts in claws.
| `:diff <n1> <n2>` | Compare two named resources |
| `:theme <name>` | Change color theme |
| `:autosave on/off` | Enable/disable config autosave |
| `:settings` | Show current settings |
| `:clear-history` | Clear navigation history (stack) |

## Mouse Support
Expand Down
48 changes: 36 additions & 12 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,15 @@ func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
return a, nil

case view.CompactHeaderChangedMsg:
if a.currentView != nil {
a.currentView.Update(msg)
}
for _, v := range a.viewStack {
v.Update(msg)
}
return a, nil

case view.ThemeChangeMsg:
theme := ui.GetPreset(msg.Name)
if theme == nil {
Expand Down Expand Up @@ -340,6 +349,16 @@ func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
chatOverlay.Init(),
a.modal.SetSize(a.width, a.height),
)

case key.Matches(msg, a.keys.CompactHeader):
compact := !config.Global().CompactHeader()
config.Global().SetCompactHeader(compact)
if config.File().PersistenceEnabled() {
if err := config.File().SaveCompactHeader(compact); err != nil {
log.Warn("failed to persist compact header", "error", err)
}
}
return a, func() tea.Msg { return view.CompactHeaderChangedMsg{} }
}

case view.ShowModalMsg:
Expand Down Expand Up @@ -504,7 +523,7 @@ func (a *App) View() tea.View {

var statusContent string
if a.commandMode {
statusContent = a.commandInput.View() + " • Esc:cancel Enter:run Tab:complete"
statusContent = a.commandInput.View() + ui.DimStyle().Render(" • Esc:cancel Enter:run Tab:complete")
} else {
if a.err != nil {
statusContent = ui.DangerStyle().Render("Error: " + a.err.Error())
Expand Down Expand Up @@ -774,17 +793,18 @@ func (a *App) refreshCurrentView() (tea.Model, tea.Cmd) {
}

type keyMap struct {
Up key.Binding
Down key.Binding
Enter key.Binding
Back key.Binding
Filter key.Binding
Command key.Binding
Region key.Binding
Profile key.Binding
AI key.Binding
Help key.Binding
Quit key.Binding
Up key.Binding
Down key.Binding
Enter key.Binding
Back key.Binding
Filter key.Binding
Command key.Binding
Region key.Binding
Profile key.Binding
AI key.Binding
CompactHeader key.Binding
Help key.Binding
Quit key.Binding
}

func defaultKeyMap() keyMap {
Expand Down Expand Up @@ -825,6 +845,10 @@ func defaultKeyMap() keyMap {
key.WithKeys("A"),
key.WithHelp("A", "ai chat"),
),
CompactHeader: key.NewBinding(
key.WithKeys("ctrl+e"),
key.WithHelp("ctrl+e", "compact header"),
),
Help: key.NewBinding(
key.WithKeys("?"),
key.WithHelp("?", "help"),
Expand Down
21 changes: 15 additions & 6 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,13 @@ func (s ProfileSelection) ID() string {
}

type Config struct {
mu sync.RWMutex
regions []string
selections []ProfileSelection
accountIDs map[string]string
warnings []string
readOnly bool
mu sync.RWMutex
regions []string
selections []ProfileSelection
accountIDs map[string]string
warnings []string
readOnly bool
compactHeader bool
}

var (
Expand Down Expand Up @@ -346,6 +347,14 @@ func (c *Config) SetReadOnly(readOnly bool) {
doWithLock(&c.mu, func() { c.readOnly = readOnly })
}

func (c *Config) CompactHeader() bool {
return withRLock(&c.mu, func() bool { return c.compactHeader })
}

func (c *Config) SetCompactHeader(compact bool) {
doWithLock(&c.mu, func() { c.compactHeader = compact })
}

func (c *Config) AddWarning(msg string) {
doWithLock(&c.mu, func() { c.warnings = append(c.warnings, msg) })
}
21 changes: 21 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,27 @@ func TestConfig_ReadOnlyGetSet(t *testing.T) {
}
}

func TestConfig_CompactHeaderGetSet(t *testing.T) {
cfg := &Config{}

// Initial value should be false
if cfg.CompactHeader() {
t.Error("CompactHeader() = true, want false")
}

// Set to true
cfg.SetCompactHeader(true)
if !cfg.CompactHeader() {
t.Error("CompactHeader() = false, want true")
}

// Set back to false
cfg.SetCompactHeader(false)
if cfg.CompactHeader() {
t.Error("CompactHeader() = true, want false")
}
}

func TestConfig_Warnings(t *testing.T) {
cfg := &Config{}

Expand Down
18 changes: 18 additions & 0 deletions internal/config/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ type FileConfig struct {
Theme ThemeConfig `yaml:"theme,omitempty"`
Navigation NavigationConfig `yaml:"navigation,omitempty"`
AI AIConfig `yaml:"ai,omitempty"`
CompactHeader bool `yaml:"compact_header,omitempty"`
}

// Duration wraps time.Duration for YAML marshal/unmarshal as string (e.g., "5s", "30s")
Expand Down Expand Up @@ -593,6 +594,23 @@ func (c *FileConfig) SavePersistence(enabled bool) error {
})
}

func (c *FileConfig) GetCompactHeader() bool {
return withRLock(&c.mu, func() bool {
return c.CompactHeader
})
}

func (c *FileConfig) SaveCompactHeader(compact bool) error {
c.mu.Lock()
defer c.mu.Unlock()

c.CompactHeader = compact

return c.patchConfigLocked(func(mapping *yaml.Node) {
setBoolValue(mapping, "compact_header", compact)
})
}

func (c *FileConfig) patchConfigLocked(patchFn func(mapping *yaml.Node)) error {
path, err := ConfigPath()
if err != nil {
Expand Down
15 changes: 15 additions & 0 deletions internal/ui/theme.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"sync"

"charm.land/bubbles/v2/spinner"
"charm.land/bubbles/v2/textinput"
"charm.land/lipgloss/v2"

"github.com/clawscli/claws/internal/config"
Expand Down Expand Up @@ -472,3 +473,17 @@ func NewSpinner() spinner.Model {
s.Style = lipgloss.NewStyle().Foreground(Current().Accent)
return s
}

func TextInputStyles() textinput.Styles {
t := Current()
state := textinput.StyleState{
Text: lipgloss.NewStyle().Foreground(t.Text),
Placeholder: lipgloss.NewStyle().Foreground(t.TextDim),
Suggestion: lipgloss.NewStyle().Foreground(t.TextDim),
Prompt: lipgloss.NewStyle().Foreground(t.Text),
}
return textinput.Styles{
Focused: state,
Blurred: state,
}
}
6 changes: 4 additions & 2 deletions internal/view/command_input.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func newCommandInputStyles() commandInputStyles {
input: ui.InputFieldStyle(),
suggestion: ui.DimStyle(),
highlight: ui.HighlightStyle(),
alias: ui.NoStyle(), // Normal text, not dimmed
alias: ui.TextStyle(),
}
}

Expand Down Expand Up @@ -80,6 +80,7 @@ func NewCommandInput(ctx context.Context, reg *registry.Registry) *CommandInput
ti.Prompt = ":"
ti.CharLimit = 150
ti.SetWidth(commandInputWidth1)
ti.SetStyles(ui.TextInputStyles())

return &CommandInput{
ctx: ctx,
Expand All @@ -101,6 +102,7 @@ func (c *CommandInput) Activate() tea.Cmd {

func (c *CommandInput) ReloadStyles() {
c.styles = newCommandInputStyles()
c.textInput.SetStyles(ui.TextInputStyles())
}

// Deactivate deactivates command mode
Expand Down Expand Up @@ -314,7 +316,7 @@ func (c *CommandInput) View() string {
if len(c.suggestions) > maxShow+1 {
suggText += " ..."
}
suggView = s.alias.Render(suggText) // alias = NoStyle (white)
suggView = s.alias.Render(suggText)
}
}

Expand Down
3 changes: 3 additions & 0 deletions internal/view/dashboard_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ func (d *DashboardView) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
d.styles = newDashboardStyles()
d.headerPanel.ReloadStyles()
return d, nil
case CompactHeaderChangedMsg:
d.lastHeaderHeight = 0 // force hitAreas rebuild
return d, nil

case tea.MouseClickMsg:
return d.handleMouseClick(msg)
Expand Down
Loading