@@ -18,6 +18,13 @@ import (
18 "github.com/tidwall/sjson"
18 "github.com/tidwall/sjson"
21+// RuntimeOverrides holds per-session settings that are never persisted to
22+// disk. They are applied on top of the loaded Config and survive only for
23+// the lifetime of the process (or workspace).
24+type RuntimeOverrides struct {
25+ SkipPermissionRequests bool
21// ConfigStore is the single entry point for all config access. It owns the
28// ConfigStore is the single entry point for all config access. It owns the
22// pure-data Config, runtime state (working directory, resolver, known
29// pure-data Config, runtime state (working directory, resolver, known
23// providers), and persistence to both global and workspace config files.
30// providers), and persistence to both global and workspace config files.
@@ -28,6 +35,7 @@ type ConfigStore struct {
28 globalDataPath string // ~/.local/share/crush/crush.json
35 globalDataPath string // ~/.local/share/crush/crush.json
29 workspacePath string // .crush/crush.json
36 workspacePath string // .crush/crush.json
30 knownProviders []catwalk.Provider
37 knownProviders []catwalk.Provider
38+ overrides RuntimeOverrides
33// Config returns the pure-data config struct (read-only after load).
41// Config returns the pure-data config struct (read-only after load).
@@ -63,20 +71,32 @@ func (s *ConfigStore) SetupAgents() {
63 s.config.SetupAgents()
71 s.config.SetupAgents()
74+// Overrides returns the runtime overrides for this store.
75+func (s *ConfigStore) Overrides() *RuntimeOverrides {
66// configPath returns the file path for the given scope.
79// configPath returns the file path for the given scope.
67−func (s *ConfigStore) configPath(scope Scope) string {
80+func (s *ConfigStore) configPath(scope Scope) (string, error) {
68 switch scope {
81 switch scope {
69 case ScopeWorkspace:
82 case ScopeWorkspace:
83+ if s.workspacePath == "" {
84+ return "", ErrNoWorkspaceConfig
70− return s.workspacePath
86+ return s.workspacePath, nil
72− return s.globalDataPath
88+ return s.globalDataPath, nil
76// HasConfigField checks whether a key exists in the config file for the given
92// HasConfigField checks whether a key exists in the config file for the given
78func (s *ConfigStore) HasConfigField(scope Scope, key string) bool {
94func (s *ConfigStore) HasConfigField(scope Scope, key string) bool {
95+ path, err := s.configPath(scope)
79− data, err := os.ReadFile(s.configPath(scope))
99+ data, err := os.ReadFile(path)
80 if err != nil {
100 if err != nil {
81 return false
101 return false
@@ -85,7 +105,10 @@ func (s *ConfigStore) HasConfigField(scope Scope, key string) bool {
86// SetConfigField sets a key/value pair in the config file for the given scope.
106// SetConfigField sets a key/value pair in the config file for the given scope.
87func (s *ConfigStore) SetConfigField(scope Scope, key string, value any) error {
107func (s *ConfigStore) SetConfigField(scope Scope, key string, value any) error {
88− path := s.configPath(scope)
108+ path, err := s.configPath(scope)
110+ return fmt.Errorf("%s: %w", key, err)
89 data, err := os.ReadFile(path)
112 data, err := os.ReadFile(path)
90 if err != nil {
113 if err != nil {
91 if os.IsNotExist(err) {
114 if os.IsNotExist(err) {
@@ -110,7 +133,10 @@ func (s *ConfigStore) SetConfigField(scope Scope, key string, value any) error {
111// RemoveConfigField removes a key from the config file for the given scope.
134// RemoveConfigField removes a key from the config file for the given scope.
112func (s *ConfigStore) RemoveConfigField(scope Scope, key string) error {
135func (s *ConfigStore) RemoveConfigField(scope Scope, key string) error {
113− path := s.configPath(scope)
136+ path, err := s.configPath(scope)
138+ return fmt.Errorf("%s: %w", key, err)
114 data, err := os.ReadFile(path)
140 data, err := os.ReadFile(path)
115 if err != nil {
141 if err != nil {
116 return fmt.Errorf("failed to read config file: %w", err)
142 return fmt.Errorf("failed to read config file: %w", err)