@dcode-dev/dcode-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/NPM_README.md +78 -0
  2. package/README.md +341 -0
  3. package/bin/dcode-bin +0 -0
  4. package/bin/dcode.js +44 -0
  5. package/cmd/agent_v2.go +448 -0
  6. package/cmd/analyze.go +97 -0
  7. package/cmd/auth.go +338 -0
  8. package/cmd/compose.go +284 -0
  9. package/cmd/context.go +111 -0
  10. package/cmd/edit.go +116 -0
  11. package/cmd/env.go +10 -0
  12. package/cmd/fix.go +145 -0
  13. package/cmd/gemini.go +20 -0
  14. package/cmd/generate.go +47 -0
  15. package/cmd/interactive.go +33 -0
  16. package/cmd/mcp.go +196 -0
  17. package/cmd/patch.go +19 -0
  18. package/cmd/providers.go +67 -0
  19. package/cmd/root.go +41 -0
  20. package/cmd/search.go +61 -0
  21. package/cmd/server.go +36 -0
  22. package/cmd/switch.go +122 -0
  23. package/cmd/terminal.go +277 -0
  24. package/go.mod +42 -0
  25. package/go.sum +86 -0
  26. package/internal/agent/agent.go +332 -0
  27. package/internal/agent/parse.go +25 -0
  28. package/internal/agents/base.go +154 -0
  29. package/internal/agents/documenter.go +77 -0
  30. package/internal/agents/generalist.go +266 -0
  31. package/internal/agents/investigator.go +60 -0
  32. package/internal/agents/registry.go +34 -0
  33. package/internal/agents/reviewer.go +67 -0
  34. package/internal/agents/tester.go +73 -0
  35. package/internal/ai/client.go +634 -0
  36. package/internal/ai/tools.go +332 -0
  37. package/internal/auth/adc.go +108 -0
  38. package/internal/auth/apikey.go +67 -0
  39. package/internal/auth/factory.go +145 -0
  40. package/internal/auth/oauth2.go +227 -0
  41. package/internal/auth/store.go +216 -0
  42. package/internal/auth/types.go +79 -0
  43. package/internal/auth/vertex.go +138 -0
  44. package/internal/config/config.go +428 -0
  45. package/internal/config/policy.go +251 -0
  46. package/internal/context/builder.go +312 -0
  47. package/internal/detector/detector.go +204 -0
  48. package/internal/diffutil/diffutil.go +30 -0
  49. package/internal/fsutil/fsutil.go +35 -0
  50. package/internal/mcp/client.go +314 -0
  51. package/internal/mcp/manager.go +221 -0
  52. package/internal/policy/policy.go +89 -0
  53. package/internal/prompt/interactive.go +338 -0
  54. package/internal/registry/agent.go +201 -0
  55. package/internal/registry/tool.go +181 -0
  56. package/internal/scheduler/scheduler.go +250 -0
  57. package/internal/server/server.go +167 -0
  58. package/internal/tools/file.go +183 -0
  59. package/internal/tools/filesystem.go +286 -0
  60. package/internal/tools/git.go +355 -0
  61. package/internal/tools/memory.go +269 -0
  62. package/internal/tools/registry.go +49 -0
  63. package/internal/tools/search.go +230 -0
  64. package/internal/tools/shell.go +84 -0
  65. package/internal/websearch/search.go +40 -0
  66. package/internal/websearch/tavily.go +79 -0
  67. package/main.go +19 -0
  68. package/package.json +57 -0
  69. package/scripts/install.js +59 -0
  70. package/scripts/uninstall.js +28 -0
@@ -0,0 +1,269 @@
1
+ package tools
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "os"
7
+ "path/filepath"
8
+ "time"
9
+
10
+ "github.com/ddhanush1/dcode/internal/registry"
11
+ )
12
+
13
+ // Memory represents a stored memory
14
+ type Memory struct {
15
+ Key string `json:"key"`
16
+ Value string `json:"value"`
17
+ Metadata map[string]interface{} `json:"metadata"`
18
+ Timestamp time.Time `json:"timestamp"`
19
+ }
20
+
21
+ // SaveMemoryTool saves information to memory
22
+ type SaveMemoryTool struct{}
23
+
24
+ func NewSaveMemoryTool() *registry.ToolDefinition {
25
+ return &registry.ToolDefinition{
26
+ ID: "save_memory",
27
+ Name: "Save Memory",
28
+ Description: "Save information to persistent memory",
29
+ InputSchema: map[string]interface{}{
30
+ "type": "object",
31
+ "properties": map[string]interface{}{
32
+ "key": map[string]interface{}{
33
+ "type": "string",
34
+ "description": "Memory key/identifier",
35
+ },
36
+ "value": map[string]interface{}{
37
+ "type": "string",
38
+ "description": "Value to store",
39
+ },
40
+ },
41
+ "required": []string{"key", "value"},
42
+ },
43
+ RequiresConfirmation: false,
44
+ RiskLevel: "low",
45
+ Category: "memory",
46
+ Executor: executeSaveMemory,
47
+ }
48
+ }
49
+
50
+ func executeSaveMemory(input *registry.ToolInput) (*registry.ToolOutput, error) {
51
+ key, ok := input.Parameters["key"].(string)
52
+ if !ok {
53
+ return &registry.ToolOutput{
54
+ Success: false,
55
+ Error: "key parameter is required",
56
+ }, fmt.Errorf("invalid key parameter")
57
+ }
58
+
59
+ value, ok := input.Parameters["value"].(string)
60
+ if !ok {
61
+ return &registry.ToolOutput{
62
+ Success: false,
63
+ Error: "value parameter is required",
64
+ }, fmt.Errorf("invalid value parameter")
65
+ }
66
+
67
+ memory := Memory{
68
+ Key: key,
69
+ Value: value,
70
+ Metadata: make(map[string]interface{}),
71
+ Timestamp: time.Now(),
72
+ }
73
+
74
+ // Save to file
75
+ memoryDir, err := getMemoryDir()
76
+ if err != nil {
77
+ return &registry.ToolOutput{
78
+ Success: false,
79
+ Error: fmt.Sprintf("failed to get memory directory: %v", err),
80
+ }, err
81
+ }
82
+
83
+ filePath := filepath.Join(memoryDir, key+".json")
84
+ data, err := json.MarshalIndent(memory, "", " ")
85
+ if err != nil {
86
+ return &registry.ToolOutput{
87
+ Success: false,
88
+ Error: fmt.Sprintf("failed to marshal memory: %v", err),
89
+ }, err
90
+ }
91
+
92
+ if err := os.WriteFile(filePath, data, 0644); err != nil {
93
+ return &registry.ToolOutput{
94
+ Success: false,
95
+ Error: fmt.Sprintf("failed to write memory: %v", err),
96
+ }, err
97
+ }
98
+
99
+ return &registry.ToolOutput{
100
+ Success: true,
101
+ Data: fmt.Sprintf("Saved memory: %s", key),
102
+ Metadata: map[string]interface{}{
103
+ "key": key,
104
+ "path": filePath,
105
+ },
106
+ }, nil
107
+ }
108
+
109
+ // RecallMemoryTool retrieves information from memory
110
+ type RecallMemoryTool struct{}
111
+
112
+ func NewRecallMemoryTool() *registry.ToolDefinition {
113
+ return &registry.ToolDefinition{
114
+ ID: "recall_memory",
115
+ Name: "Recall Memory",
116
+ Description: "Retrieve information from persistent memory",
117
+ InputSchema: map[string]interface{}{
118
+ "type": "object",
119
+ "properties": map[string]interface{}{
120
+ "key": map[string]interface{}{
121
+ "type": "string",
122
+ "description": "Memory key to retrieve",
123
+ },
124
+ },
125
+ "required": []string{"key"},
126
+ },
127
+ RequiresConfirmation: false,
128
+ RiskLevel: "low",
129
+ Category: "memory",
130
+ Executor: executeRecallMemory,
131
+ }
132
+ }
133
+
134
+ func executeRecallMemory(input *registry.ToolInput) (*registry.ToolOutput, error) {
135
+ key, ok := input.Parameters["key"].(string)
136
+ if !ok {
137
+ return &registry.ToolOutput{
138
+ Success: false,
139
+ Error: "key parameter is required",
140
+ }, fmt.Errorf("invalid key parameter")
141
+ }
142
+
143
+ memoryDir, err := getMemoryDir()
144
+ if err != nil {
145
+ return &registry.ToolOutput{
146
+ Success: false,
147
+ Error: fmt.Sprintf("failed to get memory directory: %v", err),
148
+ }, err
149
+ }
150
+
151
+ filePath := filepath.Join(memoryDir, key+".json")
152
+ data, err := os.ReadFile(filePath)
153
+ if err != nil {
154
+ return &registry.ToolOutput{
155
+ Success: false,
156
+ Error: fmt.Sprintf("memory not found: %s", key),
157
+ }, err
158
+ }
159
+
160
+ var memory Memory
161
+ if err := json.Unmarshal(data, &memory); err != nil {
162
+ return &registry.ToolOutput{
163
+ Success: false,
164
+ Error: fmt.Sprintf("failed to parse memory: %v", err),
165
+ }, err
166
+ }
167
+
168
+ return &registry.ToolOutput{
169
+ Success: true,
170
+ Data: memory.Value,
171
+ Metadata: map[string]interface{}{
172
+ "key": memory.Key,
173
+ "timestamp": memory.Timestamp,
174
+ },
175
+ }, nil
176
+ }
177
+
178
+ // ListMemoriesTool lists all stored memories
179
+ type ListMemoriesTool struct{}
180
+
181
+ func NewListMemoriesTool() *registry.ToolDefinition {
182
+ return &registry.ToolDefinition{
183
+ ID: "list_memories",
184
+ Name: "List Memories",
185
+ Description: "List all stored memories",
186
+ InputSchema: map[string]interface{}{
187
+ "type": "object",
188
+ },
189
+ RequiresConfirmation: false,
190
+ RiskLevel: "low",
191
+ Category: "memory",
192
+ Executor: executeListMemories,
193
+ }
194
+ }
195
+
196
+ func executeListMemories(input *registry.ToolInput) (*registry.ToolOutput, error) {
197
+ memoryDir, err := getMemoryDir()
198
+ if err != nil {
199
+ return &registry.ToolOutput{
200
+ Success: false,
201
+ Error: fmt.Sprintf("failed to get memory directory: %v", err),
202
+ }, err
203
+ }
204
+
205
+ entries, err := os.ReadDir(memoryDir)
206
+ if err != nil {
207
+ return &registry.ToolOutput{
208
+ Success: true,
209
+ Data: []string{},
210
+ Metadata: map[string]interface{}{
211
+ "count": 0,
212
+ },
213
+ }, nil
214
+ }
215
+
216
+ memories := make([]map[string]interface{}, 0)
217
+ for _, entry := range entries {
218
+ if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" {
219
+ continue
220
+ }
221
+
222
+ filePath := filepath.Join(memoryDir, entry.Name())
223
+
224
+ data, err := os.ReadFile(filePath)
225
+ if err != nil {
226
+ continue
227
+ }
228
+
229
+ var memory Memory
230
+ if err := json.Unmarshal(data, &memory); err != nil {
231
+ continue
232
+ }
233
+
234
+ memories = append(memories, map[string]interface{}{
235
+ "key": memory.Key,
236
+ "timestamp": memory.Timestamp,
237
+ "preview": truncate(memory.Value, 50),
238
+ })
239
+ }
240
+
241
+ return &registry.ToolOutput{
242
+ Success: true,
243
+ Data: memories,
244
+ Metadata: map[string]interface{}{
245
+ "count": len(memories),
246
+ },
247
+ }, nil
248
+ }
249
+
250
+ func getMemoryDir() (string, error) {
251
+ homeDir, err := os.UserHomeDir()
252
+ if err != nil {
253
+ return "", err
254
+ }
255
+
256
+ memoryDir := filepath.Join(homeDir, ".dcode", "memory")
257
+ if err := os.MkdirAll(memoryDir, 0755); err != nil {
258
+ return "", err
259
+ }
260
+
261
+ return memoryDir, nil
262
+ }
263
+
264
+ func truncate(s string, maxLen int) string {
265
+ if len(s) <= maxLen {
266
+ return s
267
+ }
268
+ return s[:maxLen] + "..."
269
+ }
@@ -0,0 +1,49 @@
1
+ package tools
2
+
3
+ import (
4
+ "github.com/ddhanush1/dcode/internal/registry"
5
+ )
6
+
7
+ // RegisterBuiltinTools registers all built-in tools
8
+ func RegisterBuiltinTools(toolRegistry *registry.ToolRegistry) error {
9
+ tools := []*registry.ToolDefinition{
10
+ // File operations
11
+ NewReadFileTool(),
12
+ NewWriteFileTool(),
13
+ NewListFilesTool(),
14
+ NewDeleteFileTool(),
15
+ NewMoveFileTool(),
16
+ NewCreateDirectoryTool(),
17
+
18
+ // Shell
19
+ NewShellTool(),
20
+
21
+ // Search & patterns
22
+ NewGrepTool(),
23
+ NewGlobTool(),
24
+
25
+ // Git operations
26
+ NewGitStatusTool(),
27
+ NewGitDiffTool(),
28
+ NewGitLogTool(),
29
+ NewGitCommitTool(),
30
+ NewGitBranchTool(),
31
+
32
+ // Interactive
33
+ NewAskUserTool(),
34
+ NewConfirmTool(),
35
+
36
+ // Memory
37
+ NewSaveMemoryTool(),
38
+ NewRecallMemoryTool(),
39
+ NewListMemoriesTool(),
40
+ }
41
+
42
+ for _, tool := range tools {
43
+ if err := toolRegistry.Register(tool); err != nil {
44
+ return err
45
+ }
46
+ }
47
+
48
+ return nil
49
+ }
@@ -0,0 +1,230 @@
1
+ package tools
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "os/exec"
7
+ "strings"
8
+ "time"
9
+
10
+ "github.com/ddhanush1/dcode/internal/registry"
11
+ )
12
+
13
+ // GrepTool searches for patterns in files
14
+ type GrepTool struct{}
15
+
16
+ func NewGrepTool() *registry.ToolDefinition {
17
+ return &registry.ToolDefinition{
18
+ ID: "grep",
19
+ Name: "Grep Search",
20
+ Description: "Search for patterns in files using grep",
21
+ InputSchema: map[string]interface{}{
22
+ "type": "object",
23
+ "properties": map[string]interface{}{
24
+ "pattern": map[string]interface{}{
25
+ "type": "string",
26
+ "description": "Search pattern",
27
+ },
28
+ "path": map[string]interface{}{
29
+ "type": "string",
30
+ "description": "Path to search in",
31
+ },
32
+ "recursive": map[string]interface{}{
33
+ "type": "boolean",
34
+ "description": "Search recursively",
35
+ },
36
+ "ignore_case": map[string]interface{}{
37
+ "type": "boolean",
38
+ "description": "Ignore case",
39
+ },
40
+ },
41
+ "required": []string{"pattern"},
42
+ },
43
+ RequiresConfirmation: false,
44
+ RiskLevel: "low",
45
+ Category: "search",
46
+ Executor: executeGrep,
47
+ }
48
+ }
49
+
50
+ func executeGrep(input *registry.ToolInput) (*registry.ToolOutput, error) {
51
+ pattern, ok := input.Parameters["pattern"].(string)
52
+ if !ok {
53
+ return &registry.ToolOutput{
54
+ Success: false,
55
+ Error: "pattern parameter is required",
56
+ }, fmt.Errorf("invalid pattern parameter")
57
+ }
58
+
59
+ path := "."
60
+ if p, ok := input.Parameters["path"].(string); ok {
61
+ path = p
62
+ }
63
+
64
+ recursive := true
65
+ if r, ok := input.Parameters["recursive"].(bool); ok {
66
+ recursive = r
67
+ }
68
+
69
+ ignoreCase := false
70
+ if i, ok := input.Parameters["ignore_case"].(bool); ok {
71
+ ignoreCase = i
72
+ }
73
+
74
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
75
+ defer cancel()
76
+
77
+ args := []string{}
78
+ if ignoreCase {
79
+ args = append(args, "-i")
80
+ }
81
+ if recursive {
82
+ args = append(args, "-r")
83
+ }
84
+ args = append(args, "-n", pattern, path)
85
+
86
+ cmd := exec.CommandContext(ctx, "grep", args...)
87
+ output, err := cmd.CombinedOutput()
88
+
89
+ // grep returns exit code 1 if no matches, which is not an error
90
+ if err != nil && len(output) == 0 {
91
+ return &registry.ToolOutput{
92
+ Success: true,
93
+ Data: "No matches found",
94
+ Metadata: map[string]interface{}{
95
+ "pattern": pattern,
96
+ "path": path,
97
+ "matches": 0,
98
+ },
99
+ }, nil
100
+ }
101
+
102
+ return &registry.ToolOutput{
103
+ Success: true,
104
+ Data: string(output),
105
+ Metadata: map[string]interface{}{
106
+ "pattern": pattern,
107
+ "path": path,
108
+ "matches": strings.Count(string(output), "\n"),
109
+ },
110
+ }, nil
111
+ }
112
+
113
+ // AskUserTool prompts the user for input
114
+ type AskUserTool struct{}
115
+
116
+ func NewAskUserTool() *registry.ToolDefinition {
117
+ return &registry.ToolDefinition{
118
+ ID: "ask_user",
119
+ Name: "Ask User",
120
+ Description: "Ask the user a question and get their response",
121
+ InputSchema: map[string]interface{}{
122
+ "type": "object",
123
+ "properties": map[string]interface{}{
124
+ "question": map[string]interface{}{
125
+ "type": "string",
126
+ "description": "Question to ask the user",
127
+ },
128
+ "default": map[string]interface{}{
129
+ "type": "string",
130
+ "description": "Default answer",
131
+ },
132
+ },
133
+ "required": []string{"question"},
134
+ },
135
+ RequiresConfirmation: false,
136
+ RiskLevel: "low",
137
+ Category: "interactive",
138
+ Executor: executeAskUser,
139
+ }
140
+ }
141
+
142
+ func executeAskUser(input *registry.ToolInput) (*registry.ToolOutput, error) {
143
+ question, ok := input.Parameters["question"].(string)
144
+ if !ok {
145
+ return &registry.ToolOutput{
146
+ Success: false,
147
+ Error: "question parameter is required",
148
+ }, fmt.Errorf("invalid question parameter")
149
+ }
150
+
151
+ defaultAnswer := ""
152
+ if d, ok := input.Parameters["default"].(string); ok {
153
+ defaultAnswer = d
154
+ }
155
+
156
+ // Print question
157
+ if defaultAnswer != "" {
158
+ fmt.Printf("%s [%s]: ", question, defaultAnswer)
159
+ } else {
160
+ fmt.Printf("%s: ", question)
161
+ }
162
+
163
+ // Read answer
164
+ var answer string
165
+ fmt.Scanln(&answer)
166
+
167
+ if answer == "" {
168
+ answer = defaultAnswer
169
+ }
170
+
171
+ return &registry.ToolOutput{
172
+ Success: true,
173
+ Data: answer,
174
+ Metadata: map[string]interface{}{
175
+ "question": question,
176
+ "answer": answer,
177
+ },
178
+ }, nil
179
+ }
180
+
181
+ // ConfirmTool asks for yes/no confirmation
182
+ type ConfirmTool struct{}
183
+
184
+ func NewConfirmTool() *registry.ToolDefinition {
185
+ return &registry.ToolDefinition{
186
+ ID: "confirm",
187
+ Name: "Confirm",
188
+ Description: "Ask for yes/no confirmation",
189
+ InputSchema: map[string]interface{}{
190
+ "type": "object",
191
+ "properties": map[string]interface{}{
192
+ "message": map[string]interface{}{
193
+ "type": "string",
194
+ "description": "Confirmation message",
195
+ },
196
+ },
197
+ "required": []string{"message"},
198
+ },
199
+ RequiresConfirmation: false,
200
+ RiskLevel: "low",
201
+ Category: "interactive",
202
+ Executor: executeConfirm,
203
+ }
204
+ }
205
+
206
+ func executeConfirm(input *registry.ToolInput) (*registry.ToolOutput, error) {
207
+ message, ok := input.Parameters["message"].(string)
208
+ if !ok {
209
+ return &registry.ToolOutput{
210
+ Success: false,
211
+ Error: "message parameter is required",
212
+ }, fmt.Errorf("invalid message parameter")
213
+ }
214
+
215
+ fmt.Printf("%s (y/n): ", message)
216
+
217
+ var answer string
218
+ fmt.Scanln(&answer)
219
+
220
+ confirmed := strings.ToLower(answer) == "y" || strings.ToLower(answer) == "yes"
221
+
222
+ return &registry.ToolOutput{
223
+ Success: true,
224
+ Data: confirmed,
225
+ Metadata: map[string]interface{}{
226
+ "message": message,
227
+ "confirmed": confirmed,
228
+ },
229
+ }, nil
230
+ }
@@ -0,0 +1,84 @@
1
+ package tools
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "os/exec"
7
+ "time"
8
+
9
+ "github.com/ddhanush1/dcode/internal/registry"
10
+ )
11
+
12
+ // ShellTool executes shell commands
13
+ type ShellTool struct{}
14
+
15
+ func NewShellTool() *registry.ToolDefinition {
16
+ return &registry.ToolDefinition{
17
+ ID: "shell",
18
+ Name: "Shell Command",
19
+ Description: "Execute a shell command",
20
+ InputSchema: map[string]interface{}{
21
+ "type": "object",
22
+ "properties": map[string]interface{}{
23
+ "command": map[string]interface{}{
24
+ "type": "string",
25
+ "description": "Shell command to execute",
26
+ },
27
+ "timeout": map[string]interface{}{
28
+ "type": "number",
29
+ "description": "Timeout in seconds (default: 300)",
30
+ },
31
+ },
32
+ "required": []string{"command"},
33
+ },
34
+ RequiresConfirmation: true,
35
+ RiskLevel: "high",
36
+ Category: "shell",
37
+ Executor: executeShell,
38
+ }
39
+ }
40
+
41
+ func executeShell(input *registry.ToolInput) (*registry.ToolOutput, error) {
42
+ command, ok := input.Parameters["command"].(string)
43
+ if !ok {
44
+ return &registry.ToolOutput{
45
+ Success: false,
46
+ Error: "command parameter is required and must be a string",
47
+ }, fmt.Errorf("invalid command parameter")
48
+ }
49
+
50
+ // Get timeout (default 300 seconds)
51
+ timeout := 300
52
+ if t, ok := input.Parameters["timeout"].(float64); ok {
53
+ timeout = int(t)
54
+ }
55
+
56
+ // Create context with timeout
57
+ ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
58
+ defer cancel()
59
+
60
+ // Execute command
61
+ cmd := exec.CommandContext(ctx, "sh", "-c", command)
62
+ output, err := cmd.CombinedOutput()
63
+
64
+ if err != nil {
65
+ return &registry.ToolOutput{
66
+ Success: false,
67
+ Data: string(output),
68
+ Error: fmt.Sprintf("command failed: %v", err),
69
+ Metadata: map[string]interface{}{
70
+ "command": command,
71
+ "exit_code": cmd.ProcessState.ExitCode(),
72
+ },
73
+ }, nil // Don't return error, just mark as failed
74
+ }
75
+
76
+ return &registry.ToolOutput{
77
+ Success: true,
78
+ Data: string(output),
79
+ Metadata: map[string]interface{}{
80
+ "command": command,
81
+ "exit_code": 0,
82
+ },
83
+ }, nil
84
+ }
@@ -0,0 +1,40 @@
1
+ package websearch
2
+
3
+ import (
4
+ "context"
5
+ "os"
6
+ "strings"
7
+ )
8
+
9
+ type Result struct {
10
+ Title string
11
+ URL string
12
+ Snippet string
13
+ }
14
+
15
+ type Searcher interface {
16
+ Search(ctx context.Context, query string) ([]Result, error)
17
+ }
18
+
19
+ func NewFromEnv() Searcher {
20
+ provider := strings.ToLower(strings.TrimSpace(os.Getenv("WEB_SEARCH_PROVIDER")))
21
+ if provider == "" {
22
+ provider = strings.ToLower(strings.TrimSpace(os.Getenv("SEARCH_PROVIDER")))
23
+ }
24
+
25
+ switch provider {
26
+ case "", "none", "disabled":
27
+ return nil
28
+ case "tavily":
29
+ key := strings.TrimSpace(os.Getenv("TAVILY_API_KEY"))
30
+ if key == "" {
31
+ key = strings.TrimSpace(os.Getenv("SEARCH_API_KEY"))
32
+ }
33
+ if key == "" {
34
+ return nil
35
+ }
36
+ return NewTavily(key)
37
+ default:
38
+ return nil
39
+ }
40
+ }