refactor: add Service getters for Config fields, migrate callers

Kujtim Hoxha created

Add DataDirectory, Debug, DebugLSP, DisableAutoSummarize,
Attribution, ContextPaths, SkillsPaths, Progress, DisableMetrics,
SelectedModel, Agent, MCP, LSP, Permissions, ToolLsConfig,
CompactMode, DiffMode, CompletionLimits, and more to Service.
Migrate coordinator, app, cmd, and UI callers to use Service
getters instead of direct Config field access.

🐾 Generated with Crush

Assisted-by: Claude Opus 4.6 via Crush <crush@charm.land>

Change summary

internal/agent/agent_tool.go         |   6 
internal/agent/agentic_fetch_tool.go |   8 
internal/agent/coordinator.go        |  50 +++++-----
internal/app/app.go                  |   4 
internal/app/lsp.go                  |   4 
internal/cmd/logs.go                 |   2 
internal/cmd/root.go                 |  14 +-
internal/config/service.go           | 142 ++++++++++++++++++++++++++++++
internal/ui/model/header.go          |   2 
internal/ui/model/mcp.go             |   2 
internal/ui/model/sidebar.go         |   2 
internal/ui/model/ui.go              |   8 
12 files changed, 193 insertions(+), 51 deletions(-)

Detailed changes

internal/agent/agent_tool.go 🔗

@@ -25,11 +25,11 @@ const (
 )
 
 func (c *coordinator) agentTool(ctx context.Context) (fantasy.AgentTool, error) {
-	agentCfg, ok := c.cfg.Agents[config.AgentTask]
+	agentCfg, ok := c.cfgSvc.Agents()[config.AgentTask]
 	if !ok {
 		return nil, errors.New("task agent not configured")
 	}
-	prompt, err := taskPrompt(prompt.WithWorkingDir(c.cfg.WorkingDir()))
+	prompt, err := taskPrompt(prompt.WithWorkingDir(c.cfgSvc.WorkingDir()))
 	if err != nil {
 		return nil, err
 	}
@@ -67,7 +67,7 @@ func (c *coordinator) agentTool(ctx context.Context) (fantasy.AgentTool, error)
 				maxTokens = model.ModelCfg.MaxTokens
 			}
 
-			providerCfg, ok := c.cfg.Providers.Get(model.ModelCfg.Provider)
+			providerCfg, ok := c.cfgSvc.Config().Providers.Get(model.ModelCfg.Provider)
 			if !ok {
 				return fantasy.ToolResponse{}, errors.New("model provider not configured")
 			}

internal/agent/agentic_fetch_tool.go 🔗

@@ -83,7 +83,7 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
 			p, err := c.permissions.Request(ctx,
 				permission.CreatePermissionRequest{
 					SessionID:   validationResult.SessionID,
-					Path:        c.cfg.WorkingDir(),
+					Path:        c.cfgSvc.WorkingDir(),
 					ToolCallID:  call.ID,
 					ToolName:    tools.AgenticFetchToolName,
 					Action:      "fetch",
@@ -98,7 +98,7 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
 				return fantasy.ToolResponse{}, permission.ErrorPermissionDenied
 			}
 
-			tmpDir, err := os.MkdirTemp(c.cfg.Options.DataDirectory, "crush-fetch-*")
+			tmpDir, err := os.MkdirTemp(c.cfgSvc.Config().Options.DataDirectory, "crush-fetch-*")
 			if err != nil {
 				return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to create temporary directory: %s", err)), nil
 			}
@@ -156,7 +156,7 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
 				return fantasy.ToolResponse{}, fmt.Errorf("error building system prompt: %s", err)
 			}
 
-			smallProviderCfg, ok := c.cfg.Providers.Get(small.ModelCfg.Provider)
+			smallProviderCfg, ok := c.cfgSvc.Config().Providers.Get(small.ModelCfg.Provider)
 			if !ok {
 				return fantasy.ToolResponse{}, errors.New("small model provider not configured")
 			}
@@ -177,7 +177,7 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
 				SmallModel:           small,
 				SystemPromptPrefix:   smallProviderCfg.SystemPromptPrefix,
 				SystemPrompt:         systemPrompt,
-				DisableAutoSummarize: c.cfg.Options.DisableAutoSummarize,
+				DisableAutoSummarize: c.cfgSvc.Config().Options.DisableAutoSummarize,
 				IsYolo:               c.permissions.SkipRequests(),
 				Sessions:             c.sessions,
 				Messages:             c.messages,

internal/agent/coordinator.go 🔗

@@ -147,7 +147,7 @@ func (c *coordinator) Run(ctx context.Context, sessionID string, prompt string,
 		attachments = filteredAttachments
 	}
 
-	providerCfg, ok := c.cfg.Providers.Get(model.ModelCfg.Provider)
+	providerCfg, ok := c.cfgSvc.Config().Providers.Get(model.ModelCfg.Provider)
 	if !ok {
 		return nil, errors.New("model provider not configured")
 	}
@@ -360,14 +360,14 @@ func (c *coordinator) buildAgent(ctx context.Context, prompt *prompt.Prompt, age
 		return nil, err
 	}
 
-	largeProviderCfg, _ := c.cfg.Providers.Get(large.ModelCfg.Provider)
+	largeProviderCfg, _ := c.cfgSvc.Config().Providers.Get(large.ModelCfg.Provider)
 	result := NewSessionAgent(SessionAgentOptions{
 		large,
 		small,
 		largeProviderCfg.SystemPromptPrefix,
 		"",
 		isSubAgent,
-		c.cfg.Options.DisableAutoSummarize,
+		c.cfgSvc.DisableAutoSummarize(),
 		c.permissions.SkipRequests(),
 		c.sessions,
 		c.messages,
@@ -415,14 +415,14 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan
 
 	// Get the model name for the agent
 	modelName := ""
-	if modelCfg, ok := c.cfg.Models[agent.Model]; ok {
+	if modelCfg, ok := c.cfgSvc.Config().Models[agent.Model]; ok {
 		if model := c.cfgSvc.GetModel(modelCfg.Provider, modelCfg.Model); model != nil {
 			modelName = model.Name
 		}
 	}
 
 	allTools = append(allTools,
-		tools.NewBashTool(c.permissions, c.cfgSvc.WorkingDir(), c.cfg.Options.Attribution, modelName),
+		tools.NewBashTool(c.permissions, c.cfgSvc.WorkingDir(), c.cfgSvc.Attribution(), modelName),
 		tools.NewJobOutputTool(),
 		tools.NewJobKillTool(),
 		tools.NewDownloadTool(c.permissions, c.cfgSvc.WorkingDir(), nil),
@@ -431,10 +431,10 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan
 		tools.NewFetchTool(c.permissions, c.cfgSvc.WorkingDir(), nil),
 		tools.NewGlobTool(c.cfgSvc.WorkingDir()),
 		tools.NewGrepTool(c.cfgSvc.WorkingDir()),
-		tools.NewLsTool(c.permissions, c.cfgSvc.WorkingDir(), c.cfg.Tools.Ls),
+		tools.NewLsTool(c.permissions, c.cfgSvc.WorkingDir(), c.cfgSvc.ToolLsConfig()),
 		tools.NewSourcegraphTool(nil),
 		tools.NewTodosTool(c.sessions),
-		tools.NewViewTool(c.lspClients, c.permissions, c.filetracker, c.cfgSvc.WorkingDir(), c.cfg.Options.SkillsPaths...),
+		tools.NewViewTool(c.lspClients, c.permissions, c.filetracker, c.cfgSvc.WorkingDir(), c.cfgSvc.SkillsPaths()...),
 		tools.NewWriteTool(c.lspClients, c.permissions, c.history, c.filetracker, c.cfgSvc.WorkingDir()),
 	)
 
@@ -479,16 +479,16 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan
 
 // TODO: when we support multiple agents we need to change this so that we pass in the agent specific model config
 func (c *coordinator) buildAgentModels(ctx context.Context, isSubAgent bool) (Model, Model, error) {
-	largeModelCfg, ok := c.cfg.Models[config.SelectedModelTypeLarge]
+	largeModelCfg, ok := c.cfgSvc.Config().Models[config.SelectedModelTypeLarge]
 	if !ok {
 		return Model{}, Model{}, errors.New("large model not selected")
 	}
-	smallModelCfg, ok := c.cfg.Models[config.SelectedModelTypeSmall]
+	smallModelCfg, ok := c.cfgSvc.Config().Models[config.SelectedModelTypeSmall]
 	if !ok {
 		return Model{}, Model{}, errors.New("small model not selected")
 	}
 
-	largeProviderCfg, ok := c.cfg.Providers.Get(largeModelCfg.Provider)
+	largeProviderCfg, ok := c.cfgSvc.Config().Providers.Get(largeModelCfg.Provider)
 	if !ok {
 		return Model{}, Model{}, errors.New("large model provider not configured")
 	}
@@ -498,7 +498,7 @@ func (c *coordinator) buildAgentModels(ctx context.Context, isSubAgent bool) (Mo
 		return Model{}, Model{}, err
 	}
 
-	smallProviderCfg, ok := c.cfg.Providers.Get(smallModelCfg.Provider)
+	smallProviderCfg, ok := c.cfgSvc.Config().Providers.Get(smallModelCfg.Provider)
 	if !ok {
 		return Model{}, Model{}, errors.New("large model provider not configured")
 	}
@@ -581,7 +581,7 @@ func (c *coordinator) buildAnthropicProvider(baseURL, apiKey string, headers map
 		opts = append(opts, anthropic.WithBaseURL(baseURL))
 	}
 
-	if c.cfg.Options.Debug {
+	if c.cfgSvc.Debug() {
 		httpClient := log.NewHTTPClient()
 		opts = append(opts, anthropic.WithHTTPClient(httpClient))
 	}
@@ -593,7 +593,7 @@ func (c *coordinator) buildOpenaiProvider(baseURL, apiKey string, headers map[st
 		openai.WithAPIKey(apiKey),
 		openai.WithUseResponsesAPI(),
 	}
-	if c.cfg.Options.Debug {
+	if c.cfgSvc.Debug() {
 		httpClient := log.NewHTTPClient()
 		opts = append(opts, openai.WithHTTPClient(httpClient))
 	}
@@ -610,7 +610,7 @@ func (c *coordinator) buildOpenrouterProvider(_, apiKey string, headers map[stri
 	opts := []openrouter.Option{
 		openrouter.WithAPIKey(apiKey),
 	}
-	if c.cfg.Options.Debug {
+	if c.cfgSvc.Debug() {
 		httpClient := log.NewHTTPClient()
 		opts = append(opts, openrouter.WithHTTPClient(httpClient))
 	}
@@ -624,7 +624,7 @@ func (c *coordinator) buildVercelProvider(_, apiKey string, headers map[string]s
 	opts := []vercel.Option{
 		vercel.WithAPIKey(apiKey),
 	}
-	if c.cfg.Options.Debug {
+	if c.cfgSvc.Debug() {
 		httpClient := log.NewHTTPClient()
 		opts = append(opts, vercel.WithHTTPClient(httpClient))
 	}
@@ -644,8 +644,8 @@ func (c *coordinator) buildOpenaiCompatProvider(baseURL, apiKey string, headers
 	var httpClient *http.Client
 	if providerID == string(catwalk.InferenceProviderCopilot) {
 		opts = append(opts, openaicompat.WithUseResponsesAPI())
-		httpClient = copilot.NewClient(isSubAgent, c.cfg.Options.Debug)
-	} else if c.cfg.Options.Debug {
+		httpClient = copilot.NewClient(isSubAgent, c.cfgSvc.Debug())
+	} else if c.cfgSvc.Debug() {
 		httpClient = log.NewHTTPClient()
 	}
 	if httpClient != nil {
@@ -669,7 +669,7 @@ func (c *coordinator) buildAzureProvider(baseURL, apiKey string, headers map[str
 		azure.WithAPIKey(apiKey),
 		azure.WithUseResponsesAPI(),
 	}
-	if c.cfg.Options.Debug {
+	if c.cfgSvc.Debug() {
 		httpClient := log.NewHTTPClient()
 		opts = append(opts, azure.WithHTTPClient(httpClient))
 	}
@@ -688,7 +688,7 @@ func (c *coordinator) buildAzureProvider(baseURL, apiKey string, headers map[str
 
 func (c *coordinator) buildBedrockProvider(headers map[string]string) (fantasy.Provider, error) {
 	var opts []bedrock.Option
-	if c.cfg.Options.Debug {
+	if c.cfgSvc.Debug() {
 		httpClient := log.NewHTTPClient()
 		opts = append(opts, bedrock.WithHTTPClient(httpClient))
 	}
@@ -707,7 +707,7 @@ func (c *coordinator) buildGoogleProvider(baseURL, apiKey string, headers map[st
 		google.WithBaseURL(baseURL),
 		google.WithGeminiAPIKey(apiKey),
 	}
-	if c.cfg.Options.Debug {
+	if c.cfgSvc.Debug() {
 		httpClient := log.NewHTTPClient()
 		opts = append(opts, google.WithHTTPClient(httpClient))
 	}
@@ -719,7 +719,7 @@ func (c *coordinator) buildGoogleProvider(baseURL, apiKey string, headers map[st
 
 func (c *coordinator) buildGoogleVertexProvider(headers map[string]string, options map[string]string) (fantasy.Provider, error) {
 	opts := []google.Option{}
-	if c.cfg.Options.Debug {
+	if c.cfgSvc.Debug() {
 		httpClient := log.NewHTTPClient()
 		opts = append(opts, google.WithHTTPClient(httpClient))
 	}
@@ -740,7 +740,7 @@ func (c *coordinator) buildHyperProvider(baseURL, apiKey string) (fantasy.Provid
 		hyper.WithBaseURL(baseURL),
 		hyper.WithAPIKey(apiKey),
 	}
-	if c.cfg.Options.Debug {
+	if c.cfgSvc.Debug() {
 		httpClient := log.NewHTTPClient()
 		opts = append(opts, hyper.WithHTTPClient(httpClient))
 	}
@@ -859,7 +859,7 @@ func (c *coordinator) UpdateModels(ctx context.Context) error {
 	}
 	c.currentAgent.SetModels(large, small)
 
-	agentCfg, ok := c.cfg.Agents[config.AgentCoder]
+	agentCfg, ok := c.cfgSvc.Agents()[config.AgentCoder]
 	if !ok {
 		return errors.New("coder agent not configured")
 	}
@@ -881,7 +881,7 @@ func (c *coordinator) QueuedPromptsList(sessionID string) []string {
 }
 
 func (c *coordinator) Summarize(ctx context.Context, sessionID string) error {
-	providerCfg, ok := c.cfg.Providers.Get(c.currentAgent.Model().ModelCfg.Provider)
+	providerCfg, ok := c.cfgSvc.Config().Providers.Get(c.currentAgent.Model().ModelCfg.Provider)
 	if !ok {
 		return errors.New("model provider not configured")
 	}
@@ -912,7 +912,7 @@ func (c *coordinator) refreshApiKeyTemplate(ctx context.Context, providerCfg con
 	}
 
 	providerCfg.APIKey = newAPIKey
-	c.cfg.Providers.Set(providerCfg.ID, providerCfg)
+	c.cfgSvc.Config().Providers.Set(providerCfg.ID, providerCfg)
 
 	if err := c.UpdateModels(ctx); err != nil {
 		return err

internal/app/app.go 🔗

@@ -165,7 +165,7 @@ func (app *App) RunNonInteractive(ctx context.Context, output io.Writer, prompt,
 	}
 	stderrTTY = term.IsTerminal(os.Stderr.Fd())
 	stdinTTY = term.IsTerminal(os.Stdin.Fd())
-	progress = app.config.Options.Progress == nil || *app.config.Options.Progress
+	progress = app.configService.Progress() == nil || *app.configService.Progress()
 
 	if !hideSpinner && stderrTTY {
 		t := styles.DefaultStyles()
@@ -463,7 +463,7 @@ func setupSubscriber[T any](
 }
 
 func (app *App) InitCoderAgent(ctx context.Context) error {
-	coderAgentCfg := app.config.Agents[config.AgentCoder]
+	coderAgentCfg := app.configService.Agents()[config.AgentCoder]
 	if coderAgentCfg.ID == "" {
 		return fmt.Errorf("coder agent configuration is missing")
 	}

internal/app/lsp.go 🔗

@@ -63,7 +63,7 @@ func (app *App) initLSPClients(ctx context.Context) {
 
 	var wg sync.WaitGroup
 	for name, server := range filtered {
-		if app.config.Options.AutoLSP != nil && !*app.config.Options.AutoLSP && !slices.Contains(userConfiguredLSPs, name) {
+		if app.configService.AutoLSP() != nil && !*app.configService.AutoLSP() && !slices.Contains(userConfiguredLSPs, name) {
 			slog.Debug("Ignoring non user-define LSP client due to AutoLSP being disabled", "name", name)
 			continue
 		}
@@ -114,7 +114,7 @@ func (app *App) createAndStartLSPClient(ctx context.Context, name string, config
 	updateLSPState(name, lsp.StateStarting, nil, nil, 0)
 
 	// Create LSP client.
-	lspClient, err := lsp.New(ctx, name, config, app.configService.Resolver(), app.config.Options.DebugLSP)
+	lspClient, err := lsp.New(ctx, name, config, app.configService.Resolver(), app.configService.DebugLSP())
 	if err != nil {
 		if !userConfigured {
 			slog.Warn("Default LSP config skipped due to error", "name", name, "error", err)

internal/cmd/logs.go 🔗

@@ -55,7 +55,7 @@ var logsCmd = &cobra.Command{
 		if err != nil {
 			return fmt.Errorf("failed to load configuration: %v", err)
 		}
-		logsFile := filepath.Join(cfg.Config().Options.DataDirectory, "logs", "crush.log")
+		logsFile := filepath.Join(cfg.DataDirectory(), "logs", "crush.log")
 		_, err = os.Stat(logsFile)
 		if os.IsNotExist(err) {
 			log.Warn("Looks like you are not in a crush project. No logs found.")

internal/cmd/root.go 🔗

@@ -173,7 +173,7 @@ func setupAppWithProgressBar(cmd *cobra.Command) (*app.App, error) {
 	}
 
 	// Check if progress bar is enabled in config (defaults to true if nil)
-	progressEnabled := app.Config().Options.Progress == nil || *app.Config().Options.Progress
+	progressEnabled := app.ConfigService().Progress() == nil || *app.ConfigService().Progress()
 	if progressEnabled && supportsProgressBar() {
 		_, _ = fmt.Fprintf(os.Stderr, ansi.SetIndeterminateProgressBar)
 		defer func() { _, _ = fmt.Fprintf(os.Stderr, ansi.ResetProgressBar) }()
@@ -206,18 +206,18 @@ func setupApp(cmd *cobra.Command) (*app.App, error) {
 	}
 	cfg.Permissions.SkipRequests = yolo
 
-	if err := createDotCrushDir(cfg.Options.DataDirectory); err != nil {
+	if err := createDotCrushDir(svc.DataDirectory()); err != nil {
 		return nil, err
 	}
 
 	// Register this project in the centralized projects list.
-	if err := projects.Register(cwd, cfg.Options.DataDirectory); err != nil {
+	if err := projects.Register(cwd, svc.DataDirectory()); err != nil {
 		slog.Warn("Failed to register project", "error", err)
 		// Non-fatal: continue even if registration fails
 	}
 
 	// Connect to DB; this will also run migrations.
-	conn, err := db.Connect(ctx, cfg.Options.DataDirectory)
+	conn, err := db.Connect(ctx, svc.DataDirectory())
 	if err != nil {
 		return nil, err
 	}
@@ -228,21 +228,21 @@ func setupApp(cmd *cobra.Command) (*app.App, error) {
 		return nil, err
 	}
 
-	if shouldEnableMetrics(cfg) {
+	if shouldEnableMetrics(svc) {
 		event.Init()
 	}
 
 	return appInstance, nil
 }
 
-func shouldEnableMetrics(cfg *config.Config) bool {
+func shouldEnableMetrics(svc *config.Service) bool {
 	if v, _ := strconv.ParseBool(os.Getenv("CRUSH_DISABLE_METRICS")); v {
 		return false
 	}
 	if v, _ := strconv.ParseBool(os.Getenv("DO_NOT_TRACK")); v {
 		return false
 	}
-	if cfg.Options.DisableMetrics {
+	if svc.DisableMetrics() {
 		return false
 	}
 	return true

internal/config/service.go 🔗

@@ -124,6 +124,148 @@ func (s *Service) Agents() map[string]Agent {
 	return s.agents
 }
 
+// Agent returns the agent configuration for the given name and
+// whether it exists.
+func (s *Service) Agent(name string) (Agent, bool) {
+	a, ok := s.agents[name]
+	return a, ok
+}
+
+// DataDirectory returns the data directory path.
+func (s *Service) DataDirectory() string {
+	return s.cfg.Options.DataDirectory
+}
+
+// Debug returns whether debug mode is enabled.
+func (s *Service) Debug() bool {
+	return s.cfg.Options.Debug
+}
+
+// DebugLSP returns whether LSP debug mode is enabled.
+func (s *Service) DebugLSP() bool {
+	return s.cfg.Options.DebugLSP
+}
+
+// DisableAutoSummarize returns whether auto-summarization is
+// disabled.
+func (s *Service) DisableAutoSummarize() bool {
+	return s.cfg.Options.DisableAutoSummarize
+}
+
+// Attribution returns the attribution settings.
+func (s *Service) Attribution() *Attribution {
+	return s.cfg.Options.Attribution
+}
+
+// ContextPaths returns the configured context paths.
+func (s *Service) ContextPaths() []string {
+	return s.cfg.Options.ContextPaths
+}
+
+// SkillsPaths returns the configured skills paths.
+func (s *Service) SkillsPaths() []string {
+	return s.cfg.Options.SkillsPaths
+}
+
+// Progress returns the progress setting pointer.
+func (s *Service) Progress() *bool {
+	return s.cfg.Options.Progress
+}
+
+// DisableMetrics returns whether metrics are disabled.
+func (s *Service) DisableMetrics() bool {
+	return s.cfg.Options.DisableMetrics
+}
+
+// SelectedModel returns the selected model for the given type and
+// whether it exists.
+func (s *Service) SelectedModel(modelType SelectedModelType) (SelectedModel, bool) {
+	m, ok := s.cfg.Models[modelType]
+	return m, ok
+}
+
+// MCP returns the MCP configurations.
+func (s *Service) MCP() MCPs {
+	return s.cfg.MCP
+}
+
+// LSP returns the LSP configurations.
+func (s *Service) LSP() LSPs {
+	return s.cfg.LSP
+}
+
+// Permissions returns the permissions configuration.
+func (s *Service) Permissions() *Permissions {
+	return s.cfg.Permissions
+}
+
+// SetPermissions sets the permissions configuration.
+func (s *Service) SetPermissions(p *Permissions) {
+	s.cfg.Permissions = p
+}
+
+// ToolLsConfig returns the ls tool configuration.
+func (s *Service) ToolLsConfig() ToolLs {
+	return s.cfg.Tools.Ls
+}
+
+// CompactMode returns whether compact mode is enabled.
+func (s *Service) CompactMode() bool {
+	if s.cfg.Options.TUI == nil {
+		return false
+	}
+	return s.cfg.Options.TUI.CompactMode
+}
+
+// DiffMode returns the diff mode setting.
+func (s *Service) DiffMode() string {
+	if s.cfg.Options.TUI == nil {
+		return ""
+	}
+	return s.cfg.Options.TUI.DiffMode
+}
+
+// CompletionLimits returns the completion depth and items limits.
+func (s *Service) CompletionLimits() (depth, items int) {
+	if s.cfg.Options.TUI == nil {
+		return 0, 0
+	}
+	return s.cfg.Options.TUI.Completions.Limits()
+}
+
+// DisableDefaultProviders returns whether default providers are
+// disabled.
+func (s *Service) DisableDefaultProviders() bool {
+	return s.cfg.Options.DisableDefaultProviders
+}
+
+// DisableProviderAutoUpdate returns whether provider auto-update is
+// disabled.
+func (s *Service) DisableProviderAutoUpdate() bool {
+	return s.cfg.Options.DisableProviderAutoUpdate
+}
+
+// InitializeAs returns the initialization file name.
+func (s *Service) InitializeAs() string {
+	return s.cfg.Options.InitializeAs
+}
+
+// AutoLSP returns the auto-LSP setting pointer.
+func (s *Service) AutoLSP() *bool {
+	return s.cfg.Options.AutoLSP
+}
+
+// RecentModels returns recent models for the given type.
+func (s *Service) RecentModels(modelType SelectedModelType) []SelectedModel {
+	return s.cfg.RecentModels[modelType]
+}
+
+// Options returns the full options struct. This is a temporary
+// accessor for callers that need multiple option fields.
+func (s *Service) Options() *Options {
+	return s.cfg.Options
+}
+
 // HasConfigField returns true if the given dotted key path exists in
 // the persisted config data.
 func (s *Service) HasConfigField(key string) bool {

internal/ui/model/header.go 🔗

@@ -117,7 +117,7 @@ func renderHeaderDetails(
 		parts = append(parts, t.LSP.ErrorDiagnostic.Render(fmt.Sprintf("%s%d", styles.LSPErrorIcon, errorCount)))
 	}
 
-	agentCfg := com.Config().Agents[config.AgentCoder]
+	agentCfg := com.ConfigService().Agents()[config.AgentCoder]
 	model := com.ConfigService().GetModelByType(agentCfg.Model)
 	percentage := (float64(session.CompletionTokens+session.PromptTokens) / float64(model.ContextWindow)) * 100
 	formattedPercentage := t.Header.Percentage.Render(fmt.Sprintf("%d%%", int(percentage)))

internal/ui/model/mcp.go 🔗

@@ -16,7 +16,7 @@ func (m *UI) mcpInfo(width, maxItems int, isSection bool) string {
 	var mcps []mcp.ClientInfo
 	t := m.com.Styles
 
-	for _, mcp := range m.com.Config().MCP.Sorted() {
+	for _, mcp := range m.com.ConfigService().MCP().Sorted() {
 		if state, ok := m.mcpStates[mcp.Name]; ok {
 			mcps = append(mcps, state)
 		}

internal/ui/model/sidebar.go 🔗

@@ -21,7 +21,7 @@ func (m *UI) modelInfo(width int) string {
 
 	if model != nil {
 		// Get provider name first
-		providerConfig, ok := m.com.Config().Providers.Get(model.ModelCfg.Provider)
+		providerConfig, ok := m.com.ConfigService().Config().Providers.Get(model.ModelCfg.Provider)
 		if ok {
 			providerName = providerConfig.Name
 

internal/ui/model/ui.go 🔗

@@ -289,7 +289,7 @@ func New(com *common.Common) *UI {
 	ui.status = status
 
 	// Initialize compact mode from config
-	ui.forceCompactMode = com.Config().Options.TUI.CompactMode
+	ui.forceCompactMode = com.ConfigService().Options().TUI.CompactMode
 
 	// set onboarding state defaults
 	ui.onboarding.yesInitializeSelected = true
@@ -305,7 +305,7 @@ func New(com *common.Common) *UI {
 	// set initial state
 	ui.setState(desiredState, desiredFocus)
 
-	opts := com.Config().Options
+	opts := com.ConfigService().Options()
 
 	// disable indeterminate progress bar
 	ui.progressBarEnabled = opts.Progress == nil || *opts.Progress
@@ -1635,7 +1635,7 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
 						m.completionsQuery = ""
 						m.completionsStartIndex = curIdx
 						m.completionsPositionStart = m.completionsPosition()
-						depth, limit := m.com.Config().Options.TUI.Completions.Limits()
+						depth, limit := m.com.ConfigService().Options().TUI.Completions.Limits()
 						cmds = append(cmds, m.completions.OpenWithFiles(depth, limit))
 					}
 				}
@@ -2848,7 +2848,7 @@ func (m *UI) openPermissionsDialog(perm permission.PermissionRequest) tea.Cmd {
 
 	// Get diff mode from config.
 	var opts []dialog.PermissionsOption
-	if diffMode := m.com.Config().Options.TUI.DiffMode; diffMode != "" {
+	if diffMode := m.com.ConfigService().Options().TUI.DiffMode; diffMode != "" {
 		opts = append(opts, dialog.WithDiffMode(diffMode == "split"))
 	}