mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
Theme config (#127)
* add theme configuration * add path for themes (broken) * md theme relative path not working * cleanup malpractice * mid commit * fix logical error
This commit is contained in:
+31
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
"github.com/mark3labs/mcphost/internal/agent"
|
||||
"github.com/mark3labs/mcphost/internal/config"
|
||||
@@ -199,6 +200,7 @@ func initConfig() {
|
||||
viper.Set("hooks", hooksConfig)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// loadConfigWithEnvSubstitution loads a config file with environment variable substitution
|
||||
@@ -222,13 +224,42 @@ func loadConfigWithEnvSubstitution(configPath string) error {
|
||||
configType = "json"
|
||||
}
|
||||
|
||||
config.SetConfigPath(configPath)
|
||||
|
||||
// Use viper to parse the processed content
|
||||
viper.SetConfigType(configType)
|
||||
return viper.ReadConfig(strings.NewReader(processedContent))
|
||||
}
|
||||
|
||||
func configToUiTheme(theme config.Theme) ui.Theme {
|
||||
return ui.Theme{
|
||||
Primary: lipgloss.AdaptiveColor(theme.Primary),
|
||||
Secondary: lipgloss.AdaptiveColor(theme.Secondary),
|
||||
Success: lipgloss.AdaptiveColor(theme.Success),
|
||||
Warning: lipgloss.AdaptiveColor(theme.Warning),
|
||||
Error: lipgloss.AdaptiveColor(theme.Error),
|
||||
Info: lipgloss.AdaptiveColor(theme.Info),
|
||||
Text: lipgloss.AdaptiveColor(theme.Text),
|
||||
Muted: lipgloss.AdaptiveColor(theme.Muted),
|
||||
VeryMuted: lipgloss.AdaptiveColor(theme.VeryMuted),
|
||||
Background: lipgloss.AdaptiveColor(theme.Background),
|
||||
Border: lipgloss.AdaptiveColor(theme.Border),
|
||||
MutedBorder: lipgloss.AdaptiveColor(theme.MutedBorder),
|
||||
System: lipgloss.AdaptiveColor(theme.System),
|
||||
Tool: lipgloss.AdaptiveColor(theme.Tool),
|
||||
Accent: lipgloss.AdaptiveColor(theme.Accent),
|
||||
Highlight: lipgloss.AdaptiveColor(theme.Highlight),
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
var theme config.Theme
|
||||
err := config.FilepathOr("theme", &theme)
|
||||
if err == nil && viper.InConfig("theme") {
|
||||
uiTheme := configToUiTheme(theme)
|
||||
ui.SetTheme(uiTheme)
|
||||
}
|
||||
|
||||
rootCmd.PersistentFlags().
|
||||
StringVar(&configFile, "config", "", "config file (default is $HOME/.mcp.json)")
|
||||
|
||||
@@ -6,6 +6,9 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// MCPServerConfig represents configuration for an MCP server
|
||||
@@ -97,6 +100,45 @@ func (s *MCPServerConfig) UnmarshalJSON(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type AdaptiveColor struct {
|
||||
Light string `json:"light,omitempty" yaml:"light,omitempty"`
|
||||
Dark string `json:"dark,omitempty" yaml:"dark,omitempty"`
|
||||
}
|
||||
|
||||
type Theme struct {
|
||||
Primary AdaptiveColor `json:"primary" yaml:"primary"`
|
||||
Secondary AdaptiveColor `json:"secondary" yaml:"secondary"`
|
||||
Success AdaptiveColor `json:"success" yaml:"success"`
|
||||
Warning AdaptiveColor `json:"warning" yaml:"warning"`
|
||||
Error AdaptiveColor `json:"error" yaml:"error"`
|
||||
Info AdaptiveColor `json:"info" yaml:"info"`
|
||||
Text AdaptiveColor `json:"text" yaml:"text"`
|
||||
Muted AdaptiveColor `json:"muted" yaml:"muted"`
|
||||
VeryMuted AdaptiveColor `json:"very-muted" yaml:"very-muted"`
|
||||
Background AdaptiveColor `json:"background" yaml:"background"`
|
||||
Border AdaptiveColor `json:"border" yaml:"border"`
|
||||
MutedBorder AdaptiveColor `json:"muted-border" yaml:"muted-border"`
|
||||
System AdaptiveColor `json:"system" yaml:"system"`
|
||||
Tool AdaptiveColor `json:"tool" yaml:"tool"`
|
||||
Accent AdaptiveColor `json:"accent" yaml:"accent"`
|
||||
Highlight AdaptiveColor `json:"highlight" yaml:"highlight"`
|
||||
}
|
||||
|
||||
type MarkdownTheme struct {
|
||||
Text AdaptiveColor `json:"text" yaml:"text"`
|
||||
Muted AdaptiveColor `json:"muted" yaml:"muted"`
|
||||
Heading AdaptiveColor `json:"heading" yaml:"heading"`
|
||||
Emph AdaptiveColor `json:"emph" yaml:"emph"`
|
||||
Strong AdaptiveColor `json:"strong" yaml:"strong"`
|
||||
Link AdaptiveColor `json:"link" yaml:"link"`
|
||||
Code AdaptiveColor `json:"code" yaml:"code"`
|
||||
Error AdaptiveColor `json:"error" yaml:"error"`
|
||||
Keyword AdaptiveColor `json:"keyword" yaml:"keyword"`
|
||||
String AdaptiveColor `json:"string" yaml:"string"`
|
||||
Number AdaptiveColor `json:"number" yaml:"number"`
|
||||
Comment AdaptiveColor `json:"comment" yaml:"comment"`
|
||||
}
|
||||
|
||||
// Config represents the application configuration
|
||||
type Config struct {
|
||||
MCPServers map[string]MCPServerConfig `json:"mcpServers" yaml:"mcpServers"`
|
||||
@@ -110,6 +152,8 @@ type Config struct {
|
||||
Prompt string `json:"prompt,omitempty" yaml:"prompt,omitempty"`
|
||||
NoExit bool `json:"no-exit,omitempty" yaml:"no-exit,omitempty"`
|
||||
Stream *bool `json:"stream,omitempty" yaml:"stream,omitempty"`
|
||||
Theme any `json:"theme" yaml:"theme"`
|
||||
MarkdownTheme any `json:"markdown-theme" yaml:"markdown-theme"`
|
||||
|
||||
// Model generation parameters
|
||||
MaxTokens int `json:"max-tokens,omitempty" yaml:"max-tokens,omitempty"`
|
||||
@@ -333,3 +377,57 @@ mcpServers:
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func FilepathOr[T any](key string, value *T) error {
|
||||
var field any
|
||||
err := viper.UnmarshalKey(key, &field)
|
||||
if err != nil {
|
||||
value = nil
|
||||
return err
|
||||
}
|
||||
switch f := field.(type) {
|
||||
case string:
|
||||
{
|
||||
absPath := f
|
||||
if strings.HasPrefix(absPath, "~/") {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filepath.Join(home, absPath[2:])
|
||||
}
|
||||
if !filepath.IsAbs(absPath) {
|
||||
// base := GetConfigPath()
|
||||
base := configPath
|
||||
if base == "" {
|
||||
fmt.Fprintf(os.Stderr, "unable to build relative path to config.")
|
||||
os.Exit(1)
|
||||
}
|
||||
absPath = filepath.Join(filepath.Dir(base), absPath)
|
||||
}
|
||||
b, err := os.ReadFile(absPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%q", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if filepath.Ext(absPath) == ".json" {
|
||||
return json.Unmarshal(b, value)
|
||||
}
|
||||
|
||||
if filepath.Ext(absPath) == ".yaml" {
|
||||
return yaml.Unmarshal(b, value)
|
||||
}
|
||||
}
|
||||
case map[string]any:
|
||||
return viper.UnmarshalKey(key, value)
|
||||
default:
|
||||
return fmt.Errorf("invalid type for field %q", key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var configPath string
|
||||
|
||||
func SetConfigPath(path string) {
|
||||
configPath = path
|
||||
}
|
||||
|
||||
+37
-8
@@ -4,6 +4,8 @@ import (
|
||||
"github.com/charmbracelet/glamour"
|
||||
"github.com/charmbracelet/glamour/ansi"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/mark3labs/mcphost/internal/config"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const defaultMargin = 1
|
||||
@@ -29,17 +31,42 @@ func GetMarkdownRenderer(width int) *glamour.TermRenderer {
|
||||
|
||||
// generateMarkdownStyleConfig creates an ansi.StyleConfig for markdown rendering
|
||||
func generateMarkdownStyleConfig() ansi.StyleConfig {
|
||||
// Define adaptive colors based on terminal background
|
||||
|
||||
var textColor, mutedColor string
|
||||
if lipgloss.HasDarkBackground() {
|
||||
var headingColor, emphColor, strongColor, linkColor, codeColor, errorColor, keywordColor, stringColor, numberColor, commentColor string
|
||||
var mdTheme config.MarkdownTheme
|
||||
|
||||
err := config.FilepathOr("markdown-theme", &mdTheme)
|
||||
fromConfig := err == nil && viper.InConfig("markdown-theme")
|
||||
if fromConfig && lipgloss.HasDarkBackground() {
|
||||
textColor = mdTheme.Text.Light
|
||||
mutedColor = mdTheme.Muted.Light
|
||||
headingColor = mdTheme.Heading.Light
|
||||
emphColor = mdTheme.Emph.Light
|
||||
strongColor = mdTheme.Strong.Light
|
||||
linkColor = mdTheme.Link.Light
|
||||
codeColor = mdTheme.Code.Light
|
||||
errorColor = mdTheme.Error.Light
|
||||
keywordColor = mdTheme.Keyword.Light
|
||||
stringColor = mdTheme.String.Light
|
||||
numberColor = mdTheme.Number.Light
|
||||
commentColor = mdTheme.Comment.Light
|
||||
} else if fromConfig {
|
||||
textColor = mdTheme.Text.Dark
|
||||
mutedColor = mdTheme.Muted.Dark
|
||||
headingColor = mdTheme.Heading.Dark
|
||||
emphColor = mdTheme.Emph.Dark
|
||||
strongColor = mdTheme.Strong.Dark
|
||||
linkColor = mdTheme.Link.Dark
|
||||
codeColor = mdTheme.Code.Dark
|
||||
errorColor = mdTheme.Error.Dark
|
||||
keywordColor = mdTheme.Keyword.Dark
|
||||
stringColor = mdTheme.String.Dark
|
||||
numberColor = mdTheme.Number.Dark
|
||||
commentColor = mdTheme.Comment.Dark
|
||||
} else if lipgloss.HasDarkBackground() {
|
||||
textColor = "#F9FAFB" // Light text for dark backgrounds
|
||||
mutedColor = "#9CA3AF" // Light muted for dark backgrounds
|
||||
} else {
|
||||
textColor = "#1F2937" // Dark text for light backgrounds
|
||||
mutedColor = "#6B7280" // Dark muted for light backgrounds
|
||||
}
|
||||
var headingColor, emphColor, strongColor, linkColor, codeColor, errorColor, keywordColor, stringColor, numberColor, commentColor string
|
||||
if lipgloss.HasDarkBackground() {
|
||||
// Dark background colors
|
||||
headingColor = "#22D3EE" // Cyan
|
||||
emphColor = "#FDE047" // Yellow
|
||||
@@ -52,6 +79,8 @@ func generateMarkdownStyleConfig() ansi.StyleConfig {
|
||||
numberColor = "#FBBF24" // Orange
|
||||
commentColor = "#9CA3AF" // Muted gray
|
||||
} else {
|
||||
textColor = "#1F2937" // Dark text for light backgrounds
|
||||
mutedColor = "#6B7280" // Dark muted for light backgrounds
|
||||
// Light background colors
|
||||
headingColor = "#0891B2" // Dark cyan
|
||||
emphColor = "#D97706" // Orange
|
||||
|
||||
Reference in New Issue
Block a user