@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,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 := &registry.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 &registry.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 &registry.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 &registry.ToolOutput{
47
+ Success: false,
48
+ Error: fmt.Sprintf("failed to read file: %v", err),
49
+ }, err
50
+ }
51
+
52
+ return &registry.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 &registry.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 &registry.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 &registry.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 &registry.ToolOutput{
111
+ Success: false,
112
+ Error: fmt.Sprintf("failed to write file: %v", err),
113
+ }, err
114
+ }
115
+
116
+ return &registry.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 &registry.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 &registry.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 &registry.ToolOutput{
176
+ Success: true,
177
+ Data: files,
178
+ Metadata: map[string]interface{}{
179
+ "path": path,
180
+ "count": len(files),
181
+ },
182
+ }, nil
183
+ }