2
2
package config
3
3
4
4
import (
5
+ "encoding/json"
5
6
"fmt"
6
7
"log/slog"
7
8
"os"
9
+ "path/filepath"
8
10
"strings"
9
11
10
12
"github.com/opencode-ai/opencode/internal/llm/models"
@@ -65,6 +67,11 @@ type LSPConfig struct {
65
67
Options any `json:"options"`
66
68
}
67
69
70
+ // TUIConfig defines the configuration for the Terminal User Interface.
71
+ type TUIConfig struct {
72
+ Theme string `json:"theme,omitempty"`
73
+ }
74
+
68
75
// Config is the main configuration structure for the application.
69
76
type Config struct {
70
77
Data Data `json:"data"`
@@ -76,6 +83,7 @@ type Config struct {
76
83
Debug bool `json:"debug,omitempty"`
77
84
DebugLSP bool `json:"debugLSP,omitempty"`
78
85
ContextPaths []string `json:"contextPaths,omitempty"`
86
+ TUI TUIConfig `json:"tui"`
79
87
}
80
88
81
89
// Application constants
@@ -203,6 +211,7 @@ func configureViper() {
203
211
func setDefaults (debug bool ) {
204
212
viper .SetDefault ("data.directory" , defaultDataDirectory )
205
213
viper .SetDefault ("contextPaths" , defaultContextPaths )
214
+ viper .SetDefault ("tui.theme" , "opencode" )
206
215
207
216
if debug {
208
217
viper .SetDefault ("debug" , true )
@@ -714,3 +723,62 @@ func UpdateAgentModel(agentName AgentName, modelID models.ModelID) error {
714
723
715
724
return nil
716
725
}
726
+
727
+ // UpdateTheme updates the theme in the configuration and writes it to the config file.
728
+ func UpdateTheme (themeName string ) error {
729
+ if cfg == nil {
730
+ return fmt .Errorf ("config not loaded" )
731
+ }
732
+
733
+ // Update the in-memory config
734
+ cfg .TUI .Theme = themeName
735
+
736
+ // Get the config file path
737
+ configFile := viper .ConfigFileUsed ()
738
+ var configData []byte
739
+ if configFile == "" {
740
+ homeDir , err := os .UserHomeDir ()
741
+ if err != nil {
742
+ return fmt .Errorf ("failed to get home directory: %w" , err )
743
+ }
744
+ configFile = filepath .Join (homeDir , fmt .Sprintf (".%s.json" , appName ))
745
+ logging .Info ("config file not found, creating new one" , "path" , configFile )
746
+ configData = []byte (`{}` )
747
+ } else {
748
+ // Read the existing config file
749
+ data , err := os .ReadFile (configFile )
750
+ if err != nil {
751
+ return fmt .Errorf ("failed to read config file: %w" , err )
752
+ }
753
+ configData = data
754
+ }
755
+
756
+ // Parse the JSON
757
+ var configMap map [string ]interface {}
758
+ if err := json .Unmarshal (configData , & configMap ); err != nil {
759
+ return fmt .Errorf ("failed to parse config file: %w" , err )
760
+ }
761
+
762
+ // Update just the theme value
763
+ tuiConfig , ok := configMap ["tui" ].(map [string ]interface {})
764
+ if ! ok {
765
+ // TUI config doesn't exist yet, create it
766
+ configMap ["tui" ] = map [string ]interface {}{"theme" : themeName }
767
+ } else {
768
+ // Update existing TUI config
769
+ tuiConfig ["theme" ] = themeName
770
+ configMap ["tui" ] = tuiConfig
771
+ }
772
+
773
+ // Write the updated config back to file
774
+ updatedData , err := json .MarshalIndent (configMap , "" , " " )
775
+ if err != nil {
776
+ return fmt .Errorf ("failed to marshal config: %w" , err )
777
+ }
778
+
779
+ if err := os .WriteFile (configFile , updatedData , 0o644 ); err != nil {
780
+ return fmt .Errorf ("failed to write config file: %w" , err )
781
+ }
782
+
783
+ return nil
784
+ }
0 commit comments