on main help sign in
chore: make tools config independent
a07dd6bKujtim Hoxha
cmd/root.go+3−3
@@ -100,7 +100,7 @@ to assist developers in writing, debugging, and understanding code directly from
100 defer app.Shutdown()
100 defer app.Shutdown()
101
101
102 // Initialize MCP tools early for both modes
102 // Initialize MCP tools early for both modes
103− initMCPTools(ctx, app)
103+ initMCPTools(ctx, app, cfg)
104
104
105 prompt, err = maybePrependStdin(prompt)
105 prompt, err = maybePrependStdin(prompt)
106 if err != nil {
106 if err != nil {
@@ -192,7 +192,7 @@ func attemptTUIRecovery(program *tea.Program) {
192 program.Quit()
192 program.Quit()
193}
193}
194
194
195−func initMCPTools(ctx context.Context, app *app.App) {
195+func initMCPTools(ctx context.Context, app *app.App, cfg *config.Config) {
196 go func() {
196 go func() {
197 defer log.RecoverPanic("MCP-goroutine", nil)
197 defer log.RecoverPanic("MCP-goroutine", nil)
198
198
@@ -201,7 +201,7 @@ func initMCPTools(ctx context.Context, app *app.App) {
201 defer cancel()
201 defer cancel()
202
202
203 // Set this up once with proper error handling
203 // Set this up once with proper error handling
204− agent.GetMcpTools(ctxWithTimeout, app.Permissions)
204+ agent.GetMcpTools(ctxWithTimeout, app.Permissions, cfg)
205 slog.Info("MCP message handling goroutine exiting")
205 slog.Info("MCP message handling goroutine exiting")
206 }()
206 }()
207}
207}
internal/llm/agent/agent.go+10−9
@@ -94,21 +94,22 @@ func NewAgent(
94) (Service, error) {
94) (Service, error) {
95 ctx := context.Background()
95 ctx := context.Background()
96 cfg := config.Get()
96 cfg := config.Get()
97− otherTools := GetMcpTools(ctx, permissions)
97+ otherTools := GetMcpTools(ctx, permissions, cfg)
98 if len(lspClients) > 0 {
98 if len(lspClients) > 0 {
99 otherTools = append(otherTools, tools.NewDiagnosticsTool(lspClients))
99 otherTools = append(otherTools, tools.NewDiagnosticsTool(lspClients))
100 }
100 }
101
101
102+ cwd := cfg.WorkingDir()
102 allTools := []tools.BaseTool{
103 allTools := []tools.BaseTool{
103− tools.NewBashTool(permissions),
104+ tools.NewBashTool(permissions, cwd),
104− tools.NewEditTool(lspClients, permissions, history),
105+ tools.NewEditTool(lspClients, permissions, history, cwd),
105− tools.NewFetchTool(permissions),
106+ tools.NewFetchTool(permissions, cwd),
106− tools.NewGlobTool(),
107+ tools.NewGlobTool(cwd),
107− tools.NewGrepTool(),
108+ tools.NewGrepTool(cwd),
108− tools.NewLsTool(),
109+ tools.NewLsTool(cwd),
109 tools.NewSourcegraphTool(),
110 tools.NewSourcegraphTool(),
110− tools.NewViewTool(lspClients),
111+ tools.NewViewTool(lspClients, cwd),
111− tools.NewWriteTool(lspClients, permissions, history),
112+ tools.NewWriteTool(lspClients, permissions, history, cwd),
112 }
113 }
113
114
114 if agentCfg.ID == "coder" {
115 if agentCfg.ID == "coder" {
internal/llm/agent/mcp-tools.go+11−9
@@ -22,6 +22,7 @@ type mcpTool struct {
22 tool mcp.Tool
22 tool mcp.Tool
23 mcpConfig config.MCPConfig
23 mcpConfig config.MCPConfig
24 permissions permission.Service
24 permissions permission.Service
25+ workingDir string
25}
26}
26
27
27type MCPClient interface {
28type MCPClient interface {
@@ -98,7 +99,7 @@ func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolRes
98 p := b.permissions.Request(
99 p := b.permissions.Request(
99 permission.CreatePermissionRequest{
100 permission.CreatePermissionRequest{
100 SessionID: sessionID,
101 SessionID: sessionID,
101− Path: config.Get().WorkingDir(),
102+ Path: b.workingDir,
102 ToolName: b.Info().Name,
103 ToolName: b.Info().Name,
103 Action: "execute",
104 Action: "execute",
104 Description: permissionDescription,
105 Description: permissionDescription,
@@ -143,18 +144,19 @@ func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolRes
143 return tools.NewTextErrorResponse("invalid mcp type"), nil
144 return tools.NewTextErrorResponse("invalid mcp type"), nil
144}
145}
145
146
146−func NewMcpTool(name string, tool mcp.Tool, permissions permission.Service, mcpConfig config.MCPConfig) tools.BaseTool {
147+func NewMcpTool(name string, tool mcp.Tool, permissions permission.Service, mcpConfig config.MCPConfig, workingDir string) tools.BaseTool {
147 return &mcpTool{
148 return &mcpTool{
148 mcpName: name,
149 mcpName: name,
149 tool: tool,
150 tool: tool,
150 mcpConfig: mcpConfig,
151 mcpConfig: mcpConfig,
151 permissions: permissions,
152 permissions: permissions,
153+ workingDir: workingDir,
152 }
154 }
153}
155}
154
156
155var mcpTools []tools.BaseTool
157var mcpTools []tools.BaseTool
156
158
157−func getTools(ctx context.Context, name string, m config.MCPConfig, permissions permission.Service, c MCPClient) []tools.BaseTool {
159+func getTools(ctx context.Context, name string, m config.MCPConfig, permissions permission.Service, c MCPClient, workingDir string) []tools.BaseTool {
158 var stdioTools []tools.BaseTool
160 var stdioTools []tools.BaseTool
159 initRequest := mcp.InitializeRequest{}
161 initRequest := mcp.InitializeRequest{}
160 initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
162 initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
@@ -175,17 +177,17 @@ func getTools(ctx context.Context, name string, m config.MCPConfig, permissions
175 return stdioTools
177 return stdioTools
176 }
178 }
177 for _, t := range tools.Tools {
179 for _, t := range tools.Tools {
178− stdioTools = append(stdioTools, NewMcpTool(name, t, permissions, m))
180+ stdioTools = append(stdioTools, NewMcpTool(name, t, permissions, m, workingDir))
179 }
181 }
180 defer c.Close()
182 defer c.Close()
181 return stdioTools
183 return stdioTools
182}
184}
183
185
184−func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.BaseTool {
186+func GetMcpTools(ctx context.Context, permissions permission.Service, cfg *config.Config) []tools.BaseTool {
185 if len(mcpTools) > 0 {
187 if len(mcpTools) > 0 {
186 return mcpTools
188 return mcpTools
187 }
189 }
188− for name, m := range config.Get().MCP {
190+ for name, m := range cfg.MCP {
189 switch m.Type {
191 switch m.Type {
190 case config.MCPStdio:
192 case config.MCPStdio:
191 c, err := client.NewStdioMCPClient(
193 c, err := client.NewStdioMCPClient(
@@ -198,7 +200,7 @@ func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.Ba
198 continue
200 continue
199 }
201 }
200
202
201− mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c)...)
203+ mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
202 case config.MCPHttp:
204 case config.MCPHttp:
203 c, err := client.NewStreamableHttpClient(
205 c, err := client.NewStreamableHttpClient(
204 m.URL,
206 m.URL,
@@ -208,7 +210,7 @@ func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.Ba
208 slog.Error("error creating mcp client", "error", err)
210 slog.Error("error creating mcp client", "error", err)
209 continue
211 continue
210 }
212 }
211− mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c)...)
213+ mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
212 case config.MCPSse:
214 case config.MCPSse:
213 c, err := client.NewSSEMCPClient(
215 c, err := client.NewSSEMCPClient(
214 m.URL,
216 m.URL,
@@ -218,7 +220,7 @@ func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.Ba
218 slog.Error("error creating mcp client", "error", err)
220 slog.Error("error creating mcp client", "error", err)
219 continue
221 continue
220 }
222 }
221− mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c)...)
223+ mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
222 }
224 }
223 }
225 }
224
226
internal/llm/prompt/coder.go+1−1
@@ -384,7 +384,7 @@ func getEnvironmentInfo() string {
384 isGit := isGitRepo(cwd)
384 isGit := isGitRepo(cwd)
385 platform := runtime.GOOS
385 platform := runtime.GOOS
386 date := time.Now().Format("1/2/2006")
386 date := time.Now().Format("1/2/2006")
387− ls := tools.NewLsTool()
387+ ls := tools.NewLsTool(cwd)
388 r, _ := ls.Run(context.Background(), tools.ToolCall{
388 r, _ := ls.Run(context.Background(), tools.ToolCall{
389 Input: `{"path":"."}`,
389 Input: `{"path":"."}`,
390 })
390 })
internal/llm/tools/bash.go+5−4
@@ -8,7 +8,6 @@ import (
8 "strings"
8 "strings"
9 "time"
9 "time"
10
10
11− "github.com/charmbracelet/crush/internal/config"
12 "github.com/charmbracelet/crush/internal/permission"
11 "github.com/charmbracelet/crush/internal/permission"
13 "github.com/charmbracelet/crush/internal/shell"
12 "github.com/charmbracelet/crush/internal/shell"
14)
13)
@@ -29,6 +28,7 @@ type BashResponseMetadata struct {
29}
28}
30type bashTool struct {
29type bashTool struct {
31 permissions permission.Service
30 permissions permission.Service
31+ workingDir string
32}
32}
33
33
34const (
34const (
@@ -244,9 +244,10 @@ Important:
244- Never update git config`, bannedCommandsStr, MaxOutputLength)
244- Never update git config`, bannedCommandsStr, MaxOutputLength)
245}
245}
246
246
247−func NewBashTool(permission permission.Service) BaseTool {
247+func NewBashTool(permission permission.Service, workingDir string) BaseTool {
248 return &bashTool{
248 return &bashTool{
249 permissions: permission,
249 permissions: permission,
250+ workingDir: workingDir,
250 }
251 }
251}
252}
252
253
@@ -317,7 +318,7 @@ func (b *bashTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
317 p := b.permissions.Request(
318 p := b.permissions.Request(
318 permission.CreatePermissionRequest{
319 permission.CreatePermissionRequest{
319 SessionID: sessionID,
320 SessionID: sessionID,
320− Path: config.Get().WorkingDir(),
321+ Path: b.workingDir,
321 ToolName: BashToolName,
322 ToolName: BashToolName,
322 Action: "execute",
323 Action: "execute",
323 Description: fmt.Sprintf("Execute command: %s", params.Command),
324 Description: fmt.Sprintf("Execute command: %s", params.Command),
@@ -337,7 +338,7 @@ func (b *bashTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
337 defer cancel()
338 defer cancel()
338 }
339 }
339 stdout, stderr, err := shell.
340 stdout, stderr, err := shell.
340− GetPersistentShell(config.Get().WorkingDir()).
341+ GetPersistentShell(b.workingDir).
341 Exec(ctx, params.Command)
342 Exec(ctx, params.Command)
342 interrupted := shell.IsInterrupt(err)
343 interrupted := shell.IsInterrupt(err)
343 exitCode := shell.ExitCode(err)
344 exitCode := shell.ExitCode(err)
internal/llm/tools/edit.go+10−10
@@ -10,7 +10,6 @@ import (
10 "strings"
10 "strings"
11 "time"
11 "time"
12
12
13− "github.com/charmbracelet/crush/internal/config"
14 "github.com/charmbracelet/crush/internal/diff"
13 "github.com/charmbracelet/crush/internal/diff"
15 "github.com/charmbracelet/crush/internal/history"
14 "github.com/charmbracelet/crush/internal/history"
16
15
@@ -41,6 +40,7 @@ type editTool struct {
41 lspClients map[string]*lsp.Client
40 lspClients map[string]*lsp.Client
42 permissions permission.Service
41 permissions permission.Service
43 files history.Service
42 files history.Service
43+ workingDir string
44}
44}
45
45
46const (
46const (
@@ -99,11 +99,12 @@ WINDOWS NOTES:
99Remember: when making multiple file edits in a row to the same file, you should prefer to send all edits in a single message with multiple calls to this tool, rather than multiple messages with a single call each.`
99Remember: when making multiple file edits in a row to the same file, you should prefer to send all edits in a single message with multiple calls to this tool, rather than multiple messages with a single call each.`
100)
100)
101
101
102−func NewEditTool(lspClients map[string]*lsp.Client, permissions permission.Service, files history.Service) BaseTool {
102+func NewEditTool(lspClients map[string]*lsp.Client, permissions permission.Service, files history.Service, workingDir string) BaseTool {
103 return &editTool{
103 return &editTool{
104 lspClients: lspClients,
104 lspClients: lspClients,
105 permissions: permissions,
105 permissions: permissions,
106 files: files,
106 files: files,
107+ workingDir: workingDir,
107 }
108 }
108}
109}
109
110
@@ -144,8 +145,7 @@ func (e *editTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
144 }
145 }
145
146
146 if !filepath.IsAbs(params.FilePath) {
147 if !filepath.IsAbs(params.FilePath) {
147− wd := config.Get().WorkingDir()
148− params.FilePath = filepath.Join(wd, params.FilePath)
148+ params.FilePath = filepath.Join(e.workingDir, params.FilePath)
149 }
149 }
150
150
151 var response ToolResponse
151 var response ToolResponse
@@ -206,9 +206,9 @@ func (e *editTool) createNewFile(ctx context.Context, filePath, content string)
206 _, additions, removals := diff.GenerateDiff(
206 _, additions, removals := diff.GenerateDiff(
207 "",
207 "",
208 content,
208 content,
209− strings.TrimPrefix(filePath, config.Get().WorkingDir()),
209+ strings.TrimPrefix(filePath, e.workingDir),
210 )
210 )
211− rootDir := config.Get().WorkingDir()
211+ rootDir := e.workingDir
212 permissionPath := filepath.Dir(filePath)
212 permissionPath := filepath.Dir(filePath)
213 if strings.HasPrefix(filePath, rootDir) {
213 if strings.HasPrefix(filePath, rootDir) {
214 permissionPath = rootDir
214 permissionPath = rootDir
@@ -318,10 +318,10 @@ func (e *editTool) deleteContent(ctx context.Context, filePath, oldString string
318 _, additions, removals := diff.GenerateDiff(
318 _, additions, removals := diff.GenerateDiff(
319 oldContent,
319 oldContent,
320 newContent,
320 newContent,
321− strings.TrimPrefix(filePath, config.Get().WorkingDir()),
321+ strings.TrimPrefix(filePath, e.workingDir),
322 )
322 )
323
323
324− rootDir := config.Get().WorkingDir()
324+ rootDir := e.workingDir
325 permissionPath := filepath.Dir(filePath)
325 permissionPath := filepath.Dir(filePath)
326 if strings.HasPrefix(filePath, rootDir) {
326 if strings.HasPrefix(filePath, rootDir) {
327 permissionPath = rootDir
327 permissionPath = rootDir
@@ -441,9 +441,9 @@ func (e *editTool) replaceContent(ctx context.Context, filePath, oldString, newS
441 _, additions, removals := diff.GenerateDiff(
441 _, additions, removals := diff.GenerateDiff(
442 oldContent,
442 oldContent,
443 newContent,
443 newContent,
444− strings.TrimPrefix(filePath, config.Get().WorkingDir()),
444+ strings.TrimPrefix(filePath, e.workingDir),
445 )
445 )
446− rootDir := config.Get().WorkingDir()
446+ rootDir := e.workingDir
447 permissionPath := filepath.Dir(filePath)
447 permissionPath := filepath.Dir(filePath)
448 if strings.HasPrefix(filePath, rootDir) {
448 if strings.HasPrefix(filePath, rootDir) {
449 permissionPath = rootDir
449 permissionPath = rootDir
internal/llm/tools/fetch.go+4−3
@@ -11,7 +11,6 @@ import (
11
11
12 md "github.com/JohannesKaufmann/html-to-markdown"
12 md "github.com/JohannesKaufmann/html-to-markdown"
13 "github.com/PuerkitoBio/goquery"
13 "github.com/PuerkitoBio/goquery"
14− "github.com/charmbracelet/crush/internal/config"
15 "github.com/charmbracelet/crush/internal/permission"
14 "github.com/charmbracelet/crush/internal/permission"
16)
15)
17
16
@@ -30,6 +29,7 @@ type FetchPermissionsParams struct {
30type fetchTool struct {
29type fetchTool struct {
31 client *http.Client
30 client *http.Client
32 permissions permission.Service
31 permissions permission.Service
32+ workingDir string
33}
33}
34
34
35const (
35const (
@@ -65,7 +65,7 @@ TIPS:
65- Set appropriate timeouts for potentially slow websites`
65- Set appropriate timeouts for potentially slow websites`
66)
66)
67
67
68−func NewFetchTool(permissions permission.Service) BaseTool {
68+func NewFetchTool(permissions permission.Service, workingDir string) BaseTool {
69 return &fetchTool{
69 return &fetchTool{
70 client: &http.Client{
70 client: &http.Client{
71 Timeout: 30 * time.Second,
71 Timeout: 30 * time.Second,
@@ -76,6 +76,7 @@ func NewFetchTool(permissions permission.Service) BaseTool {
76 },
76 },
77 },
77 },
78 permissions: permissions,
78 permissions: permissions,
79+ workingDir: workingDir,
79 }
80 }
80}
81}
81
82
@@ -133,7 +134,7 @@ func (t *fetchTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
133 p := t.permissions.Request(
134 p := t.permissions.Request(
134 permission.CreatePermissionRequest{
135 permission.CreatePermissionRequest{
135 SessionID: sessionID,
136 SessionID: sessionID,
136− Path: config.Get().WorkingDir(),
137+ Path: t.workingDir,
137 ToolName: FetchToolName,
138 ToolName: FetchToolName,
138 Action: "fetch",
139 Action: "fetch",
139 Description: fmt.Sprintf("Fetch content from URL: %s", params.URL),
140 Description: fmt.Sprintf("Fetch content from URL: %s", params.URL),
internal/llm/tools/glob.go+8−5
@@ -11,7 +11,6 @@ import (
11 "sort"
11 "sort"
12 "strings"
12 "strings"
13
13
14− "github.com/charmbracelet/crush/internal/config"
15 "github.com/charmbracelet/crush/internal/fsext"
14 "github.com/charmbracelet/crush/internal/fsext"
16)
15)
17
16
@@ -68,10 +67,14 @@ type GlobResponseMetadata struct {
68 Truncated bool `json:"truncated"`
67 Truncated bool `json:"truncated"`
69}
68}
70
69
71−type globTool struct{}
70+type globTool struct {
71+ workingDir string
72+}
72
73
73−func NewGlobTool() BaseTool {
74+func NewGlobTool(workingDir string) BaseTool {
74− return &globTool{}
75+ return &globTool{
76+ workingDir: workingDir,
77+ }
75}
78}
76
79
77func (g *globTool) Name() string {
80func (g *globTool) Name() string {
@@ -108,7 +111,7 @@ func (g *globTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
108
111
109 searchPath := params.Path
112 searchPath := params.Path
110 if searchPath == "" {
113 if searchPath == "" {
111− searchPath = config.Get().WorkingDir()
114+ searchPath = g.workingDir
112 }
115 }
113
116
114 files, truncated, err := globFiles(params.Pattern, searchPath, 100)
117 files, truncated, err := globFiles(params.Pattern, searchPath, 100)
internal/llm/tools/grep.go+8−5
@@ -16,7 +16,6 @@ import (
16 "sync"
16 "sync"
17 "time"
17 "time"
18
18
19− "github.com/charmbracelet/crush/internal/config"
20 "github.com/charmbracelet/crush/internal/fsext"
19 "github.com/charmbracelet/crush/internal/fsext"
21)
20)
22
21
@@ -89,7 +88,9 @@ type GrepResponseMetadata struct {
89 Truncated bool `json:"truncated"`
88 Truncated bool `json:"truncated"`
90}
89}
91
90
92−type grepTool struct{}
91+type grepTool struct {
92+ workingDir string
93+}
93
94
94const (
95const (
95 GrepToolName = "grep"
96 GrepToolName = "grep"
@@ -136,8 +137,10 @@ TIPS:
136- Use literal_text=true when searching for exact text containing special characters like dots, parentheses, etc.`
137- Use literal_text=true when searching for exact text containing special characters like dots, parentheses, etc.`
137)
138)
138
139
139−func NewGrepTool() BaseTool {
140+func NewGrepTool(workingDir string) BaseTool {
140− return &grepTool{}
141+ return &grepTool{
142+ workingDir: workingDir,
143+ }
141}
144}
142
145
143func (g *grepTool) Name() string {
146func (g *grepTool) Name() string {
@@ -200,7 +203,7 @@ func (g *grepTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
200
203
201 searchPath := params.Path
204 searchPath := params.Path
202 if searchPath == "" {
205 if searchPath == "" {
203− searchPath = config.Get().WorkingDir()
206+ searchPath = g.workingDir
204 }
207 }
205
208
206 matches, truncated, err := searchFiles(searchPattern, searchPath, params.Include, 100)
209 matches, truncated, err := searchFiles(searchPattern, searchPath, params.Include, 100)
internal/llm/tools/ls.go+9−6
@@ -8,7 +8,6 @@ import (
8 "path/filepath"
8 "path/filepath"
9 "strings"
9 "strings"
10
10
11− "github.com/charmbracelet/crush/internal/config"
12 "github.com/charmbracelet/crush/internal/fsext"
11 "github.com/charmbracelet/crush/internal/fsext"
13)
12)
14
13
@@ -29,7 +28,9 @@ type LSResponseMetadata struct {
29 Truncated bool `json:"truncated"`
28 Truncated bool `json:"truncated"`
30}
29}
31
30
32−type lsTool struct{}
31+type lsTool struct {
32+ workingDir string
33+}
33
34
34const (
35const (
35 LSToolName = "ls"
36 LSToolName = "ls"
@@ -70,8 +71,10 @@ TIPS:
70- Combine with other tools for more effective exploration`
71- Combine with other tools for more effective exploration`
71)
72)
72
73
73−func NewLsTool() BaseTool {
74+func NewLsTool(workingDir string) BaseTool {
74− return &lsTool{}
75+ return &lsTool{
76+ workingDir: workingDir,
77+ }
75}
78}
76
79
77func (l *lsTool) Name() string {
80func (l *lsTool) Name() string {
@@ -107,11 +110,11 @@ func (l *lsTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) {
107
110
108 searchPath := params.Path
111 searchPath := params.Path
109 if searchPath == "" {
112 if searchPath == "" {
110− searchPath = config.Get().WorkingDir()
113+ searchPath = l.workingDir
111 }
114 }
112
115
113 if !filepath.IsAbs(searchPath) {
116 if !filepath.IsAbs(searchPath) {
114− searchPath = filepath.Join(config.Get().WorkingDir(), searchPath)
117+ searchPath = filepath.Join(l.workingDir, searchPath)
115 }
118 }
116
119
117 if _, err := os.Stat(searchPath); os.IsNotExist(err) {
120 if _, err := os.Stat(searchPath); os.IsNotExist(err) {
internal/llm/tools/view.go+5−4
@@ -10,7 +10,6 @@ import (
10 "path/filepath"
10 "path/filepath"
11 "strings"
11 "strings"
12
12
13− "github.com/charmbracelet/crush/internal/config"
14 "github.com/charmbracelet/crush/internal/lsp"
13 "github.com/charmbracelet/crush/internal/lsp"
15)
14)
16
15
@@ -22,6 +21,7 @@ type ViewParams struct {
22
21
23type viewTool struct {
22type viewTool struct {
24 lspClients map[string]*lsp.Client
23 lspClients map[string]*lsp.Client
24+ workingDir string
25}
25}
26
26
27type ViewResponseMetadata struct {
27type ViewResponseMetadata struct {
@@ -71,9 +71,10 @@ TIPS:
71- When viewing large files, use the offset parameter to read specific sections`
71- When viewing large files, use the offset parameter to read specific sections`
72)
72)
73
73
74−func NewViewTool(lspClients map[string]*lsp.Client) BaseTool {
74+func NewViewTool(lspClients map[string]*lsp.Client, workingDir string) BaseTool {
75 return &viewTool{
75 return &viewTool{
76− lspClients,
76+ lspClients: lspClients,
77+ workingDir: workingDir,
77 }
78 }
78}
79}
79
80
@@ -117,7 +118,7 @@ func (v *viewTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
117 // Handle relative paths
118 // Handle relative paths
118 filePath := params.FilePath
119 filePath := params.FilePath
119 if !filepath.IsAbs(filePath) {
120 if !filepath.IsAbs(filePath) {
120− filePath = filepath.Join(config.Get().WorkingDir(), filePath)
121+ filePath = filepath.Join(v.workingDir, filePath)
121 }
122 }
122
123
123 // Check if file exists
124 // Check if file exists
internal/llm/tools/write.go+6−5
@@ -10,7 +10,6 @@ import (
10 "strings"
10 "strings"
11 "time"
11 "time"
12
12
13− "github.com/charmbracelet/crush/internal/config"
14 "github.com/charmbracelet/crush/internal/diff"
13 "github.com/charmbracelet/crush/internal/diff"
15 "github.com/charmbracelet/crush/internal/history"
14 "github.com/charmbracelet/crush/internal/history"
16
15
@@ -33,6 +32,7 @@ type writeTool struct {
33 lspClients map[string]*lsp.Client
32 lspClients map[string]*lsp.Client
34 permissions permission.Service
33 permissions permission.Service
35 files history.Service
34 files history.Service
35+ workingDir string
36}
36}
37
37
38type WriteResponseMetadata struct {
38type WriteResponseMetadata struct {
@@ -77,11 +77,12 @@ TIPS:
77- Always include descriptive comments when making changes to existing code`
77- Always include descriptive comments when making changes to existing code`
78)
78)
79
79
80−func NewWriteTool(lspClients map[string]*lsp.Client, permissions permission.Service, files history.Service) BaseTool {
80+func NewWriteTool(lspClients map[string]*lsp.Client, permissions permission.Service, files history.Service, workingDir string) BaseTool {
81 return &writeTool{
81 return &writeTool{
82 lspClients: lspClients,
82 lspClients: lspClients,
83 permissions: permissions,
83 permissions: permissions,
84 files: files,
84 files: files,
85+ workingDir: workingDir,
85 }
86 }
86}
87}
87
88
@@ -123,7 +124,7 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
123
124
124 filePath := params.FilePath
125 filePath := params.FilePath
125 if !filepath.IsAbs(filePath) {
126 if !filepath.IsAbs(filePath) {
126− filePath = filepath.Join(config.Get().WorkingDir(), filePath)
127+ filePath = filepath.Join(w.workingDir, filePath)
127 }
128 }
128
129
129 fileInfo, err := os.Stat(filePath)
130 fileInfo, err := os.Stat(filePath)
@@ -168,10 +169,10 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
168 diff, additions, removals := diff.GenerateDiff(
169 diff, additions, removals := diff.GenerateDiff(
169 oldContent,
170 oldContent,
170 params.Content,
171 params.Content,
171− strings.TrimPrefix(filePath, config.Get().WorkingDir()),
172+ strings.TrimPrefix(filePath, w.workingDir),
172 )
173 )
173
174
174− rootDir := config.Get().WorkingDir()
175+ rootDir := w.workingDir
175 permissionPath := filepath.Dir(filePath)
176 permissionPath := filepath.Dir(filePath)
176 if strings.HasPrefix(filePath, rootDir) {
177 if strings.HasPrefix(filePath, rootDir) {
177 permissionPath = rootDir
178 permissionPath = rootDir