Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion internal/session/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,8 @@ func (i *Instance) buildClaudeExtraFlags(opts *ClaudeOptions) string {
if opts != nil {
if opts.SkipPermissions {
flags = append(flags, "--dangerously-skip-permissions")
} else if opts.AutoMode {
flags = append(flags, "--permission-mode auto")
} else if opts.AllowSkipPermissions {
flags = append(flags, "--allow-dangerously-skip-permissions")
}
Expand Down Expand Up @@ -4080,6 +4082,7 @@ func (i *Instance) buildClaudeResumeCommand() string {
opts = NewClaudeOptions(userConfig)
}
dangerousMode := opts.SkipPermissions
autoMode := opts.AutoMode
allowDangerousMode := opts.AllowSkipPermissions

// Check if session has actual conversation data
Expand All @@ -4092,10 +4095,12 @@ func (i *Instance) buildClaudeResumeCommand() string {
slog.Bool("use_resume", useResume),
)

// Build dangerous mode flag (--dangerously-skip-permissions wins over --allow-...)
// Build permission flag (--dangerously-skip-permissions wins over --permission-mode auto wins over --allow-...)
dangerousFlag := ""
if dangerousMode {
dangerousFlag = " --dangerously-skip-permissions"
} else if autoMode {
dangerousFlag = " --permission-mode auto"
} else if allowDangerousMode {
dangerousFlag = " --allow-dangerously-skip-permissions"
}
Expand Down
9 changes: 9 additions & 0 deletions internal/session/tooloptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ type ClaudeOptions struct {
// AllowSkipPermissions adds --allow-dangerously-skip-permissions flag
// Only used when SkipPermissions is false (SkipPermissions takes precedence)
AllowSkipPermissions bool `json:"allow_skip_permissions,omitempty"`
// AutoMode adds --permission-mode auto flag
// Uses a classifier model to auto-approve safe operations while blocking risky ones.
// Only used when SkipPermissions is false (SkipPermissions takes precedence).
AutoMode bool `json:"auto_mode,omitempty"`
// UseChrome adds --chrome flag
UseChrome bool `json:"use_chrome,omitempty"`
// UseTeammateMode adds --teammate-mode tmux flag
Expand Down Expand Up @@ -60,6 +64,8 @@ func (o *ClaudeOptions) ToArgs() []string {
// Permission flags (mutually exclusive, SkipPermissions takes precedence)
if o.SkipPermissions {
args = append(args, "--dangerously-skip-permissions")
} else if o.AutoMode {
args = append(args, "--permission-mode", "auto")
} else if o.AllowSkipPermissions {
args = append(args, "--allow-dangerously-skip-permissions")
}
Expand All @@ -80,6 +86,8 @@ func (o *ClaudeOptions) ToArgsForFork() []string {

if o.SkipPermissions {
args = append(args, "--dangerously-skip-permissions")
} else if o.AutoMode {
args = append(args, "--permission-mode", "auto")
} else if o.AllowSkipPermissions {
args = append(args, "--allow-dangerously-skip-permissions")
}
Expand All @@ -100,6 +108,7 @@ func NewClaudeOptions(config *UserConfig) *ClaudeOptions {
}
if config != nil {
opts.SkipPermissions = config.Claude.GetDangerousMode()
opts.AutoMode = config.Claude.AutoMode
opts.AllowSkipPermissions = config.Claude.AllowDangerousMode
}
return opts
Expand Down
57 changes: 57 additions & 0 deletions internal/session/tooloptions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,29 @@ func TestClaudeOptions_ToArgs(t *testing.T) {
},
expected: []string{"--dangerously-skip-permissions"},
},
{
name: "auto mode only",
opts: ClaudeOptions{
AutoMode: true,
},
expected: []string{"--permission-mode", "auto"},
},
{
name: "skip permissions takes precedence over auto mode",
opts: ClaudeOptions{
SkipPermissions: true,
AutoMode: true,
},
expected: []string{"--dangerously-skip-permissions"},
},
{
name: "auto mode takes precedence over allow skip permissions",
opts: ClaudeOptions{
AutoMode: true,
AllowSkipPermissions: true,
},
expected: []string{"--permission-mode", "auto"},
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -174,6 +197,21 @@ func TestClaudeOptions_ToArgsForFork(t *testing.T) {
},
expected: []string{"--dangerously-skip-permissions"},
},
{
name: "auto mode for fork",
opts: ClaudeOptions{
AutoMode: true,
},
expected: []string{"--permission-mode", "auto"},
},
{
name: "skip permissions takes precedence over auto mode for fork",
opts: ClaudeOptions{
SkipPermissions: true,
AutoMode: true,
},
expected: []string{"--dangerously-skip-permissions"},
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -205,6 +243,25 @@ func TestNewClaudeOptions_WithConfig(t *testing.T) {
}
}

func TestNewClaudeOptions_AutoMode(t *testing.T) {
dangerousModeFalse := false
config := &UserConfig{
Claude: ClaudeSettings{
DangerousMode: &dangerousModeFalse,
AutoMode: true,
},
}

opts := NewClaudeOptions(config)

if opts.SkipPermissions {
t.Error("expected SkipPermissions=false when dangerous_mode=false")
}
if !opts.AutoMode {
t.Error("expected AutoMode=true when auto_mode=true")
}
}

func TestNewClaudeOptions_NilConfig(t *testing.T) {
opts := NewClaudeOptions(nil)

Expand Down
7 changes: 7 additions & 0 deletions internal/session/userconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,13 @@ type ClaudeSettings struct {
// Default: false
AllowDangerousMode bool `toml:"allow_dangerous_mode"`

// AutoMode enables --permission-mode auto flag for Claude sessions
// A classifier model reviews commands before they run, blocking scope escalation
// and hostile-content-driven actions while letting routine work proceed without prompts.
// Ignored when dangerous_mode is true (the stronger flag takes precedence).
// Default: false
AutoMode bool `toml:"auto_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
Expand Down
43 changes: 34 additions & 9 deletions internal/ui/claudeoptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type ClaudeOptionsPanel struct {
// Checkbox states
skipPermissions bool
allowSkipPermissions bool
autoMode bool
useChrome bool
useTeammateMode bool
// Focus tracking
Expand Down Expand Up @@ -67,6 +68,7 @@ func (p *ClaudeOptionsPanel) SetDefaults(config *session.UserConfig) {
if config != nil {
p.skipPermissions = config.Claude.GetDangerousMode()
p.allowSkipPermissions = config.Claude.AllowDangerousMode
p.autoMode = config.Claude.AutoMode
}
}

Expand All @@ -86,6 +88,7 @@ func (p *ClaudeOptionsPanel) SetFromOptions(opts *session.ClaudeOptions) {
}
p.skipPermissions = opts.SkipPermissions
p.allowSkipPermissions = opts.AllowSkipPermissions
p.autoMode = opts.AutoMode
p.useChrome = opts.UseChrome
p.useTeammateMode = opts.UseTeammateMode
p.updateInputFocus()
Expand Down Expand Up @@ -119,6 +122,7 @@ func (p *ClaudeOptionsPanel) GetOptions() *session.ClaudeOptions {
opts := &session.ClaudeOptions{
SkipPermissions: p.skipPermissions,
AllowSkipPermissions: p.allowSkipPermissions,
AutoMode: p.autoMode,
UseChrome: p.useChrome,
UseTeammateMode: p.useTeammateMode,
}
Expand Down Expand Up @@ -209,8 +213,10 @@ func (p *ClaudeOptionsPanel) handleSpaceKey() {
case 0:
p.skipPermissions = !p.skipPermissions
case 1:
p.useChrome = !p.useChrome
p.autoMode = !p.autoMode
case 2:
p.useChrome = !p.useChrome
case 3:
p.useTeammateMode = !p.useTeammateMode
}
} else {
Expand All @@ -221,6 +227,8 @@ func (p *ClaudeOptionsPanel) handleSpaceKey() {
p.sessionMode = (p.sessionMode + 1) % 3
case "skipPermissions":
p.skipPermissions = !p.skipPermissions
case "autoMode":
p.autoMode = !p.autoMode
case "chrome":
p.useChrome = !p.useChrome
case "teammateMode":
Expand All @@ -236,8 +244,10 @@ func (p *ClaudeOptionsPanel) getFocusType() string {
case 0:
return "skipPermissions"
case 1:
return "chrome"
return "autoMode"
case 2:
return "chrome"
case 3:
return "teammateMode"
}
} else {
Expand All @@ -257,12 +267,16 @@ func (p *ClaudeOptionsPanel) getFocusType() string {
if idx == 1 {
return "skipPermissions"
}
// 3: chrome
// 3: auto mode
if idx == 2 {
return "chrome"
return "autoMode"
}
// 4: teammate mode
// 4: chrome
if idx == 3 {
return "chrome"
}
// 5: teammate mode
if idx == 4 {
return "teammateMode"
}
}
Expand All @@ -272,10 +286,10 @@ func (p *ClaudeOptionsPanel) getFocusType() string {
// getFocusCount returns the number of focusable elements
func (p *ClaudeOptionsPanel) getFocusCount() int {
if p.isForkMode {
return 3 // skip, chrome, teammate
return 4 // skip, auto, chrome, teammate
}

count := 4 // session mode, skip, chrome, teammate
count := 5 // session mode, skip, auto, chrome, teammate
if p.sessionMode == 2 {
count++ // resume input
}
Expand Down Expand Up @@ -319,8 +333,12 @@ func (p *ClaudeOptionsPanel) viewForkMode(labelStyle, activeStyle, dimStyle, hea
var content string
content += headerStyle.Render("─ Advanced Options ─") + "\n"
content += renderCheckboxLine("Skip permissions", p.skipPermissions, p.focusIndex == 0)
content += renderCheckboxLine("Chrome mode", p.useChrome, p.focusIndex == 1)
content += renderCheckboxLine("Teammate mode", p.useTeammateMode, p.focusIndex == 2)
content += renderCheckboxLine("Auto mode", p.autoMode, p.focusIndex == 1)
if p.autoMode && p.skipPermissions {
content += dimStyle.Render(" ↑ overridden by skip permissions") + "\n"
}
content += renderCheckboxLine("Chrome mode", p.useChrome, p.focusIndex == 2)
content += renderCheckboxLine("Teammate mode", p.useTeammateMode, p.focusIndex == 3)
return content
}

Expand Down Expand Up @@ -355,6 +373,13 @@ func (p *ClaudeOptionsPanel) viewNewMode(labelStyle, activeStyle, dimStyle, head
content += renderCheckboxLine("Skip permissions", p.skipPermissions, p.focusIndex == focusIdx)
focusIdx++

// Auto mode checkbox
content += renderCheckboxLine("Auto mode", p.autoMode, p.focusIndex == focusIdx)
if p.autoMode && p.skipPermissions {
content += dimStyle.Render(" ↑ overridden by skip permissions") + "\n"
}
focusIdx++

// Chrome checkbox
content += renderCheckboxLine("Chrome mode", p.useChrome, p.focusIndex == focusIdx)
focusIdx++
Expand Down
28 changes: 24 additions & 4 deletions internal/ui/setup_wizard.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ type SetupWizard struct {

// Step 2: Claude settings (only if Claude selected)
dangerousMode bool
autoMode bool
useDefaultConfigDir bool
customConfigDir string
configDirInput textinput.Model
claudeSettingsCursor int // 0=dangerous mode, 1=config dir
claudeSettingsCursor int // 0=dangerous mode, 1=auto mode, 2=config dir

// Theme setting
selectedTheme int // 0=dark, 1=light
Expand Down Expand Up @@ -146,6 +147,7 @@ func (w *SetupWizard) GetConfig() *session.UserConfig {
// Set Claude settings
dangerousModeVal := w.dangerousMode
config.Claude.DangerousMode = &dangerousModeVal
config.Claude.AutoMode = w.autoMode
if !w.useDefaultConfigDir && w.customConfigDir != "" {
config.Claude.ConfigDir = w.customConfigDir
}
Expand Down Expand Up @@ -221,7 +223,7 @@ func (w *SetupWizard) Update(msg tea.Msg) (*SetupWizard, tea.Cmd) {
case stepClaudeSettings:
w.claudeSettingsCursor--
if w.claudeSettingsCursor < 0 {
w.claudeSettingsCursor = 1
w.claudeSettingsCursor = 2
}
}
return w, nil
Expand All @@ -231,7 +233,7 @@ func (w *SetupWizard) Update(msg tea.Msg) (*SetupWizard, tea.Cmd) {
case stepToolSelection:
w.selectedTool = (w.selectedTool + 1) % len(w.toolOptions)
case stepClaudeSettings:
w.claudeSettingsCursor = (w.claudeSettingsCursor + 1) % 2
w.claudeSettingsCursor = (w.claudeSettingsCursor + 1) % 3
}
return w, nil

Expand All @@ -241,6 +243,8 @@ func (w *SetupWizard) Update(msg tea.Msg) (*SetupWizard, tea.Cmd) {
case 0:
w.dangerousMode = !w.dangerousMode
case 1:
w.autoMode = !w.autoMode
case 2:
w.useDefaultConfigDir = !w.useDefaultConfigDir
if !w.useDefaultConfigDir {
w.configDirInput.Focus()
Expand Down Expand Up @@ -424,13 +428,29 @@ func (w *SetupWizard) View() string {
content.WriteString(lipgloss.NewStyle().Foreground(ColorTextDim).Render(" Skip permission prompts (--dangerously-skip-permissions)"))
content.WriteString("\n\n")

// Config directory radio buttons
// Auto mode checkbox
checkbox = checkboxOff
if w.autoMode {
checkbox = checkboxOn
}
cursor = " "
style = labelStyle
if w.claudeSettingsCursor == 1 {
cursor = "> "
style = lipgloss.NewStyle().Foreground(ColorAccent).Bold(true)
}
content.WriteString(cursor + checkbox + " " + style.Render("Enable auto mode"))
content.WriteString("\n")
content.WriteString(lipgloss.NewStyle().Foreground(ColorTextDim).Render(" Classifier-based auto-approval (--permission-mode auto)"))
content.WriteString("\n\n")

// Config directory radio buttons
cursor = " "
style = labelStyle
if w.claudeSettingsCursor == 2 {
cursor = "> "
style = lipgloss.NewStyle().Foreground(ColorAccent).Bold(true)
}
content.WriteString(cursor + style.Render("Claude config directory:"))
content.WriteString("\n")

Expand Down
6 changes: 4 additions & 2 deletions skills/agent-deck/references/config-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Claude Code integration settings.
[claude]
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
env_file = "~/.claude.env" # .env file specific to Claude sessions

Expand All @@ -69,8 +70,9 @@ config_dir = "~/.claude-work" # Optional override for profile "work"
|-----|------|---------|-------------|
| `config_dir` | string | `~/.claude` | Claude config directory. Override with `CLAUDE_CONFIG_DIR` env. |
| `profiles.<name>.claude.config_dir` | string | none | Profile-specific Claude config directory. Takes precedence over `[claude].config_dir` when that profile is active. |
| `dangerous_mode` | bool | `false` | Adds `--dangerously-skip-permissions`. Forces bypass on. Takes precedence over `allow_dangerous_mode`. |
| `allow_dangerous_mode` | bool | `false` | Adds `--allow-dangerously-skip-permissions`. Unlocks bypass as an option without activating it. Ignored when `dangerous_mode` is true. |
| `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. |
| `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:
Expand Down