@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,250 @@
|
|
|
1
|
+
package scheduler
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"sync"
|
|
7
|
+
"time"
|
|
8
|
+
|
|
9
|
+
"github.com/ddhanush1/dcode/internal/config"
|
|
10
|
+
"github.com/ddhanush1/dcode/internal/registry"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
// Scheduler manages tool execution with confirmation policies
|
|
14
|
+
type Scheduler struct {
|
|
15
|
+
toolRegistry *registry.ToolRegistry
|
|
16
|
+
policy *config.PolicyFile
|
|
17
|
+
mu sync.Mutex
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// NewScheduler creates a new scheduler
|
|
21
|
+
func NewScheduler(toolRegistry *registry.ToolRegistry, policy *config.PolicyFile) *Scheduler {
|
|
22
|
+
return &Scheduler{
|
|
23
|
+
toolRegistry: toolRegistry,
|
|
24
|
+
policy: policy,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ExecutionRequest represents a tool execution request
|
|
29
|
+
type ExecutionRequest struct {
|
|
30
|
+
ToolID string
|
|
31
|
+
Parameters map[string]interface{}
|
|
32
|
+
Context map[string]interface{}
|
|
33
|
+
Timeout time.Duration
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ExecutionResult represents the result of a tool execution
|
|
37
|
+
type ExecutionResult struct {
|
|
38
|
+
Success bool
|
|
39
|
+
Data interface{}
|
|
40
|
+
Error string
|
|
41
|
+
ExecutionTime time.Duration
|
|
42
|
+
RequiredConfirm bool
|
|
43
|
+
Confirmed bool
|
|
44
|
+
Metadata map[string]interface{}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ConfirmationRequest represents a request for user confirmation
|
|
48
|
+
type ConfirmationRequest struct {
|
|
49
|
+
ToolName string
|
|
50
|
+
Description string
|
|
51
|
+
Parameters map[string]interface{}
|
|
52
|
+
RiskLevel string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ConfirmationHandler is a function that handles confirmation requests
|
|
56
|
+
type ConfirmationHandler func(req *ConfirmationRequest) (bool, error)
|
|
57
|
+
|
|
58
|
+
// Execute executes a tool with confirmation policy
|
|
59
|
+
func (s *Scheduler) Execute(ctx context.Context, req *ExecutionRequest, confirmHandler ConfirmationHandler) (*ExecutionResult, error) {
|
|
60
|
+
s.mu.Lock()
|
|
61
|
+
defer s.mu.Unlock()
|
|
62
|
+
|
|
63
|
+
startTime := time.Now()
|
|
64
|
+
|
|
65
|
+
// Get tool definition
|
|
66
|
+
tool, err := s.toolRegistry.Get(req.ToolID)
|
|
67
|
+
if err != nil {
|
|
68
|
+
return &ExecutionResult{
|
|
69
|
+
Success: false,
|
|
70
|
+
Error: fmt.Sprintf("tool not found: %s", req.ToolID),
|
|
71
|
+
}, err
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
result := &ExecutionResult{
|
|
75
|
+
Metadata: make(map[string]interface{}),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check if confirmation is needed
|
|
79
|
+
needsConfirm := s.needsConfirmation(tool)
|
|
80
|
+
result.RequiredConfirm = needsConfirm
|
|
81
|
+
|
|
82
|
+
if needsConfirm {
|
|
83
|
+
// Request confirmation
|
|
84
|
+
confirmReq := &ConfirmationRequest{
|
|
85
|
+
ToolName: tool.Name,
|
|
86
|
+
Description: tool.Description,
|
|
87
|
+
Parameters: req.Parameters,
|
|
88
|
+
RiskLevel: tool.RiskLevel,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
confirmed, err := confirmHandler(confirmReq)
|
|
92
|
+
if err != nil {
|
|
93
|
+
return &ExecutionResult{
|
|
94
|
+
Success: false,
|
|
95
|
+
Error: fmt.Sprintf("confirmation failed: %v", err),
|
|
96
|
+
}, err
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
result.Confirmed = confirmed
|
|
100
|
+
|
|
101
|
+
if !confirmed {
|
|
102
|
+
return &ExecutionResult{
|
|
103
|
+
Success: false,
|
|
104
|
+
Error: "execution cancelled by user",
|
|
105
|
+
RequiredConfirm: true,
|
|
106
|
+
Confirmed: false,
|
|
107
|
+
}, nil
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
result.Confirmed = true
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Execute the tool
|
|
114
|
+
toolInput := ®istry.ToolInput{
|
|
115
|
+
ToolName: tool.Name,
|
|
116
|
+
Parameters: req.Parameters,
|
|
117
|
+
Context: req.Context,
|
|
118
|
+
Config: tool.Config,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Execute with timeout
|
|
122
|
+
execCtx := ctx
|
|
123
|
+
if req.Timeout > 0 {
|
|
124
|
+
var cancel context.CancelFunc
|
|
125
|
+
execCtx, cancel = context.WithTimeout(ctx, req.Timeout)
|
|
126
|
+
defer cancel()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Run tool in goroutine with context
|
|
130
|
+
type execResult struct {
|
|
131
|
+
output *registry.ToolOutput
|
|
132
|
+
err error
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
resultChan := make(chan execResult, 1)
|
|
136
|
+
|
|
137
|
+
go func() {
|
|
138
|
+
output, err := tool.Executor(toolInput)
|
|
139
|
+
resultChan <- execResult{output: output, err: err}
|
|
140
|
+
}()
|
|
141
|
+
|
|
142
|
+
select {
|
|
143
|
+
case <-execCtx.Done():
|
|
144
|
+
return &ExecutionResult{
|
|
145
|
+
Success: false,
|
|
146
|
+
Error: "execution timeout",
|
|
147
|
+
}, execCtx.Err()
|
|
148
|
+
|
|
149
|
+
case res := <-resultChan:
|
|
150
|
+
if res.err != nil {
|
|
151
|
+
return &ExecutionResult{
|
|
152
|
+
Success: false,
|
|
153
|
+
Error: res.err.Error(),
|
|
154
|
+
ExecutionTime: time.Since(startTime),
|
|
155
|
+
}, res.err
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
result.Success = res.output.Success
|
|
159
|
+
result.Data = res.output.Data
|
|
160
|
+
result.Error = res.output.Error
|
|
161
|
+
result.ExecutionTime = time.Since(startTime)
|
|
162
|
+
|
|
163
|
+
// Merge metadata
|
|
164
|
+
for k, v := range res.output.Metadata {
|
|
165
|
+
result.Metadata[k] = v
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return result, nil
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ExecuteMultiple executes multiple tools in parallel
|
|
173
|
+
func (s *Scheduler) ExecuteMultiple(ctx context.Context, requests []*ExecutionRequest, confirmHandler ConfirmationHandler) ([]*ExecutionResult, error) {
|
|
174
|
+
results := make([]*ExecutionResult, len(requests))
|
|
175
|
+
errors := make([]error, len(requests))
|
|
176
|
+
|
|
177
|
+
var wg sync.WaitGroup
|
|
178
|
+
|
|
179
|
+
for i, req := range requests {
|
|
180
|
+
wg.Add(1)
|
|
181
|
+
go func(index int, request *ExecutionRequest) {
|
|
182
|
+
defer wg.Done()
|
|
183
|
+
|
|
184
|
+
result, err := s.Execute(ctx, request, confirmHandler)
|
|
185
|
+
results[index] = result
|
|
186
|
+
errors[index] = err
|
|
187
|
+
}(i, req)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
wg.Wait()
|
|
191
|
+
|
|
192
|
+
// Check if any execution failed
|
|
193
|
+
var firstErr error
|
|
194
|
+
for _, err := range errors {
|
|
195
|
+
if err != nil && firstErr == nil {
|
|
196
|
+
firstErr = err
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return results, firstErr
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// needsConfirmation checks if a tool needs confirmation based on policy
|
|
204
|
+
func (s *Scheduler) needsConfirmation(tool *registry.ToolDefinition) bool {
|
|
205
|
+
// Check if tool should always be confirmed
|
|
206
|
+
if s.policy.ShouldAlwaysConfirm(tool.ID) {
|
|
207
|
+
return true
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Check if tool should be auto-approved
|
|
211
|
+
if s.policy.ShouldAutoApprove(tool.ID) {
|
|
212
|
+
return false
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Default behavior based on policy mode
|
|
216
|
+
switch s.policy.Mode {
|
|
217
|
+
case "always":
|
|
218
|
+
return true
|
|
219
|
+
case "auto":
|
|
220
|
+
return false
|
|
221
|
+
case "risk_based":
|
|
222
|
+
riskLevel := s.policy.GetRiskLevel(tool.ID)
|
|
223
|
+
switch riskLevel {
|
|
224
|
+
case "low":
|
|
225
|
+
return false
|
|
226
|
+
case "medium":
|
|
227
|
+
return true
|
|
228
|
+
case "high":
|
|
229
|
+
return true
|
|
230
|
+
default:
|
|
231
|
+
return true // Unknown risk, be safe
|
|
232
|
+
}
|
|
233
|
+
default:
|
|
234
|
+
return true // Be safe by default
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// SetPolicy updates the scheduler's policy
|
|
239
|
+
func (s *Scheduler) SetPolicy(policy *config.PolicyFile) {
|
|
240
|
+
s.mu.Lock()
|
|
241
|
+
defer s.mu.Unlock()
|
|
242
|
+
s.policy = policy
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// GetPolicy returns the current policy
|
|
246
|
+
func (s *Scheduler) GetPolicy() *config.PolicyFile {
|
|
247
|
+
s.mu.Lock()
|
|
248
|
+
defer s.mu.Unlock()
|
|
249
|
+
return s.policy
|
|
250
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
package server
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"errors"
|
|
7
|
+
"fmt"
|
|
8
|
+
"log"
|
|
9
|
+
"net/http"
|
|
10
|
+
"os"
|
|
11
|
+
"strconv"
|
|
12
|
+
"time"
|
|
13
|
+
|
|
14
|
+
"github.com/ddhanush1/dcode/internal/agent"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
type Config struct {
|
|
18
|
+
Port int
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func ConfigFromEnv() Config {
|
|
22
|
+
port := 8765
|
|
23
|
+
if v := os.Getenv("SERVER_PORT"); v != "" {
|
|
24
|
+
if p, err := strconv.Atoi(v); err == nil {
|
|
25
|
+
port = p
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return Config{Port: port}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type APIServer struct {
|
|
32
|
+
agent *agent.Agent
|
|
33
|
+
mux *http.ServeMux
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func New(agent *agent.Agent) *APIServer {
|
|
37
|
+
s := &APIServer{agent: agent, mux: http.NewServeMux()}
|
|
38
|
+
|
|
39
|
+
s.mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
|
40
|
+
w.Header().Set("Content-Type", "application/json")
|
|
41
|
+
_ = json.NewEncoder(w).Encode(map[string]any{"ok": true, "time": time.Now().UTC().Format(time.RFC3339)})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
s.mux.HandleFunc("/v1/chat", s.handleChat)
|
|
45
|
+
s.mux.HandleFunc("/v1/edit", s.handleEdit)
|
|
46
|
+
s.mux.HandleFunc("/v1/fix", s.handleFix)
|
|
47
|
+
|
|
48
|
+
return s
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func (s *APIServer) ListenAndServe(ctx context.Context, cfg Config) error {
|
|
52
|
+
httpServer := &http.Server{
|
|
53
|
+
Addr: fmt.Sprintf(":%d", cfg.Port),
|
|
54
|
+
Handler: s.withCORS(s.mux),
|
|
55
|
+
ReadHeaderTimeout: 15 * time.Second,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
errCh := make(chan error, 1)
|
|
59
|
+
go func() {
|
|
60
|
+
log.Printf("dcode server listening on %s", httpServer.Addr)
|
|
61
|
+
errCh <- httpServer.ListenAndServe()
|
|
62
|
+
}()
|
|
63
|
+
|
|
64
|
+
select {
|
|
65
|
+
case <-ctx.Done():
|
|
66
|
+
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
67
|
+
defer cancel()
|
|
68
|
+
_ = httpServer.Shutdown(shutdownCtx)
|
|
69
|
+
return ctx.Err()
|
|
70
|
+
case err := <-errCh:
|
|
71
|
+
if errors.Is(err, http.ErrServerClosed) {
|
|
72
|
+
return nil
|
|
73
|
+
}
|
|
74
|
+
return err
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
func (s *APIServer) withCORS(next http.Handler) http.Handler {
|
|
79
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
80
|
+
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
81
|
+
w.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
|
|
82
|
+
w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Authorization")
|
|
83
|
+
if r.Method == http.MethodOptions {
|
|
84
|
+
w.WriteHeader(http.StatusNoContent)
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
next.ServeHTTP(w, r)
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func decodeJSON(w http.ResponseWriter, r *http.Request, dst any) bool {
|
|
92
|
+
defer r.Body.Close()
|
|
93
|
+
dec := json.NewDecoder(r.Body)
|
|
94
|
+
dec.DisallowUnknownFields()
|
|
95
|
+
if err := dec.Decode(dst); err != nil {
|
|
96
|
+
writeJSONError(w, http.StatusBadRequest, fmt.Sprintf("invalid json: %v", err))
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
return true
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func writeJSON(w http.ResponseWriter, status int, v any) {
|
|
103
|
+
w.Header().Set("Content-Type", "application/json")
|
|
104
|
+
w.WriteHeader(status)
|
|
105
|
+
_ = json.NewEncoder(w).Encode(v)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
func writeJSONError(w http.ResponseWriter, status int, message string) {
|
|
109
|
+
writeJSON(w, status, map[string]any{"error": map[string]any{"message": message}})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func (s *APIServer) handleChat(w http.ResponseWriter, r *http.Request) {
|
|
113
|
+
if r.Method != http.MethodPost {
|
|
114
|
+
writeJSONError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
var req agent.ChatRequest
|
|
119
|
+
if ok := decodeJSON(w, r, &req); !ok {
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
resp, err := s.agent.Chat(r.Context(), req)
|
|
124
|
+
if err != nil {
|
|
125
|
+
writeJSONError(w, http.StatusInternalServerError, err.Error())
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
writeJSON(w, http.StatusOK, resp)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
func (s *APIServer) handleEdit(w http.ResponseWriter, r *http.Request) {
|
|
132
|
+
if r.Method != http.MethodPost {
|
|
133
|
+
writeJSONError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
var req agent.EditRequest
|
|
138
|
+
if ok := decodeJSON(w, r, &req); !ok {
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
resp, err := s.agent.Edit(r.Context(), req)
|
|
143
|
+
if err != nil {
|
|
144
|
+
writeJSONError(w, http.StatusInternalServerError, err.Error())
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
writeJSON(w, http.StatusOK, resp)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
func (s *APIServer) handleFix(w http.ResponseWriter, r *http.Request) {
|
|
151
|
+
if r.Method != http.MethodPost {
|
|
152
|
+
writeJSONError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
var req agent.FixRequest
|
|
157
|
+
if ok := decodeJSON(w, r, &req); !ok {
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
resp, err := s.agent.Fix(r.Context(), req)
|
|
162
|
+
if err != nil {
|
|
163
|
+
writeJSONError(w, http.StatusInternalServerError, err.Error())
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
writeJSON(w, http.StatusOK, resp)
|
|
167
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
package tools
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
|
|
7
|
+
"github.com/ddhanush1/dcode/internal/registry"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// ReadFileTool reads a file from the filesystem
|
|
11
|
+
type ReadFileTool struct{}
|
|
12
|
+
|
|
13
|
+
func NewReadFileTool() *registry.ToolDefinition {
|
|
14
|
+
return ®istry.ToolDefinition{
|
|
15
|
+
ID: "read_file",
|
|
16
|
+
Name: "Read File",
|
|
17
|
+
Description: "Read the contents of a file",
|
|
18
|
+
InputSchema: map[string]interface{}{
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": map[string]interface{}{
|
|
21
|
+
"path": map[string]interface{}{
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Path to the file to read",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
"required": []string{"path"},
|
|
27
|
+
},
|
|
28
|
+
RequiresConfirmation: false,
|
|
29
|
+
RiskLevel: "low",
|
|
30
|
+
Category: "file",
|
|
31
|
+
Executor: executeReadFile,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func executeReadFile(input *registry.ToolInput) (*registry.ToolOutput, error) {
|
|
36
|
+
path, ok := input.Parameters["path"].(string)
|
|
37
|
+
if !ok {
|
|
38
|
+
return ®istry.ToolOutput{
|
|
39
|
+
Success: false,
|
|
40
|
+
Error: "path parameter is required and must be a string",
|
|
41
|
+
}, fmt.Errorf("invalid path parameter")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
content, err := os.ReadFile(path)
|
|
45
|
+
if err != nil {
|
|
46
|
+
return ®istry.ToolOutput{
|
|
47
|
+
Success: false,
|
|
48
|
+
Error: fmt.Sprintf("failed to read file: %v", err),
|
|
49
|
+
}, err
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return ®istry.ToolOutput{
|
|
53
|
+
Success: true,
|
|
54
|
+
Data: string(content),
|
|
55
|
+
Metadata: map[string]interface{}{
|
|
56
|
+
"path": path,
|
|
57
|
+
"size": len(content),
|
|
58
|
+
},
|
|
59
|
+
}, nil
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// WriteFileTool writes content to a file
|
|
63
|
+
type WriteFileTool struct{}
|
|
64
|
+
|
|
65
|
+
func NewWriteFileTool() *registry.ToolDefinition {
|
|
66
|
+
return ®istry.ToolDefinition{
|
|
67
|
+
ID: "write_file",
|
|
68
|
+
Name: "Write File",
|
|
69
|
+
Description: "Write content to a file",
|
|
70
|
+
InputSchema: map[string]interface{}{
|
|
71
|
+
"type": "object",
|
|
72
|
+
"properties": map[string]interface{}{
|
|
73
|
+
"path": map[string]interface{}{
|
|
74
|
+
"type": "string",
|
|
75
|
+
"description": "Path to the file to write",
|
|
76
|
+
},
|
|
77
|
+
"content": map[string]interface{}{
|
|
78
|
+
"type": "string",
|
|
79
|
+
"description": "Content to write to the file",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
"required": []string{"path", "content"},
|
|
83
|
+
},
|
|
84
|
+
RequiresConfirmation: true,
|
|
85
|
+
RiskLevel: "medium",
|
|
86
|
+
Category: "file",
|
|
87
|
+
Executor: executeWriteFile,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func executeWriteFile(input *registry.ToolInput) (*registry.ToolOutput, error) {
|
|
92
|
+
path, ok := input.Parameters["path"].(string)
|
|
93
|
+
if !ok {
|
|
94
|
+
return ®istry.ToolOutput{
|
|
95
|
+
Success: false,
|
|
96
|
+
Error: "path parameter is required and must be a string",
|
|
97
|
+
}, fmt.Errorf("invalid path parameter")
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
content, ok := input.Parameters["content"].(string)
|
|
101
|
+
if !ok {
|
|
102
|
+
return ®istry.ToolOutput{
|
|
103
|
+
Success: false,
|
|
104
|
+
Error: "content parameter is required and must be a string",
|
|
105
|
+
}, fmt.Errorf("invalid content parameter")
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
err := os.WriteFile(path, []byte(content), 0644)
|
|
109
|
+
if err != nil {
|
|
110
|
+
return ®istry.ToolOutput{
|
|
111
|
+
Success: false,
|
|
112
|
+
Error: fmt.Sprintf("failed to write file: %v", err),
|
|
113
|
+
}, err
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return ®istry.ToolOutput{
|
|
117
|
+
Success: true,
|
|
118
|
+
Data: fmt.Sprintf("Successfully wrote %d bytes to %s", len(content), path),
|
|
119
|
+
Metadata: map[string]interface{}{
|
|
120
|
+
"path": path,
|
|
121
|
+
"size": len(content),
|
|
122
|
+
},
|
|
123
|
+
}, nil
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ListFilesTool lists files in a directory
|
|
127
|
+
type ListFilesTool struct{}
|
|
128
|
+
|
|
129
|
+
func NewListFilesTool() *registry.ToolDefinition {
|
|
130
|
+
return ®istry.ToolDefinition{
|
|
131
|
+
ID: "ls",
|
|
132
|
+
Name: "List Files",
|
|
133
|
+
Description: "List files in a directory",
|
|
134
|
+
InputSchema: map[string]interface{}{
|
|
135
|
+
"type": "object",
|
|
136
|
+
"properties": map[string]interface{}{
|
|
137
|
+
"path": map[string]interface{}{
|
|
138
|
+
"type": "string",
|
|
139
|
+
"description": "Path to the directory to list",
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
"required": []string{"path"},
|
|
143
|
+
},
|
|
144
|
+
RequiresConfirmation: false,
|
|
145
|
+
RiskLevel: "low",
|
|
146
|
+
Category: "file",
|
|
147
|
+
Executor: executeListFiles,
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
func executeListFiles(input *registry.ToolInput) (*registry.ToolOutput, error) {
|
|
152
|
+
path, ok := input.Parameters["path"].(string)
|
|
153
|
+
if !ok {
|
|
154
|
+
path = "."
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
entries, err := os.ReadDir(path)
|
|
158
|
+
if err != nil {
|
|
159
|
+
return ®istry.ToolOutput{
|
|
160
|
+
Success: false,
|
|
161
|
+
Error: fmt.Sprintf("failed to list directory: %v", err),
|
|
162
|
+
}, err
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
files := make([]map[string]interface{}, 0, len(entries))
|
|
166
|
+
for _, entry := range entries {
|
|
167
|
+
info, _ := entry.Info()
|
|
168
|
+
files = append(files, map[string]interface{}{
|
|
169
|
+
"name": entry.Name(),
|
|
170
|
+
"is_dir": entry.IsDir(),
|
|
171
|
+
"size": info.Size(),
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return ®istry.ToolOutput{
|
|
176
|
+
Success: true,
|
|
177
|
+
Data: files,
|
|
178
|
+
Metadata: map[string]interface{}{
|
|
179
|
+
"path": path,
|
|
180
|
+
"count": len(files),
|
|
181
|
+
},
|
|
182
|
+
}, nil
|
|
183
|
+
}
|