diff --git a/internal/session/tooloptions.go b/internal/session/tooloptions.go index fd559100f..f59c5af6c 100644 --- a/internal/session/tooloptions.go +++ b/internal/session/tooloptions.go @@ -110,6 +110,8 @@ func NewClaudeOptions(config *UserConfig) *ClaudeOptions { opts.SkipPermissions = config.Claude.GetDangerousMode() opts.AutoMode = config.Claude.AutoMode opts.AllowSkipPermissions = config.Claude.AllowDangerousMode + opts.UseChrome = config.Claude.UseChrome + opts.UseTeammateMode = config.Claude.UseTeammateMode } return opts } diff --git a/internal/session/tooloptions_test.go b/internal/session/tooloptions_test.go index 796a0c2ff..e0c318f6d 100644 --- a/internal/session/tooloptions_test.go +++ b/internal/session/tooloptions_test.go @@ -229,7 +229,9 @@ func TestNewClaudeOptions_WithConfig(t *testing.T) { dangerousModeBool := true config := &UserConfig{ Claude: ClaudeSettings{ - DangerousMode: &dangerousModeBool, + DangerousMode: &dangerousModeBool, + UseChrome: true, + UseTeammateMode: true, }, } @@ -241,6 +243,12 @@ func TestNewClaudeOptions_WithConfig(t *testing.T) { if !opts.SkipPermissions { t.Error("expected SkipPermissions=true when config.DangerousMode=true") } + if !opts.UseChrome { + t.Error("expected UseChrome=true") + } + if !opts.UseTeammateMode { + t.Error("expected UseTeammateMode=true") + } } func TestNewClaudeOptions_AutoMode(t *testing.T) { diff --git a/internal/session/userconfig.go b/internal/session/userconfig.go index 3706397bd..9fb55937d 100644 --- a/internal/session/userconfig.go +++ b/internal/session/userconfig.go @@ -671,6 +671,17 @@ type ClaudeSettings struct { // Default: false AutoMode bool `toml:"auto_mode"` + // ExtraArgs are user-supplied Claude CLI flags used as the New Session + // dialog default. They are persisted as discrete TOML array entries and + // copied to Instance.ExtraArgs when a Claude session is created. + ExtraArgs []string `toml:"extra_args"` + + // UseChrome enables --chrome by default for Claude sessions. + UseChrome bool `toml:"use_chrome"` + + // UseTeammateMode enables --teammate-mode tmux by default for Claude sessions. + UseTeammateMode bool `toml:"use_teammate_mode"` + // EnvFile is a .env file specific to Claude sessions // Sourced AFTER global [shell].env_files // Path can be absolute, ~ for home, $HOME/${VAR} for env vars, or relative to session working directory @@ -2350,6 +2361,11 @@ func CreateExampleConfig() error { # config_dir = "~/.claude-work" # Enable --dangerously-skip-permissions by default (default: false) # dangerous_mode = true +# Extra Claude CLI flags remembered from the New Session dialog +# extra_args = ["--agent", "reviewer"] +# Enable Chrome / teammate mode by default +# use_chrome = false +# use_teammate_mode = false # Gemini CLI integration # [gemini] diff --git a/internal/session/userconfig_test.go b/internal/session/userconfig_test.go index 7e616c4a0..62726d1b6 100644 --- a/internal/session/userconfig_test.go +++ b/internal/session/userconfig_test.go @@ -466,6 +466,46 @@ func TestSaveUserConfig(t *testing.T) { } } +func TestClaudeExtraArgsConfigRoundTrip(t *testing.T) { + tempDir := t.TempDir() + originalHome := os.Getenv("HOME") + os.Setenv("HOME", tempDir) + defer os.Setenv("HOME", originalHome) + ClearUserConfigCache() + defer ClearUserConfigCache() + + config := &UserConfig{ + Claude: ClaudeSettings{ + ExtraArgs: []string{"--agent", "reviewer", "--model", "opus"}, + UseChrome: true, + UseTeammateMode: true, + }, + } + if err := SaveUserConfig(config); err != nil { + t.Fatalf("SaveUserConfig failed: %v", err) + } + + loaded, err := LoadUserConfig() + if err != nil { + t.Fatalf("LoadUserConfig failed: %v", err) + } + want := []string{"--agent", "reviewer", "--model", "opus"} + if len(loaded.Claude.ExtraArgs) != len(want) { + t.Fatalf("Claude.ExtraArgs = %v, want %v", loaded.Claude.ExtraArgs, want) + } + for i := range want { + if loaded.Claude.ExtraArgs[i] != want[i] { + t.Fatalf("Claude.ExtraArgs[%d] = %q, want %q", i, loaded.Claude.ExtraArgs[i], want[i]) + } + } + if !loaded.Claude.UseChrome { + t.Fatal("Claude.UseChrome = false, want true") + } + if !loaded.Claude.UseTeammateMode { + t.Fatal("Claude.UseTeammateMode = false, want true") + } +} + func TestGetTheme_Default(t *testing.T) { // Setup: use temp directory with no config tempDir := t.TempDir() diff --git a/internal/ui/claudeoptions.go b/internal/ui/claudeoptions.go index d95f011b2..969e83526 100644 --- a/internal/ui/claudeoptions.go +++ b/internal/ui/claudeoptions.go @@ -93,6 +93,9 @@ func (p *ClaudeOptionsPanel) SetDefaults(config *session.UserConfig) { p.skipPermissions = config.Claude.GetDangerousMode() p.allowSkipPermissions = config.Claude.AllowDangerousMode p.autoMode = config.Claude.AutoMode + p.SetExtraArgs(config.Claude.ExtraArgs) + p.useChrome = config.Claude.UseChrome + p.useTeammateMode = config.Claude.UseTeammateMode } } diff --git a/internal/ui/home.go b/internal/ui/home.go index b049092a7..a3a8f2a18 100644 --- a/internal/ui/home.go +++ b/internal/ui/home.go @@ -5192,6 +5192,7 @@ func (h *Home) handleNewDialogKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { if command == "claude" && claudeOpts != nil { toolOptionsJSON, _ = session.MarshalToolOptions(claudeOpts) claudeExtraArgs = h.newDialog.GetClaudeExtraArgs() + persistClaudeDialogDefaults(claudeOpts, claudeExtraArgs) claudeStartQuery = h.newDialog.GetClaudeStartQuery() } else if command == "codex" { yolo := h.newDialog.GetCodexYoloMode() @@ -5277,6 +5278,30 @@ func (h *Home) handleNewDialogKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return h, cmd } +func persistClaudeDialogDefaults(opts *session.ClaudeOptions, args []string) { + cfg, err := session.LoadUserConfig() + if err != nil || cfg == nil || opts == nil { + return + } + cleaned := make([]string, 0, len(args)) + for _, arg := range args { + if tok := strings.TrimSpace(arg); tok != "" { + cleaned = append(cleaned, tok) + } + } + if len(cleaned) == 0 { + cfg.Claude.ExtraArgs = nil + } else { + cfg.Claude.ExtraArgs = cleaned + } + cfg.Claude.DangerousMode = &opts.SkipPermissions + cfg.Claude.AllowDangerousMode = opts.AllowSkipPermissions + cfg.Claude.AutoMode = opts.AutoMode + cfg.Claude.UseChrome = opts.UseChrome + cfg.Claude.UseTeammateMode = opts.UseTeammateMode + _ = session.SaveUserConfig(cfg) +} + func (h *Home) beginNotesEditing(inst *session.Instance) { if inst == nil { return diff --git a/internal/ui/home_test.go b/internal/ui/home_test.go index 944419ab3..b460bdbba 100644 --- a/internal/ui/home_test.go +++ b/internal/ui/home_test.go @@ -82,6 +82,62 @@ func TestApplyCreateSessionToolOverrides_NonGeminiNoop(t *testing.T) { } } +func TestPersistClaudeDialogDefaults(t *testing.T) { + origHome := os.Getenv("HOME") + tmpHome := t.TempDir() + os.Setenv("HOME", tmpHome) + session.ClearUserConfigCache() + defer func() { + os.Setenv("HOME", origHome) + session.ClearUserConfigCache() + }() + + persistClaudeDialogDefaults(&session.ClaudeOptions{ + SkipPermissions: false, + AllowSkipPermissions: true, + AutoMode: true, + UseChrome: true, + UseTeammateMode: true, + }, []string{"--agent", "reviewer", "", " --model "}) + cfg, err := session.LoadUserConfig() + if err != nil { + t.Fatalf("LoadUserConfig: %v", err) + } + want := []string{"--agent", "reviewer", "--model"} + if len(cfg.Claude.ExtraArgs) != len(want) { + t.Fatalf("Claude.ExtraArgs = %v, want %v", cfg.Claude.ExtraArgs, want) + } + for i := range want { + if cfg.Claude.ExtraArgs[i] != want[i] { + t.Fatalf("Claude.ExtraArgs[%d] = %q, want %q", i, cfg.Claude.ExtraArgs[i], want[i]) + } + } + if cfg.Claude.DangerousMode == nil || *cfg.Claude.DangerousMode { + t.Fatalf("Claude.DangerousMode = %v, want explicit false", cfg.Claude.DangerousMode) + } + if !cfg.Claude.AllowDangerousMode { + t.Fatal("Claude.AllowDangerousMode = false, want true") + } + if !cfg.Claude.AutoMode { + t.Fatal("Claude.AutoMode = false, want true") + } + if !cfg.Claude.UseChrome { + t.Fatal("Claude.UseChrome = false, want true") + } + if !cfg.Claude.UseTeammateMode { + t.Fatal("Claude.UseTeammateMode = false, want true") + } + + persistClaudeDialogDefaults(&session.ClaudeOptions{}, nil) + cfg, err = session.LoadUserConfig() + if err != nil { + t.Fatalf("LoadUserConfig after clear: %v", err) + } + if cfg.Claude.ExtraArgs != nil { + t.Fatalf("Claude.ExtraArgs should clear to nil, got %v", cfg.Claude.ExtraArgs) + } +} + // Co-credit @masta-g3 (PR #674): TUI session creation must produce // Tool="pi" rather than Tool="shell" with Command="pi", matching the // tmux/userconfig wiring already present. diff --git a/internal/ui/newdialog_test.go b/internal/ui/newdialog_test.go index 357c4ab60..4c85327f7 100644 --- a/internal/ui/newdialog_test.go +++ b/internal/ui/newdialog_test.go @@ -1577,6 +1577,52 @@ func TestNewDialog_GetClaudeStartQuery_ReturnsInputValue(t *testing.T) { } } +func TestNewDialog_ShowInGroup_LoadsConfiguredClaudeExtraArgs(t *testing.T) { + origHome := os.Getenv("HOME") + tmpHome := t.TempDir() + os.Setenv("HOME", tmpHome) + session.ClearUserConfigCache() + defer func() { + os.Setenv("HOME", origHome) + session.ClearUserConfigCache() + }() + + if err := session.SaveUserConfig(&session.UserConfig{ + Claude: session.ClaudeSettings{ + ExtraArgs: []string{"--agent", "reviewer", "--model", "opus"}, + UseChrome: true, + UseTeammateMode: true, + }, + }); err != nil { + t.Fatalf("SaveUserConfig: %v", err) + } + + dialog := NewNewDialog() + dialog.SetDefaultTool("claude") + dialog.ShowInGroup("default", "default", "", nil, "") + + got := dialog.GetClaudeExtraArgs() + want := []string{"--agent", "reviewer", "--model", "opus"} + if len(got) != len(want) { + t.Fatalf("GetClaudeExtraArgs() = %v, want %v", got, want) + } + for i := range want { + if got[i] != want[i] { + t.Fatalf("GetClaudeExtraArgs()[%d] = %q, want %q", i, got[i], want[i]) + } + } + opts := dialog.GetClaudeOptions() + if opts == nil { + t.Fatal("GetClaudeOptions() = nil") + } + if !opts.UseChrome { + t.Fatal("GetClaudeOptions().UseChrome = false, want true") + } + if !opts.UseTeammateMode { + t.Fatal("GetClaudeOptions().UseTeammateMode = false, want true") + } +} + // TestNewDialog_StartQuery_ClearsBetweenOpenings is the RED regression for // #741 (@Clindbergh). Filed against v1.7.67 after #725 shipped the dedicated // "Start query" field: opening the new-session dialog a second time showed diff --git a/skills/agent-deck/SKILL.md b/skills/agent-deck/SKILL.md index 2850575d2..7c93c07ed 100644 --- a/skills/agent-deck/SKILL.md +++ b/skills/agent-deck/SKILL.md @@ -375,6 +375,9 @@ See `agent-deck watcher --help` for the full command surface and per-adapter exa [claude] config_dir = "~/.claude-work" # Custom Claude profile dangerous_mode = true # --dangerously-skip-permissions +use_chrome = false # --chrome +use_teammate_mode = false # --teammate-mode tmux +extra_args = ["--agent", "reviewer"] [logs] max_size_mb = 10 # Max before truncation diff --git a/skills/agent-deck/references/config-reference.md b/skills/agent-deck/references/config-reference.md index 503a6a5c7..16f37235d 100644 --- a/skills/agent-deck/references/config-reference.md +++ b/skills/agent-deck/references/config-reference.md @@ -61,6 +61,9 @@ config_dir = "~/.claude" # Path to Claude config directory dangerous_mode = true # Enable --dangerously-skip-permissions auto_mode = false # Enable --permission-mode auto (classifier-based) allow_dangerous_mode = false # Enable --allow-dangerously-skip-permissions +use_chrome = false # Enable --chrome +use_teammate_mode = false # Enable --teammate-mode tmux +extra_args = ["--agent", "reviewer"] # Extra Claude CLI flags env_file = "~/.claude.env" # .env file specific to Claude sessions [profiles.work.claude] @@ -74,6 +77,9 @@ config_dir = "~/.claude-work" # Optional override for profile "work" | `dangerous_mode` | bool | `false` | Adds `--dangerously-skip-permissions`. Forces bypass on. Takes precedence over `auto_mode` and `allow_dangerous_mode`. | | `auto_mode` | bool | `false` | Adds `--permission-mode auto`. A classifier model auto-approves safe operations while blocking risky ones. Ignored when `dangerous_mode` is true. | | `allow_dangerous_mode` | bool | `false` | Adds `--allow-dangerously-skip-permissions`. Unlocks bypass as an option without activating it. Ignored when `dangerous_mode` or `auto_mode` is true. | +| `use_chrome` | bool | `false` | Adds `--chrome` to Claude sessions and is remembered from the New Session dialog. | +| `use_teammate_mode` | bool | `false` | Adds `--teammate-mode tmux` to Claude sessions and is remembered from the New Session dialog. | +| `extra_args` | array of strings | `[]` | Extra Claude CLI flags remembered from the New Session dialog and appended to new/restarted Claude sessions. Do not store secrets here. | | `env_file` | string | `""` | A .env file sourced for Claude sessions only. Sourced after global `[shell].env_files`. See [Path Resolution](#path-resolution). | Config resolution order for Claude config dir: diff --git a/skills/agent-deck/references/tui-reference.md b/skills/agent-deck/references/tui-reference.md index 0bdd0a888..40c0a5b9c 100644 --- a/skills/agent-deck/references/tui-reference.md +++ b/skills/agent-deck/references/tui-reference.md @@ -80,9 +80,12 @@ Complete reference for agent-deck Terminal UI features. - Project path (required, supports `~/`) - Command (claude/gemini/opencode/codex/custom) - Parent group (auto-selected) +- Claude options (when Claude is selected): permission mode, Chrome, teammate mode, extra args, and start query **Controls:** `Tab` move fields | `Enter` create | `Esc` cancel +Claude New Session defaults are remembered in `~/.agent-deck/config.toml` under `[claude]`, except start query and resume IDs, which are per-launch values. + ### MCP Manager (`m`) **Layout:**