From 82e8b47985ca77054e4afd79a8b4b54c039d4c99 Mon Sep 17 00:00:00 2001 From: Tai Groot Date: Thu, 24 Jul 2025 00:22:03 -0700 Subject: [PATCH] document, cmds => tools --- README.md | 25 ++++++++-- internal/app/app.go | 6 +-- internal/permission/permission.go | 8 ++-- internal/permission/permission_test.go | 66 +++++++++++++------------- 4 files changed, 62 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index b21dae25fb7f32169e0fdd9528b3ec06f5c739f0..70075a97b2448b0c07944fcce397b2012e071647 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ rm -rf ./crush Then, run Crush by typing `crush`. -*** +--- @@ -108,7 +108,7 @@ Crush supports Model Context Protocol (MCP) servers through three transport type "mcp": { "filesystem": { "type": "stdio", - "command": "node", + "command": "node", "args": ["/path/to/mcp-server.js"], "env": { "NODE_ENV": "production" @@ -143,7 +143,7 @@ crush -d # View last 1000 lines crush logs -# Follow logs in real-time +# Follow logs in real-time crush logs -f # Show last 500 lines @@ -161,6 +161,25 @@ Add to your `crush.json` config file: } ``` +### Configurable Default Permissions + +Crush includes a permission system to control which tools can be executed without prompting. You can configure allowed tools in your `crush.json` config file: + +```json +{ + "permissions": { + "allowed_tools": ["view", "ls", "grep", "bash:read"] + } +} +``` + +The `allowed_tools` array accepts: + +- Tool names (e.g., `"view"`) - allows all actions for that tool +- Tool:action combinations (e.g., `"bash:read"`) - allows only specific actions + +You can also skip all permission prompts entirely by running Crush with the `--yolo` flag. + ### OpenAI-Compatible APIs Crush supports all OpenAI-compatible APIs. Here's an example configuration for Deepseek, which uses an OpenAI-compatible API. Don't forget to set `DEEPSEEK_API_KEY` in your environment. diff --git a/internal/app/app.go b/internal/app/app.go index b798dd789daba8fe7f227a3435293674ba4bff85..a42a994b7b78b32675fcfaedfed3153f0a7826b1 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -60,16 +60,16 @@ func New(ctx context.Context, conn *sql.DB, cfg *config.Config) (*App, error) { messages := message.NewService(q) files := history.NewService(q, conn) skipPermissionsRequests := cfg.Permissions != nil && cfg.Permissions.SkipRequests - allowedCommands := []string{} + allowedTools := []string{} if cfg.Permissions != nil && cfg.Permissions.AllowedTools != nil { - allowedCommands = cfg.Permissions.AllowedTools + allowedTools = cfg.Permissions.AllowedTools } app := &App{ Sessions: sessions, Messages: messages, History: files, - Permissions: permission.NewPermissionService(cfg.WorkingDir(), skipPermissionsRequests, allowedCommands), + Permissions: permission.NewPermissionService(cfg.WorkingDir(), skipPermissionsRequests, allowedTools), LSPClients: make(map[string]*lsp.Client), globalCtx: ctx, diff --git a/internal/permission/permission.go b/internal/permission/permission.go index a016bb9d5d76cfd32539c34f55d57317f6ecf1dd..cd149a49890b54086bd52e562eed0d44f00c407e 100644 --- a/internal/permission/permission.go +++ b/internal/permission/permission.go @@ -50,7 +50,7 @@ type permissionService struct { autoApproveSessions []string autoApproveSessionsMu sync.RWMutex skip bool - allowedCommands []string + allowedTools []string } func (s *permissionService) GrantPersistent(permission PermissionRequest) { @@ -85,7 +85,7 @@ func (s *permissionService) Request(opts CreatePermissionRequest) bool { // Check if the tool/action combination is in the allowlist commandKey := opts.ToolName + ":" + opts.Action - if slices.Contains(s.allowedCommands, commandKey) || slices.Contains(s.allowedCommands, opts.ToolName) { + if slices.Contains(s.allowedTools, commandKey) || slices.Contains(s.allowedTools, opts.ToolName) { return true } @@ -137,12 +137,12 @@ func (s *permissionService) AutoApproveSession(sessionID string) { s.autoApproveSessionsMu.Unlock() } -func NewPermissionService(workingDir string, skip bool, allowedCommands []string) Service { +func NewPermissionService(workingDir string, skip bool, allowedTools []string) Service { return &permissionService{ Broker: pubsub.NewBroker[PermissionRequest](), workingDir: workingDir, sessionPermissions: make([]PermissionRequest, 0), skip: skip, - allowedCommands: allowedCommands, + allowedTools: allowedTools, } } diff --git a/internal/permission/permission_test.go b/internal/permission/permission_test.go index 40b03d10298d3e948aed5379ad7ce84ae4e0d8b8..5d10fbd240da6a171e345938cb3382a7f7fcf19b 100644 --- a/internal/permission/permission_test.go +++ b/internal/permission/permission_test.go @@ -6,52 +6,52 @@ import ( func TestPermissionService_AllowedCommands(t *testing.T) { tests := []struct { - name string - allowedCommands []string - toolName string - action string - expected bool + name string + allowedTools []string + toolName string + action string + expected bool }{ { - name: "tool in allowlist", - allowedCommands: []string{"bash", "view"}, - toolName: "bash", - action: "execute", - expected: true, + name: "tool in allowlist", + allowedTools: []string{"bash", "view"}, + toolName: "bash", + action: "execute", + expected: true, }, { - name: "tool:action in allowlist", - allowedCommands: []string{"bash:execute", "edit:create"}, - toolName: "bash", - action: "execute", - expected: true, + name: "tool:action in allowlist", + allowedTools: []string{"bash:execute", "edit:create"}, + toolName: "bash", + action: "execute", + expected: true, }, { - name: "tool not in allowlist", - allowedCommands: []string{"view", "ls"}, - toolName: "bash", - action: "execute", - expected: false, + name: "tool not in allowlist", + allowedTools: []string{"view", "ls"}, + toolName: "bash", + action: "execute", + expected: false, }, { - name: "tool:action not in allowlist", - allowedCommands: []string{"bash:read", "edit:create"}, - toolName: "bash", - action: "execute", - expected: false, + name: "tool:action not in allowlist", + allowedTools: []string{"bash:read", "edit:create"}, + toolName: "bash", + action: "execute", + expected: false, }, { - name: "empty allowlist", - allowedCommands: []string{}, - toolName: "bash", - action: "execute", - expected: false, + name: "empty allowlist", + allowedTools: []string{}, + toolName: "bash", + action: "execute", + expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - service := NewPermissionService("/tmp", false, tt.allowedCommands) + service := NewPermissionService("/tmp", false, tt.allowedTools) // Create a channel to capture the permission request // Since we're testing the allowlist logic, we need to simulate the request @@ -60,7 +60,7 @@ func TestPermissionService_AllowedCommands(t *testing.T) { // Test the allowlist logic directly commandKey := tt.toolName + ":" + tt.action allowed := false - for _, cmd := range ps.allowedCommands { + for _, cmd := range ps.allowedTools { if cmd == commandKey || cmd == tt.toolName { allowed = true break @@ -69,7 +69,7 @@ func TestPermissionService_AllowedCommands(t *testing.T) { if allowed != tt.expected { t.Errorf("expected %v, got %v for tool %s action %s with allowlist %v", - tt.expected, allowed, tt.toolName, tt.action, tt.allowedCommands) + tt.expected, allowed, tt.toolName, tt.action, tt.allowedTools) } }) }