detached baba7f3

fix(tui): migrate callers to canonical color helper

commit
baba7f354b66aeae01627fe871a0f182f599fd47
author
brice · · 2026-08-05 15:18 UTC
parent
add512e
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
33 files · +274 -274
M packages/agent/modes/btw_dialog.go +9 -9
@@ -383,7 +383,7 @@ func (d *btwDialog) Render(th tui.Theme, width int) []string {
383383 out = append(out, frameHeaderColor(th, "btw - side chat (esc closes; nothing is added to the main thread)", width, th.Accent))
384384
385385 if len(d.turns) == 0 && !d.loading {
386- out = append(out, " "+th.FG256(th.Muted, "ask anything; replies stay private to this side chat."))
386+ out = append(out, " "+th.FGColor(th.Muted, "ask anything; replies stay private to this side chat."))
387387 }
388388
389389 for _, t := range d.turns {
@@ -402,7 +402,7 @@ func (d *btwDialog) Render(th tui.Theme, width int) []string {
402402 out = append(out, renderDialogMarkdownRows(t.Assistant, th, width)...)
403403 }
404404 if t.Err != "" {
405- out = append(out, wrapDialogTextRows(th.FG256(th.Error, "✖ "+t.Err), width)...)
405+ out = append(out, wrapDialogTextRows(th.FGColor(th.Error, "✖ "+t.Err), width)...)
406406 }
407407 }
408408
@@ -412,11 +412,11 @@ func (d *btwDialog) Render(th tui.Theme, width int) []string {
412412 // rotating funny-line, elapsed seconds, then a muted hint
413413 // that esc cancels.
414414 prefix := fmt.Sprintf("%s %s - %s",
415- th.FG256(th.Assistant, d.spin.Frame()),
416- th.FG256(th.Assistant, d.spin.Message()),
417- th.FG256(th.Muted, d.spin.Elapsed().String()),
415+ th.FGColor(th.Assistant, d.spin.Frame()),
416+ th.FGColor(th.Assistant, d.spin.Message()),
417+ th.FGColor(th.Muted, d.spin.Elapsed().String()),
418418 )
419- out = append(out, " "+prefix+" "+th.FG256(th.Muted, "(esc cancels)"))
419+ out = append(out, " "+prefix+" "+th.FGColor(th.Muted, "(esc cancels)"))
420420 }
421421
422422 out = append(out, "")
@@ -477,7 +477,7 @@ func (d *btwDialog) CursorPos(width int) (row, col int) {
477477 editorOffset += len(renderDialogMarkdownRows(t.Assistant, d.theme, width))
478478 }
479479 if t.Err != "" {
480- editorOffset += len(wrapDialogTextRows(d.theme.FG256(d.theme.Error, "✖ "+t.Err), width))
480+ editorOffset += len(wrapDialogTextRows(d.theme.FGColor(d.theme.Error, "✖ "+t.Err), width))
481481 }
482482 }
483483 if d.loading {
@@ -505,7 +505,7 @@ func btwUserBubbleRows(th tui.Theme, text string, bubbleWidth int, compactMode b
505505 if innerWidth < 1 {
506506 innerWidth = 1
507507 }
508- bar := th.BG(th.UserBubbleBG, th.FG256(th.Accent, "▌ "))
508+ bar := th.BG(th.UserBubbleBG, th.FGColor(th.Accent, "▌ "))
509509 row := func(content string) string {
510510 inner := strings.Repeat(" ", leftGutter) + content
511511 return " " + bar + th.UserBubble(inner, bubbleWidth-2)
@@ -516,7 +516,7 @@ func btwUserBubbleRows(th tui.Theme, text string, bubbleWidth int, compactMode b
516516 innerWidth = 1
517517 }
518518 row = func(content string) string {
519- return " " + th.FG256(th.Accent, "▌ ") + th.FG256(th.Muted, content)
519+ return " " + th.FGColor(th.Accent, "▌ ") + th.FGColor(th.Muted, content)
520520 }
521521 }
522522 var bubble []string
@@ -383,7 +383,7 @@ func (d *btwDialog) Render(th tui.Theme, width int) []string {
383 out = append(out, frameHeaderColor(th, "btw - side chat (esc closes; nothing is added to the main thread)", width, th.Accent))383 out = append(out, frameHeaderColor(th, "btw - side chat (esc closes; nothing is added to the main thread)", width, th.Accent))
384 384
385 if len(d.turns) == 0 && !d.loading {385 if len(d.turns) == 0 && !d.loading {
386- out = append(out, " "+th.FG256(th.Muted, "ask anything; replies stay private to this side chat."))386+ out = append(out, " "+th.FGColor(th.Muted, "ask anything; replies stay private to this side chat."))
387 }387 }
388 388
389 for _, t := range d.turns {389 for _, t := range d.turns {
@@ -402,7 +402,7 @@ func (d *btwDialog) Render(th tui.Theme, width int) []string {
402 out = append(out, renderDialogMarkdownRows(t.Assistant, th, width)...)402 out = append(out, renderDialogMarkdownRows(t.Assistant, th, width)...)
403 }403 }
404 if t.Err != "" {404 if t.Err != "" {
405- out = append(out, wrapDialogTextRows(th.FG256(th.Error, "✖ "+t.Err), width)...)405+ out = append(out, wrapDialogTextRows(th.FGColor(th.Error, "✖ "+t.Err), width)...)
406 }406 }
407 }407 }
408 408
@@ -412,11 +412,11 @@ func (d *btwDialog) Render(th tui.Theme, width int) []string {
412 // rotating funny-line, elapsed seconds, then a muted hint412 // rotating funny-line, elapsed seconds, then a muted hint
413 // that esc cancels.413 // that esc cancels.
414 prefix := fmt.Sprintf("%s %s - %s",414 prefix := fmt.Sprintf("%s %s - %s",
415- th.FG256(th.Assistant, d.spin.Frame()),415+ th.FGColor(th.Assistant, d.spin.Frame()),
416- th.FG256(th.Assistant, d.spin.Message()),416+ th.FGColor(th.Assistant, d.spin.Message()),
417- th.FG256(th.Muted, d.spin.Elapsed().String()),417+ th.FGColor(th.Muted, d.spin.Elapsed().String()),
418 )418 )
419- out = append(out, " "+prefix+" "+th.FG256(th.Muted, "(esc cancels)"))419+ out = append(out, " "+prefix+" "+th.FGColor(th.Muted, "(esc cancels)"))
420 }420 }
421 421
422 out = append(out, "")422 out = append(out, "")
@@ -477,7 +477,7 @@ func (d *btwDialog) CursorPos(width int) (row, col int) {
477 editorOffset += len(renderDialogMarkdownRows(t.Assistant, d.theme, width))477 editorOffset += len(renderDialogMarkdownRows(t.Assistant, d.theme, width))
478 }478 }
479 if t.Err != "" {479 if t.Err != "" {
480- editorOffset += len(wrapDialogTextRows(d.theme.FG256(d.theme.Error, "✖ "+t.Err), width))480+ editorOffset += len(wrapDialogTextRows(d.theme.FGColor(d.theme.Error, "✖ "+t.Err), width))
481 }481 }
482 }482 }
483 if d.loading {483 if d.loading {
@@ -505,7 +505,7 @@ func btwUserBubbleRows(th tui.Theme, text string, bubbleWidth int, compactMode b
505 if innerWidth < 1 {505 if innerWidth < 1 {
506 innerWidth = 1506 innerWidth = 1
507 }507 }
508- bar := th.BG(th.UserBubbleBG, th.FG256(th.Accent, "▌ "))508+ bar := th.BG(th.UserBubbleBG, th.FGColor(th.Accent, "▌ "))
509 row := func(content string) string {509 row := func(content string) string {
510 inner := strings.Repeat(" ", leftGutter) + content510 inner := strings.Repeat(" ", leftGutter) + content
511 return " " + bar + th.UserBubble(inner, bubbleWidth-2)511 return " " + bar + th.UserBubble(inner, bubbleWidth-2)
@@ -516,7 +516,7 @@ func btwUserBubbleRows(th tui.Theme, text string, bubbleWidth int, compactMode b
516 innerWidth = 1516 innerWidth = 1
517 }517 }
518 row = func(content string) string {518 row = func(content string) string {
519- return " " + th.FG256(th.Accent, "▌ ") + th.FG256(th.Muted, content)519+ return " " + th.FGColor(th.Accent, "▌ ") + th.FGColor(th.Muted, content)
520 }520 }
521 }521 }
522 var bubble []string522 var bubble []string
M packages/agent/modes/changelog_dialog.go +3 -3
@@ -79,7 +79,7 @@ func (d *changelogDialog) Render(th tui.Theme, width int) []string {
7979 title := fmt.Sprintf("zot %s \u2014 release notes (any key to dismiss)", d.version)
8080 out := []string{frameHeaderColor(th, title, width, th.Accent)}
8181 if d.url != "" {
82- out = append(out, " "+th.FG256(th.Muted, d.url))
82+ out = append(out, " "+th.FGColor(th.Muted, d.url))
8383 out = append(out, "")
8484 }
8585
@@ -88,7 +88,7 @@ func (d *changelogDialog) Render(th tui.Theme, width int) []string {
8888 if strings.HasPrefix(l, "\x00H:") {
8989 // Heading: render in accent color, bold.
9090 heading := strings.TrimPrefix(l, "\x00H:")
91- styled := th.FG256(th.Accent, tui.Bold(heading))
91+ styled := th.FGColor(th.Accent, tui.Bold(heading))
9292 bodyLines = append(bodyLines, wrapDialogTextRows(styled, width)...)
9393 } else {
9494 // Regular line: render and wrap through the shared dialog
@@ -110,7 +110,7 @@ func (d *changelogDialog) Render(th tui.Theme, width int) []string {
110110 }
111111 out = append(out, bodyLines[d.scroll:end]...)
112112 if end < len(bodyLines) {
113- out = append(out, " "+th.FG256(th.Muted, fmt.Sprintf("\u2193 %d more lines (down/pgdn)", len(bodyLines)-end)))
113+ out = append(out, " "+th.FGColor(th.Muted, fmt.Sprintf("\u2193 %d more lines (down/pgdn)", len(bodyLines)-end)))
114114 }
115115 out = append(out, frameRuleColor(th, width, th.Accent))
116116 return out
@@ -79,7 +79,7 @@ func (d *changelogDialog) Render(th tui.Theme, width int) []string {
79 title := fmt.Sprintf("zot %s \u2014 release notes (any key to dismiss)", d.version)79 title := fmt.Sprintf("zot %s \u2014 release notes (any key to dismiss)", d.version)
80 out := []string{frameHeaderColor(th, title, width, th.Accent)}80 out := []string{frameHeaderColor(th, title, width, th.Accent)}
81 if d.url != "" {81 if d.url != "" {
82- out = append(out, " "+th.FG256(th.Muted, d.url))82+ out = append(out, " "+th.FGColor(th.Muted, d.url))
83 out = append(out, "")83 out = append(out, "")
84 }84 }
85 85
@@ -88,7 +88,7 @@ func (d *changelogDialog) Render(th tui.Theme, width int) []string {
88 if strings.HasPrefix(l, "\x00H:") {88 if strings.HasPrefix(l, "\x00H:") {
89 // Heading: render in accent color, bold.89 // Heading: render in accent color, bold.
90 heading := strings.TrimPrefix(l, "\x00H:")90 heading := strings.TrimPrefix(l, "\x00H:")
91- styled := th.FG256(th.Accent, tui.Bold(heading))91+ styled := th.FGColor(th.Accent, tui.Bold(heading))
92 bodyLines = append(bodyLines, wrapDialogTextRows(styled, width)...)92 bodyLines = append(bodyLines, wrapDialogTextRows(styled, width)...)
93 } else {93 } else {
94 // Regular line: render and wrap through the shared dialog94 // Regular line: render and wrap through the shared dialog
@@ -110,7 +110,7 @@ func (d *changelogDialog) Render(th tui.Theme, width int) []string {
110 }110 }
111 out = append(out, bodyLines[d.scroll:end]...)111 out = append(out, bodyLines[d.scroll:end]...)
112 if end < len(bodyLines) {112 if end < len(bodyLines) {
113- out = append(out, " "+th.FG256(th.Muted, fmt.Sprintf("\u2193 %d more lines (down/pgdn)", len(bodyLines)-end)))113+ out = append(out, " "+th.FGColor(th.Muted, fmt.Sprintf("\u2193 %d more lines (down/pgdn)", len(bodyLines)-end)))
114 }114 }
115 out = append(out, frameRuleColor(th, width, th.Accent))115 out = append(out, frameRuleColor(th, width, th.Accent))
116 return out116 return out
M packages/agent/modes/confirm_dialog.go +4 -4
@@ -251,9 +251,9 @@ func (d *confirmDialog) Render(th tui.Theme, width int) []string {
251251 }
252252 lines = append(lines, frameHeader(th, title, width))
253253
254- toolLine := " " + th.FG256(th.Tool, req.toolName)
254+ toolLine := " " + th.FGColor(th.Tool, req.toolName)
255255 if req.preview != "" {
256- toolLine += " " + th.FG256(th.Muted, req.preview)
256+ toolLine += " " + th.FGColor(th.Muted, req.preview)
257257 }
258258 lines = append(lines, toolLine)
259259 lines = append(lines, "")
@@ -261,7 +261,7 @@ func (d *confirmDialog) Render(th tui.Theme, width int) []string {
261261 if !d.focused {
262262 hint = "confirmation paused (finish or esc the command to return):"
263263 }
264- lines = append(lines, th.FG256(th.Muted, hint))
264+ lines = append(lines, th.FGColor(th.Muted, hint))
265265
266266 for i, opt := range confirmOptions {
267267 label := opt.label
@@ -279,7 +279,7 @@ func (d *confirmDialog) Render(th tui.Theme, width int) []string {
279279 if i == cursor {
280280 lines = append(lines, th.PadHighlight(plain, width))
281281 } else {
282- lines = append(lines, th.FG256(th.Muted, plain))
282+ lines = append(lines, th.FGColor(th.Muted, plain))
283283 }
284284 }
285285 lines = append(lines, frameRule(th, width))
@@ -251,9 +251,9 @@ func (d *confirmDialog) Render(th tui.Theme, width int) []string {
251 }251 }
252 lines = append(lines, frameHeader(th, title, width))252 lines = append(lines, frameHeader(th, title, width))
253 253
254- toolLine := " " + th.FG256(th.Tool, req.toolName)254+ toolLine := " " + th.FGColor(th.Tool, req.toolName)
255 if req.preview != "" {255 if req.preview != "" {
256- toolLine += " " + th.FG256(th.Muted, req.preview)256+ toolLine += " " + th.FGColor(th.Muted, req.preview)
257 }257 }
258 lines = append(lines, toolLine)258 lines = append(lines, toolLine)
259 lines = append(lines, "")259 lines = append(lines, "")
@@ -261,7 +261,7 @@ func (d *confirmDialog) Render(th tui.Theme, width int) []string {
261 if !d.focused {261 if !d.focused {
262 hint = "confirmation paused (finish or esc the command to return):"262 hint = "confirmation paused (finish or esc the command to return):"
263 }263 }
264- lines = append(lines, th.FG256(th.Muted, hint))264+ lines = append(lines, th.FGColor(th.Muted, hint))
265 265
266 for i, opt := range confirmOptions {266 for i, opt := range confirmOptions {
267 label := opt.label267 label := opt.label
@@ -279,7 +279,7 @@ func (d *confirmDialog) Render(th tui.Theme, width int) []string {
279 if i == cursor {279 if i == cursor {
280 lines = append(lines, th.PadHighlight(plain, width))280 lines = append(lines, th.PadHighlight(plain, width))
281 } else {281 } else {
282- lines = append(lines, th.FG256(th.Muted, plain))282+ lines = append(lines, th.FGColor(th.Muted, plain))
283 }283 }
284 }284 }
285 lines = append(lines, frameRule(th, width))285 lines = append(lines, frameRule(th, width))
M packages/agent/modes/ext_panel_dialog.go +3 -3
@@ -74,7 +74,7 @@ func (d *extPanelDialog) Render(th tui.Theme, width int) []string {
7474 }
7575 if strings.TrimSpace(d.footer) != "" {
7676 out = append(out, "")
77- out = append(out, th.FG256(th.Muted, d.footer))
77+ out = append(out, th.FGColor(th.Muted, d.footer))
7878 }
7979 out = append(out, frameRuleColor(th, width, th.Accent))
8080 return out
@@ -92,7 +92,7 @@ func styleExtPanelLine(th tui.Theme, raw string, plain string, width int, select
9292 if raw != plain {
9393 return raw + "\x1b[0m"
9494 }
95- styled := th.FG256(th.Muted, plain)
96- styled = strings.ReplaceAll(styled, "✓", th.FG256(th.Tool, "✓"))
95+ styled := th.FGColor(th.Muted, plain)
96+ styled = strings.ReplaceAll(styled, "✓", th.FGColor(th.Tool, "✓"))
9797 return styled
9898 }
@@ -74,7 +74,7 @@ func (d *extPanelDialog) Render(th tui.Theme, width int) []string {
74 }74 }
75 if strings.TrimSpace(d.footer) != "" {75 if strings.TrimSpace(d.footer) != "" {
76 out = append(out, "")76 out = append(out, "")
77- out = append(out, th.FG256(th.Muted, d.footer))77+ out = append(out, th.FGColor(th.Muted, d.footer))
78 }78 }
79 out = append(out, frameRuleColor(th, width, th.Accent))79 out = append(out, frameRuleColor(th, width, th.Accent))
80 return out80 return out
@@ -92,7 +92,7 @@ func styleExtPanelLine(th tui.Theme, raw string, plain string, width int, select
92 if raw != plain {92 if raw != plain {
93 return raw + "\x1b[0m"93 return raw + "\x1b[0m"
94 }94 }
95- styled := th.FG256(th.Muted, plain)95+ styled := th.FGColor(th.Muted, plain)
96- styled = strings.ReplaceAll(styled, "✓", th.FG256(th.Tool, "✓"))96+ styled = strings.ReplaceAll(styled, "✓", th.FGColor(th.Tool, "✓"))
97 return styled97 return styled
98 }98 }
M packages/agent/modes/file_suggest.go +3 -3
@@ -480,7 +480,7 @@ func (s *fileSuggester) Render(input string, th tui.Theme, width int) []string {
480480
481481 // Show breadcrumb when browsing a subdirectory.
482482 if s.browseRel != "" {
483- crumb := th.FG256(th.Accent, " "+s.browseRel+"/")
483+ crumb := th.FGColor(th.Accent, " "+s.browseRel+"/")
484484 out = append(out, crumb)
485485 }
486486
@@ -494,7 +494,7 @@ func (s *fileSuggester) Render(input string, th tui.Theme, width int) []string {
494494 if idx == s.cursor {
495495 out = append(out, th.PadHighlight(plain, width))
496496 } else {
497- out = append(out, th.FG256(th.Muted, plain))
497+ out = append(out, th.FGColor(th.Muted, plain))
498498 }
499499 }
500500
@@ -508,7 +508,7 @@ func (s *fileSuggester) Render(input string, th tui.Theme, width int) []string {
508508 default:
509509 hint = " \u2191/\u2193 navigate - \u2192 open dir - enter select - esc cancel"
510510 }
511- out = append(out, th.FG256(th.Muted, hint))
511+ out = append(out, th.FGColor(th.Muted, hint))
512512 out = append(out, "")
513513 return out
514514 }
@@ -480,7 +480,7 @@ func (s *fileSuggester) Render(input string, th tui.Theme, width int) []string {
480 480
481 // Show breadcrumb when browsing a subdirectory.481 // Show breadcrumb when browsing a subdirectory.
482 if s.browseRel != "" {482 if s.browseRel != "" {
483- crumb := th.FG256(th.Accent, " "+s.browseRel+"/")483+ crumb := th.FGColor(th.Accent, " "+s.browseRel+"/")
484 out = append(out, crumb)484 out = append(out, crumb)
485 }485 }
486 486
@@ -494,7 +494,7 @@ func (s *fileSuggester) Render(input string, th tui.Theme, width int) []string {
494 if idx == s.cursor {494 if idx == s.cursor {
495 out = append(out, th.PadHighlight(plain, width))495 out = append(out, th.PadHighlight(plain, width))
496 } else {496 } else {
497- out = append(out, th.FG256(th.Muted, plain))497+ out = append(out, th.FGColor(th.Muted, plain))
498 }498 }
499 }499 }
500 500
@@ -508,7 +508,7 @@ func (s *fileSuggester) Render(input string, th tui.Theme, width int) []string {
508 default:508 default:
509 hint = " \u2191/\u2193 navigate - \u2192 open dir - enter select - esc cancel"509 hint = " \u2191/\u2193 navigate - \u2192 open dir - enter select - esc cancel"
510 }510 }
511- out = append(out, th.FG256(th.Muted, hint))511+ out = append(out, th.FGColor(th.Muted, hint))
512 out = append(out, "")512 out = append(out, "")
513 return out513 return out
514 }514 }
M packages/agent/modes/help.go +4 -4
@@ -82,16 +82,16 @@ func renderHelpBlock(th tui.Theme, width int, llamaConfigured bool) []string {
8282 continue
8383 }
8484 out = append(out, fmt.Sprintf(" %s %s",
85- th.FG256(th.Accent, pad(c.Name)),
86- th.FG256(th.Muted, c.Desc)))
85+ th.FGColor(th.Accent, pad(c.Name)),
86+ th.FGColor(th.Muted, c.Desc)))
8787 }
8888
8989 // keys section
9090 out = append(out, "", tui.Bold("keys:"))
9191 for _, k := range helpKeyRows {
9292 out = append(out, fmt.Sprintf(" %s %s",
93- th.FG256(th.Accent, pad(k[0])),
94- th.FG256(th.Muted, k[1])))
93+ th.FGColor(th.Accent, pad(k[0])),
94+ th.FGColor(th.Muted, k[1])))
9595 }
9696
9797 out = append(out, "", frameRule(th, width), "")
@@ -82,16 +82,16 @@ func renderHelpBlock(th tui.Theme, width int, llamaConfigured bool) []string {
82 continue82 continue
83 }83 }
84 out = append(out, fmt.Sprintf(" %s %s",84 out = append(out, fmt.Sprintf(" %s %s",
85- th.FG256(th.Accent, pad(c.Name)),85+ th.FGColor(th.Accent, pad(c.Name)),
86- th.FG256(th.Muted, c.Desc)))86+ th.FGColor(th.Muted, c.Desc)))
87 }87 }
88 88
89 // keys section89 // keys section
90 out = append(out, "", tui.Bold("keys:"))90 out = append(out, "", tui.Bold("keys:"))
91 for _, k := range helpKeyRows {91 for _, k := range helpKeyRows {
92 out = append(out, fmt.Sprintf(" %s %s",92 out = append(out, fmt.Sprintf(" %s %s",
93- th.FG256(th.Accent, pad(k[0])),93+ th.FGColor(th.Accent, pad(k[0])),
94- th.FG256(th.Muted, k[1])))94+ th.FGColor(th.Muted, k[1])))
95 }95 }
96 96
97 out = append(out, "", frameRule(th, width), "")97 out = append(out, "", frameRule(th, width), "")
M packages/agent/modes/interactive.go +17 -17
@@ -1274,7 +1274,7 @@ func (i *Interactive) extensionChromeLinesForLayoutLocked(cols int, rightBarActi
12741274 }
12751275 limit := maxNarrowExtensionChromeRows
12761276 trimmed := append([]string(nil), lines[:limit-1]...)
1277- trimmed = append(trimmed, i.cfg.Theme.FG256(i.cfg.Theme.Muted, truncateLine(" ... extension chrome truncated ...", cols)))
1277+ trimmed = append(trimmed, i.cfg.Theme.FGColor(i.cfg.Theme.Muted, truncateLine(" ... extension chrome truncated ...", cols)))
12781278 return trimmed
12791279 }
12801280
@@ -1310,7 +1310,7 @@ func (i *Interactive) extensionChromeLinesAtLocked(cols int, rightBarActive bool
13101310 if widget.Title != "" {
13111311 label += " " + widget.Title
13121312 }
1313- if !appendWidgetRow(i.cfg.Theme.FG256(i.cfg.Theme.Accent, truncateLine(label, cols))) {
1313+ if !appendWidgetRow(i.cfg.Theme.FGColor(i.cfg.Theme.Accent, truncateLine(label, cols))) {
13141314 stopWidgets = true
13151315 break
13161316 }
@@ -1326,7 +1326,7 @@ func (i *Interactive) extensionChromeLinesAtLocked(cols int, rightBarActive bool
13261326 }
13271327 }
13281328 if widgetTruncated {
1329- out = append(out, i.cfg.Theme.FG256(i.cfg.Theme.Muted, truncateLine(" ... extension widgets truncated ...", cols)))
1329+ out = append(out, i.cfg.Theme.FGColor(i.cfg.Theme.Muted, truncateLine(" ... extension widgets truncated ...", cols)))
13301330 }
13311331
13321332 statusRows := 0
@@ -1348,7 +1348,7 @@ func (i *Interactive) extensionChromeLinesAtLocked(cols int, rightBarActive bool
13481348 color = i.cfg.Theme.Tool
13491349 }
13501350 label := " [" + extName + "] " + status.Text
1351- out = append(out, i.cfg.Theme.FG256(color, truncateLine(label, cols)))
1351+ out = append(out, i.cfg.Theme.FGColor(color, truncateLine(label, cols)))
13521352 statusRows++
13531353 }
13541354 if statusTruncated {
@@ -1356,7 +1356,7 @@ func (i *Interactive) extensionChromeLinesAtLocked(cols int, rightBarActive bool
13561356 }
13571357 }
13581358 if statusTruncated {
1359- out = append(out, i.cfg.Theme.FG256(i.cfg.Theme.Muted, truncateLine(" ... extension statuses truncated ...", cols)))
1359+ out = append(out, i.cfg.Theme.FGColor(i.cfg.Theme.Muted, truncateLine(" ... extension statuses truncated ...", cols)))
13601360 }
13611361 return out
13621362 }
@@ -1467,7 +1467,7 @@ func (i *Interactive) buildChatLocked(cols int) []string {
14671467 if cols > 4 && len(line) > cols {
14681468 line = line[:cols-3] + "..."
14691469 }
1470- chat = append(chat, i.cfg.Theme.FG256(i.cfg.Theme.Tool, line), "")
1470+ chat = append(chat, i.cfg.Theme.FGColor(i.cfg.Theme.Tool, line), "")
14711471 }
14721472
14731473 // Live shell-escape output (!command / entry.pre) streams into the
@@ -1772,10 +1772,10 @@ func (i *Interactive) redraw() {
17721772 var busyPrefix string
17731773 if i.busy {
17741774 busyPrefix = fmt.Sprintf("%s %s %s %s",
1775- i.cfg.Theme.FG256(i.cfg.Theme.Assistant, i.spin.Frame()),
1776- i.cfg.Theme.FG256(i.cfg.Theme.Assistant, i.spin.Message()),
1777- i.cfg.Theme.FG256(i.cfg.Theme.Muted, "-"),
1778- i.cfg.Theme.FG256(i.cfg.Theme.Muted, i.spin.Elapsed().String()),
1775+ i.cfg.Theme.FGColor(i.cfg.Theme.Assistant, i.spin.Frame()),
1776+ i.cfg.Theme.FGColor(i.cfg.Theme.Assistant, i.spin.Message()),
1777+ i.cfg.Theme.FGColor(i.cfg.Theme.Muted, "-"),
1778+ i.cfg.Theme.FGColor(i.cfg.Theme.Muted, i.spin.Elapsed().String()),
17791779 )
17801780 }
17811781
@@ -1843,16 +1843,16 @@ func (i *Interactive) redraw() {
18431843 if len(queued) > 0 {
18441844 queue = append(queue, "")
18451845 for _, q := range queued {
1846- label := i.cfg.Theme.FG256(i.cfg.Theme.Accent, " sliding in: ")
1846+ label := i.cfg.Theme.FGColor(i.cfg.Theme.Accent, " sliding in: ")
18471847 text := truncateLine(q, mainCols-17)
1848- queue = append(queue, label+i.cfg.Theme.FG256(i.cfg.Theme.Muted, text))
1848+ queue = append(queue, label+i.cfg.Theme.FGColor(i.cfg.Theme.Muted, text))
18491849 }
18501850 // Hint row, rendered in the same muted tone as the model
18511851 // info on the status bar so it reads as ambient metadata
18521852 // rather than a chip. Tells the user how to recover the
18531853 // most recent queued message back into the editor.
18541854 hint := " Press " + slideBackChordHint() + " to slide back into input"
1855- queue = append(queue, i.cfg.Theme.FG256(i.cfg.Theme.Muted, hint))
1855+ queue = append(queue, i.cfg.Theme.FGColor(i.cfg.Theme.Muted, hint))
18561856 }
18571857
18581858 extensionLines := i.extensionChromeLinesForLayoutLocked(mainCols, rightBarActive, rightBarFallback)
@@ -2060,7 +2060,7 @@ func (i *Interactive) redraw() {
20602060 } else {
20612061 text = fmt.Sprintf(" ↑ %d lines more below (end to jump)", i.scrollOffset)
20622062 }
2063- note := i.cfg.Theme.FG256(i.cfg.Theme.Muted, text)
2063+ note := i.cfg.Theme.FGColor(i.cfg.Theme.Muted, text)
20642064 visibleChat = append([]string{note}, visibleChat...)
20652065 if len(visibleChat) > chatRows {
20662066 visibleChat = visibleChat[:chatRows]
@@ -3442,11 +3442,11 @@ func (i *Interactive) appendExtensionNote(extName, msg, level string) {
34423442 case "success":
34433443 color = i.cfg.Theme.Tool
34443444 }
3445- prefix := i.cfg.Theme.FG256(i.cfg.Theme.Accent, "["+extName+"] ")
3445+ prefix := i.cfg.Theme.FGColor(i.cfg.Theme.Accent, "["+extName+"] ")
34463446 for _, line := range strings.Split(msg, "\n") {
34473447 i.statusOK = "" // clear any stale ok
34483448 i.statusErr = ""
3449- i.extNotes = append(i.extNotes, prefix+i.cfg.Theme.FG256(color, line))
3449+ i.extNotes = append(i.extNotes, prefix+i.cfg.Theme.FGColor(color, line))
34503450 }
34513451 // Extension slash commands complete asynchronously. If their result is
34523452 // displayed while confirmation is waiting, keep Esc assigned to the note
@@ -6988,7 +6988,7 @@ func stripAutoCompactNotes(notes []string) []string {
69886988 // inline auto-compact heads-up. Lives in extNotes so it survives
69896989 // the busy-spinner overwrite of the status row.
69906990 func autoCompactNoteLine(th tui.Theme, msg string) string {
6991- return " " + th.FG256(th.Warning, "⚠ "+msg)
6991+ return " " + th.FGColor(th.Warning, "⚠ "+msg)
69926992 }
69936993
69946994 // isContextOverflowError matches provider responses that reject the
@@ -1274,7 +1274,7 @@ func (i *Interactive) extensionChromeLinesForLayoutLocked(cols int, rightBarActi
1274 }1274 }
1275 limit := maxNarrowExtensionChromeRows1275 limit := maxNarrowExtensionChromeRows
1276 trimmed := append([]string(nil), lines[:limit-1]...)1276 trimmed := append([]string(nil), lines[:limit-1]...)
1277- trimmed = append(trimmed, i.cfg.Theme.FG256(i.cfg.Theme.Muted, truncateLine(" ... extension chrome truncated ...", cols)))1277+ trimmed = append(trimmed, i.cfg.Theme.FGColor(i.cfg.Theme.Muted, truncateLine(" ... extension chrome truncated ...", cols)))
1278 return trimmed1278 return trimmed
1279 }1279 }
1280 1280
@@ -1310,7 +1310,7 @@ func (i *Interactive) extensionChromeLinesAtLocked(cols int, rightBarActive bool
1310 if widget.Title != "" {1310 if widget.Title != "" {
1311 label += " " + widget.Title1311 label += " " + widget.Title
1312 }1312 }
1313- if !appendWidgetRow(i.cfg.Theme.FG256(i.cfg.Theme.Accent, truncateLine(label, cols))) {1313+ if !appendWidgetRow(i.cfg.Theme.FGColor(i.cfg.Theme.Accent, truncateLine(label, cols))) {
1314 stopWidgets = true1314 stopWidgets = true
1315 break1315 break
1316 }1316 }
@@ -1326,7 +1326,7 @@ func (i *Interactive) extensionChromeLinesAtLocked(cols int, rightBarActive bool
1326 }1326 }
1327 }1327 }
1328 if widgetTruncated {1328 if widgetTruncated {
1329- out = append(out, i.cfg.Theme.FG256(i.cfg.Theme.Muted, truncateLine(" ... extension widgets truncated ...", cols)))1329+ out = append(out, i.cfg.Theme.FGColor(i.cfg.Theme.Muted, truncateLine(" ... extension widgets truncated ...", cols)))
1330 }1330 }
1331 1331
1332 statusRows := 01332 statusRows := 0
@@ -1348,7 +1348,7 @@ func (i *Interactive) extensionChromeLinesAtLocked(cols int, rightBarActive bool
1348 color = i.cfg.Theme.Tool1348 color = i.cfg.Theme.Tool
1349 }1349 }
1350 label := " [" + extName + "] " + status.Text1350 label := " [" + extName + "] " + status.Text
1351- out = append(out, i.cfg.Theme.FG256(color, truncateLine(label, cols)))1351+ out = append(out, i.cfg.Theme.FGColor(color, truncateLine(label, cols)))
1352 statusRows++1352 statusRows++
1353 }1353 }
1354 if statusTruncated {1354 if statusTruncated {
@@ -1356,7 +1356,7 @@ func (i *Interactive) extensionChromeLinesAtLocked(cols int, rightBarActive bool
1356 }1356 }
1357 }1357 }
1358 if statusTruncated {1358 if statusTruncated {
1359- out = append(out, i.cfg.Theme.FG256(i.cfg.Theme.Muted, truncateLine(" ... extension statuses truncated ...", cols)))1359+ out = append(out, i.cfg.Theme.FGColor(i.cfg.Theme.Muted, truncateLine(" ... extension statuses truncated ...", cols)))
1360 }1360 }
1361 return out1361 return out
1362 }1362 }
@@ -1467,7 +1467,7 @@ func (i *Interactive) buildChatLocked(cols int) []string {
1467 if cols > 4 && len(line) > cols {1467 if cols > 4 && len(line) > cols {
1468 line = line[:cols-3] + "..."1468 line = line[:cols-3] + "..."
1469 }1469 }
1470- chat = append(chat, i.cfg.Theme.FG256(i.cfg.Theme.Tool, line), "")1470+ chat = append(chat, i.cfg.Theme.FGColor(i.cfg.Theme.Tool, line), "")
1471 }1471 }
1472 1472
1473 // Live shell-escape output (!command / entry.pre) streams into the1473 // Live shell-escape output (!command / entry.pre) streams into the
@@ -1772,10 +1772,10 @@ func (i *Interactive) redraw() {
1772 var busyPrefix string1772 var busyPrefix string
1773 if i.busy {1773 if i.busy {
1774 busyPrefix = fmt.Sprintf("%s %s %s %s",1774 busyPrefix = fmt.Sprintf("%s %s %s %s",
1775- i.cfg.Theme.FG256(i.cfg.Theme.Assistant, i.spin.Frame()),1775+ i.cfg.Theme.FGColor(i.cfg.Theme.Assistant, i.spin.Frame()),
1776- i.cfg.Theme.FG256(i.cfg.Theme.Assistant, i.spin.Message()),1776+ i.cfg.Theme.FGColor(i.cfg.Theme.Assistant, i.spin.Message()),
1777- i.cfg.Theme.FG256(i.cfg.Theme.Muted, "-"),1777+ i.cfg.Theme.FGColor(i.cfg.Theme.Muted, "-"),
1778- i.cfg.Theme.FG256(i.cfg.Theme.Muted, i.spin.Elapsed().String()),1778+ i.cfg.Theme.FGColor(i.cfg.Theme.Muted, i.spin.Elapsed().String()),
1779 )1779 )
1780 }1780 }
1781 1781
@@ -1843,16 +1843,16 @@ func (i *Interactive) redraw() {
1843 if len(queued) > 0 {1843 if len(queued) > 0 {
1844 queue = append(queue, "")1844 queue = append(queue, "")
1845 for _, q := range queued {1845 for _, q := range queued {
1846- label := i.cfg.Theme.FG256(i.cfg.Theme.Accent, " sliding in: ")1846+ label := i.cfg.Theme.FGColor(i.cfg.Theme.Accent, " sliding in: ")
1847 text := truncateLine(q, mainCols-17)1847 text := truncateLine(q, mainCols-17)
1848- queue = append(queue, label+i.cfg.Theme.FG256(i.cfg.Theme.Muted, text))1848+ queue = append(queue, label+i.cfg.Theme.FGColor(i.cfg.Theme.Muted, text))
1849 }1849 }
1850 // Hint row, rendered in the same muted tone as the model1850 // Hint row, rendered in the same muted tone as the model
1851 // info on the status bar so it reads as ambient metadata1851 // info on the status bar so it reads as ambient metadata
1852 // rather than a chip. Tells the user how to recover the1852 // rather than a chip. Tells the user how to recover the
1853 // most recent queued message back into the editor.1853 // most recent queued message back into the editor.
1854 hint := " Press " + slideBackChordHint() + " to slide back into input"1854 hint := " Press " + slideBackChordHint() + " to slide back into input"
1855- queue = append(queue, i.cfg.Theme.FG256(i.cfg.Theme.Muted, hint))1855+ queue = append(queue, i.cfg.Theme.FGColor(i.cfg.Theme.Muted, hint))
1856 }1856 }
1857 1857
1858 extensionLines := i.extensionChromeLinesForLayoutLocked(mainCols, rightBarActive, rightBarFallback)1858 extensionLines := i.extensionChromeLinesForLayoutLocked(mainCols, rightBarActive, rightBarFallback)
@@ -2060,7 +2060,7 @@ func (i *Interactive) redraw() {
2060 } else {2060 } else {
2061 text = fmt.Sprintf(" ↑ %d lines more below (end to jump)", i.scrollOffset)2061 text = fmt.Sprintf(" ↑ %d lines more below (end to jump)", i.scrollOffset)
2062 }2062 }
2063- note := i.cfg.Theme.FG256(i.cfg.Theme.Muted, text)2063+ note := i.cfg.Theme.FGColor(i.cfg.Theme.Muted, text)
2064 visibleChat = append([]string{note}, visibleChat...)2064 visibleChat = append([]string{note}, visibleChat...)
2065 if len(visibleChat) > chatRows {2065 if len(visibleChat) > chatRows {
2066 visibleChat = visibleChat[:chatRows]2066 visibleChat = visibleChat[:chatRows]
@@ -3442,11 +3442,11 @@ func (i *Interactive) appendExtensionNote(extName, msg, level string) {
3442 case "success":3442 case "success":
3443 color = i.cfg.Theme.Tool3443 color = i.cfg.Theme.Tool
3444 }3444 }
3445- prefix := i.cfg.Theme.FG256(i.cfg.Theme.Accent, "["+extName+"] ")3445+ prefix := i.cfg.Theme.FGColor(i.cfg.Theme.Accent, "["+extName+"] ")
3446 for _, line := range strings.Split(msg, "\n") {3446 for _, line := range strings.Split(msg, "\n") {
3447 i.statusOK = "" // clear any stale ok3447 i.statusOK = "" // clear any stale ok
3448 i.statusErr = ""3448 i.statusErr = ""
3449- i.extNotes = append(i.extNotes, prefix+i.cfg.Theme.FG256(color, line))3449+ i.extNotes = append(i.extNotes, prefix+i.cfg.Theme.FGColor(color, line))
3450 }3450 }
3451 // Extension slash commands complete asynchronously. If their result is3451 // Extension slash commands complete asynchronously. If their result is
3452 // displayed while confirmation is waiting, keep Esc assigned to the note3452 // displayed while confirmation is waiting, keep Esc assigned to the note
@@ -6988,7 +6988,7 @@ func stripAutoCompactNotes(notes []string) []string {
6988 // inline auto-compact heads-up. Lives in extNotes so it survives6988 // inline auto-compact heads-up. Lives in extNotes so it survives
6989 // the busy-spinner overwrite of the status row.6989 // the busy-spinner overwrite of the status row.
6990 func autoCompactNoteLine(th tui.Theme, msg string) string {6990 func autoCompactNoteLine(th tui.Theme, msg string) string {
6991- return " " + th.FG256(th.Warning, "⚠ "+msg)6991+ return " " + th.FGColor(th.Warning, "⚠ "+msg)
6992 }6992 }
6993 6993
6994 // isContextOverflowError matches provider responses that reject the6994 // isContextOverflowError matches provider responses that reject the
M packages/agent/modes/jump_dialog.go +7 -7
@@ -104,8 +104,8 @@ func (d *jumpDialog) Render(th tui.Theme, width int) []string {
104104 var lines []string
105105 lines = append(lines, frameHeader(th, "jump to turn", width))
106106 if len(d.all) == 0 {
107- lines = append(lines, th.FG256(th.Muted, "no turns in this session yet"))
108- lines = append(lines, th.FG256(th.Muted, "press esc to close"))
107+ lines = append(lines, th.FGColor(th.Muted, "no turns in this session yet"))
108+ lines = append(lines, th.FGColor(th.Muted, "press esc to close"))
109109 lines = append(lines, frameRule(th, width))
110110 return lines
111111 }
@@ -115,10 +115,10 @@ func (d *jumpDialog) Render(th tui.Theme, width int) []string {
115115 if d.filter != "" {
116116 hint = fmt.Sprintf("filter: %q - %d match - ", d.filter, len(d.visible)) + hint
117117 }
118- lines = append(lines, th.FG256(th.Muted, hint))
118+ lines = append(lines, th.FGColor(th.Muted, hint))
119119
120120 if len(d.visible) == 0 {
121- lines = append(lines, th.FG256(th.Muted, " (nothing matches; backspace to widen)"))
121+ lines = append(lines, th.FGColor(th.Muted, " (nothing matches; backspace to widen)"))
122122 lines = append(lines, frameRule(th, width))
123123 return lines
124124 }
@@ -140,7 +140,7 @@ func (d *jumpDialog) Render(th tui.Theme, width int) []string {
140140 }
141141 }
142142 if start > 0 {
143- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" \u2191 %d more above", start)))
143+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" \u2191 %d more above", start)))
144144 }
145145 for i := start; i < end; i++ {
146146 t := d.visible[i]
@@ -148,11 +148,11 @@ func (d *jumpDialog) Render(th tui.Theme, width int) []string {
148148 if i == d.cursor {
149149 lines = append(lines, th.PadHighlight(plain, width))
150150 } else {
151- lines = append(lines, th.FG256(th.Muted, plain))
151+ lines = append(lines, th.FGColor(th.Muted, plain))
152152 }
153153 }
154154 if end < len(d.visible) {
155- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" \u2193 %d more below", len(d.visible)-end)))
155+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" \u2193 %d more below", len(d.visible)-end)))
156156 }
157157
158158 lines = append(lines, frameRule(th, width))
@@ -104,8 +104,8 @@ func (d *jumpDialog) Render(th tui.Theme, width int) []string {
104 var lines []string104 var lines []string
105 lines = append(lines, frameHeader(th, "jump to turn", width))105 lines = append(lines, frameHeader(th, "jump to turn", width))
106 if len(d.all) == 0 {106 if len(d.all) == 0 {
107- lines = append(lines, th.FG256(th.Muted, "no turns in this session yet"))107+ lines = append(lines, th.FGColor(th.Muted, "no turns in this session yet"))
108- lines = append(lines, th.FG256(th.Muted, "press esc to close"))108+ lines = append(lines, th.FGColor(th.Muted, "press esc to close"))
109 lines = append(lines, frameRule(th, width))109 lines = append(lines, frameRule(th, width))
110 return lines110 return lines
111 }111 }
@@ -115,10 +115,10 @@ func (d *jumpDialog) Render(th tui.Theme, width int) []string {
115 if d.filter != "" {115 if d.filter != "" {
116 hint = fmt.Sprintf("filter: %q - %d match - ", d.filter, len(d.visible)) + hint116 hint = fmt.Sprintf("filter: %q - %d match - ", d.filter, len(d.visible)) + hint
117 }117 }
118- lines = append(lines, th.FG256(th.Muted, hint))118+ lines = append(lines, th.FGColor(th.Muted, hint))
119 119
120 if len(d.visible) == 0 {120 if len(d.visible) == 0 {
121- lines = append(lines, th.FG256(th.Muted, " (nothing matches; backspace to widen)"))121+ lines = append(lines, th.FGColor(th.Muted, " (nothing matches; backspace to widen)"))
122 lines = append(lines, frameRule(th, width))122 lines = append(lines, frameRule(th, width))
123 return lines123 return lines
124 }124 }
@@ -140,7 +140,7 @@ func (d *jumpDialog) Render(th tui.Theme, width int) []string {
140 }140 }
141 }141 }
142 if start > 0 {142 if start > 0 {
143- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" \u2191 %d more above", start)))143+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" \u2191 %d more above", start)))
144 }144 }
145 for i := start; i < end; i++ {145 for i := start; i < end; i++ {
146 t := d.visible[i]146 t := d.visible[i]
@@ -148,11 +148,11 @@ func (d *jumpDialog) Render(th tui.Theme, width int) []string {
148 if i == d.cursor {148 if i == d.cursor {
149 lines = append(lines, th.PadHighlight(plain, width))149 lines = append(lines, th.PadHighlight(plain, width))
150 } else {150 } else {
151- lines = append(lines, th.FG256(th.Muted, plain))151+ lines = append(lines, th.FGColor(th.Muted, plain))
152 }152 }
153 }153 }
154 if end < len(d.visible) {154 if end < len(d.visible) {
155- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" \u2193 %d more below", len(d.visible)-end)))155+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" \u2193 %d more below", len(d.visible)-end)))
156 }156 }
157 157
158 lines = append(lines, frameRule(th, width))158 lines = append(lines, frameRule(th, width))
M packages/agent/modes/llama_dialog.go +19 -19
@@ -497,15 +497,15 @@ func (d *llamaDialog) Render(th tui.Theme, width int) []string {
497497 }
498498 lines := []string{frameHeader(th, "llama.cpp models", width)}
499499 if d.client != nil {
500- lines = append(lines, th.FG256(th.Muted, d.client.ServerURL))
500+ lines = append(lines, th.FGColor(th.Muted, d.client.ServerURL))
501501 }
502502 if d.message != "" {
503- lines = append(lines, th.FG256(th.Muted, d.message))
503+ lines = append(lines, th.FGColor(th.Muted, d.message))
504504 }
505505
506506 switch d.step {
507507 case llamaLoading:
508- lines = append(lines, th.FG256(th.Muted, " working..."))
508+ lines = append(lines, th.FGColor(th.Muted, " working..."))
509509 case llamaModels:
510510 const visible = 12
511511 start, end := dialogWindow(d.cursor, len(d.models)+1, visible)
@@ -523,10 +523,10 @@ func (d *llamaDialog) Render(th tui.Theme, width int) []string {
523523 if index == d.cursor {
524524 lines = append(lines, th.PadHighlight(line, width))
525525 } else {
526- lines = append(lines, th.FG256(th.Muted, line))
526+ lines = append(lines, th.FGColor(th.Muted, line))
527527 }
528528 }
529- lines = append(lines, th.FG256(th.Muted, "enter loads/unloads, d removes cached model, esc closes"))
529+ lines = append(lines, th.FGColor(th.Muted, "enter loads/unloads, d removes cached model, esc closes"))
530530 case llamaConfirmUnload:
531531 lines = append(lines, "", " Unload "+d.selected+"?")
532532 for index, label := range []string{"Yes", "No"} {
@@ -534,7 +534,7 @@ func (d *llamaDialog) Render(th tui.Theme, width int) []string {
534534 if index == d.cursor {
535535 lines = append(lines, th.PadHighlight(line, width))
536536 } else {
537- lines = append(lines, th.FG256(th.Muted, line))
537+ lines = append(lines, th.FGColor(th.Muted, line))
538538 }
539539 }
540540 case llamaConfirmRemove:
@@ -544,11 +544,11 @@ func (d *llamaDialog) Render(th tui.Theme, width int) []string {
544544 if index == d.cursor {
545545 lines = append(lines, th.PadHighlight(line, width))
546546 } else {
547- lines = append(lines, th.FG256(th.Muted, line))
547+ lines = append(lines, th.FGColor(th.Muted, line))
548548 }
549549 }
550550 case llamaSearch:
551- lines = append(lines, "", th.FG256(th.Muted, " Model name or owner/repository"), th.FG256(th.FG, " "+d.query), "")
551+ lines = append(lines, "", th.FGColor(th.Muted, " Model name or owner/repository"), th.FGColor(th.FG, " "+d.query), "")
552552 start, end := dialogWindow(d.cursor, len(d.results), 10)
553553 for index := start; index < end; index++ {
554554 result := d.results[index]
@@ -556,20 +556,20 @@ func (d *llamaDialog) Render(th tui.Theme, width int) []string {
556556 if index == d.cursor {
557557 lines = append(lines, th.PadHighlight(line, width))
558558 } else {
559- lines = append(lines, th.FG256(th.Muted, line))
559+ lines = append(lines, th.FGColor(th.Muted, line))
560560 }
561561 }
562- lines = append(lines, th.FG256(th.Muted, "enter selects, esc goes back"))
562+ lines = append(lines, th.FGColor(th.Muted, "enter selects, esc goes back"))
563563 case llamaModelDetails:
564564 lines = append(lines, "", " "+d.details.ID)
565565 if d.details.Gated != "" {
566- lines = append(lines, th.FG256(th.Error, " access approval and HF_TOKEN may be required"))
566+ lines = append(lines, th.FGColor(th.Error, " access approval and HF_TOKEN may be required"))
567567 }
568- lines = append(lines, th.FG256(th.Muted, " enter downloads, esc goes back"))
568+ lines = append(lines, th.FGColor(th.Muted, " enter downloads, esc goes back"))
569569 case llamaQuantizations:
570570 lines = append(lines, "", " Select quantization for "+d.details.ID)
571571 if d.details.Gated != "" {
572- lines = append(lines, th.FG256(th.Error, " gated model: approve access on huggingface.co and configure HF_TOKEN on the router"))
572+ lines = append(lines, th.FGColor(th.Error, " gated model: approve access on huggingface.co and configure HF_TOKEN on the router"))
573573 }
574574 start, end := dialogWindow(d.cursor, len(d.details.Quantizations), 10)
575575 for index := start; index < end; index++ {
@@ -585,24 +585,24 @@ func (d *llamaDialog) Render(th tui.Theme, width int) []string {
585585 if index == d.cursor {
586586 lines = append(lines, th.PadHighlight(line, width))
587587 } else {
588- lines = append(lines, th.FG256(th.Muted, line))
588+ lines = append(lines, th.FGColor(th.Muted, line))
589589 }
590590 }
591591 case llamaProgress:
592- lines = append(lines, "", " "+d.selected, th.FG256(th.Muted, " "+d.progress.Message))
592+ lines = append(lines, "", " "+d.selected, th.FGColor(th.Muted, " "+d.progress.Message))
593593 if d.progress.HasRatio {
594594 const cells = 32
595595 filled := int(d.progress.Ratio*float64(cells) + .5)
596596 filled = max(0, min(cells, filled))
597597 bar := strings.Repeat("#", filled) + strings.Repeat("-", cells-filled)
598- lines = append(lines, th.FG256(th.Accent, fmt.Sprintf(" %s %d%%", bar, int(d.progress.Ratio*100+.5))))
598+ lines = append(lines, th.FGColor(th.Accent, fmt.Sprintf(" %s %d%%", bar, int(d.progress.Ratio*100+.5))))
599599 }
600600 if d.progress.Detail != "" {
601- lines = append(lines, th.FG256(th.Muted, " "+d.progress.Detail))
601+ lines = append(lines, th.FGColor(th.Muted, " "+d.progress.Detail))
602602 }
603- lines = append(lines, th.FG256(th.Muted, " esc stops"))
603+ lines = append(lines, th.FGColor(th.Muted, " esc stops"))
604604 case llamaError:
605- lines = append(lines, th.FG256(th.Error, " "+d.message), th.FG256(th.Muted, " esc closes"))
605+ lines = append(lines, th.FGColor(th.Error, " "+d.message), th.FGColor(th.Muted, " esc closes"))
606606 }
607607 return append(lines, frameRule(th, width))
608608 }
@@ -497,15 +497,15 @@ func (d *llamaDialog) Render(th tui.Theme, width int) []string {
497 }497 }
498 lines := []string{frameHeader(th, "llama.cpp models", width)}498 lines := []string{frameHeader(th, "llama.cpp models", width)}
499 if d.client != nil {499 if d.client != nil {
500- lines = append(lines, th.FG256(th.Muted, d.client.ServerURL))500+ lines = append(lines, th.FGColor(th.Muted, d.client.ServerURL))
501 }501 }
502 if d.message != "" {502 if d.message != "" {
503- lines = append(lines, th.FG256(th.Muted, d.message))503+ lines = append(lines, th.FGColor(th.Muted, d.message))
504 }504 }
505 505
506 switch d.step {506 switch d.step {
507 case llamaLoading:507 case llamaLoading:
508- lines = append(lines, th.FG256(th.Muted, " working..."))508+ lines = append(lines, th.FGColor(th.Muted, " working..."))
509 case llamaModels:509 case llamaModels:
510 const visible = 12510 const visible = 12
511 start, end := dialogWindow(d.cursor, len(d.models)+1, visible)511 start, end := dialogWindow(d.cursor, len(d.models)+1, visible)
@@ -523,10 +523,10 @@ func (d *llamaDialog) Render(th tui.Theme, width int) []string {
523 if index == d.cursor {523 if index == d.cursor {
524 lines = append(lines, th.PadHighlight(line, width))524 lines = append(lines, th.PadHighlight(line, width))
525 } else {525 } else {
526- lines = append(lines, th.FG256(th.Muted, line))526+ lines = append(lines, th.FGColor(th.Muted, line))
527 }527 }
528 }528 }
529- lines = append(lines, th.FG256(th.Muted, "enter loads/unloads, d removes cached model, esc closes"))529+ lines = append(lines, th.FGColor(th.Muted, "enter loads/unloads, d removes cached model, esc closes"))
530 case llamaConfirmUnload:530 case llamaConfirmUnload:
531 lines = append(lines, "", " Unload "+d.selected+"?")531 lines = append(lines, "", " Unload "+d.selected+"?")
532 for index, label := range []string{"Yes", "No"} {532 for index, label := range []string{"Yes", "No"} {
@@ -534,7 +534,7 @@ func (d *llamaDialog) Render(th tui.Theme, width int) []string {
534 if index == d.cursor {534 if index == d.cursor {
535 lines = append(lines, th.PadHighlight(line, width))535 lines = append(lines, th.PadHighlight(line, width))
536 } else {536 } else {
537- lines = append(lines, th.FG256(th.Muted, line))537+ lines = append(lines, th.FGColor(th.Muted, line))
538 }538 }
539 }539 }
540 case llamaConfirmRemove:540 case llamaConfirmRemove:
@@ -544,11 +544,11 @@ func (d *llamaDialog) Render(th tui.Theme, width int) []string {
544 if index == d.cursor {544 if index == d.cursor {
545 lines = append(lines, th.PadHighlight(line, width))545 lines = append(lines, th.PadHighlight(line, width))
546 } else {546 } else {
547- lines = append(lines, th.FG256(th.Muted, line))547+ lines = append(lines, th.FGColor(th.Muted, line))
548 }548 }
549 }549 }
550 case llamaSearch:550 case llamaSearch:
551- lines = append(lines, "", th.FG256(th.Muted, " Model name or owner/repository"), th.FG256(th.FG, " "+d.query), "")551+ lines = append(lines, "", th.FGColor(th.Muted, " Model name or owner/repository"), th.FGColor(th.FG, " "+d.query), "")
552 start, end := dialogWindow(d.cursor, len(d.results), 10)552 start, end := dialogWindow(d.cursor, len(d.results), 10)
553 for index := start; index < end; index++ {553 for index := start; index < end; index++ {
554 result := d.results[index]554 result := d.results[index]
@@ -556,20 +556,20 @@ func (d *llamaDialog) Render(th tui.Theme, width int) []string {
556 if index == d.cursor {556 if index == d.cursor {
557 lines = append(lines, th.PadHighlight(line, width))557 lines = append(lines, th.PadHighlight(line, width))
558 } else {558 } else {
559- lines = append(lines, th.FG256(th.Muted, line))559+ lines = append(lines, th.FGColor(th.Muted, line))
560 }560 }
561 }561 }
562- lines = append(lines, th.FG256(th.Muted, "enter selects, esc goes back"))562+ lines = append(lines, th.FGColor(th.Muted, "enter selects, esc goes back"))
563 case llamaModelDetails:563 case llamaModelDetails:
564 lines = append(lines, "", " "+d.details.ID)564 lines = append(lines, "", " "+d.details.ID)
565 if d.details.Gated != "" {565 if d.details.Gated != "" {
566- lines = append(lines, th.FG256(th.Error, " access approval and HF_TOKEN may be required"))566+ lines = append(lines, th.FGColor(th.Error, " access approval and HF_TOKEN may be required"))
567 }567 }
568- lines = append(lines, th.FG256(th.Muted, " enter downloads, esc goes back"))568+ lines = append(lines, th.FGColor(th.Muted, " enter downloads, esc goes back"))
569 case llamaQuantizations:569 case llamaQuantizations:
570 lines = append(lines, "", " Select quantization for "+d.details.ID)570 lines = append(lines, "", " Select quantization for "+d.details.ID)
571 if d.details.Gated != "" {571 if d.details.Gated != "" {
572- lines = append(lines, th.FG256(th.Error, " gated model: approve access on huggingface.co and configure HF_TOKEN on the router"))572+ lines = append(lines, th.FGColor(th.Error, " gated model: approve access on huggingface.co and configure HF_TOKEN on the router"))
573 }573 }
574 start, end := dialogWindow(d.cursor, len(d.details.Quantizations), 10)574 start, end := dialogWindow(d.cursor, len(d.details.Quantizations), 10)
575 for index := start; index < end; index++ {575 for index := start; index < end; index++ {
@@ -585,24 +585,24 @@ func (d *llamaDialog) Render(th tui.Theme, width int) []string {
585 if index == d.cursor {585 if index == d.cursor {
586 lines = append(lines, th.PadHighlight(line, width))586 lines = append(lines, th.PadHighlight(line, width))
587 } else {587 } else {
588- lines = append(lines, th.FG256(th.Muted, line))588+ lines = append(lines, th.FGColor(th.Muted, line))
589 }589 }
590 }590 }
591 case llamaProgress:591 case llamaProgress:
592- lines = append(lines, "", " "+d.selected, th.FG256(th.Muted, " "+d.progress.Message))592+ lines = append(lines, "", " "+d.selected, th.FGColor(th.Muted, " "+d.progress.Message))
593 if d.progress.HasRatio {593 if d.progress.HasRatio {
594 const cells = 32594 const cells = 32
595 filled := int(d.progress.Ratio*float64(cells) + .5)595 filled := int(d.progress.Ratio*float64(cells) + .5)
596 filled = max(0, min(cells, filled))596 filled = max(0, min(cells, filled))
597 bar := strings.Repeat("#", filled) + strings.Repeat("-", cells-filled)597 bar := strings.Repeat("#", filled) + strings.Repeat("-", cells-filled)
598- lines = append(lines, th.FG256(th.Accent, fmt.Sprintf(" %s %d%%", bar, int(d.progress.Ratio*100+.5))))598+ lines = append(lines, th.FGColor(th.Accent, fmt.Sprintf(" %s %d%%", bar, int(d.progress.Ratio*100+.5))))
599 }599 }
600 if d.progress.Detail != "" {600 if d.progress.Detail != "" {
601- lines = append(lines, th.FG256(th.Muted, " "+d.progress.Detail))601+ lines = append(lines, th.FGColor(th.Muted, " "+d.progress.Detail))
602 }602 }
603- lines = append(lines, th.FG256(th.Muted, " esc stops"))603+ lines = append(lines, th.FGColor(th.Muted, " esc stops"))
604 case llamaError:604 case llamaError:
605- lines = append(lines, th.FG256(th.Error, " "+d.message), th.FG256(th.Muted, " esc closes"))605+ lines = append(lines, th.FGColor(th.Error, " "+d.message), th.FGColor(th.Muted, " esc closes"))
606 }606 }
607 return append(lines, frameRule(th, width))607 return append(lines, frameRule(th, width))
608 }608 }
M packages/agent/modes/login_dialog.go +30 -30
@@ -135,13 +135,13 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string {
135135 }
136136 lines = append(lines, frameHeader(th, "login", width))
137137 lines = append(lines, d.renderStatusLines(th)...)
138- lines = append(lines, th.FG256(th.Muted, "choose login method (↑/↓, enter, esc to cancel):"))
138+ lines = append(lines, th.FGColor(th.Muted, "choose login method (↑/↓, enter, esc to cancel):"))
139139 for i, o := range opts {
140140 plain := " " + o
141141 if i == d.cursor {
142142 lines = append(lines, th.PadHighlight(plain, width))
143143 } else {
144- lines = append(lines, th.FG256(th.Muted, plain))
144+ lines = append(lines, th.FGColor(th.Muted, plain))
145145 }
146146 }
147147 lines = append(lines, frameRule(th, width))
@@ -156,7 +156,7 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string {
156156 hint = fmt.Sprintf("filter: %s (%d matches)", d.providerQuery, len(opts))
157157 }
158158 }
159- lines = append(lines, th.FG256(th.Muted, hint))
159+ lines = append(lines, th.FGColor(th.Muted, hint))
160160 start, end := d.providerPage(len(opts))
161161 for i := start; i < end; i++ {
162162 o := opts[i]
@@ -170,81 +170,81 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string {
170170 if i == d.cursor {
171171 lines = append(lines, th.PadHighlight(plain, width))
172172 } else {
173- lines = append(lines, th.FG256(th.Muted, label)+th.FG256(th.Accent, tag))
173+ lines = append(lines, th.FGColor(th.Muted, label)+th.FGColor(th.Accent, tag))
174174 }
175175 }
176176 if len(opts) == 0 {
177- lines = append(lines, th.FG256(th.Muted, " no providers match "+fmt.Sprintf("%q", d.providerQuery)))
177+ lines = append(lines, th.FGColor(th.Muted, " no providers match "+fmt.Sprintf("%q", d.providerQuery)))
178178 } else if len(opts) > loginProviderPageSize {
179- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" (%d/%d)", d.cursor+1, len(opts))))
179+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" (%d/%d)", d.cursor+1, len(opts))))
180180 }
181181 lines = append(lines, frameRule(th, width))
182182 case loginStepLlamaURL:
183183 lines = append(lines, frameHeader(th, "login - api key - llama.cpp", width))
184- lines = append(lines, th.FG256(th.Muted, "llama.cpp router URL:"))
184+ lines = append(lines, th.FGColor(th.Muted, "llama.cpp router URL:"))
185185 if d.llamaEd == nil {
186186 d.llamaEd = tui.NewEditor(th.AccentBar(th.Accent))
187187 }
188188 edLines, _, _ := d.llamaEd.Render(width - 2)
189189 lines = append(lines, edLines...)
190190 if d.message != "" {
191- lines = append(lines, th.FG256(th.Error, d.message))
191+ lines = append(lines, th.FGColor(th.Error, d.message))
192192 }
193- lines = append(lines, "", th.FG256(th.Muted, "enter continues - esc cancels"), frameRule(th, width))
193+ lines = append(lines, "", th.FGColor(th.Muted, "enter continues - esc cancels"), frameRule(th, width))
194194 case loginStepLlamaKey:
195195 lines = append(lines, frameHeader(th, "login - api key - llama.cpp", width))
196- lines = append(lines, th.FG256(th.Muted, "router: "+d.llamaURL), "", th.FG256(th.Muted, "API key (optional):"))
196+ lines = append(lines, th.FGColor(th.Muted, "router: "+d.llamaURL), "", th.FGColor(th.Muted, "API key (optional):"))
197197 if d.llamaEd == nil {
198198 d.llamaEd = tui.NewEditor(th.AccentBar(th.Accent))
199199 }
200200 d.llamaEd.Mask = true
201201 edLines, _, _ := d.llamaEd.Render(width - 2)
202202 lines = append(lines, edLines...)
203- lines = append(lines, "", th.FG256(th.Muted, "enter saves - esc cancels"), frameRule(th, width))
203+ lines = append(lines, "", th.FGColor(th.Muted, "enter saves - esc cancels"), frameRule(th, width))
204204 case loginStepWaiting:
205205 lines = append(lines, frameHeader(th, "login - "+d.method+" - "+providerLabel(d.provider), width))
206- lines = append(lines, th.FG256(th.Muted, "open this URL in a browser:"))
206+ lines = append(lines, th.FGColor(th.Muted, "open this URL in a browser:"))
207207 wrapW := width - 2
208208 if wrapW < 20 {
209209 wrapW = 20
210210 }
211211 for _, seg := range tui.WrapANSILine(d.url, wrapW) {
212- lines = append(lines, th.FG256(th.Accent, seg))
212+ lines = append(lines, th.FGColor(th.Accent, seg))
213213 }
214214 lines = append(lines, "")
215215 if d.provider == "kimi" || d.provider == "xai" || d.provider == "github-copilot" {
216- lines = append(lines, th.FG256(th.Muted, "complete sign-in in the browser - esc cancels"))
216+ lines = append(lines, th.FGColor(th.Muted, "complete sign-in in the browser - esc cancels"))
217217 lines = append(lines, frameRule(th, width))
218218 break
219219 }
220- lines = append(lines, th.FG256(th.Muted, "paste the authorization code (or full redirect URL / code#state):"))
220+ lines = append(lines, th.FGColor(th.Muted, "paste the authorization code (or full redirect URL / code#state):"))
221221 if d.codeEd == nil {
222222 d.codeEd = tui.NewEditor(th.AccentBar(th.Accent))
223223 }
224224 edLines, _, _ := d.codeEd.Render(width - 2)
225225 lines = append(lines, edLines...)
226226 lines = append(lines, "")
227- lines = append(lines, th.FG256(th.Muted, "enter submits - esc cancels - waiting for browser callback in background"))
227+ lines = append(lines, th.FGColor(th.Muted, "enter submits - esc cancels - waiting for browser callback in background"))
228228 lines = append(lines, frameRule(th, width))
229229 case loginStepPasteCode:
230230 lines = append(lines, frameHeader(th, "login - "+d.method+" - "+providerLabel(d.provider)+" - paste code", width))
231- lines = append(lines, th.FG256(th.Muted, "open this URL in any browser:"))
231+ lines = append(lines, th.FGColor(th.Muted, "open this URL in any browser:"))
232232 wrapW := width - 2
233233 if wrapW < 20 {
234234 wrapW = 20
235235 }
236236 for _, seg := range tui.WrapANSILine(d.url, wrapW) {
237- lines = append(lines, th.FG256(th.Accent, seg))
237+ lines = append(lines, th.FGColor(th.Accent, seg))
238238 }
239239 lines = append(lines, "")
240- lines = append(lines, th.FG256(th.Muted, "paste the authorization code (or full redirect URL / code#state):"))
240+ lines = append(lines, th.FGColor(th.Muted, "paste the authorization code (or full redirect URL / code#state):"))
241241 if d.codeEd == nil {
242242 d.codeEd = tui.NewEditor(th.AccentBar(th.Accent))
243243 }
244244 edLines, _, _ := d.codeEd.Render(width - 2)
245245 lines = append(lines, edLines...)
246246 lines = append(lines, "")
247- lines = append(lines, th.FG256(th.Muted, "enter submits - esc cancels"))
247+ lines = append(lines, th.FGColor(th.Muted, "enter submits - esc cancels"))
248248 lines = append(lines, frameRule(th, width))
249249 case loginStepInfo:
250250 title := d.infoTitle
@@ -254,18 +254,18 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string {
254254 lines = append(lines, frameHeader(th, title, width))
255255 lines = append(lines, d.infoLines...)
256256 lines = append(lines, "")
257- lines = append(lines, th.FG256(th.Muted, "press any key to close"))
257+ lines = append(lines, th.FGColor(th.Muted, "press any key to close"))
258258 lines = append(lines, frameRule(th, width))
259259 case loginStepDone:
260260 title := "login - failed"
261- body := th.FG256(th.Error, d.message)
261+ body := th.FGColor(th.Error, d.message)
262262 if d.success {
263263 title = "login - success"
264- body = th.FG256(th.Tool, fmt.Sprintf("logged in to %s via %s", providerLabel(d.provider), d.method))
264+ body = th.FGColor(th.Tool, fmt.Sprintf("logged in to %s via %s", providerLabel(d.provider), d.method))
265265 }
266266 lines = append(lines, frameHeader(th, title, width))
267267 lines = append(lines, body)
268- lines = append(lines, th.FG256(th.Muted, "press any key to close"))
268+ lines = append(lines, th.FGColor(th.Muted, "press any key to close"))
269269 lines = append(lines, frameRule(th, width))
270270 }
271271 return lines
@@ -384,14 +384,14 @@ func (d *loginDialog) renderStatusLines(th tui.Theme) []string {
384384 var mark, body string
385385 switch method {
386386 case "apikey":
387- mark = th.FG256(th.Tool, "✓")
388- body = th.FG256(th.Muted, label+": api key")
387+ mark = th.FGColor(th.Tool, "✓")
388+ body = th.FGColor(th.Muted, label+": api key")
389389 case "oauth":
390- mark = th.FG256(th.Tool, "✓")
391- body = th.FG256(th.Muted, label+": subscription")
390+ mark = th.FGColor(th.Tool, "✓")
391+ body = th.FGColor(th.Muted, label+": subscription")
392392 default:
393- mark = th.FG256(th.Muted, "–")
394- body = th.FG256(th.Muted, label+": not logged in")
393+ mark = th.FGColor(th.Muted, "–")
394+ body = th.FGColor(th.Muted, label+": not logged in")
395395 }
396396 return " " + mark + " " + body
397397 }
@@ -135,13 +135,13 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string {
135 }135 }
136 lines = append(lines, frameHeader(th, "login", width))136 lines = append(lines, frameHeader(th, "login", width))
137 lines = append(lines, d.renderStatusLines(th)...)137 lines = append(lines, d.renderStatusLines(th)...)
138- lines = append(lines, th.FG256(th.Muted, "choose login method (↑/↓, enter, esc to cancel):"))138+ lines = append(lines, th.FGColor(th.Muted, "choose login method (↑/↓, enter, esc to cancel):"))
139 for i, o := range opts {139 for i, o := range opts {
140 plain := " " + o140 plain := " " + o
141 if i == d.cursor {141 if i == d.cursor {
142 lines = append(lines, th.PadHighlight(plain, width))142 lines = append(lines, th.PadHighlight(plain, width))
143 } else {143 } else {
144- lines = append(lines, th.FG256(th.Muted, plain))144+ lines = append(lines, th.FGColor(th.Muted, plain))
145 }145 }
146 }146 }
147 lines = append(lines, frameRule(th, width))147 lines = append(lines, frameRule(th, width))
@@ -156,7 +156,7 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string {
156 hint = fmt.Sprintf("filter: %s (%d matches)", d.providerQuery, len(opts))156 hint = fmt.Sprintf("filter: %s (%d matches)", d.providerQuery, len(opts))
157 }157 }
158 }158 }
159- lines = append(lines, th.FG256(th.Muted, hint))159+ lines = append(lines, th.FGColor(th.Muted, hint))
160 start, end := d.providerPage(len(opts))160 start, end := d.providerPage(len(opts))
161 for i := start; i < end; i++ {161 for i := start; i < end; i++ {
162 o := opts[i]162 o := opts[i]
@@ -170,81 +170,81 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string {
170 if i == d.cursor {170 if i == d.cursor {
171 lines = append(lines, th.PadHighlight(plain, width))171 lines = append(lines, th.PadHighlight(plain, width))
172 } else {172 } else {
173- lines = append(lines, th.FG256(th.Muted, label)+th.FG256(th.Accent, tag))173+ lines = append(lines, th.FGColor(th.Muted, label)+th.FGColor(th.Accent, tag))
174 }174 }
175 }175 }
176 if len(opts) == 0 {176 if len(opts) == 0 {
177- lines = append(lines, th.FG256(th.Muted, " no providers match "+fmt.Sprintf("%q", d.providerQuery)))177+ lines = append(lines, th.FGColor(th.Muted, " no providers match "+fmt.Sprintf("%q", d.providerQuery)))
178 } else if len(opts) > loginProviderPageSize {178 } else if len(opts) > loginProviderPageSize {
179- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" (%d/%d)", d.cursor+1, len(opts))))179+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" (%d/%d)", d.cursor+1, len(opts))))
180 }180 }
181 lines = append(lines, frameRule(th, width))181 lines = append(lines, frameRule(th, width))
182 case loginStepLlamaURL:182 case loginStepLlamaURL:
183 lines = append(lines, frameHeader(th, "login - api key - llama.cpp", width))183 lines = append(lines, frameHeader(th, "login - api key - llama.cpp", width))
184- lines = append(lines, th.FG256(th.Muted, "llama.cpp router URL:"))184+ lines = append(lines, th.FGColor(th.Muted, "llama.cpp router URL:"))
185 if d.llamaEd == nil {185 if d.llamaEd == nil {
186 d.llamaEd = tui.NewEditor(th.AccentBar(th.Accent))186 d.llamaEd = tui.NewEditor(th.AccentBar(th.Accent))
187 }187 }
188 edLines, _, _ := d.llamaEd.Render(width - 2)188 edLines, _, _ := d.llamaEd.Render(width - 2)
189 lines = append(lines, edLines...)189 lines = append(lines, edLines...)
190 if d.message != "" {190 if d.message != "" {
191- lines = append(lines, th.FG256(th.Error, d.message))191+ lines = append(lines, th.FGColor(th.Error, d.message))
192 }192 }
193- lines = append(lines, "", th.FG256(th.Muted, "enter continues - esc cancels"), frameRule(th, width))193+ lines = append(lines, "", th.FGColor(th.Muted, "enter continues - esc cancels"), frameRule(th, width))
194 case loginStepLlamaKey:194 case loginStepLlamaKey:
195 lines = append(lines, frameHeader(th, "login - api key - llama.cpp", width))195 lines = append(lines, frameHeader(th, "login - api key - llama.cpp", width))
196- lines = append(lines, th.FG256(th.Muted, "router: "+d.llamaURL), "", th.FG256(th.Muted, "API key (optional):"))196+ lines = append(lines, th.FGColor(th.Muted, "router: "+d.llamaURL), "", th.FGColor(th.Muted, "API key (optional):"))
197 if d.llamaEd == nil {197 if d.llamaEd == nil {
198 d.llamaEd = tui.NewEditor(th.AccentBar(th.Accent))198 d.llamaEd = tui.NewEditor(th.AccentBar(th.Accent))
199 }199 }
200 d.llamaEd.Mask = true200 d.llamaEd.Mask = true
201 edLines, _, _ := d.llamaEd.Render(width - 2)201 edLines, _, _ := d.llamaEd.Render(width - 2)
202 lines = append(lines, edLines...)202 lines = append(lines, edLines...)
203- lines = append(lines, "", th.FG256(th.Muted, "enter saves - esc cancels"), frameRule(th, width))203+ lines = append(lines, "", th.FGColor(th.Muted, "enter saves - esc cancels"), frameRule(th, width))
204 case loginStepWaiting:204 case loginStepWaiting:
205 lines = append(lines, frameHeader(th, "login - "+d.method+" - "+providerLabel(d.provider), width))205 lines = append(lines, frameHeader(th, "login - "+d.method+" - "+providerLabel(d.provider), width))
206- lines = append(lines, th.FG256(th.Muted, "open this URL in a browser:"))206+ lines = append(lines, th.FGColor(th.Muted, "open this URL in a browser:"))
207 wrapW := width - 2207 wrapW := width - 2
208 if wrapW < 20 {208 if wrapW < 20 {
209 wrapW = 20209 wrapW = 20
210 }210 }
211 for _, seg := range tui.WrapANSILine(d.url, wrapW) {211 for _, seg := range tui.WrapANSILine(d.url, wrapW) {
212- lines = append(lines, th.FG256(th.Accent, seg))212+ lines = append(lines, th.FGColor(th.Accent, seg))
213 }213 }
214 lines = append(lines, "")214 lines = append(lines, "")
215 if d.provider == "kimi" || d.provider == "xai" || d.provider == "github-copilot" {215 if d.provider == "kimi" || d.provider == "xai" || d.provider == "github-copilot" {
216- lines = append(lines, th.FG256(th.Muted, "complete sign-in in the browser - esc cancels"))216+ lines = append(lines, th.FGColor(th.Muted, "complete sign-in in the browser - esc cancels"))
217 lines = append(lines, frameRule(th, width))217 lines = append(lines, frameRule(th, width))
218 break218 break
219 }219 }
220- lines = append(lines, th.FG256(th.Muted, "paste the authorization code (or full redirect URL / code#state):"))220+ lines = append(lines, th.FGColor(th.Muted, "paste the authorization code (or full redirect URL / code#state):"))
221 if d.codeEd == nil {221 if d.codeEd == nil {
222 d.codeEd = tui.NewEditor(th.AccentBar(th.Accent))222 d.codeEd = tui.NewEditor(th.AccentBar(th.Accent))
223 }223 }
224 edLines, _, _ := d.codeEd.Render(width - 2)224 edLines, _, _ := d.codeEd.Render(width - 2)
225 lines = append(lines, edLines...)225 lines = append(lines, edLines...)
226 lines = append(lines, "")226 lines = append(lines, "")
227- lines = append(lines, th.FG256(th.Muted, "enter submits - esc cancels - waiting for browser callback in background"))227+ lines = append(lines, th.FGColor(th.Muted, "enter submits - esc cancels - waiting for browser callback in background"))
228 lines = append(lines, frameRule(th, width))228 lines = append(lines, frameRule(th, width))
229 case loginStepPasteCode:229 case loginStepPasteCode:
230 lines = append(lines, frameHeader(th, "login - "+d.method+" - "+providerLabel(d.provider)+" - paste code", width))230 lines = append(lines, frameHeader(th, "login - "+d.method+" - "+providerLabel(d.provider)+" - paste code", width))
231- lines = append(lines, th.FG256(th.Muted, "open this URL in any browser:"))231+ lines = append(lines, th.FGColor(th.Muted, "open this URL in any browser:"))
232 wrapW := width - 2232 wrapW := width - 2
233 if wrapW < 20 {233 if wrapW < 20 {
234 wrapW = 20234 wrapW = 20
235 }235 }
236 for _, seg := range tui.WrapANSILine(d.url, wrapW) {236 for _, seg := range tui.WrapANSILine(d.url, wrapW) {
237- lines = append(lines, th.FG256(th.Accent, seg))237+ lines = append(lines, th.FGColor(th.Accent, seg))
238 }238 }
239 lines = append(lines, "")239 lines = append(lines, "")
240- lines = append(lines, th.FG256(th.Muted, "paste the authorization code (or full redirect URL / code#state):"))240+ lines = append(lines, th.FGColor(th.Muted, "paste the authorization code (or full redirect URL / code#state):"))
241 if d.codeEd == nil {241 if d.codeEd == nil {
242 d.codeEd = tui.NewEditor(th.AccentBar(th.Accent))242 d.codeEd = tui.NewEditor(th.AccentBar(th.Accent))
243 }243 }
244 edLines, _, _ := d.codeEd.Render(width - 2)244 edLines, _, _ := d.codeEd.Render(width - 2)
245 lines = append(lines, edLines...)245 lines = append(lines, edLines...)
246 lines = append(lines, "")246 lines = append(lines, "")
247- lines = append(lines, th.FG256(th.Muted, "enter submits - esc cancels"))247+ lines = append(lines, th.FGColor(th.Muted, "enter submits - esc cancels"))
248 lines = append(lines, frameRule(th, width))248 lines = append(lines, frameRule(th, width))
249 case loginStepInfo:249 case loginStepInfo:
250 title := d.infoTitle250 title := d.infoTitle
@@ -254,18 +254,18 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string {
254 lines = append(lines, frameHeader(th, title, width))254 lines = append(lines, frameHeader(th, title, width))
255 lines = append(lines, d.infoLines...)255 lines = append(lines, d.infoLines...)
256 lines = append(lines, "")256 lines = append(lines, "")
257- lines = append(lines, th.FG256(th.Muted, "press any key to close"))257+ lines = append(lines, th.FGColor(th.Muted, "press any key to close"))
258 lines = append(lines, frameRule(th, width))258 lines = append(lines, frameRule(th, width))
259 case loginStepDone:259 case loginStepDone:
260 title := "login - failed"260 title := "login - failed"
261- body := th.FG256(th.Error, d.message)261+ body := th.FGColor(th.Error, d.message)
262 if d.success {262 if d.success {
263 title = "login - success"263 title = "login - success"
264- body = th.FG256(th.Tool, fmt.Sprintf("logged in to %s via %s", providerLabel(d.provider), d.method))264+ body = th.FGColor(th.Tool, fmt.Sprintf("logged in to %s via %s", providerLabel(d.provider), d.method))
265 }265 }
266 lines = append(lines, frameHeader(th, title, width))266 lines = append(lines, frameHeader(th, title, width))
267 lines = append(lines, body)267 lines = append(lines, body)
268- lines = append(lines, th.FG256(th.Muted, "press any key to close"))268+ lines = append(lines, th.FGColor(th.Muted, "press any key to close"))
269 lines = append(lines, frameRule(th, width))269 lines = append(lines, frameRule(th, width))
270 }270 }
271 return lines271 return lines
@@ -384,14 +384,14 @@ func (d *loginDialog) renderStatusLines(th tui.Theme) []string {
384 var mark, body string384 var mark, body string
385 switch method {385 switch method {
386 case "apikey":386 case "apikey":
387- mark = th.FG256(th.Tool, "✓")387+ mark = th.FGColor(th.Tool, "✓")
388- body = th.FG256(th.Muted, label+": api key")388+ body = th.FGColor(th.Muted, label+": api key")
389 case "oauth":389 case "oauth":
390- mark = th.FG256(th.Tool, "✓")390+ mark = th.FGColor(th.Tool, "✓")
391- body = th.FG256(th.Muted, label+": subscription")391+ body = th.FGColor(th.Muted, label+": subscription")
392 default:392 default:
393- mark = th.FG256(th.Muted, "–")393+ mark = th.FGColor(th.Muted, "–")
394- body = th.FG256(th.Muted, label+": not logged in")394+ body = th.FGColor(th.Muted, label+": not logged in")
395 }395 }
396 return " " + mark + " " + body396 return " " + mark + " " + body
397 }397 }
M packages/agent/modes/logout_dialog.go +2 -2
@@ -86,7 +86,7 @@ func (d *logoutDialog) Render(th tui.Theme, width int) []string {
8686 }
8787 var lines []string
8888 lines = append(lines, frameHeader(th, "logout", width))
89- lines = append(lines, th.FG256(th.Muted, "choose what to log out of (\u2191/\u2193, enter, esc to cancel):"))
89+ lines = append(lines, th.FGColor(th.Muted, "choose what to log out of (\u2191/\u2193, enter, esc to cancel):"))
9090 for i, it := range d.items {
9191 plain := " " + it.label
9292 if it.method != "" {
@@ -95,7 +95,7 @@ func (d *logoutDialog) Render(th tui.Theme, width int) []string {
9595 if i == d.cursor {
9696 lines = append(lines, th.PadHighlight(plain, width))
9797 } else {
98- lines = append(lines, th.FG256(th.Muted, plain))
98+ lines = append(lines, th.FGColor(th.Muted, plain))
9999 }
100100 }
101101 lines = append(lines, frameRule(th, width))
@@ -86,7 +86,7 @@ func (d *logoutDialog) Render(th tui.Theme, width int) []string {
86 }86 }
87 var lines []string87 var lines []string
88 lines = append(lines, frameHeader(th, "logout", width))88 lines = append(lines, frameHeader(th, "logout", width))
89- lines = append(lines, th.FG256(th.Muted, "choose what to log out of (\u2191/\u2193, enter, esc to cancel):"))89+ lines = append(lines, th.FGColor(th.Muted, "choose what to log out of (\u2191/\u2193, enter, esc to cancel):"))
90 for i, it := range d.items {90 for i, it := range d.items {
91 plain := " " + it.label91 plain := " " + it.label
92 if it.method != "" {92 if it.method != "" {
@@ -95,7 +95,7 @@ func (d *logoutDialog) Render(th tui.Theme, width int) []string {
95 if i == d.cursor {95 if i == d.cursor {
96 lines = append(lines, th.PadHighlight(plain, width))96 lines = append(lines, th.PadHighlight(plain, width))
97 } else {97 } else {
98- lines = append(lines, th.FG256(th.Muted, plain))98+ lines = append(lines, th.FGColor(th.Muted, plain))
99 }99 }
100 }100 }
101 lines = append(lines, frameRule(th, width))101 lines = append(lines, frameRule(th, width))
M packages/agent/modes/model_dialog.go +7 -7
@@ -153,13 +153,13 @@ func (d *modelDialog) Render(th tui.Theme, width int) []string {
153153 } else {
154154 hint += " - type to filter"
155155 }
156- lines = append(lines, th.FG256(th.Muted, hint))
156+ lines = append(lines, th.FGColor(th.Muted, hint))
157157 if d.showReasoning {
158158 reasoning := d.reasoning
159159 if reasoning == "" {
160160 reasoning = "off"
161161 }
162- lines = append(lines, th.FG256(th.Muted, "✦ supports reasoning; current reasoning: "+reasoning+"; change with /reasoning"))
162+ lines = append(lines, th.FGColor(th.Muted, "✦ supports reasoning; current reasoning: "+reasoning+"; change with /reasoning"))
163163 }
164164
165165 if len(d.view) == 0 {
@@ -167,7 +167,7 @@ func (d *modelDialog) Render(th tui.Theme, width int) []string {
167167 if len(d.all) == 0 {
168168 msg = " no credentials found - run /login to add an api key or subscription"
169169 }
170- lines = append(lines, th.FG256(th.Muted, msg))
170+ lines = append(lines, th.FGColor(th.Muted, msg))
171171 lines = append(lines, frameRule(th, width))
172172 return lines
173173 }
@@ -217,18 +217,18 @@ func (d *modelDialog) Render(th tui.Theme, width int) []string {
217217 if i == d.cursor {
218218 lines = append(lines, th.PadHighlight(plain, width))
219219 } else {
220- lines = append(lines, th.FG256(th.Muted, plain))
220+ lines = append(lines, th.FGColor(th.Muted, plain))
221221 }
222222 }
223223
224224 if start > 0 {
225- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" ... %d more above", start)))
225+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" ... %d more above", start)))
226226 }
227227 if end < len(d.view) {
228- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" ... %d more below", len(d.view)-end)))
228+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" ... %d more below", len(d.view)-end)))
229229 }
230230 if len(d.view) > visible {
231- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" (%d/%d)", d.cursor+1, len(d.view))))
231+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" (%d/%d)", d.cursor+1, len(d.view))))
232232 }
233233
234234 lines = append(lines, frameRule(th, width))
@@ -153,13 +153,13 @@ func (d *modelDialog) Render(th tui.Theme, width int) []string {
153 } else {153 } else {
154 hint += " - type to filter"154 hint += " - type to filter"
155 }155 }
156- lines = append(lines, th.FG256(th.Muted, hint))156+ lines = append(lines, th.FGColor(th.Muted, hint))
157 if d.showReasoning {157 if d.showReasoning {
158 reasoning := d.reasoning158 reasoning := d.reasoning
159 if reasoning == "" {159 if reasoning == "" {
160 reasoning = "off"160 reasoning = "off"
161 }161 }
162- lines = append(lines, th.FG256(th.Muted, "✦ supports reasoning; current reasoning: "+reasoning+"; change with /reasoning"))162+ lines = append(lines, th.FGColor(th.Muted, "✦ supports reasoning; current reasoning: "+reasoning+"; change with /reasoning"))
163 }163 }
164 164
165 if len(d.view) == 0 {165 if len(d.view) == 0 {
@@ -167,7 +167,7 @@ func (d *modelDialog) Render(th tui.Theme, width int) []string {
167 if len(d.all) == 0 {167 if len(d.all) == 0 {
168 msg = " no credentials found - run /login to add an api key or subscription"168 msg = " no credentials found - run /login to add an api key or subscription"
169 }169 }
170- lines = append(lines, th.FG256(th.Muted, msg))170+ lines = append(lines, th.FGColor(th.Muted, msg))
171 lines = append(lines, frameRule(th, width))171 lines = append(lines, frameRule(th, width))
172 return lines172 return lines
173 }173 }
@@ -217,18 +217,18 @@ func (d *modelDialog) Render(th tui.Theme, width int) []string {
217 if i == d.cursor {217 if i == d.cursor {
218 lines = append(lines, th.PadHighlight(plain, width))218 lines = append(lines, th.PadHighlight(plain, width))
219 } else {219 } else {
220- lines = append(lines, th.FG256(th.Muted, plain))220+ lines = append(lines, th.FGColor(th.Muted, plain))
221 }221 }
222 }222 }
223 223
224 if start > 0 {224 if start > 0 {
225- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" ... %d more above", start)))225+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" ... %d more above", start)))
226 }226 }
227 if end < len(d.view) {227 if end < len(d.view) {
228- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" ... %d more below", len(d.view)-end)))228+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" ... %d more below", len(d.view)-end)))
229 }229 }
230 if len(d.view) > visible {230 if len(d.view) > visible {
231- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" (%d/%d)", d.cursor+1, len(d.view))))231+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" (%d/%d)", d.cursor+1, len(d.view))))
232 }232 }
233 233
234 lines = append(lines, frameRule(th, width))234 lines = append(lines, frameRule(th, width))
M packages/agent/modes/reload_ext.go +1 -1
@@ -64,7 +64,7 @@ func renderReloadErrors(theme tui.Theme, errors []string, width int) []string {
6464 if idx > 0 {
6565 prefix = indent
6666 }
67- out = append(out, theme.FG256(theme.Error, prefix+line))
67+ out = append(out, theme.FGColor(theme.Error, prefix+line))
6868 }
6969 }
7070 return out
@@ -64,7 +64,7 @@ func renderReloadErrors(theme tui.Theme, errors []string, width int) []string {
64 if idx > 0 {64 if idx > 0 {
65 prefix = indent65 prefix = indent
66 }66 }
67- out = append(out, theme.FG256(theme.Error, prefix+line))67+ out = append(out, theme.FGColor(theme.Error, prefix+line))
68 }68 }
69 }69 }
70 return out70 return out
M packages/agent/modes/rescue_dialog.go +7 -7
@@ -107,7 +107,7 @@ func (d *rescueDialog) Render(th tui.Theme, width int) []string {
107107 lines = append(lines, frameHeader(th, header, width))
108108
109109 if d.reason != "" {
110- lines = append(lines, th.FG256(th.Warning, " "+d.reason))
110+ lines = append(lines, th.FGColor(th.Warning, " "+d.reason))
111111 }
112112
113113 hint := "retry this turn with another model (↑/↓, enter, esc to cancel)"
@@ -119,13 +119,13 @@ func (d *rescueDialog) Render(th tui.Theme, width int) []string {
119119 } else {
120120 hint += " - type to filter"
121121 }
122- lines = append(lines, th.FG256(th.Muted, hint))
122+ lines = append(lines, th.FGColor(th.Muted, hint))
123123
124124 if len(d.view) == 0 {
125125 if len(d.all) == 0 {
126- lines = append(lines, th.FG256(th.Muted, " no other models available — log in to another provider with /login"))
126+ lines = append(lines, th.FGColor(th.Muted, " no other models available — log in to another provider with /login"))
127127 } else {
128- lines = append(lines, th.FG256(th.Muted, " no models match "+fmt.Sprintf("%q", d.query)))
128+ lines = append(lines, th.FGColor(th.Muted, " no models match "+fmt.Sprintf("%q", d.query)))
129129 }
130130 lines = append(lines, frameRule(th, width))
131131 return lines
@@ -161,14 +161,14 @@ func (d *rescueDialog) Render(th tui.Theme, width int) []string {
161161 if i == d.cursor {
162162 lines = append(lines, th.PadHighlight(plain, width))
163163 } else {
164- lines = append(lines, th.FG256(th.Muted, plain))
164+ lines = append(lines, th.FGColor(th.Muted, plain))
165165 }
166166 }
167167 if start > 0 {
168- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" ... %d more above", start)))
168+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" ... %d more above", start)))
169169 }
170170 if end < len(d.view) {
171- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" ... %d more below", len(d.view)-end)))
171+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" ... %d more below", len(d.view)-end)))
172172 }
173173
174174 lines = append(lines, frameRule(th, width))
@@ -107,7 +107,7 @@ func (d *rescueDialog) Render(th tui.Theme, width int) []string {
107 lines = append(lines, frameHeader(th, header, width))107 lines = append(lines, frameHeader(th, header, width))
108 108
109 if d.reason != "" {109 if d.reason != "" {
110- lines = append(lines, th.FG256(th.Warning, " "+d.reason))110+ lines = append(lines, th.FGColor(th.Warning, " "+d.reason))
111 }111 }
112 112
113 hint := "retry this turn with another model (↑/↓, enter, esc to cancel)"113 hint := "retry this turn with another model (↑/↓, enter, esc to cancel)"
@@ -119,13 +119,13 @@ func (d *rescueDialog) Render(th tui.Theme, width int) []string {
119 } else {119 } else {
120 hint += " - type to filter"120 hint += " - type to filter"
121 }121 }
122- lines = append(lines, th.FG256(th.Muted, hint))122+ lines = append(lines, th.FGColor(th.Muted, hint))
123 123
124 if len(d.view) == 0 {124 if len(d.view) == 0 {
125 if len(d.all) == 0 {125 if len(d.all) == 0 {
126- lines = append(lines, th.FG256(th.Muted, " no other models available — log in to another provider with /login"))126+ lines = append(lines, th.FGColor(th.Muted, " no other models available — log in to another provider with /login"))
127 } else {127 } else {
128- lines = append(lines, th.FG256(th.Muted, " no models match "+fmt.Sprintf("%q", d.query)))128+ lines = append(lines, th.FGColor(th.Muted, " no models match "+fmt.Sprintf("%q", d.query)))
129 }129 }
130 lines = append(lines, frameRule(th, width))130 lines = append(lines, frameRule(th, width))
131 return lines131 return lines
@@ -161,14 +161,14 @@ func (d *rescueDialog) Render(th tui.Theme, width int) []string {
161 if i == d.cursor {161 if i == d.cursor {
162 lines = append(lines, th.PadHighlight(plain, width))162 lines = append(lines, th.PadHighlight(plain, width))
163 } else {163 } else {
164- lines = append(lines, th.FG256(th.Muted, plain))164+ lines = append(lines, th.FGColor(th.Muted, plain))
165 }165 }
166 }166 }
167 if start > 0 {167 if start > 0 {
168- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" ... %d more above", start)))168+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" ... %d more above", start)))
169 }169 }
170 if end < len(d.view) {170 if end < len(d.view) {
171- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" ... %d more below", len(d.view)-end)))171+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" ... %d more below", len(d.view)-end)))
172 }172 }
173 173
174 lines = append(lines, frameRule(th, width))174 lines = append(lines, frameRule(th, width))
M packages/agent/modes/session_dialog.go +13 -13
@@ -357,27 +357,27 @@ func (d *sessionDialog) Render(th tui.Theme, width int) []string {
357357 var lines []string
358358 lines = append(lines, frameHeader(th, "sessions", width))
359359 if d.loading {
360- lines = append(lines, th.FG256(th.Muted, d.loadingMessage(th)))
360+ lines = append(lines, th.FGColor(th.Muted, d.loadingMessage(th)))
361361 if len(d.sessions) == 0 {
362- lines = append(lines, th.FG256(th.Muted, "session entries appear as they load"))
362+ lines = append(lines, th.FGColor(th.Muted, "session entries appear as they load"))
363363 lines = append(lines, frameRule(th, width))
364364 return lines
365365 }
366366 } else if len(d.sessions) == 0 {
367- lines = append(lines, th.FG256(th.Muted, "no previous sessions for this directory"))
368- lines = append(lines, th.FG256(th.Muted, "press esc to close"))
367+ lines = append(lines, th.FGColor(th.Muted, "no previous sessions for this directory"))
368+ lines = append(lines, th.FGColor(th.Muted, "press esc to close"))
369369 lines = append(lines, frameRule(th, width))
370370 return lines
371371 }
372372 if d.renaming {
373- lines = append(lines, th.FG256(th.Muted, "rename session (enter save, esc cancel):"))
373+ lines = append(lines, th.FGColor(th.Muted, "rename session (enter save, esc cancel):"))
374374 text := d.rename
375375 if text == "" {
376- text = th.FG256(th.Muted, "session name")
376+ text = th.FGColor(th.Muted, "session name")
377377 } else {
378- text = th.FG256(th.FG, text)
378+ text = th.FGColor(th.FG, text)
379379 }
380- lines = append(lines, " "+th.FG256(th.Accent, "▌ ")+text)
380+ lines = append(lines, " "+th.FGColor(th.Accent, "▌ ")+text)
381381 lines = append(lines, frameRule(th, width))
382382 return lines
383383 }
@@ -385,7 +385,7 @@ func (d *sessionDialog) Render(th tui.Theme, width int) []string {
385385 if d.loading {
386386 hint = "pick a session (↑/↓, pgup/pgdn, enter resume, esc cancel; more loading)"
387387 }
388- lines = append(lines, th.FG256(th.Muted, hint))
388+ lines = append(lines, th.FGColor(th.Muted, hint))
389389
390390 // Viewport: windowed slice of d.sessions around d.cursor so a
391391 // list taller than the terminal still scrolls. Caller sets
@@ -406,7 +406,7 @@ func (d *sessionDialog) Render(th tui.Theme, width int) []string {
406406 // Top indicator: how many rows are above the viewport.
407407 if d.viewTop > 0 {
408408 hidden := d.viewTop
409- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" ↑ %d more above", hidden)))
409+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" ↑ %d more above", hidden)))
410410 }
411411 for i := d.viewTop; i < viewBot; i++ {
412412 s := d.sessions[i]
@@ -414,13 +414,13 @@ func (d *sessionDialog) Render(th tui.Theme, width int) []string {
414414 if i == d.cursor {
415415 lines = append(lines, th.PadHighlight(plain, width))
416416 } else {
417- lines = append(lines, th.FG256(th.Muted, plain))
417+ lines = append(lines, th.FGColor(th.Muted, plain))
418418 }
419419 }
420420 // Bottom indicator: how many rows are below the viewport.
421421 if viewBot < total {
422422 hidden := total - viewBot
423- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" ↓ %d more below", hidden)))
423+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" ↓ %d more below", hidden)))
424424 }
425425 lines = append(lines, frameRule(th, width))
426426 return lines
@@ -518,7 +518,7 @@ func (d *sessionDialog) loadingMessage(th tui.Theme) string {
518518 if d.loadingTotal > 0 {
519519 progress = fmt.Sprintf("loading sessions (%d/%d)", d.loadingDone, d.loadingTotal)
520520 }
521- return th.FG256(th.Spinner, frames[idx]) + " " + th.FG256(th.Muted, progress+" (esc cancel)")
521+ return th.FGColor(th.Spinner, frames[idx]) + " " + th.FGColor(th.Muted, progress+" (esc cancel)")
522522 }
523523
524524 func formatRelative(t time.Time) string {
@@ -357,27 +357,27 @@ func (d *sessionDialog) Render(th tui.Theme, width int) []string {
357 var lines []string357 var lines []string
358 lines = append(lines, frameHeader(th, "sessions", width))358 lines = append(lines, frameHeader(th, "sessions", width))
359 if d.loading {359 if d.loading {
360- lines = append(lines, th.FG256(th.Muted, d.loadingMessage(th)))360+ lines = append(lines, th.FGColor(th.Muted, d.loadingMessage(th)))
361 if len(d.sessions) == 0 {361 if len(d.sessions) == 0 {
362- lines = append(lines, th.FG256(th.Muted, "session entries appear as they load"))362+ lines = append(lines, th.FGColor(th.Muted, "session entries appear as they load"))
363 lines = append(lines, frameRule(th, width))363 lines = append(lines, frameRule(th, width))
364 return lines364 return lines
365 }365 }
366 } else if len(d.sessions) == 0 {366 } else if len(d.sessions) == 0 {
367- lines = append(lines, th.FG256(th.Muted, "no previous sessions for this directory"))367+ lines = append(lines, th.FGColor(th.Muted, "no previous sessions for this directory"))
368- lines = append(lines, th.FG256(th.Muted, "press esc to close"))368+ lines = append(lines, th.FGColor(th.Muted, "press esc to close"))
369 lines = append(lines, frameRule(th, width))369 lines = append(lines, frameRule(th, width))
370 return lines370 return lines
371 }371 }
372 if d.renaming {372 if d.renaming {
373- lines = append(lines, th.FG256(th.Muted, "rename session (enter save, esc cancel):"))373+ lines = append(lines, th.FGColor(th.Muted, "rename session (enter save, esc cancel):"))
374 text := d.rename374 text := d.rename
375 if text == "" {375 if text == "" {
376- text = th.FG256(th.Muted, "session name")376+ text = th.FGColor(th.Muted, "session name")
377 } else {377 } else {
378- text = th.FG256(th.FG, text)378+ text = th.FGColor(th.FG, text)
379 }379 }
380- lines = append(lines, " "+th.FG256(th.Accent, "▌ ")+text)380+ lines = append(lines, " "+th.FGColor(th.Accent, "▌ ")+text)
381 lines = append(lines, frameRule(th, width))381 lines = append(lines, frameRule(th, width))
382 return lines382 return lines
383 }383 }
@@ -385,7 +385,7 @@ func (d *sessionDialog) Render(th tui.Theme, width int) []string {
385 if d.loading {385 if d.loading {
386 hint = "pick a session (↑/↓, pgup/pgdn, enter resume, esc cancel; more loading)"386 hint = "pick a session (↑/↓, pgup/pgdn, enter resume, esc cancel; more loading)"
387 }387 }
388- lines = append(lines, th.FG256(th.Muted, hint))388+ lines = append(lines, th.FGColor(th.Muted, hint))
389 389
390 // Viewport: windowed slice of d.sessions around d.cursor so a390 // Viewport: windowed slice of d.sessions around d.cursor so a
391 // list taller than the terminal still scrolls. Caller sets391 // list taller than the terminal still scrolls. Caller sets
@@ -406,7 +406,7 @@ func (d *sessionDialog) Render(th tui.Theme, width int) []string {
406 // Top indicator: how many rows are above the viewport.406 // Top indicator: how many rows are above the viewport.
407 if d.viewTop > 0 {407 if d.viewTop > 0 {
408 hidden := d.viewTop408 hidden := d.viewTop
409- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" ↑ %d more above", hidden)))409+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" ↑ %d more above", hidden)))
410 }410 }
411 for i := d.viewTop; i < viewBot; i++ {411 for i := d.viewTop; i < viewBot; i++ {
412 s := d.sessions[i]412 s := d.sessions[i]
@@ -414,13 +414,13 @@ func (d *sessionDialog) Render(th tui.Theme, width int) []string {
414 if i == d.cursor {414 if i == d.cursor {
415 lines = append(lines, th.PadHighlight(plain, width))415 lines = append(lines, th.PadHighlight(plain, width))
416 } else {416 } else {
417- lines = append(lines, th.FG256(th.Muted, plain))417+ lines = append(lines, th.FGColor(th.Muted, plain))
418 }418 }
419 }419 }
420 // Bottom indicator: how many rows are below the viewport.420 // Bottom indicator: how many rows are below the viewport.
421 if viewBot < total {421 if viewBot < total {
422 hidden := total - viewBot422 hidden := total - viewBot
423- lines = append(lines, th.FG256(th.Muted, fmt.Sprintf(" ↓ %d more below", hidden)))423+ lines = append(lines, th.FGColor(th.Muted, fmt.Sprintf(" ↓ %d more below", hidden)))
424 }424 }
425 lines = append(lines, frameRule(th, width))425 lines = append(lines, frameRule(th, width))
426 return lines426 return lines
@@ -518,7 +518,7 @@ func (d *sessionDialog) loadingMessage(th tui.Theme) string {
518 if d.loadingTotal > 0 {518 if d.loadingTotal > 0 {
519 progress = fmt.Sprintf("loading sessions (%d/%d)", d.loadingDone, d.loadingTotal)519 progress = fmt.Sprintf("loading sessions (%d/%d)", d.loadingDone, d.loadingTotal)
520 }520 }
521- return th.FG256(th.Spinner, frames[idx]) + " " + th.FG256(th.Muted, progress+" (esc cancel)")521+ return th.FGColor(th.Spinner, frames[idx]) + " " + th.FGColor(th.Muted, progress+" (esc cancel)")
522 }522 }
523 523
524 func formatRelative(t time.Time) string {524 func formatRelative(t time.Time) string {
M packages/agent/modes/session_ops_dialog.go +2 -2
@@ -84,7 +84,7 @@ func (d *sessionOpsDialog) Render(th tui.Theme, width int) []string {
8484 }
8585 var lines []string
8686 lines = append(lines, frameHeader(th, "session", width))
87- lines = append(lines, th.FG256(th.Muted, "pick an action (\u2191/\u2193, enter, esc to cancel):"))
87+ lines = append(lines, th.FGColor(th.Muted, "pick an action (\u2191/\u2193, enter, esc to cancel):"))
8888 for i, it := range d.items {
8989 text := " " + it.label
9090 if it.hint != "" {
@@ -93,7 +93,7 @@ func (d *sessionOpsDialog) Render(th tui.Theme, width int) []string {
9393 if i == d.cursor {
9494 lines = append(lines, th.PadHighlight(text, width))
9595 } else {
96- lines = append(lines, th.FG256(th.Muted, text))
96+ lines = append(lines, th.FGColor(th.Muted, text))
9797 }
9898 }
9999 lines = append(lines, frameRule(th, width))
@@ -84,7 +84,7 @@ func (d *sessionOpsDialog) Render(th tui.Theme, width int) []string {
84 }84 }
85 var lines []string85 var lines []string
86 lines = append(lines, frameHeader(th, "session", width))86 lines = append(lines, frameHeader(th, "session", width))
87- lines = append(lines, th.FG256(th.Muted, "pick an action (\u2191/\u2193, enter, esc to cancel):"))87+ lines = append(lines, th.FGColor(th.Muted, "pick an action (\u2191/\u2193, enter, esc to cancel):"))
88 for i, it := range d.items {88 for i, it := range d.items {
89 text := " " + it.label89 text := " " + it.label
90 if it.hint != "" {90 if it.hint != "" {
@@ -93,7 +93,7 @@ func (d *sessionOpsDialog) Render(th tui.Theme, width int) []string {
93 if i == d.cursor {93 if i == d.cursor {
94 lines = append(lines, th.PadHighlight(text, width))94 lines = append(lines, th.PadHighlight(text, width))
95 } else {95 } else {
96- lines = append(lines, th.FG256(th.Muted, text))96+ lines = append(lines, th.FGColor(th.Muted, text))
97 }97 }
98 }98 }
99 lines = append(lines, frameRule(th, width))99 lines = append(lines, frameRule(th, width))
M packages/agent/modes/session_tree_dialog.go +10 -10
@@ -292,13 +292,13 @@ func (d *sessionTreeDialog) Render(th tui.Theme, width int) []string {
292292 lines := []string{line(frameHeader(th, "session tree", renderWidth))}
293293 if len(d.items) == 0 {
294294 lines = append(lines,
295- line(th.FG256(th.Muted, "no messages in this session yet")),
296- line(th.FG256(th.Muted, "press esc to close")),
295+ line(th.FGColor(th.Muted, "no messages in this session yet")),
296+ line(th.FGColor(th.Muted, "press esc to close")),
297297 line(frameRule(th, renderWidth)),
298298 )
299299 return lines
300300 }
301- lines = append(lines, line(th.FG256(th.Muted,
301+ lines = append(lines, line(th.FGColor(th.Muted,
302302 "session history and branches (↑/↓, pgup/pgdn, home/end, enter checkout, esc cancel):")))
303303
304304 d.followCursor()
@@ -311,7 +311,7 @@ func (d *sessionTreeDialog) Render(th tui.Theme, width int) []string {
311311 start = end
312312 }
313313 if start > 0 {
314- lines = append(lines, line(th.FG256(th.Muted, fmt.Sprintf(" ↑ %d more above", start))))
314+ lines = append(lines, line(th.FGColor(th.Muted, fmt.Sprintf(" ↑ %d more above", start))))
315315 }
316316 for i := start; i < end; i++ {
317317 it := d.items[i]
@@ -323,9 +323,9 @@ func (d *sessionTreeDialog) Render(th tui.Theme, width int) []string {
323323 }
324324 }
325325 if end < len(d.items) {
326- lines = append(lines, line(th.FG256(th.Muted, fmt.Sprintf(" ↓ %d more below", len(d.items)-end))))
326+ lines = append(lines, line(th.FGColor(th.Muted, fmt.Sprintf(" ↓ %d more below", len(d.items)-end))))
327327 }
328- lines = append(lines, line(th.FG256(th.Muted, fmt.Sprintf("%d/%d", d.cursor+1, len(d.items)))), line(frameRule(th, renderWidth)))
328+ lines = append(lines, line(th.FGColor(th.Muted, fmt.Sprintf("%d/%d", d.cursor+1, len(d.items)))), line(frameRule(th, renderWidth)))
329329 return lines
330330 }
331331
@@ -821,13 +821,13 @@ func colorSessionTreeLine(th tui.Theme, line string) string {
821821 plain := strings.TrimSpace(line)
822822 switch {
823823 case strings.HasPrefix(plain, "you:"):
824- return th.FG256(th.FG, line)
824+ return th.FGColor(th.FG, line)
825825 case strings.HasPrefix(plain, "zot:"):
826- return th.FG256(th.Muted, line)
826+ return th.FGColor(th.Muted, line)
827827 case strings.HasPrefix(plain, "tool:"):
828- return th.FG256(th.ToolOut, line)
828+ return th.FGColor(th.ToolOut, line)
829829 default:
830- return th.FG256(th.Muted, line)
830+ return th.FGColor(th.Muted, line)
831831 }
832832 }
833833
@@ -292,13 +292,13 @@ func (d *sessionTreeDialog) Render(th tui.Theme, width int) []string {
292 lines := []string{line(frameHeader(th, "session tree", renderWidth))}292 lines := []string{line(frameHeader(th, "session tree", renderWidth))}
293 if len(d.items) == 0 {293 if len(d.items) == 0 {
294 lines = append(lines,294 lines = append(lines,
295- line(th.FG256(th.Muted, "no messages in this session yet")),295+ line(th.FGColor(th.Muted, "no messages in this session yet")),
296- line(th.FG256(th.Muted, "press esc to close")),296+ line(th.FGColor(th.Muted, "press esc to close")),
297 line(frameRule(th, renderWidth)),297 line(frameRule(th, renderWidth)),
298 )298 )
299 return lines299 return lines
300 }300 }
301- lines = append(lines, line(th.FG256(th.Muted,301+ lines = append(lines, line(th.FGColor(th.Muted,
302 "session history and branches (↑/↓, pgup/pgdn, home/end, enter checkout, esc cancel):")))302 "session history and branches (↑/↓, pgup/pgdn, home/end, enter checkout, esc cancel):")))
303 303
304 d.followCursor()304 d.followCursor()
@@ -311,7 +311,7 @@ func (d *sessionTreeDialog) Render(th tui.Theme, width int) []string {
311 start = end311 start = end
312 }312 }
313 if start > 0 {313 if start > 0 {
314- lines = append(lines, line(th.FG256(th.Muted, fmt.Sprintf(" ↑ %d more above", start))))314+ lines = append(lines, line(th.FGColor(th.Muted, fmt.Sprintf(" ↑ %d more above", start))))
315 }315 }
316 for i := start; i < end; i++ {316 for i := start; i < end; i++ {
317 it := d.items[i]317 it := d.items[i]
@@ -323,9 +323,9 @@ func (d *sessionTreeDialog) Render(th tui.Theme, width int) []string {
323 }323 }
324 }324 }
325 if end < len(d.items) {325 if end < len(d.items) {
326- lines = append(lines, line(th.FG256(th.Muted, fmt.Sprintf(" ↓ %d more below", len(d.items)-end))))326+ lines = append(lines, line(th.FGColor(th.Muted, fmt.Sprintf(" ↓ %d more below", len(d.items)-end))))
327 }327 }
328- lines = append(lines, line(th.FG256(th.Muted, fmt.Sprintf("%d/%d", d.cursor+1, len(d.items)))), line(frameRule(th, renderWidth)))328+ lines = append(lines, line(th.FGColor(th.Muted, fmt.Sprintf("%d/%d", d.cursor+1, len(d.items)))), line(frameRule(th, renderWidth)))
329 return lines329 return lines
330 }330 }
331 331
@@ -821,13 +821,13 @@ func colorSessionTreeLine(th tui.Theme, line string) string {
821 plain := strings.TrimSpace(line)821 plain := strings.TrimSpace(line)
822 switch {822 switch {
823 case strings.HasPrefix(plain, "you:"):823 case strings.HasPrefix(plain, "you:"):
824- return th.FG256(th.FG, line)824+ return th.FGColor(th.FG, line)
825 case strings.HasPrefix(plain, "zot:"):825 case strings.HasPrefix(plain, "zot:"):
826- return th.FG256(th.Muted, line)826+ return th.FGColor(th.Muted, line)
827 case strings.HasPrefix(plain, "tool:"):827 case strings.HasPrefix(plain, "tool:"):
828- return th.FG256(th.ToolOut, line)828+ return th.FGColor(th.ToolOut, line)
829 default:829 default:
830- return th.FG256(th.Muted, line)830+ return th.FGColor(th.Muted, line)
831 }831 }
832 }832 }
833 833
M packages/agent/modes/settings_dialog.go +8 -8
@@ -238,9 +238,9 @@ func (d *settingsDialog) Render(th tui.Theme, width int) []string {
238238 var lines []string
239239 lines = append(lines, frameHeader(th, d.title, width))
240240 if len(d.parentItems) > 0 {
241- lines = append(lines, th.FG256(th.Muted, "change with enter/space, esc to go back:"))
241+ lines = append(lines, th.FGColor(th.Muted, "change with enter/space, esc to go back:"))
242242 } else {
243- lines = append(lines, th.FG256(th.Muted, "change with enter/space, esc to close:"))
243+ lines = append(lines, th.FGColor(th.Muted, "change with enter/space, esc to close:"))
244244 }
245245 for i, it := range d.items {
246246 box := "[ ]"
@@ -259,10 +259,10 @@ func (d *settingsDialog) Render(th tui.Theme, width int) []string {
259259 plain = " " + box + " " + it.label + ": " + it.options[it.choice].label
260260 }
261261 if it.hint != "" {
262- plain += " " + th.FG256(th.Muted, "("+it.hint+")")
262+ plain += " " + th.FGColor(th.Muted, "("+it.hint+")")
263263 }
264264 if it.disabled {
265- lines = append(lines, th.FG256(th.Muted, plain))
265+ lines = append(lines, th.FGColor(th.Muted, plain))
266266 } else if i == d.cursor {
267267 lines = append(lines, th.PadHighlight(plain, width))
268268 } else {
@@ -270,7 +270,7 @@ func (d *settingsDialog) Render(th tui.Theme, width int) []string {
270270 }
271271 if it.desc != "" {
272272 for _, desc := range wrapSettingDescription(it.desc, width, 6) {
273- lines = append(lines, th.FG256(th.Muted, desc))
273+ lines = append(lines, th.FGColor(th.Muted, desc))
274274 }
275275 }
276276 }
@@ -290,9 +290,9 @@ func (d *settingsDialog) renderOptions(th tui.Theme, width int) []string {
290290 }
291291 lines := []string{frameHeader(th, title, width)}
292292 if it.desc != "" {
293- lines = append(lines, th.FG256(th.Muted, it.desc))
293+ lines = append(lines, th.FGColor(th.Muted, it.desc))
294294 }
295- lines = append(lines, th.FG256(th.Muted, "select with enter/space, esc to go back:"))
295+ lines = append(lines, th.FGColor(th.Muted, "select with enter/space, esc to go back:"))
296296 for idx, opt := range it.options {
297297 marker := " "
298298 if idx == it.choice {
@@ -306,7 +306,7 @@ func (d *settingsDialog) renderOptions(th tui.Theme, width int) []string {
306306 }
307307 if opt.desc != "" {
308308 for _, desc := range wrapSettingDescription(opt.desc, width, 6) {
309- lines = append(lines, th.FG256(th.Muted, desc))
309+ lines = append(lines, th.FGColor(th.Muted, desc))
310310 }
311311 }
312312 }
@@ -238,9 +238,9 @@ func (d *settingsDialog) Render(th tui.Theme, width int) []string {
238 var lines []string238 var lines []string
239 lines = append(lines, frameHeader(th, d.title, width))239 lines = append(lines, frameHeader(th, d.title, width))
240 if len(d.parentItems) > 0 {240 if len(d.parentItems) > 0 {
241- lines = append(lines, th.FG256(th.Muted, "change with enter/space, esc to go back:"))241+ lines = append(lines, th.FGColor(th.Muted, "change with enter/space, esc to go back:"))
242 } else {242 } else {
243- lines = append(lines, th.FG256(th.Muted, "change with enter/space, esc to close:"))243+ lines = append(lines, th.FGColor(th.Muted, "change with enter/space, esc to close:"))
244 }244 }
245 for i, it := range d.items {245 for i, it := range d.items {
246 box := "[ ]"246 box := "[ ]"
@@ -259,10 +259,10 @@ func (d *settingsDialog) Render(th tui.Theme, width int) []string {
259 plain = " " + box + " " + it.label + ": " + it.options[it.choice].label259 plain = " " + box + " " + it.label + ": " + it.options[it.choice].label
260 }260 }
261 if it.hint != "" {261 if it.hint != "" {
262- plain += " " + th.FG256(th.Muted, "("+it.hint+")")262+ plain += " " + th.FGColor(th.Muted, "("+it.hint+")")
263 }263 }
264 if it.disabled {264 if it.disabled {
265- lines = append(lines, th.FG256(th.Muted, plain))265+ lines = append(lines, th.FGColor(th.Muted, plain))
266 } else if i == d.cursor {266 } else if i == d.cursor {
267 lines = append(lines, th.PadHighlight(plain, width))267 lines = append(lines, th.PadHighlight(plain, width))
268 } else {268 } else {
@@ -270,7 +270,7 @@ func (d *settingsDialog) Render(th tui.Theme, width int) []string {
270 }270 }
271 if it.desc != "" {271 if it.desc != "" {
272 for _, desc := range wrapSettingDescription(it.desc, width, 6) {272 for _, desc := range wrapSettingDescription(it.desc, width, 6) {
273- lines = append(lines, th.FG256(th.Muted, desc))273+ lines = append(lines, th.FGColor(th.Muted, desc))
274 }274 }
275 }275 }
276 }276 }
@@ -290,9 +290,9 @@ func (d *settingsDialog) renderOptions(th tui.Theme, width int) []string {
290 }290 }
291 lines := []string{frameHeader(th, title, width)}291 lines := []string{frameHeader(th, title, width)}
292 if it.desc != "" {292 if it.desc != "" {
293- lines = append(lines, th.FG256(th.Muted, it.desc))293+ lines = append(lines, th.FGColor(th.Muted, it.desc))
294 }294 }
295- lines = append(lines, th.FG256(th.Muted, "select with enter/space, esc to go back:"))295+ lines = append(lines, th.FGColor(th.Muted, "select with enter/space, esc to go back:"))
296 for idx, opt := range it.options {296 for idx, opt := range it.options {
297 marker := " "297 marker := " "
298 if idx == it.choice {298 if idx == it.choice {
@@ -306,7 +306,7 @@ func (d *settingsDialog) renderOptions(th tui.Theme, width int) []string {
306 }306 }
307 if opt.desc != "" {307 if opt.desc != "" {
308 for _, desc := range wrapSettingDescription(opt.desc, width, 6) {308 for _, desc := range wrapSettingDescription(opt.desc, width, 6) {
309- lines = append(lines, th.FG256(th.Muted, desc))309+ lines = append(lines, th.FGColor(th.Muted, desc))
310 }310 }
311 }311 }
312 }312 }
M packages/agent/modes/skills_dialog.go +9 -9
@@ -99,8 +99,8 @@ func (d *skillsDialog) Render(th tui.Theme, width int) []string {
9999
100100 out := []string{frameHeader(th, "skills (enter to view, esc to close)", width)}
101101 if len(d.skills) == 0 {
102- out = append(out, " "+th.FG256(th.Muted, "no user skills loaded"))
103- out = append(out, " "+th.FG256(th.Muted, "add SKILL.md under $ZOT_HOME/skills, .zot/skills, .claude/skills, or .agents/skills"))
102+ out = append(out, " "+th.FGColor(th.Muted, "no user skills loaded"))
103+ out = append(out, " "+th.FGColor(th.Muted, "add SKILL.md under $ZOT_HOME/skills, .zot/skills, .claude/skills, or .agents/skills"))
104104 out = append(out, frameRule(th, width))
105105 return out
106106 }
@@ -108,7 +108,7 @@ func (d *skillsDialog) Render(th tui.Theme, width int) []string {
108108 const maxRows = 12
109109 start, end := visibleWindow(d.cursor, len(d.skills), maxRows)
110110 if start > 0 {
111- out = append(out, " "+th.FG256(th.Muted, fmt.Sprintf("\u2191 %d more above", start)))
111+ out = append(out, " "+th.FGColor(th.Muted, fmt.Sprintf("\u2191 %d more above", start)))
112112 }
113113 for i := start; i < end; i++ {
114114 s := d.skills[i]
@@ -116,13 +116,13 @@ func (d *skillsDialog) Render(th tui.Theme, width int) []string {
116116 if i == d.cursor {
117117 out = append(out, th.PadHighlight(" "+row, width))
118118 } else {
119- out = append(out, " "+th.FG256(th.Muted, row))
119+ out = append(out, " "+th.FGColor(th.Muted, row))
120120 }
121121 }
122122 if end < len(d.skills) {
123- out = append(out, " "+th.FG256(th.Muted, fmt.Sprintf("\u2193 %d more below", len(d.skills)-end)))
123+ out = append(out, " "+th.FGColor(th.Muted, fmt.Sprintf("\u2193 %d more below", len(d.skills)-end)))
124124 }
125- out = append(out, " "+th.FG256(th.Muted, "run with /skill:<name> [request]"))
125+ out = append(out, " "+th.FGColor(th.Muted, "run with /skill:<name> [request]"))
126126 out = append(out, frameRule(th, width))
127127 return out
128128 }
@@ -131,8 +131,8 @@ func (d *skillsDialog) renderBody(th tui.Theme, width int) []string {
131131 s := d.viewing
132132 out := []string{
133133 frameHeader(th, "skill: "+s.Name+" (esc / enter to go back)", width),
134- " " + th.FG256(th.Muted, s.Description),
135- " " + th.FG256(th.Muted, "source: "+s.Source+" ("+s.Path+")"),
134+ " " + th.FGColor(th.Muted, s.Description),
135+ " " + th.FGColor(th.Muted, "source: "+s.Source+" ("+s.Path+")"),
136136 "",
137137 }
138138
@@ -159,7 +159,7 @@ func (d *skillsDialog) renderBody(th tui.Theme, width int) []string {
159159 out = append(out, " "+line)
160160 }
161161 if end < len(bodyLines) {
162- out = append(out, " "+th.FG256(th.Muted, fmt.Sprintf("\u2193 %d more lines (down/pgdn)", len(bodyLines)-end)))
162+ out = append(out, " "+th.FGColor(th.Muted, fmt.Sprintf("\u2193 %d more lines (down/pgdn)", len(bodyLines)-end)))
163163 }
164164 out = append(out, frameRule(th, width))
165165 return out
@@ -99,8 +99,8 @@ func (d *skillsDialog) Render(th tui.Theme, width int) []string {
99 99
100 out := []string{frameHeader(th, "skills (enter to view, esc to close)", width)}100 out := []string{frameHeader(th, "skills (enter to view, esc to close)", width)}
101 if len(d.skills) == 0 {101 if len(d.skills) == 0 {
102- out = append(out, " "+th.FG256(th.Muted, "no user skills loaded"))102+ out = append(out, " "+th.FGColor(th.Muted, "no user skills loaded"))
103- out = append(out, " "+th.FG256(th.Muted, "add SKILL.md under $ZOT_HOME/skills, .zot/skills, .claude/skills, or .agents/skills"))103+ out = append(out, " "+th.FGColor(th.Muted, "add SKILL.md under $ZOT_HOME/skills, .zot/skills, .claude/skills, or .agents/skills"))
104 out = append(out, frameRule(th, width))104 out = append(out, frameRule(th, width))
105 return out105 return out
106 }106 }
@@ -108,7 +108,7 @@ func (d *skillsDialog) Render(th tui.Theme, width int) []string {
108 const maxRows = 12108 const maxRows = 12
109 start, end := visibleWindow(d.cursor, len(d.skills), maxRows)109 start, end := visibleWindow(d.cursor, len(d.skills), maxRows)
110 if start > 0 {110 if start > 0 {
111- out = append(out, " "+th.FG256(th.Muted, fmt.Sprintf("\u2191 %d more above", start)))111+ out = append(out, " "+th.FGColor(th.Muted, fmt.Sprintf("\u2191 %d more above", start)))
112 }112 }
113 for i := start; i < end; i++ {113 for i := start; i < end; i++ {
114 s := d.skills[i]114 s := d.skills[i]
@@ -116,13 +116,13 @@ func (d *skillsDialog) Render(th tui.Theme, width int) []string {
116 if i == d.cursor {116 if i == d.cursor {
117 out = append(out, th.PadHighlight(" "+row, width))117 out = append(out, th.PadHighlight(" "+row, width))
118 } else {118 } else {
119- out = append(out, " "+th.FG256(th.Muted, row))119+ out = append(out, " "+th.FGColor(th.Muted, row))
120 }120 }
121 }121 }
122 if end < len(d.skills) {122 if end < len(d.skills) {
123- out = append(out, " "+th.FG256(th.Muted, fmt.Sprintf("\u2193 %d more below", len(d.skills)-end)))123+ out = append(out, " "+th.FGColor(th.Muted, fmt.Sprintf("\u2193 %d more below", len(d.skills)-end)))
124 }124 }
125- out = append(out, " "+th.FG256(th.Muted, "run with /skill:<name> [request]"))125+ out = append(out, " "+th.FGColor(th.Muted, "run with /skill:<name> [request]"))
126 out = append(out, frameRule(th, width))126 out = append(out, frameRule(th, width))
127 return out127 return out
128 }128 }
@@ -131,8 +131,8 @@ func (d *skillsDialog) renderBody(th tui.Theme, width int) []string {
131 s := d.viewing131 s := d.viewing
132 out := []string{132 out := []string{
133 frameHeader(th, "skill: "+s.Name+" (esc / enter to go back)", width),133 frameHeader(th, "skill: "+s.Name+" (esc / enter to go back)", width),
134- " " + th.FG256(th.Muted, s.Description),134+ " " + th.FGColor(th.Muted, s.Description),
135- " " + th.FG256(th.Muted, "source: "+s.Source+" ("+s.Path+")"),135+ " " + th.FGColor(th.Muted, "source: "+s.Source+" ("+s.Path+")"),
136 "",136 "",
137 }137 }
138 138
@@ -159,7 +159,7 @@ func (d *skillsDialog) renderBody(th tui.Theme, width int) []string {
159 out = append(out, " "+line)159 out = append(out, " "+line)
160 }160 }
161 if end < len(bodyLines) {161 if end < len(bodyLines) {
162- out = append(out, " "+th.FG256(th.Muted, fmt.Sprintf("\u2193 %d more lines (down/pgdn)", len(bodyLines)-end)))162+ out = append(out, " "+th.FGColor(th.Muted, fmt.Sprintf("\u2193 %d more lines (down/pgdn)", len(bodyLines)-end)))
163 }163 }
164 out = append(out, frameRule(th, width))164 out = append(out, frameRule(th, width))
165 return out165 return out
M packages/agent/modes/slash_suggest.go +3 -3
@@ -492,7 +492,7 @@ func (s *slashSuggester) Render(input string, th tui.Theme, width int) []string
492492 if lw := runewidth.StringWidth(label); lw < width {
493493 rule = label + strings.Repeat("─", width-lw)
494494 }
495- lines = append(lines, th.FG256(th.Muted, rule))
495+ lines = append(lines, th.FGColor(th.Muted, rule))
496496 lines = append(lines, "")
497497 continue
498498 }
@@ -504,13 +504,13 @@ func (s *slashSuggester) Render(input string, th tui.Theme, width int) []string
504504 if i == s.cursor {
505505 lines = append(lines, th.PadHighlight(plain, width))
506506 } else {
507- lines = append(lines, th.FG256(th.Muted, plain))
507+ lines = append(lines, th.FGColor(th.Muted, plain))
508508 }
509509 }
510510 // Blank row before the hint visually detaches it from the
511511 // command list and groups it with its trailing blank.
512512 lines = append(lines, "")
513- lines = append(lines, th.FG256(th.Muted, " ↑/↓ navigate - tab complete - enter run"))
513+ lines = append(lines, th.FGColor(th.Muted, " ↑/↓ navigate - tab complete - enter run"))
514514 // Blank row after the hint separates the popup from the status
515515 // bar / editor below it.
516516 lines = append(lines, "")
@@ -492,7 +492,7 @@ func (s *slashSuggester) Render(input string, th tui.Theme, width int) []string
492 if lw := runewidth.StringWidth(label); lw < width {492 if lw := runewidth.StringWidth(label); lw < width {
493 rule = label + strings.Repeat("─", width-lw)493 rule = label + strings.Repeat("─", width-lw)
494 }494 }
495- lines = append(lines, th.FG256(th.Muted, rule))495+ lines = append(lines, th.FGColor(th.Muted, rule))
496 lines = append(lines, "")496 lines = append(lines, "")
497 continue497 continue
498 }498 }
@@ -504,13 +504,13 @@ func (s *slashSuggester) Render(input string, th tui.Theme, width int) []string
504 if i == s.cursor {504 if i == s.cursor {
505 lines = append(lines, th.PadHighlight(plain, width))505 lines = append(lines, th.PadHighlight(plain, width))
506 } else {506 } else {
507- lines = append(lines, th.FG256(th.Muted, plain))507+ lines = append(lines, th.FGColor(th.Muted, plain))
508 }508 }
509 }509 }
510 // Blank row before the hint visually detaches it from the510 // Blank row before the hint visually detaches it from the
511 // command list and groups it with its trailing blank.511 // command list and groups it with its trailing blank.
512 lines = append(lines, "")512 lines = append(lines, "")
513- lines = append(lines, th.FG256(th.Muted, " ↑/↓ navigate - tab complete - enter run"))513+ lines = append(lines, th.FGColor(th.Muted, " ↑/↓ navigate - tab complete - enter run"))
514 // Blank row after the hint separates the popup from the status514 // Blank row after the hint separates the popup from the status
515 // bar / editor below it.515 // bar / editor below it.
516 lines = append(lines, "")516 lines = append(lines, "")
M packages/agent/modes/subagents_dialog.go +24 -24
@@ -1018,7 +1018,7 @@ func (d *subagentsDialog) Render(th tui.Theme, width int) []string {
10181018 d.openModelPicker()
10191019 }
10201020 out = d.modelPicker.Render(th, width)
1021- out = append(out, " "+th.FG256(th.Muted, "select model for next spawn, esc to cancel"))
1021+ out = append(out, " "+th.FGColor(th.Muted, "select model for next spawn, esc to cancel"))
10221022 return out
10231023 }
10241024 if d.spawning {
@@ -1036,7 +1036,7 @@ func (d *subagentsDialog) Render(th tui.Theme, width int) []string {
10361036 if d.pendingProvider != "" {
10371037 label += " (" + d.pendingProvider + ")"
10381038 }
1039- out = append(out, " "+th.FG256(th.Accent, label))
1039+ out = append(out, " "+th.FGColor(th.Accent, label))
10401040 }
10411041 if d.fileSuggest != nil {
10421042 d.fileSuggest.SetCWD(d.cwd)
@@ -1056,26 +1056,26 @@ func (d *subagentsDialog) Render(th tui.Theme, width int) []string {
10561056 // Matching blank row below before the hint, mirroring the
10571057 // main input's editor breathing room.
10581058 out = append(out, "")
1059- out = append(out, " "+th.FG256(th.Muted, "enter spawn, /model pick model, @ file/dir picker, paste/drop paths become [file:] / [dir:] chips, esc cancel"))
1059+ out = append(out, " "+th.FGColor(th.Muted, "enter spawn, /model pick model, @ file/dir picker, paste/drop paths become [file:] / [dir:] chips, esc cancel"))
10601060 out = append(out, frameRule(th, width))
10611061 return out
10621062 }
10631063 if len(d.rows) == 0 {
1064- out = append(out, " "+th.FG256(th.Muted, "no agents — press n to spawn one (or /subagents new <task>)"))
1064+ out = append(out, " "+th.FGColor(th.Muted, "no agents — press n to spawn one (or /subagents new <task>)"))
10651065 out = append(out, frameRule(th, width))
10661066 return out
10671067 }
10681068
10691069 // Column header for readability.
10701070 header := fmt.Sprintf(" %-18s %-26s %-8s %s", "PROC/TURN STATUS", "ID", "AGE", "ACTIVITY")
1071- out = append(out, th.FG256(th.Muted, header))
1071+ out = append(out, th.FGColor(th.Muted, header))
10721072
10731073 for i, r := range d.rows {
10741074 row := formatSupervisorRow(r, width-2)
10751075 if i == d.cursor {
10761076 out = append(out, th.PadHighlight(" "+row, width))
10771077 } else {
1078- out = append(out, " "+th.FG256(th.FG, row))
1078+ out = append(out, " "+th.FGColor(th.FG, row))
10791079 }
10801080 }
10811081 out = append(out, frameRule(th, width))
@@ -1090,29 +1090,29 @@ func (d *subagentsDialog) renderTranscript(th tui.Theme, width int) []string {
10901090 }
10911091 header := []string{
10921092 frameHeader(th, "subagent: "+a.ID+" (type to send, esc back)", width),
1093- " " + th.FG256(th.Muted, "task: "+a.Task),
1094- " " + th.FG256(th.Muted, "dir: "+a.Dir),
1095- " " + th.FG256(th.Muted, fmt.Sprintf("status: %s, %s", a.Status, a.Activity)),
1093+ " " + th.FGColor(th.Muted, "task: "+a.Task),
1094+ " " + th.FGColor(th.Muted, "dir: "+a.Dir),
1095+ " " + th.FGColor(th.Muted, fmt.Sprintf("status: %s, %s", a.Status, a.Activity)),
10961096 }
10971097 if a.Model != "" {
10981098 modelLine := "model: " + a.Model
10991099 if a.Provider != "" {
11001100 modelLine += " (" + a.Provider + ")"
11011101 }
1102- header = append(header, " "+th.FG256(th.Muted, modelLine))
1102+ header = append(header, " "+th.FGColor(th.Muted, modelLine))
11031103 }
11041104 if a.Subagent != "" {
1105- header = append(header, " "+th.FG256(th.Muted, "agent: "+a.Subagent))
1105+ header = append(header, " "+th.FGColor(th.Muted, "agent: "+a.Subagent))
11061106 }
11071107 if a.Reasoning != "" {
1108- header = append(header, " "+th.FG256(th.Muted, "reasoning: "+a.Reasoning))
1108+ header = append(header, " "+th.FGColor(th.Muted, "reasoning: "+a.Reasoning))
11091109 }
11101110 if a.Err != "" {
1111- header = append(header, " "+th.FG256(th.Muted, "error: "+a.Err))
1111+ header = append(header, " "+th.FGColor(th.Muted, "error: "+a.Err))
11121112 }
11131113 header = append(header,
11141114 "",
1115- " "+th.FG256(th.Muted, "── transcript ──"),
1115+ " "+th.FGColor(th.Muted, "── transcript ──"),
11161116 "",
11171117 )
11181118
@@ -1123,7 +1123,7 @@ func (d *subagentsDialog) renderTranscript(th tui.Theme, width int) []string {
11231123 raw = strings.Split(a.Tail, "\n")
11241124 }
11251125 if len(raw) == 0 {
1126- header = append(header, " "+th.FG256(th.Muted, "(no transcript yet)"))
1126+ header = append(header, " "+th.FGColor(th.Muted, "(no transcript yet)"))
11271127 header = d.appendTranscriptEditor(header, th, width, a)
11281128 header = append(header, frameRule(th, width))
11291129 return header
@@ -1167,7 +1167,7 @@ func (d *subagentsDialog) appendTranscriptEditor(out []string, th tui.Theme, wid
11671167 }
11681168 if a.Status != subagents.StatusRunning || d.send == nil {
11691169 out = append(out, "")
1170- out = append(out, " "+th.FG256(th.Muted, promptDisabledHint(a.Status)))
1170+ out = append(out, " "+th.FGColor(th.Muted, promptDisabledHint(a.Status)))
11711171 return out
11721172 }
11731173 // Lazy-init the editor here too so the very first render of the
@@ -1201,9 +1201,9 @@ func (d *subagentsDialog) appendTranscriptEditor(out []string, th tui.Theme, wid
12011201 }
12021202 out = append(out, "")
12031203 prefix := fmt.Sprintf("%s %s, %s",
1204- th.FG256(th.Assistant, d.transcriptSpin.Frame()),
1205- th.FG256(th.Assistant, a.Activity),
1206- th.FG256(th.Muted, d.transcriptSpin.Elapsed().String()),
1204+ th.FGColor(th.Assistant, d.transcriptSpin.Frame()),
1205+ th.FGColor(th.Assistant, a.Activity),
1206+ th.FGColor(th.Muted, d.transcriptSpin.Elapsed().String()),
12071207 )
12081208 out = append(out, " "+prefix)
12091209 } else {
@@ -1218,7 +1218,7 @@ func (d *subagentsDialog) appendTranscriptEditor(out []string, th tui.Theme, wid
12181218 out = append(out, " "+l)
12191219 }
12201220 out = append(out, "")
1221- out = append(out, " "+th.FG256(th.Muted, "enter send, @ file/dir picker, esc back"))
1221+ out = append(out, " "+th.FGColor(th.Muted, "enter send, @ file/dir picker, esc back"))
12221222 return out
12231223 }
12241224
@@ -1325,11 +1325,11 @@ func renderSupervisorTranscriptBlocks(lines []string, th tui.Theme, width int, c
13251325 out = append(out, renderDialogMarkdownRows(strings.TrimLeft(text, "\n"), th, width)...)
13261326 case kindStderr:
13271327 for _, line := range b.body {
1328- out = append(out, wrapDialogTextRows(th.FG256(th.Muted, "stderr "+line), width)...)
1328+ out = append(out, wrapDialogTextRows(th.FGColor(th.Muted, "stderr "+line), width)...)
13291329 }
13301330 case kindError:
13311331 for _, line := range b.body {
1332- out = append(out, wrapDialogTextRows(th.FG256(th.Error, "✖ "+line), width)...)
1332+ out = append(out, wrapDialogTextRows(th.FGColor(th.Error, "✖ "+line), width)...)
13331333 }
13341334 }
13351335 }
@@ -1353,7 +1353,7 @@ func (d *subagentsDialog) renderPromptEditor(th tui.Theme, width int, out []stri
13531353 if target == "" {
13541354 target = "<unknown>"
13551355 }
1356- out = append(out, " "+th.FG256(th.Muted, "send to "+target+":"))
1356+ out = append(out, " "+th.FGColor(th.Muted, "send to "+target+":"))
13571357 if d.promptSuggest != nil {
13581358 d.promptSuggest.SetCWD(d.cwd)
13591359 if popup := d.promptSuggest.Render(d.promptEd.Value(), th, width); len(popup) > 0 {
@@ -1370,7 +1370,7 @@ func (d *subagentsDialog) renderPromptEditor(th tui.Theme, width int, out []stri
13701370 }
13711371 // Matching blank row below before the hint.
13721372 out = append(out, "")
1373- out = append(out, " "+th.FG256(th.Muted, "enter send, @ file/dir picker, esc cancel"))
1373+ out = append(out, " "+th.FGColor(th.Muted, "enter send, @ file/dir picker, esc cancel"))
13741374 out = append(out, frameRule(th, width))
13751375 return out
13761376 }
@@ -1018,7 +1018,7 @@ func (d *subagentsDialog) Render(th tui.Theme, width int) []string {
1018 d.openModelPicker()1018 d.openModelPicker()
1019 }1019 }
1020 out = d.modelPicker.Render(th, width)1020 out = d.modelPicker.Render(th, width)
1021- out = append(out, " "+th.FG256(th.Muted, "select model for next spawn, esc to cancel"))1021+ out = append(out, " "+th.FGColor(th.Muted, "select model for next spawn, esc to cancel"))
1022 return out1022 return out
1023 }1023 }
1024 if d.spawning {1024 if d.spawning {
@@ -1036,7 +1036,7 @@ func (d *subagentsDialog) Render(th tui.Theme, width int) []string {
1036 if d.pendingProvider != "" {1036 if d.pendingProvider != "" {
1037 label += " (" + d.pendingProvider + ")"1037 label += " (" + d.pendingProvider + ")"
1038 }1038 }
1039- out = append(out, " "+th.FG256(th.Accent, label))1039+ out = append(out, " "+th.FGColor(th.Accent, label))
1040 }1040 }
1041 if d.fileSuggest != nil {1041 if d.fileSuggest != nil {
1042 d.fileSuggest.SetCWD(d.cwd)1042 d.fileSuggest.SetCWD(d.cwd)
@@ -1056,26 +1056,26 @@ func (d *subagentsDialog) Render(th tui.Theme, width int) []string {
1056 // Matching blank row below before the hint, mirroring the1056 // Matching blank row below before the hint, mirroring the
1057 // main input's editor breathing room.1057 // main input's editor breathing room.
1058 out = append(out, "")1058 out = append(out, "")
1059- out = append(out, " "+th.FG256(th.Muted, "enter spawn, /model pick model, @ file/dir picker, paste/drop paths become [file:] / [dir:] chips, esc cancel"))1059+ out = append(out, " "+th.FGColor(th.Muted, "enter spawn, /model pick model, @ file/dir picker, paste/drop paths become [file:] / [dir:] chips, esc cancel"))
1060 out = append(out, frameRule(th, width))1060 out = append(out, frameRule(th, width))
1061 return out1061 return out
1062 }1062 }
1063 if len(d.rows) == 0 {1063 if len(d.rows) == 0 {
1064- out = append(out, " "+th.FG256(th.Muted, "no agents — press n to spawn one (or /subagents new <task>)"))1064+ out = append(out, " "+th.FGColor(th.Muted, "no agents — press n to spawn one (or /subagents new <task>)"))
1065 out = append(out, frameRule(th, width))1065 out = append(out, frameRule(th, width))
1066 return out1066 return out
1067 }1067 }
1068 1068
1069 // Column header for readability.1069 // Column header for readability.
1070 header := fmt.Sprintf(" %-18s %-26s %-8s %s", "PROC/TURN STATUS", "ID", "AGE", "ACTIVITY")1070 header := fmt.Sprintf(" %-18s %-26s %-8s %s", "PROC/TURN STATUS", "ID", "AGE", "ACTIVITY")
1071- out = append(out, th.FG256(th.Muted, header))1071+ out = append(out, th.FGColor(th.Muted, header))
1072 1072
1073 for i, r := range d.rows {1073 for i, r := range d.rows {
1074 row := formatSupervisorRow(r, width-2)1074 row := formatSupervisorRow(r, width-2)
1075 if i == d.cursor {1075 if i == d.cursor {
1076 out = append(out, th.PadHighlight(" "+row, width))1076 out = append(out, th.PadHighlight(" "+row, width))
1077 } else {1077 } else {
1078- out = append(out, " "+th.FG256(th.FG, row))1078+ out = append(out, " "+th.FGColor(th.FG, row))
1079 }1079 }
1080 }1080 }
1081 out = append(out, frameRule(th, width))1081 out = append(out, frameRule(th, width))
@@ -1090,29 +1090,29 @@ func (d *subagentsDialog) renderTranscript(th tui.Theme, width int) []string {
1090 }1090 }
1091 header := []string{1091 header := []string{
1092 frameHeader(th, "subagent: "+a.ID+" (type to send, esc back)", width),1092 frameHeader(th, "subagent: "+a.ID+" (type to send, esc back)", width),
1093- " " + th.FG256(th.Muted, "task: "+a.Task),1093+ " " + th.FGColor(th.Muted, "task: "+a.Task),
1094- " " + th.FG256(th.Muted, "dir: "+a.Dir),1094+ " " + th.FGColor(th.Muted, "dir: "+a.Dir),
1095- " " + th.FG256(th.Muted, fmt.Sprintf("status: %s, %s", a.Status, a.Activity)),1095+ " " + th.FGColor(th.Muted, fmt.Sprintf("status: %s, %s", a.Status, a.Activity)),
1096 }1096 }
1097 if a.Model != "" {1097 if a.Model != "" {
1098 modelLine := "model: " + a.Model1098 modelLine := "model: " + a.Model
1099 if a.Provider != "" {1099 if a.Provider != "" {
1100 modelLine += " (" + a.Provider + ")"1100 modelLine += " (" + a.Provider + ")"
1101 }1101 }
1102- header = append(header, " "+th.FG256(th.Muted, modelLine))1102+ header = append(header, " "+th.FGColor(th.Muted, modelLine))
1103 }1103 }
1104 if a.Subagent != "" {1104 if a.Subagent != "" {
1105- header = append(header, " "+th.FG256(th.Muted, "agent: "+a.Subagent))1105+ header = append(header, " "+th.FGColor(th.Muted, "agent: "+a.Subagent))
1106 }1106 }
1107 if a.Reasoning != "" {1107 if a.Reasoning != "" {
1108- header = append(header, " "+th.FG256(th.Muted, "reasoning: "+a.Reasoning))1108+ header = append(header, " "+th.FGColor(th.Muted, "reasoning: "+a.Reasoning))
1109 }1109 }
1110 if a.Err != "" {1110 if a.Err != "" {
1111- header = append(header, " "+th.FG256(th.Muted, "error: "+a.Err))1111+ header = append(header, " "+th.FGColor(th.Muted, "error: "+a.Err))
1112 }1112 }
1113 header = append(header,1113 header = append(header,
1114 "",1114 "",
1115- " "+th.FG256(th.Muted, "── transcript ──"),1115+ " "+th.FGColor(th.Muted, "── transcript ──"),
1116 "",1116 "",
1117 )1117 )
1118 1118
@@ -1123,7 +1123,7 @@ func (d *subagentsDialog) renderTranscript(th tui.Theme, width int) []string {
1123 raw = strings.Split(a.Tail, "\n")1123 raw = strings.Split(a.Tail, "\n")
1124 }1124 }
1125 if len(raw) == 0 {1125 if len(raw) == 0 {
1126- header = append(header, " "+th.FG256(th.Muted, "(no transcript yet)"))1126+ header = append(header, " "+th.FGColor(th.Muted, "(no transcript yet)"))
1127 header = d.appendTranscriptEditor(header, th, width, a)1127 header = d.appendTranscriptEditor(header, th, width, a)
1128 header = append(header, frameRule(th, width))1128 header = append(header, frameRule(th, width))
1129 return header1129 return header
@@ -1167,7 +1167,7 @@ func (d *subagentsDialog) appendTranscriptEditor(out []string, th tui.Theme, wid
1167 }1167 }
1168 if a.Status != subagents.StatusRunning || d.send == nil {1168 if a.Status != subagents.StatusRunning || d.send == nil {
1169 out = append(out, "")1169 out = append(out, "")
1170- out = append(out, " "+th.FG256(th.Muted, promptDisabledHint(a.Status)))1170+ out = append(out, " "+th.FGColor(th.Muted, promptDisabledHint(a.Status)))
1171 return out1171 return out
1172 }1172 }
1173 // Lazy-init the editor here too so the very first render of the1173 // Lazy-init the editor here too so the very first render of the
@@ -1201,9 +1201,9 @@ func (d *subagentsDialog) appendTranscriptEditor(out []string, th tui.Theme, wid
1201 }1201 }
1202 out = append(out, "")1202 out = append(out, "")
1203 prefix := fmt.Sprintf("%s %s, %s",1203 prefix := fmt.Sprintf("%s %s, %s",
1204- th.FG256(th.Assistant, d.transcriptSpin.Frame()),1204+ th.FGColor(th.Assistant, d.transcriptSpin.Frame()),
1205- th.FG256(th.Assistant, a.Activity),1205+ th.FGColor(th.Assistant, a.Activity),
1206- th.FG256(th.Muted, d.transcriptSpin.Elapsed().String()),1206+ th.FGColor(th.Muted, d.transcriptSpin.Elapsed().String()),
1207 )1207 )
1208 out = append(out, " "+prefix)1208 out = append(out, " "+prefix)
1209 } else {1209 } else {
@@ -1218,7 +1218,7 @@ func (d *subagentsDialog) appendTranscriptEditor(out []string, th tui.Theme, wid
1218 out = append(out, " "+l)1218 out = append(out, " "+l)
1219 }1219 }
1220 out = append(out, "")1220 out = append(out, "")
1221- out = append(out, " "+th.FG256(th.Muted, "enter send, @ file/dir picker, esc back"))1221+ out = append(out, " "+th.FGColor(th.Muted, "enter send, @ file/dir picker, esc back"))
1222 return out1222 return out
1223 }1223 }
1224 1224
@@ -1325,11 +1325,11 @@ func renderSupervisorTranscriptBlocks(lines []string, th tui.Theme, width int, c
1325 out = append(out, renderDialogMarkdownRows(strings.TrimLeft(text, "\n"), th, width)...)1325 out = append(out, renderDialogMarkdownRows(strings.TrimLeft(text, "\n"), th, width)...)
1326 case kindStderr:1326 case kindStderr:
1327 for _, line := range b.body {1327 for _, line := range b.body {
1328- out = append(out, wrapDialogTextRows(th.FG256(th.Muted, "stderr "+line), width)...)1328+ out = append(out, wrapDialogTextRows(th.FGColor(th.Muted, "stderr "+line), width)...)
1329 }1329 }
1330 case kindError:1330 case kindError:
1331 for _, line := range b.body {1331 for _, line := range b.body {
1332- out = append(out, wrapDialogTextRows(th.FG256(th.Error, "✖ "+line), width)...)1332+ out = append(out, wrapDialogTextRows(th.FGColor(th.Error, "✖ "+line), width)...)
1333 }1333 }
1334 }1334 }
1335 }1335 }
@@ -1353,7 +1353,7 @@ func (d *subagentsDialog) renderPromptEditor(th tui.Theme, width int, out []stri
1353 if target == "" {1353 if target == "" {
1354 target = "<unknown>"1354 target = "<unknown>"
1355 }1355 }
1356- out = append(out, " "+th.FG256(th.Muted, "send to "+target+":"))1356+ out = append(out, " "+th.FGColor(th.Muted, "send to "+target+":"))
1357 if d.promptSuggest != nil {1357 if d.promptSuggest != nil {
1358 d.promptSuggest.SetCWD(d.cwd)1358 d.promptSuggest.SetCWD(d.cwd)
1359 if popup := d.promptSuggest.Render(d.promptEd.Value(), th, width); len(popup) > 0 {1359 if popup := d.promptSuggest.Render(d.promptEd.Value(), th, width); len(popup) > 0 {
@@ -1370,7 +1370,7 @@ func (d *subagentsDialog) renderPromptEditor(th tui.Theme, width int, out []stri
1370 }1370 }
1371 // Matching blank row below before the hint.1371 // Matching blank row below before the hint.
1372 out = append(out, "")1372 out = append(out, "")
1373- out = append(out, " "+th.FG256(th.Muted, "enter send, @ file/dir picker, esc cancel"))1373+ out = append(out, " "+th.FGColor(th.Muted, "enter send, @ file/dir picker, esc cancel"))
1374 out = append(out, frameRule(th, width))1374 out = append(out, frameRule(th, width))
1375 return out1375 return out
1376 }1376 }
M packages/agent/modes/telegram_dialog.go +3 -3
@@ -83,16 +83,16 @@ func (d *telegramDialog) Render(th tui.Theme, width int) []string {
8383 }
8484 var lines []string
8585 lines = append(lines, frameHeader(th, "telegram", width))
86- lines = append(lines, th.FG256(th.Muted, "pick an action (\u2191/\u2193, enter, esc to cancel):"))
86+ lines = append(lines, th.FGColor(th.Muted, "pick an action (\u2191/\u2193, enter, esc to cancel):"))
8787 for i, it := range d.items {
8888 plain := " " + it.label
8989 if it.hint != "" {
90- plain += " " + th.FG256(th.Muted, "("+it.hint+")")
90+ plain += " " + th.FGColor(th.Muted, "("+it.hint+")")
9191 }
9292 if i == d.cursor {
9393 lines = append(lines, th.PadHighlight(plain, width))
9494 } else {
95- lines = append(lines, th.FG256(th.Muted, plain))
95+ lines = append(lines, th.FGColor(th.Muted, plain))
9696 }
9797 }
9898 lines = append(lines, frameRule(th, width))
@@ -83,16 +83,16 @@ func (d *telegramDialog) Render(th tui.Theme, width int) []string {
83 }83 }
84 var lines []string84 var lines []string
85 lines = append(lines, frameHeader(th, "telegram", width))85 lines = append(lines, frameHeader(th, "telegram", width))
86- lines = append(lines, th.FG256(th.Muted, "pick an action (\u2191/\u2193, enter, esc to cancel):"))86+ lines = append(lines, th.FGColor(th.Muted, "pick an action (\u2191/\u2193, enter, esc to cancel):"))
87 for i, it := range d.items {87 for i, it := range d.items {
88 plain := " " + it.label88 plain := " " + it.label
89 if it.hint != "" {89 if it.hint != "" {
90- plain += " " + th.FG256(th.Muted, "("+it.hint+")")90+ plain += " " + th.FGColor(th.Muted, "("+it.hint+")")
91 }91 }
92 if i == d.cursor {92 if i == d.cursor {
93 lines = append(lines, th.PadHighlight(plain, width))93 lines = append(lines, th.PadHighlight(plain, width))
94 } else {94 } else {
95- lines = append(lines, th.FG256(th.Muted, plain))95+ lines = append(lines, th.FGColor(th.Muted, plain))
96 }96 }
97 }97 }
98 lines = append(lines, frameRule(th, width))98 lines = append(lines, frameRule(th, width))
M packages/agent/modes/update_banner.go +3 -3
@@ -33,12 +33,12 @@ func renderUpdateBanner(th tui.Theme, info UpdateInfo, width int) []string {
3333 out = append(out, "")
3434
3535 title := fmt.Sprintf("zot %s is available (you're on %s).", info.Latest, info.Current)
36- out = append(out, " "+th.FG256(color, tui.Bold(title)))
36+ out = append(out, " "+th.FGColor(color, tui.Bold(title)))
3737 out = append(out, "")
38- out = append(out, " "+th.FG256(th.Muted, "run: ")+th.FG256(color, "zot update"))
38+ out = append(out, " "+th.FGColor(th.Muted, "run: ")+th.FGColor(color, "zot update"))
3939
4040 if info.URL != "" {
41- out = append(out, " "+th.FG256(th.Muted, "changelog: ")+th.FG256(color, info.URL))
41+ out = append(out, " "+th.FGColor(th.Muted, "changelog: ")+th.FGColor(color, info.URL))
4242 }
4343
4444 out = append(out, "")
@@ -33,12 +33,12 @@ func renderUpdateBanner(th tui.Theme, info UpdateInfo, width int) []string {
33 out = append(out, "")33 out = append(out, "")
34 34
35 title := fmt.Sprintf("zot %s is available (you're on %s).", info.Latest, info.Current)35 title := fmt.Sprintf("zot %s is available (you're on %s).", info.Latest, info.Current)
36- out = append(out, " "+th.FG256(color, tui.Bold(title)))36+ out = append(out, " "+th.FGColor(color, tui.Bold(title)))
37 out = append(out, "")37 out = append(out, "")
38- out = append(out, " "+th.FG256(th.Muted, "run: ")+th.FG256(color, "zot update"))38+ out = append(out, " "+th.FGColor(th.Muted, "run: ")+th.FGColor(color, "zot update"))
39 39
40 if info.URL != "" {40 if info.URL != "" {
41- out = append(out, " "+th.FG256(th.Muted, "changelog: ")+th.FG256(color, info.URL))41+ out = append(out, " "+th.FGColor(th.Muted, "changelog: ")+th.FGColor(color, info.URL))
42 }42 }
43 43
44 out = append(out, "")44 out = append(out, "")
M packages/agent/modes/welcome.go +2 -2
@@ -14,10 +14,10 @@ func welcomeBanner(th tui.Theme, version string, showVersion bool) []string {
1414 if showVersion && version != "" {
1515 text = "zot (" + version + "). yet another coding agent harness."
1616 }
17- headline := th.AccentBar(th.Assistant) + th.FG256(th.Assistant, tui.Bold(text))
17+ headline := th.AccentBar(th.Assistant) + th.FGColor(th.Assistant, tui.Bold(text))
1818 return []string{
1919 headline,
20- th.FG256(th.Muted, " ask anything, or type /help to see commands."),
20+ th.FGColor(th.Muted, " ask anything, or type /help to see commands."),
2121 "",
2222 }
2323 }
@@ -14,10 +14,10 @@ func welcomeBanner(th tui.Theme, version string, showVersion bool) []string {
14 if showVersion && version != "" {14 if showVersion && version != "" {
15 text = "zot (" + version + "). yet another coding agent harness."15 text = "zot (" + version + "). yet another coding agent harness."
16 }16 }
17- headline := th.AccentBar(th.Assistant) + th.FG256(th.Assistant, tui.Bold(text))17+ headline := th.AccentBar(th.Assistant) + th.FGColor(th.Assistant, tui.Bold(text))
18 return []string{18 return []string{
19 headline,19 headline,
20- th.FG256(th.Muted, " ask anything, or type /help to see commands."),20+ th.FGColor(th.Muted, " ask anything, or type /help to see commands."),
21 "",21 "",
22 }22 }
23 }23 }
M packages/agent/zotfile_test.go +7 -7
@@ -342,13 +342,13 @@ func TestFormatZotfileConsentUsesThemeColors(t *testing.T) {
342342
343343 got := formatZotfileConsent(zf, perms, th, true)
344344 for _, want := range []string{
345- th.FG256(th.Assistant, "Agent"),
346- th.FG256(th.Accent, "reviewer@1.2.3"),
347- th.FG256(th.Muted, " fs read: "),
348- th.FG256(th.FG, "/workspace"),
349- th.FG256(th.Warning, "ask"),
350- th.FG256(th.Assistant, "Allow?"),
351- th.FG256(th.Muted, "[y/n]"),
345+ th.FGColor(th.Assistant, "Agent"),
346+ th.FGColor(th.Accent, "reviewer@1.2.3"),
347+ th.FGColor(th.Muted, " fs read: "),
348+ th.FGColor(th.FG, "/workspace"),
349+ th.FGColor(th.Warning, "ask"),
350+ th.FGColor(th.Assistant, "Allow?"),
351+ th.FGColor(th.Muted, "[y/n]"),
352352 } {
353353 if !strings.Contains(got, want) {
354354 t.Fatalf("themed consent missing %q:\n%s", want, got)
@@ -342,13 +342,13 @@ func TestFormatZotfileConsentUsesThemeColors(t *testing.T) {
342 342
343 got := formatZotfileConsent(zf, perms, th, true)343 got := formatZotfileConsent(zf, perms, th, true)
344 for _, want := range []string{344 for _, want := range []string{
345- th.FG256(th.Assistant, "Agent"),345+ th.FGColor(th.Assistant, "Agent"),
346- th.FG256(th.Accent, "reviewer@1.2.3"),346+ th.FGColor(th.Accent, "reviewer@1.2.3"),
347- th.FG256(th.Muted, " fs read: "),347+ th.FGColor(th.Muted, " fs read: "),
348- th.FG256(th.FG, "/workspace"),348+ th.FGColor(th.FG, "/workspace"),
349- th.FG256(th.Warning, "ask"),349+ th.FGColor(th.Warning, "ask"),
350- th.FG256(th.Assistant, "Allow?"),350+ th.FGColor(th.Assistant, "Allow?"),
351- th.FG256(th.Muted, "[y/n]"),351+ th.FGColor(th.Muted, "[y/n]"),
352 } {352 } {
353 if !strings.Contains(got, want) {353 if !strings.Contains(got, want) {
354 t.Fatalf("themed consent missing %q:\n%s", want, got)354 t.Fatalf("themed consent missing %q:\n%s", want, got)
M packages/tui/layout.go +1 -1
@@ -47,7 +47,7 @@ func InputLines(th Theme, lines []string, width int) []string {
4747 if width < 1 {
4848 width = 1
4949 }
50- rule := th.FG256(th.Muted, strings.Repeat("─", width))
50+ rule := th.FGColor(th.Muted, strings.Repeat("─", width))
5151 out := make([]string, 0, len(lines)+2)
5252 out = append(out, rule)
5353 out = append(out, lines...)
@@ -47,7 +47,7 @@ func InputLines(th Theme, lines []string, width int) []string {
47 if width < 1 {47 if width < 1 {
48 width = 148 width = 1
49 }49 }
50- rule := th.FG256(th.Muted, strings.Repeat("─", width))50+ rule := th.FGColor(th.Muted, strings.Repeat("─", width))
51 out := make([]string, 0, len(lines)+2)51 out := make([]string, 0, len(lines)+2)
52 out = append(out, rule)52 out = append(out, rule)
53 out = append(out, lines...)53 out = append(out, lines...)
M packages/tui/markdown.go +11 -11
@@ -52,7 +52,7 @@ func RenderMarkdown(src string, th Theme, width int) string {
5252 }
5353 } else {
5454 for _, l := range strings.Split(code, "\n") {
55- out.WriteString(th.FG256(th.Accent, l))
55+ out.WriteString(th.FGColor(th.Accent, l))
5656 out.WriteString("\n")
5757 }
5858 }
@@ -106,25 +106,25 @@ func RenderMarkdown(src string, th Theme, width int) string {
106106 level := len(m[1])
107107 body := strings.TrimSpace(m[2])
108108 prefix := strings.Repeat("#", level) + " "
109- out.WriteString(Bold(th.FG256(th.Accent, prefix+body)) + "\n")
109+ out.WriteString(Bold(th.FGColor(th.Accent, prefix+body)) + "\n")
110110 continue
111111 }
112112 // Blockquote.
113113 if strings.HasPrefix(trim, "> ") {
114114 body := strings.TrimPrefix(trim, "> ")
115- out.WriteString(th.FG256(th.Muted, "┃ ") + renderInline(body, th) + "\n")
115+ out.WriteString(th.FGColor(th.Muted, "┃ ") + renderInline(body, th) + "\n")
116116 continue
117117 }
118118 // Bullet list.
119119 if m := bulletRE.FindStringSubmatch(line); m != nil {
120120 indent, body := m[1], m[2]
121- out.WriteString(indent + th.FG256(th.Accent, "• ") + renderInline(body, th) + "\n")
121+ out.WriteString(indent + th.FGColor(th.Accent, "• ") + renderInline(body, th) + "\n")
122122 continue
123123 }
124124 // Numbered list.
125125 if m := numberRE.FindStringSubmatch(line); m != nil {
126126 indent, num, body := m[1], m[2], m[3]
127- out.WriteString(indent + th.FG256(th.Accent, num+". ") + renderInline(body, th) + "\n")
127+ out.WriteString(indent + th.FGColor(th.Accent, num+". ") + renderInline(body, th) + "\n")
128128 continue
129129 }
130130 out.WriteString(renderInline(line, th) + "\n")
@@ -354,7 +354,7 @@ func renderTableRow(row []string, widths []int, aligns []tableAlign, th Theme, h
354354 out := make([]string, 0, height)
355355 for r := 0; r < height; r++ {
356356 var b strings.Builder
357- b.WriteString(th.FG256(th.Muted, "|"))
357+ b.WriteString(th.FGColor(th.Muted, "|"))
358358 for c := range widths {
359359 cell := ""
360360 if r < len(wrapped[c]) {
@@ -363,7 +363,7 @@ func renderTableRow(row []string, widths []int, aligns []tableAlign, th Theme, h
363363 b.WriteByte(' ')
364364 b.WriteString(alignCell(cell, widths[c], aligns[c]))
365365 b.WriteByte(' ')
366- b.WriteString(th.FG256(th.Muted, "|"))
366+ b.WriteString(th.FGColor(th.Muted, "|"))
367367 }
368368 out = append(out, b.String())
369369 }
@@ -372,7 +372,7 @@ func renderTableRow(row []string, widths []int, aligns []tableAlign, th Theme, h
372372
373373 func renderTableSeparator(widths []int, aligns []tableAlign, th Theme) string {
374374 var b strings.Builder
375- b.WriteString(th.FG256(th.Muted, "|"))
375+ b.WriteString(th.FGColor(th.Muted, "|"))
376376 for c, w := range widths {
377377 dashes := strings.Repeat("-", w)
378378 switch aligns[c] {
@@ -382,9 +382,9 @@ func renderTableSeparator(widths []int, aligns []tableAlign, th Theme) string {
382382 dashes = strings.Repeat("-", maxInt(1, w-1)) + ":"
383383 }
384384 b.WriteByte(' ')
385- b.WriteString(th.FG256(th.Muted, dashes))
385+ b.WriteString(th.FGColor(th.Muted, dashes))
386386 b.WriteByte(' ')
387- b.WriteString(th.FG256(th.Muted, "|"))
387+ b.WriteString(th.FGColor(th.Muted, "|"))
388388 }
389389 return b.String()
390390 }
@@ -416,7 +416,7 @@ func maxInt(a, b int) int {
416416 func renderInline(s string, th Theme) string {
417417 s = codeRE.ReplaceAllStringFunc(s, func(m string) string {
418418 inner := m[1 : len(m)-1]
419- return th.FG256(th.Accent, inner)
419+ return th.FGColor(th.Accent, inner)
420420 })
421421 s = boldRE.ReplaceAllStringFunc(s, func(m string) string {
422422 inner := m[2 : len(m)-2]
@@ -52,7 +52,7 @@ func RenderMarkdown(src string, th Theme, width int) string {
52 }52 }
53 } else {53 } else {
54 for _, l := range strings.Split(code, "\n") {54 for _, l := range strings.Split(code, "\n") {
55- out.WriteString(th.FG256(th.Accent, l))55+ out.WriteString(th.FGColor(th.Accent, l))
56 out.WriteString("\n")56 out.WriteString("\n")
57 }57 }
58 }58 }
@@ -106,25 +106,25 @@ func RenderMarkdown(src string, th Theme, width int) string {
106 level := len(m[1])106 level := len(m[1])
107 body := strings.TrimSpace(m[2])107 body := strings.TrimSpace(m[2])
108 prefix := strings.Repeat("#", level) + " "108 prefix := strings.Repeat("#", level) + " "
109- out.WriteString(Bold(th.FG256(th.Accent, prefix+body)) + "\n")109+ out.WriteString(Bold(th.FGColor(th.Accent, prefix+body)) + "\n")
110 continue110 continue
111 }111 }
112 // Blockquote.112 // Blockquote.
113 if strings.HasPrefix(trim, "> ") {113 if strings.HasPrefix(trim, "> ") {
114 body := strings.TrimPrefix(trim, "> ")114 body := strings.TrimPrefix(trim, "> ")
115- out.WriteString(th.FG256(th.Muted, "┃ ") + renderInline(body, th) + "\n")115+ out.WriteString(th.FGColor(th.Muted, "┃ ") + renderInline(body, th) + "\n")
116 continue116 continue
117 }117 }
118 // Bullet list.118 // Bullet list.
119 if m := bulletRE.FindStringSubmatch(line); m != nil {119 if m := bulletRE.FindStringSubmatch(line); m != nil {
120 indent, body := m[1], m[2]120 indent, body := m[1], m[2]
121- out.WriteString(indent + th.FG256(th.Accent, "• ") + renderInline(body, th) + "\n")121+ out.WriteString(indent + th.FGColor(th.Accent, "• ") + renderInline(body, th) + "\n")
122 continue122 continue
123 }123 }
124 // Numbered list.124 // Numbered list.
125 if m := numberRE.FindStringSubmatch(line); m != nil {125 if m := numberRE.FindStringSubmatch(line); m != nil {
126 indent, num, body := m[1], m[2], m[3]126 indent, num, body := m[1], m[2], m[3]
127- out.WriteString(indent + th.FG256(th.Accent, num+". ") + renderInline(body, th) + "\n")127+ out.WriteString(indent + th.FGColor(th.Accent, num+". ") + renderInline(body, th) + "\n")
128 continue128 continue
129 }129 }
130 out.WriteString(renderInline(line, th) + "\n")130 out.WriteString(renderInline(line, th) + "\n")
@@ -354,7 +354,7 @@ func renderTableRow(row []string, widths []int, aligns []tableAlign, th Theme, h
354 out := make([]string, 0, height)354 out := make([]string, 0, height)
355 for r := 0; r < height; r++ {355 for r := 0; r < height; r++ {
356 var b strings.Builder356 var b strings.Builder
357- b.WriteString(th.FG256(th.Muted, "|"))357+ b.WriteString(th.FGColor(th.Muted, "|"))
358 for c := range widths {358 for c := range widths {
359 cell := ""359 cell := ""
360 if r < len(wrapped[c]) {360 if r < len(wrapped[c]) {
@@ -363,7 +363,7 @@ func renderTableRow(row []string, widths []int, aligns []tableAlign, th Theme, h
363 b.WriteByte(' ')363 b.WriteByte(' ')
364 b.WriteString(alignCell(cell, widths[c], aligns[c]))364 b.WriteString(alignCell(cell, widths[c], aligns[c]))
365 b.WriteByte(' ')365 b.WriteByte(' ')
366- b.WriteString(th.FG256(th.Muted, "|"))366+ b.WriteString(th.FGColor(th.Muted, "|"))
367 }367 }
368 out = append(out, b.String())368 out = append(out, b.String())
369 }369 }
@@ -372,7 +372,7 @@ func renderTableRow(row []string, widths []int, aligns []tableAlign, th Theme, h
372 372
373 func renderTableSeparator(widths []int, aligns []tableAlign, th Theme) string {373 func renderTableSeparator(widths []int, aligns []tableAlign, th Theme) string {
374 var b strings.Builder374 var b strings.Builder
375- b.WriteString(th.FG256(th.Muted, "|"))375+ b.WriteString(th.FGColor(th.Muted, "|"))
376 for c, w := range widths {376 for c, w := range widths {
377 dashes := strings.Repeat("-", w)377 dashes := strings.Repeat("-", w)
378 switch aligns[c] {378 switch aligns[c] {
@@ -382,9 +382,9 @@ func renderTableSeparator(widths []int, aligns []tableAlign, th Theme) string {
382 dashes = strings.Repeat("-", maxInt(1, w-1)) + ":"382 dashes = strings.Repeat("-", maxInt(1, w-1)) + ":"
383 }383 }
384 b.WriteByte(' ')384 b.WriteByte(' ')
385- b.WriteString(th.FG256(th.Muted, dashes))385+ b.WriteString(th.FGColor(th.Muted, dashes))
386 b.WriteByte(' ')386 b.WriteByte(' ')
387- b.WriteString(th.FG256(th.Muted, "|"))387+ b.WriteString(th.FGColor(th.Muted, "|"))
388 }388 }
389 return b.String()389 return b.String()
390 }390 }
@@ -416,7 +416,7 @@ func maxInt(a, b int) int {
416 func renderInline(s string, th Theme) string {416 func renderInline(s string, th Theme) string {
417 s = codeRE.ReplaceAllStringFunc(s, func(m string) string {417 s = codeRE.ReplaceAllStringFunc(s, func(m string) string {
418 inner := m[1 : len(m)-1]418 inner := m[1 : len(m)-1]
419- return th.FG256(th.Accent, inner)419+ return th.FGColor(th.Accent, inner)
420 })420 })
421 s = boldRE.ReplaceAllStringFunc(s, func(m string) string {421 s = boldRE.ReplaceAllStringFunc(s, func(m string) string {
422 inner := m[2 : len(m)-2]422 inner := m[2 : len(m)-2]
M packages/tui/rightbar_test.go +3 -3
@@ -127,8 +127,8 @@ func TestRenderRightBarDimsChecklistMarkers(t *testing.T) {
127127 }}, 24, 8)
128128 rendered := strings.Join(lines, "\n")
129129 for _, marker := range []string{"[x]", "[>]", "[ ]"} {
130- if !strings.Contains(rendered, Dark.FG256(Dark.Muted, marker)) &&
131- !strings.Contains(rendered, Dark.FG256(Dark.Muted, Dim(marker))) {
130+ if !strings.Contains(rendered, Dark.FGColor(Dark.Muted, marker)) &&
131+ !strings.Contains(rendered, Dark.FGColor(Dark.Muted, Dim(marker))) {
132132 t.Fatalf("marker %q was not muted: %q", marker, rendered)
133133 }
134134 }
@@ -169,7 +169,7 @@ func TestRenderRightBarDimsInactiveChecklistSections(t *testing.T) {
169169
170170 func TestJoinRightBarUsesOnlyMutedSeparator(t *testing.T) {
171171 line := JoinRightBar(Dark, "main", "right", 8, 8)
172- if !strings.Contains(line, Dark.FG256(Dark.Muted, "│")) {
172+ if !strings.Contains(line, Dark.FGColor(Dark.Muted, "│")) {
173173 t.Fatalf("separator is not muted: %q", line)
174174 }
175175 if strings.ContainsAny(stripANSI(line), "┌┐└┘─") {
@@ -127,8 +127,8 @@ func TestRenderRightBarDimsChecklistMarkers(t *testing.T) {
127 }}, 24, 8)127 }}, 24, 8)
128 rendered := strings.Join(lines, "\n")128 rendered := strings.Join(lines, "\n")
129 for _, marker := range []string{"[x]", "[>]", "[ ]"} {129 for _, marker := range []string{"[x]", "[>]", "[ ]"} {
130- if !strings.Contains(rendered, Dark.FG256(Dark.Muted, marker)) &&130+ if !strings.Contains(rendered, Dark.FGColor(Dark.Muted, marker)) &&
131- !strings.Contains(rendered, Dark.FG256(Dark.Muted, Dim(marker))) {131+ !strings.Contains(rendered, Dark.FGColor(Dark.Muted, Dim(marker))) {
132 t.Fatalf("marker %q was not muted: %q", marker, rendered)132 t.Fatalf("marker %q was not muted: %q", marker, rendered)
133 }133 }
134 }134 }
@@ -169,7 +169,7 @@ func TestRenderRightBarDimsInactiveChecklistSections(t *testing.T) {
169 169
170 func TestJoinRightBarUsesOnlyMutedSeparator(t *testing.T) {170 func TestJoinRightBarUsesOnlyMutedSeparator(t *testing.T) {
171 line := JoinRightBar(Dark, "main", "right", 8, 8)171 line := JoinRightBar(Dark, "main", "right", 8, 8)
172- if !strings.Contains(line, Dark.FG256(Dark.Muted, "│")) {172+ if !strings.Contains(line, Dark.FGColor(Dark.Muted, "│")) {
173 t.Fatalf("separator is not muted: %q", line)173 t.Fatalf("separator is not muted: %q", line)
174 }174 }
175 if strings.ContainsAny(stripANSI(line), "┌┐└┘─") {175 if strings.ContainsAny(stripANSI(line), "┌┐└┘─") {
M packages/tui/theme_inherited_test.go +5 -5
@@ -119,10 +119,10 @@ func TestInheritedThemeUsesTrueColorAndTerminalPalette(t *testing.T) {
119119 if !th.Inherited || !th.Terminal.TrueColor {
120120 t.Fatalf("inherited theme mode = (inherited=%v, trueColor=%v), want truecolor", th.Inherited, th.Terminal.TrueColor)
121121 }
122- if got := th.FG256(th.Accent, "x"); !strings.Contains(got, "\x1b[38;2;40;100;240m") {
122+ if got := th.FGColor(th.Accent, "x"); !strings.Contains(got, "\x1b[38;2;40;100;240m") {
123123 t.Fatalf("accent did not use the reported terminal palette: %q", got)
124124 }
125- if got := th.FG256(th.FG, "x"); !strings.Contains(got, "\x1b[38;2;220;210;200m") {
125+ if got := th.FGColor(th.FG, "x"); !strings.Contains(got, "\x1b[38;2;220;210;200m") {
126126 t.Fatalf("foreground did not inherit the terminal default: %q", got)
127127 }
128128 if got := th.UserBubble("x", 3); !strings.Contains(got, "\x1b[48;2;") {
@@ -141,7 +141,7 @@ func TestInheritedThemeFallsBackTo256Colors(t *testing.T) {
141141 if !th.Inherited || th.Terminal.TrueColor {
142142 t.Fatalf("inherited theme mode = (inherited=%v, trueColor=%v), want 256", th.Inherited, th.Terminal.TrueColor)
143143 }
144- if got := th.FG256(th.Accent, "x"); !strings.Contains(got, "\x1b[38;5;12m") || strings.Contains(got, "\x1b[38;2;") {
144+ if got := th.FGColor(th.Accent, "x"); !strings.Contains(got, "\x1b[38;5;12m") || strings.Contains(got, "\x1b[38;2;") {
145145 t.Fatalf("accent did not use 256-color fallback: %q", got)
146146 }
147147 if got := th.UserBubble("x", 3); strings.Contains(got, "\x1b[48;2;") || !strings.Contains(got, "\x1b[48;5;") {
@@ -156,7 +156,7 @@ func TestInheritedThemeFallsBackTo256Colors(t *testing.T) {
156156 }
157157
158158 func TestExplicitThemesFollowTerminalCapability(t *testing.T) {
159- if got, want := Dark.FG256(Dark.Accent, "x"), "\x1b[38;5;111mx\x1b[0m"; got != want {
159+ if got, want := Dark.FGColor(Dark.Accent, "x"), "\x1b[38;5;111mx\x1b[0m"; got != want {
160160 t.Fatalf("explicit theme output = %q, want %q", got, want)
161161 }
162162 if got := Dark.UserBubble("x", 1); !strings.Contains(got, "\x1b[48;5;") || strings.Contains(got, "\x1b[48;2;") {
@@ -164,7 +164,7 @@ func TestExplicitThemesFollowTerminalCapability(t *testing.T) {
164164 }
165165
166166 trueColor := withTerminalProfile(Dark, Theme{Terminal: TerminalProfile{TrueColor: true}})
167- if got := trueColor.FG256(trueColor.Accent, "x"); !strings.Contains(got, "\x1b[38;2;") {
167+ if got := trueColor.FGColor(trueColor.Accent, "x"); !strings.Contains(got, "\x1b[38;2;") {
168168 t.Fatalf("truecolor explicit theme did not emit RGB: %q", got)
169169 }
170170 if got := trueColor.UserBubble("x", 1); !strings.Contains(got, "\x1b[48;2;66;69;75m") {
@@ -119,10 +119,10 @@ func TestInheritedThemeUsesTrueColorAndTerminalPalette(t *testing.T) {
119 if !th.Inherited || !th.Terminal.TrueColor {119 if !th.Inherited || !th.Terminal.TrueColor {
120 t.Fatalf("inherited theme mode = (inherited=%v, trueColor=%v), want truecolor", th.Inherited, th.Terminal.TrueColor)120 t.Fatalf("inherited theme mode = (inherited=%v, trueColor=%v), want truecolor", th.Inherited, th.Terminal.TrueColor)
121 }121 }
122- if got := th.FG256(th.Accent, "x"); !strings.Contains(got, "\x1b[38;2;40;100;240m") {122+ if got := th.FGColor(th.Accent, "x"); !strings.Contains(got, "\x1b[38;2;40;100;240m") {
123 t.Fatalf("accent did not use the reported terminal palette: %q", got)123 t.Fatalf("accent did not use the reported terminal palette: %q", got)
124 }124 }
125- if got := th.FG256(th.FG, "x"); !strings.Contains(got, "\x1b[38;2;220;210;200m") {125+ if got := th.FGColor(th.FG, "x"); !strings.Contains(got, "\x1b[38;2;220;210;200m") {
126 t.Fatalf("foreground did not inherit the terminal default: %q", got)126 t.Fatalf("foreground did not inherit the terminal default: %q", got)
127 }127 }
128 if got := th.UserBubble("x", 3); !strings.Contains(got, "\x1b[48;2;") {128 if got := th.UserBubble("x", 3); !strings.Contains(got, "\x1b[48;2;") {
@@ -141,7 +141,7 @@ func TestInheritedThemeFallsBackTo256Colors(t *testing.T) {
141 if !th.Inherited || th.Terminal.TrueColor {141 if !th.Inherited || th.Terminal.TrueColor {
142 t.Fatalf("inherited theme mode = (inherited=%v, trueColor=%v), want 256", th.Inherited, th.Terminal.TrueColor)142 t.Fatalf("inherited theme mode = (inherited=%v, trueColor=%v), want 256", th.Inherited, th.Terminal.TrueColor)
143 }143 }
144- if got := th.FG256(th.Accent, "x"); !strings.Contains(got, "\x1b[38;5;12m") || strings.Contains(got, "\x1b[38;2;") {144+ if got := th.FGColor(th.Accent, "x"); !strings.Contains(got, "\x1b[38;5;12m") || strings.Contains(got, "\x1b[38;2;") {
145 t.Fatalf("accent did not use 256-color fallback: %q", got)145 t.Fatalf("accent did not use 256-color fallback: %q", got)
146 }146 }
147 if got := th.UserBubble("x", 3); strings.Contains(got, "\x1b[48;2;") || !strings.Contains(got, "\x1b[48;5;") {147 if got := th.UserBubble("x", 3); strings.Contains(got, "\x1b[48;2;") || !strings.Contains(got, "\x1b[48;5;") {
@@ -156,7 +156,7 @@ func TestInheritedThemeFallsBackTo256Colors(t *testing.T) {
156 }156 }
157 157
158 func TestExplicitThemesFollowTerminalCapability(t *testing.T) {158 func TestExplicitThemesFollowTerminalCapability(t *testing.T) {
159- if got, want := Dark.FG256(Dark.Accent, "x"), "\x1b[38;5;111mx\x1b[0m"; got != want {159+ if got, want := Dark.FGColor(Dark.Accent, "x"), "\x1b[38;5;111mx\x1b[0m"; got != want {
160 t.Fatalf("explicit theme output = %q, want %q", got, want)160 t.Fatalf("explicit theme output = %q, want %q", got, want)
161 }161 }
162 if got := Dark.UserBubble("x", 1); !strings.Contains(got, "\x1b[48;5;") || strings.Contains(got, "\x1b[48;2;") {162 if got := Dark.UserBubble("x", 1); !strings.Contains(got, "\x1b[48;5;") || strings.Contains(got, "\x1b[48;2;") {
@@ -164,7 +164,7 @@ func TestExplicitThemesFollowTerminalCapability(t *testing.T) {
164 }164 }
165 165
166 trueColor := withTerminalProfile(Dark, Theme{Terminal: TerminalProfile{TrueColor: true}})166 trueColor := withTerminalProfile(Dark, Theme{Terminal: TerminalProfile{TrueColor: true}})
167- if got := trueColor.FG256(trueColor.Accent, "x"); !strings.Contains(got, "\x1b[38;2;") {167+ if got := trueColor.FGColor(trueColor.Accent, "x"); !strings.Contains(got, "\x1b[38;2;") {
168 t.Fatalf("truecolor explicit theme did not emit RGB: %q", got)168 t.Fatalf("truecolor explicit theme did not emit RGB: %q", got)
169 }169 }
170 if got := trueColor.UserBubble("x", 1); !strings.Contains(got, "\x1b[48;2;66;69;75m") {170 if got := trueColor.UserBubble("x", 1); !strings.Contains(got, "\x1b[48;2;66;69;75m") {
M packages/tui/theme_loader_test.go +2 -2
@@ -131,7 +131,7 @@ func TestLoadThemeAcceptsRGBSemanticColors(t *testing.T) {
131131 if th.SelectionBG != Color256(237) || th.SelectionFG != Color256(250) {
132132 t.Fatalf("indexed semantic colors were not preserved: bg=%#v fg=%#v", th.SelectionBG, th.SelectionFG)
133133 }
134- if got := th.FG256(th.Accent, "x"); !strings.Contains(got, "\x1b[38;2;200;100;50m") {
134+ if got := th.FGColor(th.Accent, "x"); !strings.Contains(got, "\x1b[38;2;200;100;50m") {
135135 t.Fatalf("RGB accent was not rendered as truecolor: %q", got)
136136 }
137137 }
@@ -144,7 +144,7 @@ func TestLoadThemeQuantizesRGBSemanticColorsWithoutTrueColor(t *testing.T) {
144144 t.Fatal(err)
145145 }
146146 want := nearestXtermColor(200, 100, 50)
147- if got := th.FG256(th.Accent, "x"); got != sgrFG(want)+"x\x1b[0m" {
147+ if got := th.FGColor(th.Accent, "x"); got != sgrFG(want)+"x\x1b[0m" {
148148 t.Fatalf("RGB accent fallback = %q, want xterm-256 index %d", got, want)
149149 }
150150 }
@@ -131,7 +131,7 @@ func TestLoadThemeAcceptsRGBSemanticColors(t *testing.T) {
131 if th.SelectionBG != Color256(237) || th.SelectionFG != Color256(250) {131 if th.SelectionBG != Color256(237) || th.SelectionFG != Color256(250) {
132 t.Fatalf("indexed semantic colors were not preserved: bg=%#v fg=%#v", th.SelectionBG, th.SelectionFG)132 t.Fatalf("indexed semantic colors were not preserved: bg=%#v fg=%#v", th.SelectionBG, th.SelectionFG)
133 }133 }
134- if got := th.FG256(th.Accent, "x"); !strings.Contains(got, "\x1b[38;2;200;100;50m") {134+ if got := th.FGColor(th.Accent, "x"); !strings.Contains(got, "\x1b[38;2;200;100;50m") {
135 t.Fatalf("RGB accent was not rendered as truecolor: %q", got)135 t.Fatalf("RGB accent was not rendered as truecolor: %q", got)
136 }136 }
137 }137 }
@@ -144,7 +144,7 @@ func TestLoadThemeQuantizesRGBSemanticColorsWithoutTrueColor(t *testing.T) {
144 t.Fatal(err)144 t.Fatal(err)
145 }145 }
146 want := nearestXtermColor(200, 100, 50)146 want := nearestXtermColor(200, 100, 50)
147- if got := th.FG256(th.Accent, "x"); got != sgrFG(want)+"x\x1b[0m" {147+ if got := th.FGColor(th.Accent, "x"); got != sgrFG(want)+"x\x1b[0m" {
148 t.Fatalf("RGB accent fallback = %q, want xterm-256 index %d", got, want)148 t.Fatalf("RGB accent fallback = %q, want xterm-256 index %d", got, want)
149 }149 }
150 }150 }
M packages/tui/view.go +49 -49
@@ -325,7 +325,7 @@ func (v *View) renderErr(width int) []string {
325325 if idx > 0 {
326326 prefix = indent
327327 }
328- out = append(out, v.Theme.FG256(v.Theme.Error, prefix+line))
328+ out = append(out, v.Theme.FGColor(v.Theme.Error, prefix+line))
329329 }
330330 return out
331331 }
@@ -340,9 +340,9 @@ func (v *View) renderStartupResources(width int) []string {
340340 if len(items) == 0 {
341341 return
342342 }
343- out = append(out, v.Theme.FG256(v.Theme.Accent, "["+label+"]"))
343+ out = append(out, v.Theme.FGColor(v.Theme.Accent, "["+label+"]"))
344344 for _, line := range wrapLine(strings.Join(items, ", "), bodyWidth, "") {
345- out = append(out, v.Theme.FG256(v.Theme.Muted, " "+line))
345+ out = append(out, v.Theme.FGColor(v.Theme.Muted, " "+line))
346346 }
347347 out = append(out, "")
348348 }
@@ -796,7 +796,7 @@ func (v *View) renderMessage(m provider.Message, width int, turnOpen bool) []str
796796 }
797797 lines = append(lines, toolHeaderLine(v.Theme, label, width, v.CompactMode))
798798 if tr.IsError {
799- lines = append(lines, toolBodyLine(v.Theme, v.Theme.FG256(color, " error"), width, v.CompactMode))
799+ lines = append(lines, toolBodyLine(v.Theme, v.Theme.FGColor(color, " error"), width, v.CompactMode))
800800 }
801801 for _, line := range v.renderToolResultContent(tr.Content, width, color, path, startLine) {
802802 _, stripped := parseImageFootprint(line)
@@ -810,7 +810,7 @@ func (v *View) renderMessage(m provider.Message, width int, turnOpen bool) []str
810810 lines = append(lines, toolBoxTop(v.Theme, label, width))
811811 lines = append(lines, toolBoxSide(v.Theme, "", width))
812812 if tr.IsError {
813- lines = append(lines, toolBoxSide(v.Theme, v.Theme.FG256(color, " error"), width))
813+ lines = append(lines, toolBoxSide(v.Theme, v.Theme.FGColor(color, " error"), width))
814814 }
815815 for _, line := range v.renderToolResultContent(tr.Content, width, color, path, startLine) {
816816 // Image-footprint rows (the escape row, the blank
@@ -1010,7 +1010,7 @@ func (v *View) renderLiveToolBody(tc ToolCallView, width int) []string {
10101010 // Header line hints which edit is streaming and, when more
10111011 // than one has landed, how many the model is doing.
10121012 hint := fmt.Sprintf("edit %d (streaming)", idx)
1013- body := []string{" " + v.Theme.FG256(v.Theme.Muted, hint), ""}
1013+ body := []string{" " + v.Theme.FGColor(v.Theme.Muted, hint), ""}
10141014 body = append(body, v.renderRawFile(partial, tc.LivePath, 1)...)
10151015 return v.wrapLiveBody(body, width)
10161016 case "bash", "Bash":
@@ -1028,7 +1028,7 @@ func (v *View) renderLiveBashCommand(command string, width int) []string {
10281028 if v.FlatTools || v.CompactMode {
10291029 inner = flatToolBodyRenderWidth(width)
10301030 }
1031- prompt := v.Theme.FG256(v.Theme.Muted, "$ ")
1031+ prompt := v.Theme.FGColor(v.Theme.Muted, "$ ")
10321032 var out []string
10331033 for i, line := range strings.Split(command, "\n") {
10341034 firstPrefix := " "
@@ -1042,7 +1042,7 @@ func (v *View) renderLiveBashCommand(command string, width int) []string {
10421042 if wrapWidth < 10 {
10431043 wrapWidth = 10
10441044 }
1045- for j, wrapped := range wrapANSILine(v.Theme.FG256(v.Theme.ToolOut, line), wrapWidth) {
1045+ for j, wrapped := range wrapANSILine(v.Theme.FGColor(v.Theme.ToolOut, line), wrapWidth) {
10461046 prefix := firstPrefix
10471047 if j > 0 {
10481048 prefix = contPrefix
@@ -1133,7 +1133,7 @@ func toolBoxTop(th Theme, label string, width int) string {
11331133 }
11341134 fillStr := strings.Repeat("─", fill)
11351135 name, rest := splitToolLabel(label)
1136- return margin + th.FG256(th.Muted, prefix) + th.FG256(th.FG, name) + th.FG256(th.Muted, rest+suffix+fillStr+"┐") + margin
1136+ return margin + th.FGColor(th.Muted, prefix) + th.FGColor(th.FG, name) + th.FGColor(th.Muted, rest+suffix+fillStr+"┐") + margin
11371137 }
11381138
11391139 func oneLineToolLabel(label string) string {
@@ -1172,7 +1172,7 @@ func flatToolHeader(th Theme, label string, width int) string {
11721172 label = oneLineToolLabel(label)
11731173 name, rest := splitToolLabel(label)
11741174 margin := strings.Repeat(" ", toolBoxOuterMargin)
1175- gutter := th.FG256(th.Assistant, "▌") + " "
1175+ gutter := th.FGColor(th.Assistant, "▌") + " "
11761176 // Budget the visible text to the content width so a very long
11771177 // argument summary doesn't run off the right edge; truncate with
11781178 // an ellipsis like the box header does.
@@ -1189,7 +1189,7 @@ func flatToolHeader(th Theme, label string, width int) string {
11891189 rest = ""
11901190 }
11911191 }
1192- return margin + gutter + th.FG256(th.FG, name) + th.FG256(th.Muted, rest)
1192+ return margin + gutter + th.FGColor(th.FG, name) + th.FGColor(th.Muted, rest)
11931193 }
11941194
11951195 func compactToolBlank(th Theme, width int) string {
@@ -1266,7 +1266,7 @@ func toolBoxBottom(th Theme, width int) string {
12661266 }
12671267 margin := strings.Repeat(" ", toolBoxOuterMargin)
12681268 line := "└" + strings.Repeat("─", w-2) + "┘"
1269- return margin + th.FG256(th.Muted, line) + margin
1269+ return margin + th.FGColor(th.Muted, line) + margin
12701270 }
12711271
12721272 // hasImageEscapeLine reports whether s contains a Kitty (\x1b_G) or
@@ -1503,7 +1503,7 @@ func (v *View) collapseToolBody(lines []string, hasImage bool) []string {
15031503 hidden := len(lines) - ToolCollapsePreview
15041504 total := len(lines)
15051505 footer := fmt.Sprintf(" ... (%d more lines, %d total, ctrl+o to expand)", hidden, total)
1506- footer = v.Theme.FG256(v.Theme.Muted, footer)
1506+ footer = v.Theme.FGColor(v.Theme.Muted, footer)
15071507 out := append([]string(nil), kept...)
15081508 out = append(out, "")
15091509 return append(out, footer)
@@ -1560,7 +1560,7 @@ func (v *View) renderToolText(text string, width int, defaultColor TerminalColor
15601560 // Detect diff header: "--- name" followed somewhere by "+++ name".
15611561 if strings.HasPrefix(l, "--- ") || strings.HasPrefix(l, "+++ ") {
15621562 inDiff = true
1563- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, l))
1563+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, l))
15641564 continue
15651565 }
15661566 // Hunk header "@@ -a,b +c,d @@" resets the counters so patches
@@ -1569,12 +1569,12 @@ func (v *View) renderToolText(text string, width int, defaultColor TerminalColor
15691569 if o, n, ok := parseHunkHeader(l); ok {
15701570 oldLine, newLine = o, n
15711571 }
1572- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, l))
1572+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, l))
15731573 continue
15741574 }
15751575 if inDiff && strings.TrimSpace(l) == "..." {
15761576 out = append(out, "")
1577- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, "..."))
1577+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, "..."))
15781578 continue
15791579 }
15801580 if inDiff && len(l) > 0 {
@@ -1596,7 +1596,7 @@ func (v *View) renderToolText(text string, width int, defaultColor TerminalColor
15961596 }
15971597 // Regular line.
15981598 for _, w := range wrapLine(l, width-4, " ") {
1599- out = append(out, " "+v.Theme.FG256(defaultColor, w))
1599+ out = append(out, " "+v.Theme.FGColor(defaultColor, w))
16001600 }
16011601 }
16021602 return out
@@ -1697,9 +1697,9 @@ func (v *View) renderDiffRow(line string, width int, color TerminalColor, lineNo
16971697 if mark == ' ' {
16981698 codeColor = v.Theme.Muted
16991699 }
1700- codeRendered = v.Theme.FG256(codeColor, chunk)
1700+ codeRendered = v.Theme.FGColor(codeColor, chunk)
17011701 }
1702- out = append(out, " "+v.Theme.FG256(gutterColor, rowGutter)+codeRendered)
1702+ out = append(out, " "+v.Theme.FGColor(gutterColor, rowGutter)+codeRendered)
17031703 }
17041704 return out
17051705 }
@@ -1805,11 +1805,11 @@ func (v *View) renderImageBlock(b provider.ImageBlock, width int) []string {
18051805 out = append(out, imageFootprintSentinel)
18061806 }
18071807 out = append(out, imageFootprintSentinel)
1808- out = append(out, v.Theme.FG256(v.Theme.Muted, info))
1808+ out = append(out, v.Theme.FGColor(v.Theme.Muted, info))
18091809 return out
18101810 }
18111811 }
1812- return []string{v.Theme.FG256(v.Theme.Muted, info)}
1812+ return []string{v.Theme.FGColor(v.Theme.Muted, info)}
18131813 }
18141814
18151815 // looksLikeNumberedFile returns true when text matches the `read`
@@ -1877,17 +1877,17 @@ func (v *View) renderNumberedFile(text, sourcePath string) []string {
18771877 // code is visually distinct from the muted gutter.
18781878 highlighted = make([]string, len(codes))
18791879 for i, c := range codes {
1880- highlighted[i] = v.Theme.FG256(v.Theme.ToolOut, c)
1880+ highlighted[i] = v.Theme.FGColor(v.Theme.ToolOut, c)
18811881 }
18821882 }
18831883 out := make([]string, 0, len(codes))
18841884 for i, code := range highlighted {
18851885 g := gutters[i]
18861886 if g == "" {
1887- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, code))
1887+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, code))
18881888 continue
18891889 }
1890- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, g)+code)
1890+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, g)+code)
18911891 }
18921892 return out
18931893 }
@@ -1927,15 +1927,15 @@ func (v *View) renderBashResult(lines []string, width int, defaultColor Terminal
19271927 // further processing / wrapping so the shell-style
19281928 // prompt reads at a glance.
19291929 for _, w := range wrapLine(l, width-4, " ") {
1930- out = append(out, " "+v.Theme.FG256(v.Theme.Accent, w))
1930+ out = append(out, " "+v.Theme.FGColor(v.Theme.Accent, w))
19311931 }
19321932 case i == footerIdx:
19331933 for _, w := range wrapLine(l, width-4, " ") {
1934- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, w))
1934+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, w))
19351935 }
19361936 default:
19371937 for _, w := range wrapLine(l, width-4, " ") {
1938- out = append(out, " "+v.Theme.FG256(defaultColor, w))
1938+ out = append(out, " "+v.Theme.FGColor(defaultColor, w))
19391939 }
19401940 }
19411941 }
@@ -2097,7 +2097,7 @@ func (v *View) renderUnifiedDiff(text string, width int, sourcePath string) []st
20972097 continue
20982098 }
20992099 if l == "..." {
2100- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, "..."))
2100+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, "..."))
21012101 continue
21022102 }
21032103 switch l[0] {
@@ -2113,7 +2113,7 @@ func (v *View) renderUnifiedDiff(text string, width int, sourcePath string) []st
21132113 newLine++
21142114 default:
21152115 for _, w := range wrapLine(l, width-4, " ") {
2116- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, w))
2116+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, w))
21172117 }
21182118 }
21192119 }
@@ -2176,17 +2176,17 @@ func (v *View) renderRawFile(text, sourcePath string, startLine int) []string {
21762176 } else {
21772177 highlighted = make([]string, len(code))
21782178 for i, c := range code {
2179- highlighted[i] = v.Theme.FG256(v.Theme.ToolOut, c)
2179+ highlighted[i] = v.Theme.FGColor(v.Theme.ToolOut, c)
21802180 }
21812181 }
21822182
21832183 out := make([]string, 0, len(lines))
21842184 for i, c := range highlighted {
21852185 gutter := fmt.Sprintf("%4d ", startLine+i)
2186- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, gutter)+c)
2186+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, gutter)+c)
21872187 }
21882188 for _, f := range footer {
2189- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, f))
2189+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, f))
21902190 }
21912191 return out
21922192 }
@@ -2327,7 +2327,7 @@ func (v *View) renderCompactionBlock(m provider.Message, width int) []string {
23272327
23282328 if v.ExpandAll {
23292329 var lines []string
2330- header := th.FG256(th.Muted, fmt.Sprintf("compacted from ~%s tokens", tokens))
2330+ header := th.FGColor(th.Muted, fmt.Sprintf("compacted from ~%s tokens", tokens))
23312331 lines = append(lines, indent+header)
23322332 lines = append(lines, "")
23332333 for _, c := range m.Content {
@@ -2349,7 +2349,7 @@ func (v *View) renderCompactionBlock(m provider.Message, width int) []string {
23492349 }
23502350
23512351 // Collapsed: single line, no banner.
2352- line := th.FG256(th.Muted, fmt.Sprintf("compacted from ~%s tokens (ctrl+o to expand)", tokens))
2352+ line := th.FGColor(th.Muted, fmt.Sprintf("compacted from ~%s tokens (ctrl+o to expand)", tokens))
23532353 return []string{indent + line}
23542354 }
23552355
@@ -2447,7 +2447,7 @@ func StatusBar(p StatusBarParams) []string {
24472447 if p.AutoCompacting {
24482448 ctx += " (auto)"
24492449 }
2450- stats = append(stats, th.FG256(ctxColor, ctx))
2450+ stats = append(stats, th.FGColor(ctxColor, ctx))
24512451 }
24522452
24532453 // Layout uses exactly 2 spaces of horizontal padding everywhere:
@@ -2497,11 +2497,11 @@ func StatusBar(p StatusBarParams) []string {
24972497 // prefix there's no trailing separator to double-pad.
24982498 leftBuilder.WriteString(pad)
24992499 }
2500- leftBuilder.WriteString(th.FG256(th.Muted, left))
2500+ leftBuilder.WriteString(th.FGColor(th.Muted, left))
25012501 if middle != "" {
25022502 leftBuilder.WriteString(pad)
25032503 // Highlight the opt-in max tier; other status information stays muted.
2504- leftBuilder.WriteString(th.FG256(reasoningStatusColor(th, reasoningText), middle))
2504+ leftBuilder.WriteString(th.FGColor(reasoningStatusColor(th, reasoningText), middle))
25052505 }
25062506
25072507 cwd := shortenHome(p.CWD)
@@ -2527,21 +2527,21 @@ func StatusBar(p StatusBarParams) []string {
25272527 // stats on their own rows. This mirrors the idle split below.
25282528 if p.Cols > 0 && p.BusyPrefix != "" && visibleWidth(primary) > p.Cols {
25292529 busyLine := pad + p.BusyPrefix
2530- modelLine := pad + th.FG256(th.Muted, left)
2530+ modelLine := pad + th.FGColor(th.Muted, left)
25312531 lines := []string{busyLine}
2532- if middle != "" && visibleWidth(modelLine+pad+th.FG256(th.Muted, middle)) > p.Cols {
2532+ if middle != "" && visibleWidth(modelLine+pad+th.FGColor(th.Muted, middle)) > p.Cols {
25332533 lines = appendWrappedStatusLines(lines, th, pad, left, fastText, reasoningText, statsText, p.Cols)
25342534 } else {
25352535 var infoBuilder strings.Builder
25362536 infoBuilder.WriteString(modelLine)
25372537 if middle != "" {
25382538 infoBuilder.WriteString(pad)
2539- infoBuilder.WriteString(th.FG256(reasoningStatusColor(th, reasoningText), middle))
2539+ infoBuilder.WriteString(th.FGColor(reasoningStatusColor(th, reasoningText), middle))
25402540 }
25412541 lines = append(lines, infoBuilder.String())
25422542 }
25432543 if cwd != "" {
2544- lines = append(lines, pad+th.FG256(th.Muted, cwd))
2544+ lines = append(lines, pad+th.FGColor(th.Muted, cwd))
25452545 }
25462546 return lines
25472547 }
@@ -2554,7 +2554,7 @@ func StatusBar(p StatusBarParams) []string {
25542554 var lines []string
25552555 lines = appendWrappedStatusLines(lines, th, pad, left, fastText, reasoningText, statsText, p.Cols)
25562556 if cwd != "" {
2557- lines = append(lines, pad+th.FG256(th.Muted, cwd))
2557+ lines = append(lines, pad+th.FGColor(th.Muted, cwd))
25582558 }
25592559 return lines
25602560 }
@@ -2565,12 +2565,12 @@ func StatusBar(p StatusBarParams) []string {
25652565
25662566 // Second line: indent with the same 2-space pad so the cwd lines
25672567 // up under the "(provider)" column on line 1.
2568- cwdRendered := pad + th.FG256(th.Muted, cwd)
2568+ cwdRendered := pad + th.FGColor(th.Muted, cwd)
25692569 return []string{primary, cwdRendered}
25702570 }
25712571
25722572 func appendWrappedStatusLines(lines []string, th Theme, pad, modelText, fastText, reasoningText, statsText string, cols int) []string {
2573- modelLine := pad + th.FG256(th.Muted, modelText)
2573+ modelLine := pad + th.FGColor(th.Muted, modelText)
25742574 infoParts := make([]string, 0, 2)
25752575 if fastText != "" {
25762576 infoParts = append(infoParts, fastText)
@@ -2582,7 +2582,7 @@ func appendWrappedStatusLines(lines []string, th Theme, pad, modelText, fastText
25822582 if infoText == "" {
25832583 lines = append(lines, modelLine)
25842584 if statsText != "" {
2585- lines = append(lines, pad+th.FG256(th.Muted, statsText))
2585+ lines = append(lines, pad+th.FGColor(th.Muted, statsText))
25862586 }
25872587 return lines
25882588 }
@@ -2593,18 +2593,18 @@ func appendWrappedStatusLines(lines []string, th Theme, pad, modelText, fastText
25932593 infoColor = reasoningStatusColor(th, reasoningText)
25942594 }
25952595 if cols <= 0 || visibleWidth(modelInfoPlain) <= cols {
2596- lines = append(lines, pad+th.FG256(infoColor, modelText+pad+infoText))
2596+ lines = append(lines, pad+th.FGColor(infoColor, modelText+pad+infoText))
25972597 } else {
25982598 lines = append(lines, modelLine)
25992599 if fastText != "" {
2600- lines = append(lines, pad+th.FG256(th.Muted, fastText))
2600+ lines = append(lines, pad+th.FGColor(th.Muted, fastText))
26012601 }
26022602 if reasoningText != "" {
2603- lines = append(lines, pad+th.FG256(reasoningStatusColor(th, reasoningText), reasoningText))
2603+ lines = append(lines, pad+th.FGColor(reasoningStatusColor(th, reasoningText), reasoningText))
26042604 }
26052605 }
26062606 if statsText != "" {
2607- lines = append(lines, pad+th.FG256(th.Muted, statsText))
2607+ lines = append(lines, pad+th.FGColor(th.Muted, statsText))
26082608 }
26092609 return lines
26102610 }
@@ -325,7 +325,7 @@ func (v *View) renderErr(width int) []string {
325 if idx > 0 {325 if idx > 0 {
326 prefix = indent326 prefix = indent
327 }327 }
328- out = append(out, v.Theme.FG256(v.Theme.Error, prefix+line))328+ out = append(out, v.Theme.FGColor(v.Theme.Error, prefix+line))
329 }329 }
330 return out330 return out
331 }331 }
@@ -340,9 +340,9 @@ func (v *View) renderStartupResources(width int) []string {
340 if len(items) == 0 {340 if len(items) == 0 {
341 return341 return
342 }342 }
343- out = append(out, v.Theme.FG256(v.Theme.Accent, "["+label+"]"))343+ out = append(out, v.Theme.FGColor(v.Theme.Accent, "["+label+"]"))
344 for _, line := range wrapLine(strings.Join(items, ", "), bodyWidth, "") {344 for _, line := range wrapLine(strings.Join(items, ", "), bodyWidth, "") {
345- out = append(out, v.Theme.FG256(v.Theme.Muted, " "+line))345+ out = append(out, v.Theme.FGColor(v.Theme.Muted, " "+line))
346 }346 }
347 out = append(out, "")347 out = append(out, "")
348 }348 }
@@ -796,7 +796,7 @@ func (v *View) renderMessage(m provider.Message, width int, turnOpen bool) []str
796 }796 }
797 lines = append(lines, toolHeaderLine(v.Theme, label, width, v.CompactMode))797 lines = append(lines, toolHeaderLine(v.Theme, label, width, v.CompactMode))
798 if tr.IsError {798 if tr.IsError {
799- lines = append(lines, toolBodyLine(v.Theme, v.Theme.FG256(color, " error"), width, v.CompactMode))799+ lines = append(lines, toolBodyLine(v.Theme, v.Theme.FGColor(color, " error"), width, v.CompactMode))
800 }800 }
801 for _, line := range v.renderToolResultContent(tr.Content, width, color, path, startLine) {801 for _, line := range v.renderToolResultContent(tr.Content, width, color, path, startLine) {
802 _, stripped := parseImageFootprint(line)802 _, stripped := parseImageFootprint(line)
@@ -810,7 +810,7 @@ func (v *View) renderMessage(m provider.Message, width int, turnOpen bool) []str
810 lines = append(lines, toolBoxTop(v.Theme, label, width))810 lines = append(lines, toolBoxTop(v.Theme, label, width))
811 lines = append(lines, toolBoxSide(v.Theme, "", width))811 lines = append(lines, toolBoxSide(v.Theme, "", width))
812 if tr.IsError {812 if tr.IsError {
813- lines = append(lines, toolBoxSide(v.Theme, v.Theme.FG256(color, " error"), width))813+ lines = append(lines, toolBoxSide(v.Theme, v.Theme.FGColor(color, " error"), width))
814 }814 }
815 for _, line := range v.renderToolResultContent(tr.Content, width, color, path, startLine) {815 for _, line := range v.renderToolResultContent(tr.Content, width, color, path, startLine) {
816 // Image-footprint rows (the escape row, the blank816 // Image-footprint rows (the escape row, the blank
@@ -1010,7 +1010,7 @@ func (v *View) renderLiveToolBody(tc ToolCallView, width int) []string {
1010 // Header line hints which edit is streaming and, when more1010 // Header line hints which edit is streaming and, when more
1011 // than one has landed, how many the model is doing.1011 // than one has landed, how many the model is doing.
1012 hint := fmt.Sprintf("edit %d (streaming)", idx)1012 hint := fmt.Sprintf("edit %d (streaming)", idx)
1013- body := []string{" " + v.Theme.FG256(v.Theme.Muted, hint), ""}1013+ body := []string{" " + v.Theme.FGColor(v.Theme.Muted, hint), ""}
1014 body = append(body, v.renderRawFile(partial, tc.LivePath, 1)...)1014 body = append(body, v.renderRawFile(partial, tc.LivePath, 1)...)
1015 return v.wrapLiveBody(body, width)1015 return v.wrapLiveBody(body, width)
1016 case "bash", "Bash":1016 case "bash", "Bash":
@@ -1028,7 +1028,7 @@ func (v *View) renderLiveBashCommand(command string, width int) []string {
1028 if v.FlatTools || v.CompactMode {1028 if v.FlatTools || v.CompactMode {
1029 inner = flatToolBodyRenderWidth(width)1029 inner = flatToolBodyRenderWidth(width)
1030 }1030 }
1031- prompt := v.Theme.FG256(v.Theme.Muted, "$ ")1031+ prompt := v.Theme.FGColor(v.Theme.Muted, "$ ")
1032 var out []string1032 var out []string
1033 for i, line := range strings.Split(command, "\n") {1033 for i, line := range strings.Split(command, "\n") {
1034 firstPrefix := " "1034 firstPrefix := " "
@@ -1042,7 +1042,7 @@ func (v *View) renderLiveBashCommand(command string, width int) []string {
1042 if wrapWidth < 10 {1042 if wrapWidth < 10 {
1043 wrapWidth = 101043 wrapWidth = 10
1044 }1044 }
1045- for j, wrapped := range wrapANSILine(v.Theme.FG256(v.Theme.ToolOut, line), wrapWidth) {1045+ for j, wrapped := range wrapANSILine(v.Theme.FGColor(v.Theme.ToolOut, line), wrapWidth) {
1046 prefix := firstPrefix1046 prefix := firstPrefix
1047 if j > 0 {1047 if j > 0 {
1048 prefix = contPrefix1048 prefix = contPrefix
@@ -1133,7 +1133,7 @@ func toolBoxTop(th Theme, label string, width int) string {
1133 }1133 }
1134 fillStr := strings.Repeat("─", fill)1134 fillStr := strings.Repeat("─", fill)
1135 name, rest := splitToolLabel(label)1135 name, rest := splitToolLabel(label)
1136- return margin + th.FG256(th.Muted, prefix) + th.FG256(th.FG, name) + th.FG256(th.Muted, rest+suffix+fillStr+"┐") + margin1136+ return margin + th.FGColor(th.Muted, prefix) + th.FGColor(th.FG, name) + th.FGColor(th.Muted, rest+suffix+fillStr+"┐") + margin
1137 }1137 }
1138 1138
1139 func oneLineToolLabel(label string) string {1139 func oneLineToolLabel(label string) string {
@@ -1172,7 +1172,7 @@ func flatToolHeader(th Theme, label string, width int) string {
1172 label = oneLineToolLabel(label)1172 label = oneLineToolLabel(label)
1173 name, rest := splitToolLabel(label)1173 name, rest := splitToolLabel(label)
1174 margin := strings.Repeat(" ", toolBoxOuterMargin)1174 margin := strings.Repeat(" ", toolBoxOuterMargin)
1175- gutter := th.FG256(th.Assistant, "▌") + " "1175+ gutter := th.FGColor(th.Assistant, "▌") + " "
1176 // Budget the visible text to the content width so a very long1176 // Budget the visible text to the content width so a very long
1177 // argument summary doesn't run off the right edge; truncate with1177 // argument summary doesn't run off the right edge; truncate with
1178 // an ellipsis like the box header does.1178 // an ellipsis like the box header does.
@@ -1189,7 +1189,7 @@ func flatToolHeader(th Theme, label string, width int) string {
1189 rest = ""1189 rest = ""
1190 }1190 }
1191 }1191 }
1192- return margin + gutter + th.FG256(th.FG, name) + th.FG256(th.Muted, rest)1192+ return margin + gutter + th.FGColor(th.FG, name) + th.FGColor(th.Muted, rest)
1193 }1193 }
1194 1194
1195 func compactToolBlank(th Theme, width int) string {1195 func compactToolBlank(th Theme, width int) string {
@@ -1266,7 +1266,7 @@ func toolBoxBottom(th Theme, width int) string {
1266 }1266 }
1267 margin := strings.Repeat(" ", toolBoxOuterMargin)1267 margin := strings.Repeat(" ", toolBoxOuterMargin)
1268 line := "└" + strings.Repeat("─", w-2) + "┘"1268 line := "└" + strings.Repeat("─", w-2) + "┘"
1269- return margin + th.FG256(th.Muted, line) + margin1269+ return margin + th.FGColor(th.Muted, line) + margin
1270 }1270 }
1271 1271
1272 // hasImageEscapeLine reports whether s contains a Kitty (\x1b_G) or1272 // hasImageEscapeLine reports whether s contains a Kitty (\x1b_G) or
@@ -1503,7 +1503,7 @@ func (v *View) collapseToolBody(lines []string, hasImage bool) []string {
1503 hidden := len(lines) - ToolCollapsePreview1503 hidden := len(lines) - ToolCollapsePreview
1504 total := len(lines)1504 total := len(lines)
1505 footer := fmt.Sprintf(" ... (%d more lines, %d total, ctrl+o to expand)", hidden, total)1505 footer := fmt.Sprintf(" ... (%d more lines, %d total, ctrl+o to expand)", hidden, total)
1506- footer = v.Theme.FG256(v.Theme.Muted, footer)1506+ footer = v.Theme.FGColor(v.Theme.Muted, footer)
1507 out := append([]string(nil), kept...)1507 out := append([]string(nil), kept...)
1508 out = append(out, "")1508 out = append(out, "")
1509 return append(out, footer)1509 return append(out, footer)
@@ -1560,7 +1560,7 @@ func (v *View) renderToolText(text string, width int, defaultColor TerminalColor
1560 // Detect diff header: "--- name" followed somewhere by "+++ name".1560 // Detect diff header: "--- name" followed somewhere by "+++ name".
1561 if strings.HasPrefix(l, "--- ") || strings.HasPrefix(l, "+++ ") {1561 if strings.HasPrefix(l, "--- ") || strings.HasPrefix(l, "+++ ") {
1562 inDiff = true1562 inDiff = true
1563- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, l))1563+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, l))
1564 continue1564 continue
1565 }1565 }
1566 // Hunk header "@@ -a,b +c,d @@" resets the counters so patches1566 // Hunk header "@@ -a,b +c,d @@" resets the counters so patches
@@ -1569,12 +1569,12 @@ func (v *View) renderToolText(text string, width int, defaultColor TerminalColor
1569 if o, n, ok := parseHunkHeader(l); ok {1569 if o, n, ok := parseHunkHeader(l); ok {
1570 oldLine, newLine = o, n1570 oldLine, newLine = o, n
1571 }1571 }
1572- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, l))1572+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, l))
1573 continue1573 continue
1574 }1574 }
1575 if inDiff && strings.TrimSpace(l) == "..." {1575 if inDiff && strings.TrimSpace(l) == "..." {
1576 out = append(out, "")1576 out = append(out, "")
1577- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, "..."))1577+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, "..."))
1578 continue1578 continue
1579 }1579 }
1580 if inDiff && len(l) > 0 {1580 if inDiff && len(l) > 0 {
@@ -1596,7 +1596,7 @@ func (v *View) renderToolText(text string, width int, defaultColor TerminalColor
1596 }1596 }
1597 // Regular line.1597 // Regular line.
1598 for _, w := range wrapLine(l, width-4, " ") {1598 for _, w := range wrapLine(l, width-4, " ") {
1599- out = append(out, " "+v.Theme.FG256(defaultColor, w))1599+ out = append(out, " "+v.Theme.FGColor(defaultColor, w))
1600 }1600 }
1601 }1601 }
1602 return out1602 return out
@@ -1697,9 +1697,9 @@ func (v *View) renderDiffRow(line string, width int, color TerminalColor, lineNo
1697 if mark == ' ' {1697 if mark == ' ' {
1698 codeColor = v.Theme.Muted1698 codeColor = v.Theme.Muted
1699 }1699 }
1700- codeRendered = v.Theme.FG256(codeColor, chunk)1700+ codeRendered = v.Theme.FGColor(codeColor, chunk)
1701 }1701 }
1702- out = append(out, " "+v.Theme.FG256(gutterColor, rowGutter)+codeRendered)1702+ out = append(out, " "+v.Theme.FGColor(gutterColor, rowGutter)+codeRendered)
1703 }1703 }
1704 return out1704 return out
1705 }1705 }
@@ -1805,11 +1805,11 @@ func (v *View) renderImageBlock(b provider.ImageBlock, width int) []string {
1805 out = append(out, imageFootprintSentinel)1805 out = append(out, imageFootprintSentinel)
1806 }1806 }
1807 out = append(out, imageFootprintSentinel)1807 out = append(out, imageFootprintSentinel)
1808- out = append(out, v.Theme.FG256(v.Theme.Muted, info))1808+ out = append(out, v.Theme.FGColor(v.Theme.Muted, info))
1809 return out1809 return out
1810 }1810 }
1811 }1811 }
1812- return []string{v.Theme.FG256(v.Theme.Muted, info)}1812+ return []string{v.Theme.FGColor(v.Theme.Muted, info)}
1813 }1813 }
1814 1814
1815 // looksLikeNumberedFile returns true when text matches the `read`1815 // looksLikeNumberedFile returns true when text matches the `read`
@@ -1877,17 +1877,17 @@ func (v *View) renderNumberedFile(text, sourcePath string) []string {
1877 // code is visually distinct from the muted gutter.1877 // code is visually distinct from the muted gutter.
1878 highlighted = make([]string, len(codes))1878 highlighted = make([]string, len(codes))
1879 for i, c := range codes {1879 for i, c := range codes {
1880- highlighted[i] = v.Theme.FG256(v.Theme.ToolOut, c)1880+ highlighted[i] = v.Theme.FGColor(v.Theme.ToolOut, c)
1881 }1881 }
1882 }1882 }
1883 out := make([]string, 0, len(codes))1883 out := make([]string, 0, len(codes))
1884 for i, code := range highlighted {1884 for i, code := range highlighted {
1885 g := gutters[i]1885 g := gutters[i]
1886 if g == "" {1886 if g == "" {
1887- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, code))1887+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, code))
1888 continue1888 continue
1889 }1889 }
1890- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, g)+code)1890+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, g)+code)
1891 }1891 }
1892 return out1892 return out
1893 }1893 }
@@ -1927,15 +1927,15 @@ func (v *View) renderBashResult(lines []string, width int, defaultColor Terminal
1927 // further processing / wrapping so the shell-style1927 // further processing / wrapping so the shell-style
1928 // prompt reads at a glance.1928 // prompt reads at a glance.
1929 for _, w := range wrapLine(l, width-4, " ") {1929 for _, w := range wrapLine(l, width-4, " ") {
1930- out = append(out, " "+v.Theme.FG256(v.Theme.Accent, w))1930+ out = append(out, " "+v.Theme.FGColor(v.Theme.Accent, w))
1931 }1931 }
1932 case i == footerIdx:1932 case i == footerIdx:
1933 for _, w := range wrapLine(l, width-4, " ") {1933 for _, w := range wrapLine(l, width-4, " ") {
1934- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, w))1934+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, w))
1935 }1935 }
1936 default:1936 default:
1937 for _, w := range wrapLine(l, width-4, " ") {1937 for _, w := range wrapLine(l, width-4, " ") {
1938- out = append(out, " "+v.Theme.FG256(defaultColor, w))1938+ out = append(out, " "+v.Theme.FGColor(defaultColor, w))
1939 }1939 }
1940 }1940 }
1941 }1941 }
@@ -2097,7 +2097,7 @@ func (v *View) renderUnifiedDiff(text string, width int, sourcePath string) []st
2097 continue2097 continue
2098 }2098 }
2099 if l == "..." {2099 if l == "..." {
2100- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, "..."))2100+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, "..."))
2101 continue2101 continue
2102 }2102 }
2103 switch l[0] {2103 switch l[0] {
@@ -2113,7 +2113,7 @@ func (v *View) renderUnifiedDiff(text string, width int, sourcePath string) []st
2113 newLine++2113 newLine++
2114 default:2114 default:
2115 for _, w := range wrapLine(l, width-4, " ") {2115 for _, w := range wrapLine(l, width-4, " ") {
2116- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, w))2116+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, w))
2117 }2117 }
2118 }2118 }
2119 }2119 }
@@ -2176,17 +2176,17 @@ func (v *View) renderRawFile(text, sourcePath string, startLine int) []string {
2176 } else {2176 } else {
2177 highlighted = make([]string, len(code))2177 highlighted = make([]string, len(code))
2178 for i, c := range code {2178 for i, c := range code {
2179- highlighted[i] = v.Theme.FG256(v.Theme.ToolOut, c)2179+ highlighted[i] = v.Theme.FGColor(v.Theme.ToolOut, c)
2180 }2180 }
2181 }2181 }
2182 2182
2183 out := make([]string, 0, len(lines))2183 out := make([]string, 0, len(lines))
2184 for i, c := range highlighted {2184 for i, c := range highlighted {
2185 gutter := fmt.Sprintf("%4d ", startLine+i)2185 gutter := fmt.Sprintf("%4d ", startLine+i)
2186- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, gutter)+c)2186+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, gutter)+c)
2187 }2187 }
2188 for _, f := range footer {2188 for _, f := range footer {
2189- out = append(out, " "+v.Theme.FG256(v.Theme.Muted, f))2189+ out = append(out, " "+v.Theme.FGColor(v.Theme.Muted, f))
2190 }2190 }
2191 return out2191 return out
2192 }2192 }
@@ -2327,7 +2327,7 @@ func (v *View) renderCompactionBlock(m provider.Message, width int) []string {
2327 2327
2328 if v.ExpandAll {2328 if v.ExpandAll {
2329 var lines []string2329 var lines []string
2330- header := th.FG256(th.Muted, fmt.Sprintf("compacted from ~%s tokens", tokens))2330+ header := th.FGColor(th.Muted, fmt.Sprintf("compacted from ~%s tokens", tokens))
2331 lines = append(lines, indent+header)2331 lines = append(lines, indent+header)
2332 lines = append(lines, "")2332 lines = append(lines, "")
2333 for _, c := range m.Content {2333 for _, c := range m.Content {
@@ -2349,7 +2349,7 @@ func (v *View) renderCompactionBlock(m provider.Message, width int) []string {
2349 }2349 }
2350 2350
2351 // Collapsed: single line, no banner.2351 // Collapsed: single line, no banner.
2352- line := th.FG256(th.Muted, fmt.Sprintf("compacted from ~%s tokens (ctrl+o to expand)", tokens))2352+ line := th.FGColor(th.Muted, fmt.Sprintf("compacted from ~%s tokens (ctrl+o to expand)", tokens))
2353 return []string{indent + line}2353 return []string{indent + line}
2354 }2354 }
2355 2355
@@ -2447,7 +2447,7 @@ func StatusBar(p StatusBarParams) []string {
2447 if p.AutoCompacting {2447 if p.AutoCompacting {
2448 ctx += " (auto)"2448 ctx += " (auto)"
2449 }2449 }
2450- stats = append(stats, th.FG256(ctxColor, ctx))2450+ stats = append(stats, th.FGColor(ctxColor, ctx))
2451 }2451 }
2452 2452
2453 // Layout uses exactly 2 spaces of horizontal padding everywhere:2453 // Layout uses exactly 2 spaces of horizontal padding everywhere:
@@ -2497,11 +2497,11 @@ func StatusBar(p StatusBarParams) []string {
2497 // prefix there's no trailing separator to double-pad.2497 // prefix there's no trailing separator to double-pad.
2498 leftBuilder.WriteString(pad)2498 leftBuilder.WriteString(pad)
2499 }2499 }
2500- leftBuilder.WriteString(th.FG256(th.Muted, left))2500+ leftBuilder.WriteString(th.FGColor(th.Muted, left))
2501 if middle != "" {2501 if middle != "" {
2502 leftBuilder.WriteString(pad)2502 leftBuilder.WriteString(pad)
2503 // Highlight the opt-in max tier; other status information stays muted.2503 // Highlight the opt-in max tier; other status information stays muted.
2504- leftBuilder.WriteString(th.FG256(reasoningStatusColor(th, reasoningText), middle))2504+ leftBuilder.WriteString(th.FGColor(reasoningStatusColor(th, reasoningText), middle))
2505 }2505 }
2506 2506
2507 cwd := shortenHome(p.CWD)2507 cwd := shortenHome(p.CWD)
@@ -2527,21 +2527,21 @@ func StatusBar(p StatusBarParams) []string {
2527 // stats on their own rows. This mirrors the idle split below.2527 // stats on their own rows. This mirrors the idle split below.
2528 if p.Cols > 0 && p.BusyPrefix != "" && visibleWidth(primary) > p.Cols {2528 if p.Cols > 0 && p.BusyPrefix != "" && visibleWidth(primary) > p.Cols {
2529 busyLine := pad + p.BusyPrefix2529 busyLine := pad + p.BusyPrefix
2530- modelLine := pad + th.FG256(th.Muted, left)2530+ modelLine := pad + th.FGColor(th.Muted, left)
2531 lines := []string{busyLine}2531 lines := []string{busyLine}
2532- if middle != "" && visibleWidth(modelLine+pad+th.FG256(th.Muted, middle)) > p.Cols {2532+ if middle != "" && visibleWidth(modelLine+pad+th.FGColor(th.Muted, middle)) > p.Cols {
2533 lines = appendWrappedStatusLines(lines, th, pad, left, fastText, reasoningText, statsText, p.Cols)2533 lines = appendWrappedStatusLines(lines, th, pad, left, fastText, reasoningText, statsText, p.Cols)
2534 } else {2534 } else {
2535 var infoBuilder strings.Builder2535 var infoBuilder strings.Builder
2536 infoBuilder.WriteString(modelLine)2536 infoBuilder.WriteString(modelLine)
2537 if middle != "" {2537 if middle != "" {
2538 infoBuilder.WriteString(pad)2538 infoBuilder.WriteString(pad)
2539- infoBuilder.WriteString(th.FG256(reasoningStatusColor(th, reasoningText), middle))2539+ infoBuilder.WriteString(th.FGColor(reasoningStatusColor(th, reasoningText), middle))
2540 }2540 }
2541 lines = append(lines, infoBuilder.String())2541 lines = append(lines, infoBuilder.String())
2542 }2542 }
2543 if cwd != "" {2543 if cwd != "" {
2544- lines = append(lines, pad+th.FG256(th.Muted, cwd))2544+ lines = append(lines, pad+th.FGColor(th.Muted, cwd))
2545 }2545 }
2546 return lines2546 return lines
2547 }2547 }
@@ -2554,7 +2554,7 @@ func StatusBar(p StatusBarParams) []string {
2554 var lines []string2554 var lines []string
2555 lines = appendWrappedStatusLines(lines, th, pad, left, fastText, reasoningText, statsText, p.Cols)2555 lines = appendWrappedStatusLines(lines, th, pad, left, fastText, reasoningText, statsText, p.Cols)
2556 if cwd != "" {2556 if cwd != "" {
2557- lines = append(lines, pad+th.FG256(th.Muted, cwd))2557+ lines = append(lines, pad+th.FGColor(th.Muted, cwd))
2558 }2558 }
2559 return lines2559 return lines
2560 }2560 }
@@ -2565,12 +2565,12 @@ func StatusBar(p StatusBarParams) []string {
2565 2565
2566 // Second line: indent with the same 2-space pad so the cwd lines2566 // Second line: indent with the same 2-space pad so the cwd lines
2567 // up under the "(provider)" column on line 1.2567 // up under the "(provider)" column on line 1.
2568- cwdRendered := pad + th.FG256(th.Muted, cwd)2568+ cwdRendered := pad + th.FGColor(th.Muted, cwd)
2569 return []string{primary, cwdRendered}2569 return []string{primary, cwdRendered}
2570 }2570 }
2571 2571
2572 func appendWrappedStatusLines(lines []string, th Theme, pad, modelText, fastText, reasoningText, statsText string, cols int) []string {2572 func appendWrappedStatusLines(lines []string, th Theme, pad, modelText, fastText, reasoningText, statsText string, cols int) []string {
2573- modelLine := pad + th.FG256(th.Muted, modelText)2573+ modelLine := pad + th.FGColor(th.Muted, modelText)
2574 infoParts := make([]string, 0, 2)2574 infoParts := make([]string, 0, 2)
2575 if fastText != "" {2575 if fastText != "" {
2576 infoParts = append(infoParts, fastText)2576 infoParts = append(infoParts, fastText)
@@ -2582,7 +2582,7 @@ func appendWrappedStatusLines(lines []string, th Theme, pad, modelText, fastText
2582 if infoText == "" {2582 if infoText == "" {
2583 lines = append(lines, modelLine)2583 lines = append(lines, modelLine)
2584 if statsText != "" {2584 if statsText != "" {
2585- lines = append(lines, pad+th.FG256(th.Muted, statsText))2585+ lines = append(lines, pad+th.FGColor(th.Muted, statsText))
2586 }2586 }
2587 return lines2587 return lines
2588 }2588 }
@@ -2593,18 +2593,18 @@ func appendWrappedStatusLines(lines []string, th Theme, pad, modelText, fastText
2593 infoColor = reasoningStatusColor(th, reasoningText)2593 infoColor = reasoningStatusColor(th, reasoningText)
2594 }2594 }
2595 if cols <= 0 || visibleWidth(modelInfoPlain) <= cols {2595 if cols <= 0 || visibleWidth(modelInfoPlain) <= cols {
2596- lines = append(lines, pad+th.FG256(infoColor, modelText+pad+infoText))2596+ lines = append(lines, pad+th.FGColor(infoColor, modelText+pad+infoText))
2597 } else {2597 } else {
2598 lines = append(lines, modelLine)2598 lines = append(lines, modelLine)
2599 if fastText != "" {2599 if fastText != "" {
2600- lines = append(lines, pad+th.FG256(th.Muted, fastText))2600+ lines = append(lines, pad+th.FGColor(th.Muted, fastText))
2601 }2601 }
2602 if reasoningText != "" {2602 if reasoningText != "" {
2603- lines = append(lines, pad+th.FG256(reasoningStatusColor(th, reasoningText), reasoningText))2603+ lines = append(lines, pad+th.FGColor(reasoningStatusColor(th, reasoningText), reasoningText))
2604 }2604 }
2605 }2605 }
2606 if statsText != "" {2606 if statsText != "" {
2607- lines = append(lines, pad+th.FG256(th.Muted, statsText))2607+ lines = append(lines, pad+th.FGColor(th.Muted, statsText))
2608 }2608 }
2609 return lines2609 return lines
2610 }2610 }
M packages/tui/view_tool_box_test.go +1 -1
@@ -8,7 +8,7 @@ import (
88 func TestToolBoxSideTruncationPreservesANSIColor(t *testing.T) {
99 const width = 32
1010 const contentColor = 196
11- line := " " + Dark.FG256(Color256(contentColor), strings.Repeat("x", 80))
11+ line := " " + Dark.FGColor(Color256(contentColor), strings.Repeat("x", 80))
1212
1313 got := toolBoxSide(Dark, line, width)
1414 if !strings.Contains(got, sgrFG(contentColor)) {
@@ -8,7 +8,7 @@ import (
8 func TestToolBoxSideTruncationPreservesANSIColor(t *testing.T) {8 func TestToolBoxSideTruncationPreservesANSIColor(t *testing.T) {
9 const width = 329 const width = 32
10 const contentColor = 19610 const contentColor = 196
11- line := " " + Dark.FG256(Color256(contentColor), strings.Repeat("x", 80))11+ line := " " + Dark.FGColor(Color256(contentColor), strings.Repeat("x", 80))
12 12
13 got := toolBoxSide(Dark, line, width)13 got := toolBoxSide(Dark, line, width)
14 if !strings.Contains(got, sgrFG(contentColor)) {14 if !strings.Contains(got, sgrFG(contentColor)) {
M packages/tui/view_tool_overlay_test.go +2 -2
@@ -126,8 +126,8 @@ func TestEditConfirmationPreviewRendersDiffInsteadOfNewText(t *testing.T) {
126126 }
127127 }
128128 for _, want := range []string{
129- Dark.FG256(Dark.Error, "- 1 "),
130- Dark.FG256(Dark.Tool, "+ 1 "),
129+ Dark.FGColor(Dark.Error, "- 1 "),
130+ Dark.FGColor(Dark.Tool, "+ 1 "),
131131 } {
132132 if !strings.Contains(rendered, want) {
133133 t.Fatalf("confirmation preview missing diff color %q:\n%s", want, rendered)
@@ -126,8 +126,8 @@ func TestEditConfirmationPreviewRendersDiffInsteadOfNewText(t *testing.T) {
126 }126 }
127 }127 }
128 for _, want := range []string{128 for _, want := range []string{
129- Dark.FG256(Dark.Error, "- 1 "),129+ Dark.FGColor(Dark.Error, "- 1 "),
130- Dark.FG256(Dark.Tool, "+ 1 "),130+ Dark.FGColor(Dark.Tool, "+ 1 "),
131 } {131 } {
132 if !strings.Contains(rendered, want) {132 if !strings.Contains(rendered, want) {
133 t.Fatalf("confirmation preview missing diff color %q:\n%s", want, rendered)133 t.Fatalf("confirmation preview missing diff color %q:\n%s", want, rendered)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close