@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.
- package/NPM_README.md +78 -0
- package/README.md +341 -0
- package/bin/dcode-bin +0 -0
- package/bin/dcode.js +44 -0
- package/cmd/agent_v2.go +448 -0
- package/cmd/analyze.go +97 -0
- package/cmd/auth.go +338 -0
- package/cmd/compose.go +284 -0
- package/cmd/context.go +111 -0
- package/cmd/edit.go +116 -0
- package/cmd/env.go +10 -0
- package/cmd/fix.go +145 -0
- package/cmd/gemini.go +20 -0
- package/cmd/generate.go +47 -0
- package/cmd/interactive.go +33 -0
- package/cmd/mcp.go +196 -0
- package/cmd/patch.go +19 -0
- package/cmd/providers.go +67 -0
- package/cmd/root.go +41 -0
- package/cmd/search.go +61 -0
- package/cmd/server.go +36 -0
- package/cmd/switch.go +122 -0
- package/cmd/terminal.go +277 -0
- package/go.mod +42 -0
- package/go.sum +86 -0
- package/internal/agent/agent.go +332 -0
- package/internal/agent/parse.go +25 -0
- package/internal/agents/base.go +154 -0
- package/internal/agents/documenter.go +77 -0
- package/internal/agents/generalist.go +266 -0
- package/internal/agents/investigator.go +60 -0
- package/internal/agents/registry.go +34 -0
- package/internal/agents/reviewer.go +67 -0
- package/internal/agents/tester.go +73 -0
- package/internal/ai/client.go +634 -0
- package/internal/ai/tools.go +332 -0
- package/internal/auth/adc.go +108 -0
- package/internal/auth/apikey.go +67 -0
- package/internal/auth/factory.go +145 -0
- package/internal/auth/oauth2.go +227 -0
- package/internal/auth/store.go +216 -0
- package/internal/auth/types.go +79 -0
- package/internal/auth/vertex.go +138 -0
- package/internal/config/config.go +428 -0
- package/internal/config/policy.go +251 -0
- package/internal/context/builder.go +312 -0
- package/internal/detector/detector.go +204 -0
- package/internal/diffutil/diffutil.go +30 -0
- package/internal/fsutil/fsutil.go +35 -0
- package/internal/mcp/client.go +314 -0
- package/internal/mcp/manager.go +221 -0
- package/internal/policy/policy.go +89 -0
- package/internal/prompt/interactive.go +338 -0
- package/internal/registry/agent.go +201 -0
- package/internal/registry/tool.go +181 -0
- package/internal/scheduler/scheduler.go +250 -0
- package/internal/server/server.go +167 -0
- package/internal/tools/file.go +183 -0
- package/internal/tools/filesystem.go +286 -0
- package/internal/tools/git.go +355 -0
- package/internal/tools/memory.go +269 -0
- package/internal/tools/registry.go +49 -0
- package/internal/tools/search.go +230 -0
- package/internal/tools/shell.go +84 -0
- package/internal/websearch/search.go +40 -0
- package/internal/websearch/tavily.go +79 -0
- package/main.go +19 -0
- package/package.json +57 -0
- package/scripts/install.js +59 -0
- 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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.ToolOutput{
|
|
94
|
+
Success: false,
|
|
95
|
+
Error: fmt.Sprintf("failed to write memory: %v", err),
|
|
96
|
+
}, err
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.ToolOutput{
|
|
163
|
+
Success: false,
|
|
164
|
+
Error: fmt.Sprintf("failed to parse memory: %v", err),
|
|
165
|
+
}, err
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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 ®istry.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
|
+
}
|