From 83457366d0da8dd5a7891377301145dfabab0b78 Mon Sep 17 00:00:00 2001 From: Marcus Vorwaller Date: Mon, 23 Mar 2026 20:50:28 -0700 Subject: [PATCH] feat: allow disabling plugins via config (fixes #243) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Enabled flag support for file-browser plugin (which was missing a config struct entirely), and wire Enabled checks in main.go for td-monitor, git-status, file-browser, and conversations plugins. Previously, setting 'enabled: false' in config had no effect — plugins were registered unconditionally. Now: - Each registerable plugin checks cfg.Plugins..Enabled before registering - file-browser gets a proper FileBrowserPluginConfig struct with Enabled - Workspace plugin is not gated (it's the core plugin) - Notes plugin keeps its existing feature-flag gate Config example: {"plugins": {"file-browser": {"enabled": false}}} Fixes #243 --- cmd/sidecar/main.go | 24 ++++++++++++++-------- internal/config/config.go | 10 +++++++++ internal/config/loader.go | 10 +++++++++ internal/config/loader_test.go | 37 ++++++++++++++++++++++++++++++++++ internal/config/saver.go | 8 ++++++++ 5 files changed, 81 insertions(+), 8 deletions(-) diff --git a/cmd/sidecar/main.go b/cmd/sidecar/main.go index d922cf0c..e5152e23 100644 --- a/cmd/sidecar/main.go +++ b/cmd/sidecar/main.go @@ -171,17 +171,25 @@ func main() { // Register plugins (order determines tab order) // TD plugin registers its bindings dynamically via p.ctx.Keymap - if err := registry.Register(tdmonitor.New()); err != nil { - logger.Warn("failed to register tdmonitor plugin", "err", err) + if cfg.Plugins.TDMonitor.Enabled { + if err := registry.Register(tdmonitor.New()); err != nil { + logger.Warn("failed to register tdmonitor plugin", "err", err) + } } - if err := registry.Register(gitstatus.New()); err != nil { - logger.Warn("failed to register gitstatus plugin", "err", err) + if cfg.Plugins.GitStatus.Enabled { + if err := registry.Register(gitstatus.New()); err != nil { + logger.Warn("failed to register gitstatus plugin", "err", err) + } } - if err := registry.Register(filebrowser.New()); err != nil { - logger.Warn("failed to register filebrowser plugin", "err", err) + if cfg.Plugins.FileBrowser.Enabled { + if err := registry.Register(filebrowser.New()); err != nil { + logger.Warn("failed to register filebrowser plugin", "err", err) + } } - if err := registry.Register(conversations.New()); err != nil { - logger.Warn("failed to register conversations plugin", "err", err) + if cfg.Plugins.Conversations.Enabled { + if err := registry.Register(conversations.New()); err != nil { + logger.Warn("failed to register conversations plugin", "err", err) + } } if err := registry.Register(workspace.New()); err != nil { logger.Warn("failed to register workspace plugin", "err", err) diff --git a/internal/config/config.go b/internal/config/config.go index 859fae4c..5e4068ce 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -35,6 +35,7 @@ type ProjectConfig struct { type PluginsConfig struct { GitStatus GitStatusPluginConfig `json:"git-status"` TDMonitor TDMonitorPluginConfig `json:"td-monitor"` + FileBrowser FileBrowserPluginConfig `json:"file-browser"` Conversations ConversationsPluginConfig `json:"conversations"` Workspace WorkspacePluginConfig `json:"workspace"` Notes NotesPluginConfig `json:"notes"` @@ -46,6 +47,12 @@ type GitStatusPluginConfig struct { RefreshInterval time.Duration `json:"refreshInterval"` } +// FileBrowserPluginConfig configures the file browser plugin. +type FileBrowserPluginConfig struct { + // Enabled controls whether the file browser plugin is loaded. Default: true. + Enabled bool `json:"enabled"` +} + // TDMonitorPluginConfig configures the TD monitor plugin. type TDMonitorPluginConfig struct { Enabled bool `json:"enabled"` @@ -149,6 +156,9 @@ func Default() *Config { RefreshInterval: 2 * time.Second, DBPath: ".todos/issues.db", }, + FileBrowser: FileBrowserPluginConfig{ + Enabled: true, + }, Conversations: ConversationsPluginConfig{ Enabled: true, ClaudeDataDir: "~/.claude", diff --git a/internal/config/loader.go b/internal/config/loader.go index 7c751ec4..9f0afe83 100644 --- a/internal/config/loader.go +++ b/internal/config/loader.go @@ -74,6 +74,7 @@ type rawProjectConfig struct { type rawPluginsConfig struct { GitStatus rawGitStatusConfig `json:"git-status"` TDMonitor rawTDMonitorConfig `json:"td-monitor"` + FileBrowser rawFileBrowserConfig `json:"file-browser"` Conversations rawConversationsConfig `json:"conversations"` Workspace rawWorkspaceConfig `json:"workspace"` } @@ -109,6 +110,10 @@ type rawTDMonitorConfig struct { DBPath string `json:"dbPath"` } +type rawFileBrowserConfig struct { + Enabled *bool `json:"enabled"` +} + type rawConversationsConfig struct { Enabled *bool `json:"enabled"` ClaudeDataDir string `json:"claudeDataDir"` @@ -217,6 +222,11 @@ func mergeConfig(cfg *Config, raw *rawConfig) { cfg.Plugins.TDMonitor.DBPath = raw.Plugins.TDMonitor.DBPath } + // File Browser + if raw.Plugins.FileBrowser.Enabled != nil { + cfg.Plugins.FileBrowser.Enabled = *raw.Plugins.FileBrowser.Enabled + } + // Conversations if raw.Plugins.Conversations.Enabled != nil { cfg.Plugins.Conversations.Enabled = *raw.Plugins.Conversations.Enabled diff --git a/internal/config/loader_test.go b/internal/config/loader_test.go index 67d56129..470daaaa 100644 --- a/internal/config/loader_test.go +++ b/internal/config/loader_test.go @@ -371,3 +371,40 @@ func TestApplyEnvOverrides_NeitherVarSet(t *testing.T) { t.Errorf("DefaultAgentType = %q, want %q (should be unchanged)", cfg.Plugins.Workspace.DefaultAgentType, "original") } } + +func TestDefault_FileBrowserEnabled(t *testing.T) { + cfg := Default() + if !cfg.Plugins.FileBrowser.Enabled { + t.Error("file-browser should be enabled by default") + } +} + +func TestLoadFrom_FileBrowserDisabled(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "config.json") + + content := []byte(`{ + "plugins": { + "file-browser": { + "enabled": false + } + } + }`) + + if err := os.WriteFile(path, content, 0644); err != nil { + t.Fatal(err) + } + + cfg, err := LoadFrom(path) + if err != nil { + t.Fatalf("LoadFrom failed: %v", err) + } + + if cfg.Plugins.FileBrowser.Enabled { + t.Error("file-browser should be disabled when set to false in config") + } + // Other plugins should still have defaults + if !cfg.Plugins.GitStatus.Enabled { + t.Error("git-status should still be enabled (default)") + } +} diff --git a/internal/config/saver.go b/internal/config/saver.go index ab178562..b0f9b5cb 100644 --- a/internal/config/saver.go +++ b/internal/config/saver.go @@ -26,10 +26,15 @@ type saveProjectsConfig struct { type savePluginsConfig struct { GitStatus saveGitStatusConfig `json:"git-status,omitempty"` TDMonitor saveTDMonitorConfig `json:"td-monitor,omitempty"` + FileBrowser saveFileBrowserConfig `json:"file-browser,omitempty"` Conversations saveConversationsConfig `json:"conversations,omitempty"` Workspace saveWorkspaceConfig `json:"workspace,omitempty"` } +type saveFileBrowserConfig struct { + Enabled *bool `json:"enabled,omitempty"` +} + type saveGitStatusConfig struct { Enabled *bool `json:"enabled,omitempty"` RefreshInterval string `json:"refreshInterval,omitempty"` @@ -76,6 +81,9 @@ func toSaveConfig(cfg *Config) saveConfig { RefreshInterval: cfg.Plugins.TDMonitor.RefreshInterval.String(), DBPath: cfg.Plugins.TDMonitor.DBPath, }, + FileBrowser: saveFileBrowserConfig{ + Enabled: &cfg.Plugins.FileBrowser.Enabled, + }, Conversations: saveConversationsConfig{ Enabled: &cfg.Plugins.Conversations.Enabled, ClaudeDataDir: cfg.Plugins.Conversations.ClaudeDataDir,