1package backend
2
3import (
4 "context"
5
6 "github.com/charmbracelet/crush/internal/agent"
7 mcptools "github.com/charmbracelet/crush/internal/agent/tools/mcp"
8 "github.com/charmbracelet/crush/internal/commands"
9 "github.com/charmbracelet/crush/internal/config"
10 "github.com/charmbracelet/crush/internal/oauth"
11)
12
13// MCPResourceContents holds the contents of an MCP resource returned
14// by the backend.
15type MCPResourceContents struct {
16 URI string `json:"uri"`
17 MIMEType string `json:"mime_type,omitempty"`
18 Text string `json:"text,omitempty"`
19 Blob []byte `json:"blob,omitempty"`
20}
21
22// SetConfigField sets a key/value pair in the config file for the
23// given scope.
24func (b *Backend) SetConfigField(workspaceID string, scope config.Scope, key string, value any) error {
25 ws, err := b.GetWorkspace(workspaceID)
26 if err != nil {
27 return err
28 }
29 return ws.Cfg.SetConfigField(scope, key, value)
30}
31
32// RemoveConfigField removes a key from the config file for the given
33// scope.
34func (b *Backend) RemoveConfigField(workspaceID string, scope config.Scope, key string) error {
35 ws, err := b.GetWorkspace(workspaceID)
36 if err != nil {
37 return err
38 }
39 return ws.Cfg.RemoveConfigField(scope, key)
40}
41
42// UpdatePreferredModel updates the preferred model for the given type
43// and persists it to the config file at the given scope.
44func (b *Backend) UpdatePreferredModel(workspaceID string, scope config.Scope, modelType config.SelectedModelType, model config.SelectedModel) error {
45 ws, err := b.GetWorkspace(workspaceID)
46 if err != nil {
47 return err
48 }
49 return ws.Cfg.UpdatePreferredModel(scope, modelType, model)
50}
51
52// SetCompactMode sets the compact mode setting and persists it.
53func (b *Backend) SetCompactMode(workspaceID string, scope config.Scope, enabled bool) error {
54 ws, err := b.GetWorkspace(workspaceID)
55 if err != nil {
56 return err
57 }
58 return ws.Cfg.SetCompactMode(scope, enabled)
59}
60
61// SetProviderAPIKey sets the API key for a provider and persists it.
62func (b *Backend) SetProviderAPIKey(workspaceID string, scope config.Scope, providerID string, apiKey any) error {
63 ws, err := b.GetWorkspace(workspaceID)
64 if err != nil {
65 return err
66 }
67 return ws.Cfg.SetProviderAPIKey(scope, providerID, apiKey)
68}
69
70// ImportCopilot attempts to import a GitHub Copilot token from disk.
71func (b *Backend) ImportCopilot(workspaceID string) (*oauth.Token, bool, error) {
72 ws, err := b.GetWorkspace(workspaceID)
73 if err != nil {
74 return nil, false, err
75 }
76 token, ok := ws.Cfg.ImportCopilot()
77 return token, ok, nil
78}
79
80// RefreshOAuthToken refreshes the OAuth token for a provider.
81func (b *Backend) RefreshOAuthToken(ctx context.Context, workspaceID string, scope config.Scope, providerID string) error {
82 ws, err := b.GetWorkspace(workspaceID)
83 if err != nil {
84 return err
85 }
86 return ws.Cfg.RefreshOAuthToken(ctx, scope, providerID)
87}
88
89// ProjectNeedsInitialization checks whether the project in this
90// workspace needs initialization.
91func (b *Backend) ProjectNeedsInitialization(workspaceID string) (bool, error) {
92 ws, err := b.GetWorkspace(workspaceID)
93 if err != nil {
94 return false, err
95 }
96 return config.ProjectNeedsInitialization(ws.Cfg)
97}
98
99// MarkProjectInitialized marks the project as initialized.
100func (b *Backend) MarkProjectInitialized(workspaceID string) error {
101 ws, err := b.GetWorkspace(workspaceID)
102 if err != nil {
103 return err
104 }
105 return config.MarkProjectInitialized(ws.Cfg)
106}
107
108// InitializePrompt builds the initialization prompt for the workspace.
109func (b *Backend) InitializePrompt(workspaceID string) (string, error) {
110 ws, err := b.GetWorkspace(workspaceID)
111 if err != nil {
112 return "", err
113 }
114 return agent.InitializePrompt(ws.Cfg)
115}
116
117// RefreshMCPTools refreshes the tools for a named MCP server.
118func (b *Backend) RefreshMCPTools(ctx context.Context, workspaceID, name string) error {
119 ws, err := b.GetWorkspace(workspaceID)
120 if err != nil {
121 return err
122 }
123 mcptools.RefreshTools(ctx, ws.Cfg, name)
124 return nil
125}
126
127// ReadMCPResource reads a resource from a named MCP server.
128func (b *Backend) ReadMCPResource(ctx context.Context, workspaceID, name, uri string) ([]MCPResourceContents, error) {
129 ws, err := b.GetWorkspace(workspaceID)
130 if err != nil {
131 return nil, err
132 }
133 contents, err := mcptools.ReadResource(ctx, ws.Cfg, name, uri)
134 if err != nil {
135 return nil, err
136 }
137 result := make([]MCPResourceContents, len(contents))
138 for i, c := range contents {
139 result[i] = MCPResourceContents{
140 URI: c.URI,
141 MIMEType: c.MIMEType,
142 Text: c.Text,
143 Blob: c.Blob,
144 }
145 }
146 return result, nil
147}
148
149// GetMCPPrompt retrieves a prompt from a named MCP server.
150func (b *Backend) GetMCPPrompt(workspaceID, clientID, promptID string, args map[string]string) (string, error) {
151 ws, err := b.GetWorkspace(workspaceID)
152 if err != nil {
153 return "", err
154 }
155 return commands.GetMCPPrompt(ws.Cfg, clientID, promptID, args)
156}
157
158// GetWorkingDir returns the working directory for a workspace.
159func (b *Backend) GetWorkingDir(workspaceID string) (string, error) {
160 ws, err := b.GetWorkspace(workspaceID)
161 if err != nil {
162 return "", err
163 }
164 return ws.Cfg.WorkingDir(), nil
165}