on main help sign in
Merge branch 'main' into ui
commit4dd11b3d506495bbe33aecbf4746dd80d86f4053 authorAyman Bagabas <ayman.bagabas@gmail.com> dateWed 10 Dec 2025, 14:25 (1 year ago) parent6f38a79d8f8889
9 files changed+81−41
README.md+22 go.mod+2−2 go.sum+4 internal/agent/coordinator.go+6 internal/config/config.go+9−8 internal/tui/components/chat/editor/editor.go+6−1 internal/tui/page/chat/chat.go+4−3 internal/tui/tui.go+11−21 schema.json+17−6
README.md+22
@@ -275,6 +275,7 @@ using `$(echo $VAR)` syntax.
275275 "args": ["/path/to/mcp-server.js"],
276276 "timeout": 120,
277277 "disabled": false,
278+ "disabled_tools": ["some-tool-name"],
278279 "env": {
279280 "NODE_ENV": "production"
280281 }
@@ -284,6 +285,7 @@ using `$(echo $VAR)` syntax.
284285 "url": "https://api.githubcopilot.com/mcp/",
285286 "timeout": 120,
286287 "disabled": false,
288+ "disabled_tools": ["create_issue", "create_pull_request"],
287289 "headers": {
288290 "Authorization": "Bearer $GH_PAT"
289291 }
@@ -335,6 +337,26 @@ permissions. Use this with care.
335337You can also skip all permission prompts entirely by running Crush with the
336338`--yolo` flag. Be very, very careful with this feature.
337339
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+
338360### Initialization
339361
340362When you initialize a project, Crush analyzes your codebase and creates
go.mod+2−2
@@ -6,7 +6,7 @@ require (
66 charm.land/bubbles/v2 v2.0.0-rc.1
77 charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251202162339-5fa38b798f16
88 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
1010 charm.land/x/vcr v0.1.1
1111 github.com/JohannesKaufmann/html-to-markdown v1.6.0
1212 github.com/MakeNowJust/heredoc v1.0.0
@@ -21,7 +21,7 @@ require (
2121 github.com/charmbracelet/fang v0.4.4
2222 github.com/charmbracelet/glamour/v2 v2.0.0-20251106195642-800eb8175930
2323 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
2525 github.com/charmbracelet/x/ansi v0.11.2
2626 github.com/charmbracelet/x/exp/charmtone v0.0.0-20250708181618-a60a724ba6c3
2727 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=
66charm.land/fantasy v0.5.1/go.mod h1:SPOsnIlkBKnhw2Wnasv+wZ82EmCMIGesx0je3tgR6+M=
77charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251119143523-0334bb4562ca h1:6bVc8OFotCS4sS7HKqxTudP7yn8Y0ODR6df2pdlY/+s=
88charm.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=
911charm.land/x/vcr v0.1.1 h1:PXCFMUG0rPtyk35rhfzYCJEduOzWXCIbrXTFq4OF/9Q=
1012charm.land/x/vcr v0.1.1/go.mod h1:eByq2gqzWvcct/8XE2XO5KznoWEBiXH56+y2gphbltM=
1113cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
@@ -100,6 +102,8 @@ github.com/charmbracelet/log/v2 v2.0.0-20251106192421-eb64aaa963a0 h1:lxHzxsHd4P
100102github.com/charmbracelet/log/v2 v2.0.0-20251106192421-eb64aaa963a0/go.mod h1:Q7oMtlboDPnnrYiJDXNwdWmJblOmuOnycPKczlVju6I=
101103github.com/charmbracelet/ultraviolet v0.0.0-20251202162030-ecc8c1ae4b2b h1:jY1J0PcfetoB1uJ+w8rd86gUFSpKpJJI35gnfpKF5hg=
102104github.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=
103107github.com/charmbracelet/x/ansi v0.11.2 h1:XAG3FSjiVtFvgEgGrNBkCNNYrsucAt8c6bfxHyROLLs=
104108github.com/charmbracelet/x/ansi v0.11.2/go.mod h1:9tY2bzX5SiJCU0iWyskjBeI2BRQfvPqI+J760Mjf+Rg=
105109github.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
388388 }
389389
390390 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+ }
391397 if agent.AllowedMCP == nil {
392398 // No MCP restrictions
393399 filteredTools = append(filteredTools, tool)
internal/config/config.go+9−8
@@ -144,13 +144,14 @@ const (
144144)
145145
146146type MCPConfig struct {
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"`
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"`
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"`
153− Timeout int `json:"timeout,omitempty" jsonschema:"description=Timeout in seconds for MCP server connections,default=15,example=30,example=60,example=120"`
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"`
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"`
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"`
153+ DisabledTools []string `json:"disabled_tools,omitempty" jsonschema:"description=List of tools from this MCP server to disable,example=get-library-doc"`
154+ Timeout int `json:"timeout,omitempty" jsonschema:"description=Timeout in seconds for MCP server connections,default=15,example=30,example=60,example=120"`
154155
155156 // TODO: maybe make it possible to get the value from the env
156157 Headers map[string]string `json:"headers,omitempty" jsonschema:"description=HTTP headers for HTTP/SSE MCP servers"`
@@ -221,7 +222,7 @@ type Options struct {
221222 DebugLSP bool `json:"debug_lsp,omitempty" jsonschema:"description=Enable debug logging for LSP servers,default=false"`
222223 DisableAutoSummarize bool `json:"disable_auto_summarize,omitempty" jsonschema:"description=Disable automatic conversation summarization,default=false"`
223224 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"`
225226 DisableProviderAutoUpdate bool `json:"disable_provider_auto_update,omitempty" jsonschema:"description=Disable providers auto-update,default=false"`
226227 Attribution *Attribution `json:"attribution,omitempty" jsonschema:"description=Attribution settings for generated content"`
227228 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 {
4141 SetSession(session session.Session) tea.Cmd
4242 IsCompletionsOpen() bool
4343 HasAttachments() bool
44+ IsEmpty() bool
4445 Cursor() *tea.Cursor
4546}
4647
@@ -261,7 +262,7 @@ func (m *editorCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
261262 curIdx := m.textarea.Width()*cur.Y + cur.X
262263 switch {
263264 // 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():
265266 return m, util.CmdHandler(dialogs.OpenDialogMsg{
266267 Model: commands.NewCommandDialog(m.session.ID),
267268 })
@@ -542,6 +543,10 @@ func (c *editorCmp) HasAttachments() bool {
542543 return len(c.attachments) > 0
543544}
544545
546+func (c *editorCmp) IsEmpty() bool {
547+ return strings.TrimSpace(c.textarea.Value()) == ""
548+}
549+
545550func normalPromptFunc(info textarea.PromptInfo) string {
546551 t := styles.CurrentTheme()
547552 if info.LineNumber == 0 {
internal/tui/page/chat/chat.go+4−3
@@ -525,9 +525,7 @@ func (p *chatPage) View() string {
525525 )
526526 layers = append(layers, lipgloss.NewLayer(details).X(1).Y(1))
527527 }
528− canvas := lipgloss.NewCanvas(
529− layers...,
530− )
528+ canvas := lipgloss.NewCompositor(layers...)
531529 return canvas.Render()
532530}
533531
@@ -1024,6 +1022,9 @@ func (p *chatPage) Help() help.KeyMap {
10241022 key.WithKeys("ctrl+p"),
10251023 key.WithHelp("ctrl+p", "commands"),
10261024 )
1025+ if p.focusedPane == PanelTypeEditor && p.editor.IsEmpty() {
1026+ commandsBinding.SetHelp("/ or ctrl+p", "commands")
1027+ }
10271028 modelsBinding := key.NewBinding(
10281029 key.WithKeys("ctrl+m", "ctrl+l"),
10291030 key.WithHelp("ctrl+l", "models"),
internal/tui/tui.go+11−21
@@ -593,22 +593,15 @@ func (a *appModel) View() tea.View {
593593 view.MouseMode = tea.MouseModeCellMotion
594594 view.BackgroundColor = t.BgBase
595595 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).
600− Align(lipgloss.Center, lipgloss.Center).
601− Render(
602− t.S().Base.
603− Padding(1, 4).
604− Foreground(t.White).
605− BorderStyle(lipgloss.RoundedBorder()).
606− BorderForeground(t.Primary).
607− Render("Window too small!"),
608− ),
609− ),
610− ).Render(),
611− )
596+ view.Content = t.S().Base.Width(a.wWidth).Height(a.wHeight).
597+ Align(lipgloss.Center, lipgloss.Center).
598+ Render(t.S().Base.
599+ Padding(1, 4).
600+ Foreground(t.White).
601+ BorderStyle(lipgloss.RoundedBorder()).
602+ BorderForeground(t.Primary).
603+ Render("Window too small!"),
604+ )
612605 return view
613606 }
614607
@@ -659,11 +652,8 @@ func (a *appModel) View() tea.View {
659652 )
660653 }
661654
662− canvas := lipgloss.NewCanvas(
663− layers...,
664− )
665−
666− view.Content = canvas.Render()
655+ comp := lipgloss.NewCompositor(layers...)
656+ view.Content = comp.Render()
667657 view.Cursor = cursor
668658
669659 if a.sendProgressBar && a.app != nil && a.app.AgentCoordinator != nil && a.app.AgentCoordinator.IsBusy() {
schema.json+17−6
@@ -229,6 +229,16 @@
229229 "description": "Whether this MCP server is disabled",
230230 "default": false
231231 },
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+ },
232242 "timeout": {
233243 "type": "integer",
234244 "description": "Timeout in seconds for MCP server connections",
@@ -386,10 +396,14 @@
386396 },
387397 "disabled_tools": {
388398 "items": {
389− "type": "string"
399+ "type": "string",
400+ "examples": [
401+ "bash",
402+ "sourcegraph"
403+ ]
390404 },
391405 "type": "array",
392− "description": "Tools to disable"
406+ "description": "List of built-in tools to disable and hide from the agent"
393407 },
394408 "disable_provider_auto_update": {
395409 "type": "boolean",
@@ -418,10 +432,7 @@
418432 }
419433 },
420434 "additionalProperties": false,
421− "type": "object",
422− "required": [
423− "disabled_tools"
424− ]
435+ "type": "object"
425436 },
426437 "Permissions": {
427438 "properties": {