Skip to content
Merged
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
27 changes: 27 additions & 0 deletions cmd/container-use/agent/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,30 @@ func tools(prefix string) []string {
}
return tools
}

// writeMcpConfig handles the common read-update-write cycle for agent MCP
// configuration files. It creates the parent directory if needed, reads and
// unmarshals any existing config, applies the update, and writes the result
// back to disk.
func writeMcpConfig[T any](path string, unmarshal func([]byte, *T) error, update func(T) ([]byte, error)) error {
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return fmt.Errorf("failed to create config directory: %w", err)
}

var cfg T
if data, err := os.ReadFile(path); err == nil {
if err := unmarshal(data, &cfg); err != nil {
return fmt.Errorf("failed to parse existing config: %w", err)
}
}

data, err := update(cfg)
if err != nil {
return err
}

if err := os.WriteFile(path, data, 0600); err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
return nil
}
37 changes: 5 additions & 32 deletions cmd/container-use/agent/configure_claude.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package agent
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
Expand Down Expand Up @@ -42,62 +41,36 @@ func (c *ConfigureClaude) description() string {
}

func (c *ConfigureClaude) editMcpConfig() error {
// Remove existing MCP server (ignore errors if it doesn't exist)
removeCmd := exec.Command("claude", "mcp", "remove", "container-use")
_ = removeCmd.Run() // Ignore error - server might not exist
_ = removeCmd.Run()

// Add MCP server
cmd := exec.Command("claude", "mcp", "add", "container-use", "--", ContainerUseBinary, "stdio")
err := cmd.Run()
if err != nil {
if err := cmd.Run(); err != nil {
return fmt.Errorf("could not automatically add MCP server: %w", err)
}

// Configure auto approve settings
configPath := filepath.Join(".claude", "settings.local.json")
// Create directory if it doesn't exist
if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
return fmt.Errorf("failed to create config directory: %w", err)
}
var config ClaudeSettingsLocal
if data, err := os.ReadFile(configPath); err == nil {
if err := json.Unmarshal(data, &config); err != nil {
return fmt.Errorf("failed to parse existing config: %w", err)
}
}

data, err := c.updateSettingsLocal(config)
if err != nil {
return err
}

err = os.WriteFile(configPath, data, 0600)
if err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
return nil
return writeMcpConfig(configPath, func(data []byte, cfg *ClaudeSettingsLocal) error {
return json.Unmarshal(data, cfg)
}, c.updateSettingsLocal)
}

func (c *ConfigureClaude) updateSettingsLocal(config ClaudeSettingsLocal) ([]byte, error) {
// Initialize permissions map if nil
if config.Permissions == nil {
config.Permissions = &ClaudePermissions{Allow: []string{}}
}

// remove save non-container-use items from allow
allows := []string{}
for _, tool := range config.Permissions.Allow {
if !strings.HasPrefix(tool, "mcp__container-use") {
allows = append(allows, tool)
}
}

// Add container-use tools to allow
tools := tools("mcp__container-use__")
allows = append(allows, tools...)
config.Permissions.Allow = allows

// Write config back
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to marshal config: %w", err)
Expand Down
38 changes: 6 additions & 32 deletions cmd/container-use/agent/configure_codex.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package agent

import (
"fmt"
"os"
"os/exec"
"path/filepath"

Expand All @@ -23,52 +22,30 @@ func NewConfigureCodex() *ConfigureCodex {
}
}

// Return the agents full name
func (a *ConfigureCodex) name() string {
return a.Name
}

// Return a description of the agent
func (a *ConfigureCodex) description() string {
return a.Description
}

// Save the MCP config with container-use enabled
func (a *ConfigureCodex) editMcpConfig() error {
configPath, err := homedir.Expand(filepath.Join("~", ".codex", "config.toml"))
if err != nil {
return err
}

// Create directory if it doesn't exist
if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
return fmt.Errorf("failed to create config directory: %w", err)
}
return writeMcpConfig(configPath, func(data []byte, cfg *map[string]any) error {
return toml.Unmarshal(data, cfg)
}, a.updateCodexConfig)
}

// Read existing config or create new
var config map[string]any
if data, err := os.ReadFile(configPath); err == nil {
if err := toml.Unmarshal(data, &config); err != nil {
return fmt.Errorf("failed to parse existing config: %w", err)
}
} else {
func (a *ConfigureCodex) updateCodexConfig(config map[string]any) ([]byte, error) {
if config == nil {
config = make(map[string]any)
}

data, err := a.updateCodexConfig(config)
if err != nil {
return err
}

err = os.WriteFile(configPath, data, 0600)
if err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
return nil
}

func (a *ConfigureCodex) updateCodexConfig(config map[string]any) ([]byte, error) {
// Get mcp_servers map
var mcpServers map[string]any
if servers, ok := config["mcp_servers"]; ok {
mcpServers = servers.(map[string]any)
Expand All @@ -77,22 +54,19 @@ func (a *ConfigureCodex) updateCodexConfig(config map[string]any) ([]byte, error
config["mcp_servers"] = mcpServers
}

// Add container-use server
mcpServers["container-use"] = map[string]any{
"command": ContainerUseBinary,
"args": []any{"stdio"},
"auto_approve": tools(""),
}

// Write config back
data, err := toml.Marshal(&config)
if err != nil {
return nil, fmt.Errorf("failed to marshal config: %w", err)
}
return data, nil
}

// Save the agent rules with the container-use prompt
func (a *ConfigureCodex) editRules() error {
agentsFile := "AGENTS.md"
return saveRulesFile(agentsFile, rules.AgentRules)
Expand Down
35 changes: 3 additions & 32 deletions cmd/container-use/agent/configure_cursor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package agent
import (
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/dagger/container-use/rules"
Expand All @@ -21,43 +20,18 @@ func NewConfigureCursor() *ConfigureCursor {
}
}

// Return the agents full name
func (a *ConfigureCursor) name() string {
return a.Name
}

// Return a description of the agent
func (a *ConfigureCursor) description() string {
return a.Description
}

// Save the MCP config with container-use enabled
func (a *ConfigureCursor) editMcpConfig() error {
configPath := filepath.Join(".cursor", "mcp.json")

// Create directory if it doesn't exist
if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
return fmt.Errorf("failed to create config directory: %w", err)
}

// Read existing config or create new
var config MCPServersConfig
if data, err := os.ReadFile(configPath); err == nil {
if err := json.Unmarshal(data, &config); err != nil {
return fmt.Errorf("failed to parse existing config: %w", err)
}
}

data, err := a.updateMcpConfig(config)
if err != nil {
return err
}

err = os.WriteFile(configPath, data, 0600)
if err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
return nil
return writeMcpConfig(filepath.Join(".cursor", "mcp.json"), func(data []byte, cfg *MCPServersConfig) error {
return json.Unmarshal(data, cfg)
}, a.updateMcpConfig)
}

func (a *ConfigureCursor) updateMcpConfig(config MCPServersConfig) ([]byte, error) {
Expand All @@ -66,21 +40,18 @@ func (a *ConfigureCursor) updateMcpConfig(config MCPServersConfig) ([]byte, erro
config.MCPServers = make(map[string]MCPServer)
}

// Add container-use server
config.MCPServers["container-use"] = MCPServer{
Command: ContainerUseBinary,
Args: []string{"stdio"},
}

// Write config back
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to marshal config: %w", err)
}
return data, nil
}

// Save the agent rules with the container-use prompt
func (a *ConfigureCursor) editRules() error {
rulesFile := filepath.Join(".cursor", "rules", "container-use.mdc")
return saveRulesFile(rulesFile, rules.CursorRules)
Expand Down
55 changes: 14 additions & 41 deletions cmd/container-use/agent/configure_goose.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,65 +24,41 @@ func NewConfigureGoose() *ConfigureGoose {
}
}

// Return the agents full name
func (a *ConfigureGoose) name() string {
return a.Name
}

// Return a description of the agent
func (a *ConfigureGoose) description() string {
return a.Description
}

// Save the MCP config with container-use enabled
func (a *ConfigureGoose) editMcpConfig() error {
var configPath string
var err error

func gooseConfigPath() (string, error) {
if runtime.GOOS == "windows" {
// Windows: %APPDATA%\Block\goose\config\config.yaml
// Reference: https://block.github.io/goose/docs/guides/config-file
appData := os.Getenv("APPDATA")
if appData == "" {
return fmt.Errorf("APPDATA environment variable not set")
}
configPath = filepath.Join(appData, "Block", "goose", "config", "config.yaml")
} else {
// macOS/Linux: ~/.config/goose/config.yaml
configPath, err = homedir.Expand(filepath.Join("~", ".config", "goose", "config.yaml"))
if err != nil {
return err
return "", fmt.Errorf("APPDATA environment variable not set")
}
return filepath.Join(appData, "Block", "goose", "config", "config.yaml"), nil
}
return homedir.Expand(filepath.Join("~", ".config", "goose", "config.yaml"))
}

// Create directory if it doesn't exist
if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
return fmt.Errorf("failed to create config directory: %w", err)
}

// Read existing config or create new
var config map[string]any
if data, err := os.ReadFile(configPath); err == nil {
if err := yaml.Unmarshal(data, &config); err != nil {
return fmt.Errorf("failed to parse existing config: %w", err)
}
} else {
config = make(map[string]any)
}

data, err := a.updateGooseConfig(config)
func (a *ConfigureGoose) editMcpConfig() error {
configPath, err := gooseConfigPath()
if err != nil {
return err
}

if err := os.WriteFile(configPath, data, 0600); err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
return nil
return writeMcpConfig(configPath, func(data []byte, cfg *map[string]any) error {
return yaml.Unmarshal(data, cfg)
}, a.updateGooseConfig)
}

func (a *ConfigureGoose) updateGooseConfig(config map[string]any) ([]byte, error) {
// Get extensions map
if config == nil {
config = make(map[string]any)
}

var extensions map[string]any
if ext, ok := config["extensions"]; ok {
extensions = ext.(map[string]any)
Expand All @@ -91,7 +67,6 @@ func (a *ConfigureGoose) updateGooseConfig(config map[string]any) ([]byte, error
config["extensions"] = extensions
}

// Add container-use extension
extensions["container-use"] = map[string]any{
"name": "container-use",
"type": "stdio",
Expand All @@ -101,15 +76,13 @@ func (a *ConfigureGoose) updateGooseConfig(config map[string]any) ([]byte, error
"envs": map[string]any{},
}

// Write config back
data, err := yaml.Marshal(&config)
if err != nil {
return nil, fmt.Errorf("failed to marshal config: %w", err)
}
return data, nil
}

// Save the agent rules with the container-use prompt
func (a *ConfigureGoose) editRules() error {
return saveRulesFile(".goosehints", rules.AgentRules)
}
Expand Down
Loading