on main help sign in
Merge branch 'main' into ui
4dd11b3Ayman Bagabas
README.md+22
@@ -275,6 +275,7 @@ using `$(echo $VAR)` syntax.
275 "args": ["/path/to/mcp-server.js"],
275 "args": ["/path/to/mcp-server.js"],
276 "timeout": 120,
276 "timeout": 120,
277 "disabled": false,
277 "disabled": false,
278+ "disabled_tools": ["some-tool-name"],
278 "env": {
279 "env": {
279 "NODE_ENV": "production"
280 "NODE_ENV": "production"
280 }
281 }
@@ -284,6 +285,7 @@ using `$(echo $VAR)` syntax.
284 "url": "https://api.githubcopilot.com/mcp/",
285 "url": "https://api.githubcopilot.com/mcp/",
285 "timeout": 120,
286 "timeout": 120,
286 "disabled": false,
287 "disabled": false,
288+ "disabled_tools": ["create_issue", "create_pull_request"],
287 "headers": {
289 "headers": {
288 "Authorization": "Bearer $GH_PAT"
290 "Authorization": "Bearer $GH_PAT"
289 }
291 }
@@ -335,6 +337,26 @@ permissions. Use this with care.
335You can also skip all permission prompts entirely by running Crush with the
337You can also skip all permission prompts entirely by running Crush with the
336`--yolo` flag. Be very, very careful with this feature.
338`--yolo` flag. Be very, very careful with this feature.
337
339
340+### Disabling Built-In Tools
341+
342+If you'd like to prevent Crush from using certain built-in tools entirely, you
343+can disable them via the `options.disabled_tools` list. Disabled tools are
344+completely hidden from the agent.
345+
346+```json
347+{
348+ "$schema": "https://charm.land/crush.json",
349+ "options": {
350+ "disabled_tools": [
351+ "bash",
352+ "sourcegraph"
353+ ]
354+ }
355+}
356+```
357+
358+To disable tools from MCP servers, see the [MCP config section](#mcps).
359+
338### Initialization
360### Initialization
339
361
340When you initialize a project, Crush analyzes your codebase and creates
362When you initialize a project, Crush analyzes your codebase and creates
go.mod+2−2
@@ -6,7 +6,7 @@ require (
6 charm.land/bubbles/v2 v2.0.0-rc.1
6 charm.land/bubbles/v2 v2.0.0-rc.1
7 charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251202162339-5fa38b798f16
7 charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251202162339-5fa38b798f16
8 charm.land/fantasy v0.5.1
8 charm.land/fantasy v0.5.1
9− charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251119143523-0334bb4562ca
9+ charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251205162909-7869489d8971
10 charm.land/x/vcr v0.1.1
10 charm.land/x/vcr v0.1.1
11 github.com/JohannesKaufmann/html-to-markdown v1.6.0
11 github.com/JohannesKaufmann/html-to-markdown v1.6.0
12 github.com/MakeNowJust/heredoc v1.0.0
12 github.com/MakeNowJust/heredoc v1.0.0
@@ -21,7 +21,7 @@ require (
21 github.com/charmbracelet/fang v0.4.4
21 github.com/charmbracelet/fang v0.4.4
22 github.com/charmbracelet/glamour/v2 v2.0.0-20251106195642-800eb8175930
22 github.com/charmbracelet/glamour/v2 v2.0.0-20251106195642-800eb8175930
23 github.com/charmbracelet/log/v2 v2.0.0-20251106192421-eb64aaa963a0
23 github.com/charmbracelet/log/v2 v2.0.0-20251106192421-eb64aaa963a0
24− github.com/charmbracelet/ultraviolet v0.0.0-20251202162030-ecc8c1ae4b2b
24+ github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318
25 github.com/charmbracelet/x/ansi v0.11.2
25 github.com/charmbracelet/x/ansi v0.11.2
26 github.com/charmbracelet/x/exp/charmtone v0.0.0-20250708181618-a60a724ba6c3
26 github.com/charmbracelet/x/exp/charmtone v0.0.0-20250708181618-a60a724ba6c3
27 github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f
27 github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f
go.sum+4
@@ -6,6 +6,8 @@ charm.land/fantasy v0.5.1 h1:Svi/UpI4/DwVjTqNYceDXoJJYn6SVEM5dnLH92UBiEs=
6charm.land/fantasy v0.5.1/go.mod h1:SPOsnIlkBKnhw2Wnasv+wZ82EmCMIGesx0je3tgR6+M=
6charm.land/fantasy v0.5.1/go.mod h1:SPOsnIlkBKnhw2Wnasv+wZ82EmCMIGesx0je3tgR6+M=
7charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251119143523-0334bb4562ca h1:6bVc8OFotCS4sS7HKqxTudP7yn8Y0ODR6df2pdlY/+s=
7charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251119143523-0334bb4562ca h1:6bVc8OFotCS4sS7HKqxTudP7yn8Y0ODR6df2pdlY/+s=
8charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251119143523-0334bb4562ca/go.mod h1:XSJjv7DaH4zd1Y27kZis295RkEj9OFR9zh2WffQQsKQ=
8charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251119143523-0334bb4562ca/go.mod h1:XSJjv7DaH4zd1Y27kZis295RkEj9OFR9zh2WffQQsKQ=
9+charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251205162909-7869489d8971 h1:xZFcNsJMiIDbFtWRyDmkKNk1sjojfaom4Zoe0cyH/8c=
10+charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251205162909-7869489d8971/go.mod h1:i61Y3FmdbcBNSKa+pKB3DaE4uVQmBLMs/xlvRyHcXAE=
9charm.land/x/vcr v0.1.1 h1:PXCFMUG0rPtyk35rhfzYCJEduOzWXCIbrXTFq4OF/9Q=
11charm.land/x/vcr v0.1.1 h1:PXCFMUG0rPtyk35rhfzYCJEduOzWXCIbrXTFq4OF/9Q=
10charm.land/x/vcr v0.1.1/go.mod h1:eByq2gqzWvcct/8XE2XO5KznoWEBiXH56+y2gphbltM=
12charm.land/x/vcr v0.1.1/go.mod h1:eByq2gqzWvcct/8XE2XO5KznoWEBiXH56+y2gphbltM=
11cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
13cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
@@ -100,6 +102,8 @@ github.com/charmbracelet/log/v2 v2.0.0-20251106192421-eb64aaa963a0 h1:lxHzxsHd4P
100github.com/charmbracelet/log/v2 v2.0.0-20251106192421-eb64aaa963a0/go.mod h1:Q7oMtlboDPnnrYiJDXNwdWmJblOmuOnycPKczlVju6I=
102github.com/charmbracelet/log/v2 v2.0.0-20251106192421-eb64aaa963a0/go.mod h1:Q7oMtlboDPnnrYiJDXNwdWmJblOmuOnycPKczlVju6I=
101github.com/charmbracelet/ultraviolet v0.0.0-20251202162030-ecc8c1ae4b2b h1:jY1J0PcfetoB1uJ+w8rd86gUFSpKpJJI35gnfpKF5hg=
103github.com/charmbracelet/ultraviolet v0.0.0-20251202162030-ecc8c1ae4b2b h1:jY1J0PcfetoB1uJ+w8rd86gUFSpKpJJI35gnfpKF5hg=
102github.com/charmbracelet/ultraviolet v0.0.0-20251202162030-ecc8c1ae4b2b/go.mod h1:Y6kE2GzHfkyQQVCSL9r2hwokSrIlHGzZG+71+wDYSZI=
104github.com/charmbracelet/ultraviolet v0.0.0-20251202162030-ecc8c1ae4b2b/go.mod h1:Y6kE2GzHfkyQQVCSL9r2hwokSrIlHGzZG+71+wDYSZI=
105+github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 h1:OqDqxQZliC7C8adA7KjelW3OjtAxREfeHkNcd66wpeI=
106+github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318/go.mod h1:Y6kE2GzHfkyQQVCSL9r2hwokSrIlHGzZG+71+wDYSZI=
103github.com/charmbracelet/x/ansi v0.11.2 h1:XAG3FSjiVtFvgEgGrNBkCNNYrsucAt8c6bfxHyROLLs=
107github.com/charmbracelet/x/ansi v0.11.2 h1:XAG3FSjiVtFvgEgGrNBkCNNYrsucAt8c6bfxHyROLLs=
104github.com/charmbracelet/x/ansi v0.11.2/go.mod h1:9tY2bzX5SiJCU0iWyskjBeI2BRQfvPqI+J760Mjf+Rg=
108github.com/charmbracelet/x/ansi v0.11.2/go.mod h1:9tY2bzX5SiJCU0iWyskjBeI2BRQfvPqI+J760Mjf+Rg=
105github.com/charmbracelet/x/exp/charmtone v0.0.0-20250708181618-a60a724ba6c3 h1:1xwHZg6eMZ9Wv5TE1UGub6ARubyOd1Lo5kPUI/6VL50=
109github.com/charmbracelet/x/exp/charmtone v0.0.0-20250708181618-a60a724ba6c3 h1:1xwHZg6eMZ9Wv5TE1UGub6ARubyOd1Lo5kPUI/6VL50=
internal/agent/coordinator.go+6
@@ -388,6 +388,12 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan
388 }
388 }
389
389
390 for _, tool := range tools.GetMCPTools(c.permissions, c.cfg.WorkingDir()) {
390 for _, tool := range tools.GetMCPTools(c.permissions, c.cfg.WorkingDir()) {
391+ // Check MCP-specific disabled tools.
392+ if mcpCfg, ok := c.cfg.MCP[tool.MCP()]; ok {
393+ if slices.Contains(mcpCfg.DisabledTools, tool.MCPToolName()) {
394+ continue
395+ }
396+ }
391 if agent.AllowedMCP == nil {
397 if agent.AllowedMCP == nil {
392 // No MCP restrictions
398 // No MCP restrictions
393 filteredTools = append(filteredTools, tool)
399 filteredTools = append(filteredTools, tool)
internal/config/config.go+9−8
@@ -144,13 +144,14 @@ const (
144)
144)
145
145
146type MCPConfig struct {
146type MCPConfig struct {
147− Command string `json:"command,omitempty" jsonschema:"description=Command to execute for stdio MCP servers,example=npx"`
147+ Command string `json:"command,omitempty" jsonschema:"description=Command to execute for stdio MCP servers,example=npx"`
148− Env map[string]string `json:"env,omitempty" jsonschema:"description=Environment variables to set for the MCP server"`
148+ Env map[string]string `json:"env,omitempty" jsonschema:"description=Environment variables to set for the MCP server"`
149− Args []string `json:"args,omitempty" jsonschema:"description=Arguments to pass to the MCP server command"`
149+ Args []string `json:"args,omitempty" jsonschema:"description=Arguments to pass to the MCP server command"`
150− Type MCPType `json:"type" jsonschema:"required,description=Type of MCP connection,enum=stdio,enum=sse,enum=http,default=stdio"`
150+ Type MCPType `json:"type" jsonschema:"required,description=Type of MCP connection,enum=stdio,enum=sse,enum=http,default=stdio"`
151− URL string `json:"url,omitempty" jsonschema:"description=URL for HTTP or SSE MCP servers,format=uri,example=http://localhost:3000/mcp"`
151+ URL string `json:"url,omitempty" jsonschema:"description=URL for HTTP or SSE MCP servers,format=uri,example=http://localhost:3000/mcp"`
152− Disabled bool `json:"disabled,omitempty" jsonschema:"description=Whether this MCP server is disabled,default=false"`
152+ Disabled bool `json:"disabled,omitempty" jsonschema:"description=Whether this MCP server is disabled,default=false"`
153+ DisabledTools []string `json:"disabled_tools,omitempty" jsonschema:"description=List of tools from this MCP server to disable,example=get-library-doc"`
153− Timeout int `json:"timeout,omitempty" jsonschema:"description=Timeout in seconds for MCP server connections,default=15,example=30,example=60,example=120"`
154+ Timeout int `json:"timeout,omitempty" jsonschema:"description=Timeout in seconds for MCP server connections,default=15,example=30,example=60,example=120"`
154
155
155 // TODO: maybe make it possible to get the value from the env
156 // TODO: maybe make it possible to get the value from the env
156 Headers map[string]string `json:"headers,omitempty" jsonschema:"description=HTTP headers for HTTP/SSE MCP servers"`
157 Headers map[string]string `json:"headers,omitempty" jsonschema:"description=HTTP headers for HTTP/SSE MCP servers"`
@@ -221,7 +222,7 @@ type Options struct {
221 DebugLSP bool `json:"debug_lsp,omitempty" jsonschema:"description=Enable debug logging for LSP servers,default=false"`
222 DebugLSP bool `json:"debug_lsp,omitempty" jsonschema:"description=Enable debug logging for LSP servers,default=false"`
222 DisableAutoSummarize bool `json:"disable_auto_summarize,omitempty" jsonschema:"description=Disable automatic conversation summarization,default=false"`
223 DisableAutoSummarize bool `json:"disable_auto_summarize,omitempty" jsonschema:"description=Disable automatic conversation summarization,default=false"`
223 DataDirectory string `json:"data_directory,omitempty" jsonschema:"description=Directory for storing application data (relative to working directory),default=.crush,example=.crush"` // Relative to the cwd
224 DataDirectory string `json:"data_directory,omitempty" jsonschema:"description=Directory for storing application data (relative to working directory),default=.crush,example=.crush"` // Relative to the cwd
224− DisabledTools []string `json:"disabled_tools" jsonschema:"description=Tools to disable"`
225+ DisabledTools []string `json:"disabled_tools,omitempty" jsonschema:"description=List of built-in tools to disable and hide from the agent,example=bash,example=sourcegraph"`
225 DisableProviderAutoUpdate bool `json:"disable_provider_auto_update,omitempty" jsonschema:"description=Disable providers auto-update,default=false"`
226 DisableProviderAutoUpdate bool `json:"disable_provider_auto_update,omitempty" jsonschema:"description=Disable providers auto-update,default=false"`
226 Attribution *Attribution `json:"attribution,omitempty" jsonschema:"description=Attribution settings for generated content"`
227 Attribution *Attribution `json:"attribution,omitempty" jsonschema:"description=Attribution settings for generated content"`
227 DisableMetrics bool `json:"disable_metrics,omitempty" jsonschema:"description=Disable sending metrics,default=false"`
228 DisableMetrics bool `json:"disable_metrics,omitempty" jsonschema:"description=Disable sending metrics,default=false"`
internal/tui/components/chat/editor/editor.go+6−1
@@ -41,6 +41,7 @@ type Editor interface {
41 SetSession(session session.Session) tea.Cmd
41 SetSession(session session.Session) tea.Cmd
42 IsCompletionsOpen() bool
42 IsCompletionsOpen() bool
43 HasAttachments() bool
43 HasAttachments() bool
44+ IsEmpty() bool
44 Cursor() *tea.Cursor
45 Cursor() *tea.Cursor
45}
46}
46
47
@@ -261,7 +262,7 @@ func (m *editorCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
261 curIdx := m.textarea.Width()*cur.Y + cur.X
262 curIdx := m.textarea.Width()*cur.Y + cur.X
262 switch {
263 switch {
263 // Open command palette when "/" is pressed on empty prompt
264 // Open command palette when "/" is pressed on empty prompt
264− case msg.String() == "/" && len(strings.TrimSpace(m.textarea.Value())) == 0:
265+ case msg.String() == "/" && m.IsEmpty():
265 return m, util.CmdHandler(dialogs.OpenDialogMsg{
266 return m, util.CmdHandler(dialogs.OpenDialogMsg{
266 Model: commands.NewCommandDialog(m.session.ID),
267 Model: commands.NewCommandDialog(m.session.ID),
267 })
268 })
@@ -542,6 +543,10 @@ func (c *editorCmp) HasAttachments() bool {
542 return len(c.attachments) > 0
543 return len(c.attachments) > 0
543}
544}
544
545
546+func (c *editorCmp) IsEmpty() bool {
547+ return strings.TrimSpace(c.textarea.Value()) == ""
548+}
549+
545func normalPromptFunc(info textarea.PromptInfo) string {
550func normalPromptFunc(info textarea.PromptInfo) string {
546 t := styles.CurrentTheme()
551 t := styles.CurrentTheme()
547 if info.LineNumber == 0 {
552 if info.LineNumber == 0 {
internal/tui/page/chat/chat.go+4−3
@@ -525,9 +525,7 @@ func (p *chatPage) View() string {
525 )
525 )
526 layers = append(layers, lipgloss.NewLayer(details).X(1).Y(1))
526 layers = append(layers, lipgloss.NewLayer(details).X(1).Y(1))
527 }
527 }
528− canvas := lipgloss.NewCanvas(
528+ canvas := lipgloss.NewCompositor(layers...)
529− layers...,
530− )
531 return canvas.Render()
529 return canvas.Render()
532}
530}
533
531
@@ -1024,6 +1022,9 @@ func (p *chatPage) Help() help.KeyMap {
1024 key.WithKeys("ctrl+p"),
1022 key.WithKeys("ctrl+p"),
1025 key.WithHelp("ctrl+p", "commands"),
1023 key.WithHelp("ctrl+p", "commands"),
1026 )
1024 )
1025+ if p.focusedPane == PanelTypeEditor && p.editor.IsEmpty() {
1026+ commandsBinding.SetHelp("/ or ctrl+p", "commands")
1027+ }
1027 modelsBinding := key.NewBinding(
1028 modelsBinding := key.NewBinding(
1028 key.WithKeys("ctrl+m", "ctrl+l"),
1029 key.WithKeys("ctrl+m", "ctrl+l"),
1029 key.WithHelp("ctrl+l", "models"),
1030 key.WithHelp("ctrl+l", "models"),
internal/tui/tui.go+11−21
@@ -593,22 +593,15 @@ func (a *appModel) View() tea.View {
593 view.MouseMode = tea.MouseModeCellMotion
593 view.MouseMode = tea.MouseModeCellMotion
594 view.BackgroundColor = t.BgBase
594 view.BackgroundColor = t.BgBase
595 if a.wWidth < 25 || a.wHeight < 15 {
595 if a.wWidth < 25 || a.wHeight < 15 {
596− view.SetContent(
597− lipgloss.NewCanvas(
598− lipgloss.NewLayer(
599− t.S().Base.Width(a.wWidth).Height(a.wHeight).
596+ view.Content = t.S().Base.Width(a.wWidth).Height(a.wHeight).
600− Align(lipgloss.Center, lipgloss.Center).
597+ Align(lipgloss.Center, lipgloss.Center).
601− Render(
598+ Render(t.S().Base.
602− t.S().Base.
603− Padding(1, 4).
599+ Padding(1, 4).
604− Foreground(t.White).
600+ Foreground(t.White).
605− BorderStyle(lipgloss.RoundedBorder()).
601+ BorderStyle(lipgloss.RoundedBorder()).
606− BorderForeground(t.Primary).
602+ BorderForeground(t.Primary).
607− Render("Window too small!"),
603+ Render("Window too small!"),
608− ),
609− ),
610− ).Render(),
611− )
604+ )
612 return view
605 return view
613 }
606 }
614
607
@@ -659,11 +652,8 @@ func (a *appModel) View() tea.View {
659 )
652 )
660 }
653 }
661
654
662− canvas := lipgloss.NewCanvas(
655+ comp := lipgloss.NewCompositor(layers...)
663− layers...,
664− )
665−
666− view.Content = canvas.Render()
656+ view.Content = comp.Render()
667 view.Cursor = cursor
657 view.Cursor = cursor
668
658
669 if a.sendProgressBar && a.app != nil && a.app.AgentCoordinator != nil && a.app.AgentCoordinator.IsBusy() {
659 if a.sendProgressBar && a.app != nil && a.app.AgentCoordinator != nil && a.app.AgentCoordinator.IsBusy() {
schema.json+17−6
@@ -229,6 +229,16 @@
229 "description": "Whether this MCP server is disabled",
229 "description": "Whether this MCP server is disabled",
230 "default": false
230 "default": false
231 },
231 },
232+ "disabled_tools": {
233+ "items": {
234+ "type": "string",
235+ "examples": [
236+ "get-library-doc"
237+ ]
238+ },
239+ "type": "array",
240+ "description": "List of tools from this MCP server to disable"
241+ },
232 "timeout": {
242 "timeout": {
233 "type": "integer",
243 "type": "integer",
234 "description": "Timeout in seconds for MCP server connections",
244 "description": "Timeout in seconds for MCP server connections",
@@ -386,10 +396,14 @@
386 },
396 },
387 "disabled_tools": {
397 "disabled_tools": {
388 "items": {
398 "items": {
389− "type": "string"
399+ "type": "string",
400+ "examples": [
401+ "bash",
402+ "sourcegraph"
403+ ]
390 },
404 },
391 "type": "array",
405 "type": "array",
392− "description": "Tools to disable"
406+ "description": "List of built-in tools to disable and hide from the agent"
393 },
407 },
394 "disable_provider_auto_update": {
408 "disable_provider_auto_update": {
395 "type": "boolean",
409 "type": "boolean",
@@ -418,10 +432,7 @@
418 }
432 }
419 },
433 },
420 "additionalProperties": false,
434 "additionalProperties": false,
421− "type": "object",
435+ "type": "object"
422− "required": [
423− "disabled_tools"
424− ]
425 },
436 },
426 "Permissions": {
437 "Permissions": {
427 "properties": {
438 "properties": {