on main help sign in
document, cmds => tools
82e8b47Tai Groot
README.md+22−3
@@ -43,7 +43,7 @@ rm -rf ./crush
43
43
44Then, run Crush by typing `crush`.
44Then, run Crush by typing `crush`.
45
45
46−***
46+---
47
47
48</details>
48</details>
49
49
@@ -108,7 +108,7 @@ Crush supports Model Context Protocol (MCP) servers through three transport type
108 "mcp": {
108 "mcp": {
109 "filesystem": {
109 "filesystem": {
110 "type": "stdio",
110 "type": "stdio",
111− "command": "node",
111+ "command": "node",
112 "args": ["/path/to/mcp-server.js"],
112 "args": ["/path/to/mcp-server.js"],
113 "env": {
113 "env": {
114 "NODE_ENV": "production"
114 "NODE_ENV": "production"
@@ -143,7 +143,7 @@ crush -d
143# View last 1000 lines
143# View last 1000 lines
144crush logs
144crush logs
145
145
146−# Follow logs in real-time
146+# Follow logs in real-time
147crush logs -f
147crush logs -f
148
148
149# Show last 500 lines
149# Show last 500 lines
@@ -161,6 +161,25 @@ Add to your `crush.json` config file:
161}
161}
162```
162```
163
163
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+
164### OpenAI-Compatible APIs
183### OpenAI-Compatible APIs
165
184
166Crush 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.
185Crush 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) {
60 messages := message.NewService(q)
60 messages := message.NewService(q)
61 files := history.NewService(q, conn)
61 files := history.NewService(q, conn)
62 skipPermissionsRequests := cfg.Permissions != nil && cfg.Permissions.SkipRequests
62 skipPermissionsRequests := cfg.Permissions != nil && cfg.Permissions.SkipRequests
63− allowedCommands := []string{}
63+ allowedTools := []string{}
64 if cfg.Permissions != nil && cfg.Permissions.AllowedTools != nil {
64 if cfg.Permissions != nil && cfg.Permissions.AllowedTools != nil {
65− allowedCommands = cfg.Permissions.AllowedTools
65+ allowedTools = cfg.Permissions.AllowedTools
66 }
66 }
67
67
68 app := &App{
68 app := &App{
69 Sessions: sessions,
69 Sessions: sessions,
70 Messages: messages,
70 Messages: messages,
71 History: files,
71 History: files,
72− Permissions: permission.NewPermissionService(cfg.WorkingDir(), skipPermissionsRequests, allowedCommands),
72+ Permissions: permission.NewPermissionService(cfg.WorkingDir(), skipPermissionsRequests, allowedTools),
73 LSPClients: make(map[string]*lsp.Client),
73 LSPClients: make(map[string]*lsp.Client),
74
74
75 globalCtx: ctx,
75 globalCtx: ctx,
internal/permission/permission.go+4−4
@@ -50,7 +50,7 @@ type permissionService struct {
50 autoApproveSessions []string
50 autoApproveSessions []string
51 autoApproveSessionsMu sync.RWMutex
51 autoApproveSessionsMu sync.RWMutex
52 skip bool
52 skip bool
53− allowedCommands []string
53+ allowedTools []string
54}
54}
55
55
56func (s *permissionService) GrantPersistent(permission PermissionRequest) {
56func (s *permissionService) GrantPersistent(permission PermissionRequest) {
@@ -85,7 +85,7 @@ func (s *permissionService) Request(opts CreatePermissionRequest) bool {
85
85
86 // Check if the tool/action combination is in the allowlist
86 // Check if the tool/action combination is in the allowlist
87 commandKey := opts.ToolName + ":" + opts.Action
87 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) {
89 return true
89 return true
90 }
90 }
91
91
@@ -137,12 +137,12 @@ func (s *permissionService) AutoApproveSession(sessionID string) {
137 s.autoApproveSessionsMu.Unlock()
137 s.autoApproveSessionsMu.Unlock()
138}
138}
139
139
140−func NewPermissionService(workingDir string, skip bool, allowedCommands []string) Service {
140+func NewPermissionService(workingDir string, skip bool, allowedTools []string) Service {
141 return &permissionService{
141 return &permissionService{
142 Broker: pubsub.NewBroker[PermissionRequest](),
142 Broker: pubsub.NewBroker[PermissionRequest](),
143 workingDir: workingDir,
143 workingDir: workingDir,
144 sessionPermissions: make([]PermissionRequest, 0),
144 sessionPermissions: make([]PermissionRequest, 0),
145 skip: skip,
145 skip: skip,
146− allowedCommands: allowedCommands,
146+ allowedTools: allowedTools,
147 }
147 }
148}
148}
internal/permission/permission_test.go+33−33
@@ -6,52 +6,52 @@ import (
6
6
7func TestPermissionService_AllowedCommands(t *testing.T) {
7func TestPermissionService_AllowedCommands(t *testing.T) {
8 tests := []struct {
8 tests := []struct {
9− name string
9+ name string
10− allowedCommands []string
10+ allowedTools []string
11− toolName string
11+ toolName string
12− action string
12+ action string
13− expected bool
13+ expected bool
14 }{
14 }{
15 {
15 {
16− name: "tool in allowlist",
16+ name: "tool in allowlist",
17− allowedCommands: []string{"bash", "view"},
17+ allowedTools: []string{"bash", "view"},
18− toolName: "bash",
18+ toolName: "bash",
19− action: "execute",
19+ action: "execute",
20− expected: true,
20+ expected: true,
21 },
21 },
22 {
22 {
23− name: "tool:action in allowlist",
23+ name: "tool:action in allowlist",
24− allowedCommands: []string{"bash:execute", "edit:create"},
24+ allowedTools: []string{"bash:execute", "edit:create"},
25− toolName: "bash",
25+ toolName: "bash",
26− action: "execute",
26+ action: "execute",
27− expected: true,
27+ expected: true,
28 },
28 },
29 {
29 {
30− name: "tool not in allowlist",
30+ name: "tool not in allowlist",
31− allowedCommands: []string{"view", "ls"},
31+ allowedTools: []string{"view", "ls"},
32− toolName: "bash",
32+ toolName: "bash",
33− action: "execute",
33+ action: "execute",
34− expected: false,
34+ expected: false,
35 },
35 },
36 {
36 {
37− name: "tool:action not in allowlist",
37+ name: "tool:action not in allowlist",
38− allowedCommands: []string{"bash:read", "edit:create"},
38+ allowedTools: []string{"bash:read", "edit:create"},
39− toolName: "bash",
39+ toolName: "bash",
40− action: "execute",
40+ action: "execute",
41− expected: false,
41+ expected: false,
42 },
42 },
43 {
43 {
44− name: "empty allowlist",
44+ name: "empty allowlist",
45− allowedCommands: []string{},
45+ allowedTools: []string{},
46− toolName: "bash",
46+ toolName: "bash",
47− action: "execute",
47+ action: "execute",
48− expected: false,
48+ expected: false,
49 },
49 },
50 }
50 }
51
51
52 for _, tt := range tests {
52 for _, tt := range tests {
53 t.Run(tt.name, func(t *testing.T) {
53 t.Run(tt.name, func(t *testing.T) {
54− service := NewPermissionService("/tmp", false, tt.allowedCommands)
54+ service := NewPermissionService("/tmp", false, tt.allowedTools)
55
55
56 // Create a channel to capture the permission request
56 // Create a channel to capture the permission request
57 // Since we're testing the allowlist logic, we need to simulate the request
57 // Since we're testing the allowlist logic, we need to simulate the request
@@ -60,7 +60,7 @@ func TestPermissionService_AllowedCommands(t *testing.T) {
60 // Test the allowlist logic directly
60 // Test the allowlist logic directly
61 commandKey := tt.toolName + ":" + tt.action
61 commandKey := tt.toolName + ":" + tt.action
62 allowed := false
62 allowed := false
63− for _, cmd := range ps.allowedCommands {
63+ for _, cmd := range ps.allowedTools {
64 if cmd == commandKey || cmd == tt.toolName {
64 if cmd == commandKey || cmd == tt.toolName {
65 allowed = true
65 allowed = true
66 break
66 break
@@ -69,7 +69,7 @@ func TestPermissionService_AllowedCommands(t *testing.T) {
69
69
70 if allowed != tt.expected {
70 if allowed != tt.expected {
71 t.Errorf("expected %v, got %v for tool %s action %s with allowlist %v",
71 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)
73 }
73 }
74 })
74 })
75 }
75 }