on main help sign in
document, cmds => tools
commit82e8b47985ca77054e4afd79a8b4b54c039d4c99 authorTai Groot <tai@taigrr.com> dateThu 24 Jul 2025, 00:22 (1 year ago) parentd4575e3
4 files changed+62−43
README.md+22−3 internal/app/app.go+3−3 internal/permission/permission.go+4−4 internal/permission/permission_test.go+33−33
README.md+22−3
@@ -43,7 +43,7 @@ rm -rf ./crush
4343
4444Then, run Crush by typing `crush`.
4545
46−***
46+---
4747
4848</details>
4949
@@ -108,7 +108,7 @@ Crush supports Model Context Protocol (MCP) servers through three transport type
108108 "mcp": {
109109 "filesystem": {
110110 "type": "stdio",
111− "command": "node",
111+ "command": "node",
112112 "args": ["/path/to/mcp-server.js"],
113113 "env": {
114114 "NODE_ENV": "production"
@@ -143,7 +143,7 @@ crush -d
143143# View last 1000 lines
144144crush logs
145145
146−# Follow logs in real-time
146+# Follow logs in real-time
147147crush logs -f
148148
149149# Show last 500 lines
@@ -161,6 +161,25 @@ Add to your `crush.json` config file:
161161}
162162```
163163
164+### Configurable Default Permissions
165+
166+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:
167+
168+```json
169+{
170+ "permissions": {
171+ "allowed_tools": ["view", "ls", "grep", "bash:read"]
172+ }
173+}
174+```
175+
176+The `allowed_tools` array accepts:
177+
178+- Tool names (e.g., `"view"`) - allows all actions for that tool
179+- Tool:action combinations (e.g., `"bash:read"`) - allows only specific actions
180+
181+You can also skip all permission prompts entirely by running Crush with the `--yolo` flag.
182+
164183### OpenAI-Compatible APIs
165184
166185Crush 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.
internal/app/app.go+3−3
@@ -60,16 +60,16 @@ func New(ctx context.Context, conn *sql.DB, cfg *config.Config) (*App, error) {
6060 messages := message.NewService(q)
6161 files := history.NewService(q, conn)
6262 skipPermissionsRequests := cfg.Permissions != nil && cfg.Permissions.SkipRequests
63− allowedCommands := []string{}
63+ allowedTools := []string{}
6464 if cfg.Permissions != nil && cfg.Permissions.AllowedTools != nil {
65− allowedCommands = cfg.Permissions.AllowedTools
65+ allowedTools = cfg.Permissions.AllowedTools
6666 }
6767
6868 app := &App{
6969 Sessions: sessions,
7070 Messages: messages,
7171 History: files,
72− Permissions: permission.NewPermissionService(cfg.WorkingDir(), skipPermissionsRequests, allowedCommands),
72+ Permissions: permission.NewPermissionService(cfg.WorkingDir(), skipPermissionsRequests, allowedTools),
7373 LSPClients: make(map[string]*lsp.Client),
7474
7575 globalCtx: ctx,
internal/permission/permission.go+4−4
@@ -50,7 +50,7 @@ type permissionService struct {
5050 autoApproveSessions []string
5151 autoApproveSessionsMu sync.RWMutex
5252 skip bool
53− allowedCommands []string
53+ allowedTools []string
5454}
5555
5656func (s *permissionService) GrantPersistent(permission PermissionRequest) {
@@ -85,7 +85,7 @@ func (s *permissionService) Request(opts CreatePermissionRequest) bool {
8585
8686 // Check if the tool/action combination is in the allowlist
8787 commandKey := opts.ToolName + ":" + opts.Action
88− if slices.Contains(s.allowedCommands, commandKey) || slices.Contains(s.allowedCommands, opts.ToolName) {
88+ if slices.Contains(s.allowedTools, commandKey) || slices.Contains(s.allowedTools, opts.ToolName) {
8989 return true
9090 }
9191
@@ -137,12 +137,12 @@ func (s *permissionService) AutoApproveSession(sessionID string) {
137137 s.autoApproveSessionsMu.Unlock()
138138}
139139
140−func NewPermissionService(workingDir string, skip bool, allowedCommands []string) Service {
140+func NewPermissionService(workingDir string, skip bool, allowedTools []string) Service {
141141 return &permissionService{
142142 Broker: pubsub.NewBroker[PermissionRequest](),
143143 workingDir: workingDir,
144144 sessionPermissions: make([]PermissionRequest, 0),
145145 skip: skip,
146− allowedCommands: allowedCommands,
146+ allowedTools: allowedTools,
147147 }
148148}
internal/permission/permission_test.go+33−33
@@ -6,52 +6,52 @@ import (
66
77func TestPermissionService_AllowedCommands(t *testing.T) {
88 tests := []struct {
9− name string
10− allowedCommands []string
11− toolName string
12− action string
13− expected bool
9+ name string
10+ allowedTools []string
11+ toolName string
12+ action string
13+ expected bool
1414 }{
1515 {
16− name: "tool in allowlist",
17− allowedCommands: []string{"bash", "view"},
18− toolName: "bash",
19− action: "execute",
20− expected: true,
16+ name: "tool in allowlist",
17+ allowedTools: []string{"bash", "view"},
18+ toolName: "bash",
19+ action: "execute",
20+ expected: true,
2121 },
2222 {
23− name: "tool:action in allowlist",
24− allowedCommands: []string{"bash:execute", "edit:create"},
25− toolName: "bash",
26− action: "execute",
27− expected: true,
23+ name: "tool:action in allowlist",
24+ allowedTools: []string{"bash:execute", "edit:create"},
25+ toolName: "bash",
26+ action: "execute",
27+ expected: true,
2828 },
2929 {
30− name: "tool not in allowlist",
31− allowedCommands: []string{"view", "ls"},
32− toolName: "bash",
33− action: "execute",
34− expected: false,
30+ name: "tool not in allowlist",
31+ allowedTools: []string{"view", "ls"},
32+ toolName: "bash",
33+ action: "execute",
34+ expected: false,
3535 },
3636 {
37− name: "tool:action not in allowlist",
38− allowedCommands: []string{"bash:read", "edit:create"},
39− toolName: "bash",
40− action: "execute",
41− expected: false,
37+ name: "tool:action not in allowlist",
38+ allowedTools: []string{"bash:read", "edit:create"},
39+ toolName: "bash",
40+ action: "execute",
41+ expected: false,
4242 },
4343 {
44− name: "empty allowlist",
45− allowedCommands: []string{},
46− toolName: "bash",
47− action: "execute",
48− expected: false,
44+ name: "empty allowlist",
45+ allowedTools: []string{},
46+ toolName: "bash",
47+ action: "execute",
48+ expected: false,
4949 },
5050 }
5151
5252 for _, tt := range tests {
5353 t.Run(tt.name, func(t *testing.T) {
54− service := NewPermissionService("/tmp", false, tt.allowedCommands)
54+ service := NewPermissionService("/tmp", false, tt.allowedTools)
5555
5656 // Create a channel to capture the permission request
5757 // Since we're testing the allowlist logic, we need to simulate the request
@@ -60,7 +60,7 @@ func TestPermissionService_AllowedCommands(t *testing.T) {
6060 // Test the allowlist logic directly
6161 commandKey := tt.toolName + ":" + tt.action
6262 allowed := false
63− for _, cmd := range ps.allowedCommands {
63+ for _, cmd := range ps.allowedTools {
6464 if cmd == commandKey || cmd == tt.toolName {
6565 allowed = true
6666 break
@@ -69,7 +69,7 @@ func TestPermissionService_AllowedCommands(t *testing.T) {
6969
7070 if allowed != tt.expected {
7171 t.Errorf("expected %v, got %v for tool %s action %s with allowlist %v",
72− tt.expected, allowed, tt.toolName, tt.action, tt.allowedCommands)
72+ tt.expected, allowed, tt.toolName, tt.action, tt.allowedTools)
7373 }
7474 })
7575 }