@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,312 @@
1
+ package context
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "path/filepath"
7
+ "sort"
8
+ "strings"
9
+ "time"
10
+
11
+ "github.com/ddhanush1/dcode/internal/detector"
12
+ "github.com/ddhanush1/dcode/internal/fsutil"
13
+ )
14
+
15
+ // ProjectContext holds information about the current project
16
+ type ProjectContext struct {
17
+ RootDir string
18
+ ProjectType *detector.ProjectType
19
+ RelevantFiles []FileInfo
20
+ Summary string
21
+ UpdatedAt time.Time
22
+ }
23
+
24
+ // FileInfo contains metadata about a file
25
+ type FileInfo struct {
26
+ Path string
27
+ RelPath string
28
+ Size int64
29
+ ModTime time.Time
30
+ Language string
31
+ Relevance float64 // 0.0 to 1.0
32
+ }
33
+
34
+ // Builder builds project context
35
+ type Builder struct {
36
+ rootDir string
37
+ }
38
+
39
+ // NewBuilder creates a new context builder
40
+ func NewBuilder(rootDir string) *Builder {
41
+ return &Builder{rootDir: rootDir}
42
+ }
43
+
44
+ // Build analyzes the project and builds context
45
+ func (b *Builder) Build() (*ProjectContext, error) {
46
+ // Detect project type
47
+ projectType, err := detector.Detect(b.rootDir)
48
+ if err != nil {
49
+ return nil, fmt.Errorf("failed to detect project: %w", err)
50
+ }
51
+
52
+ // Get relevant files
53
+ files, err := b.getRelevantFiles(100) // Limit to 100 files
54
+ if err != nil {
55
+ return nil, fmt.Errorf("failed to scan files: %w", err)
56
+ }
57
+
58
+ // Score and sort files by relevance
59
+ b.scoreFiles(files, projectType)
60
+ sort.Slice(files, func(i, j int) bool {
61
+ return files[i].Relevance > files[j].Relevance
62
+ })
63
+
64
+ // Generate summary
65
+ summary := b.generateSummary(projectType, files)
66
+
67
+ ctx := &ProjectContext{
68
+ RootDir: b.rootDir,
69
+ ProjectType: projectType,
70
+ RelevantFiles: files,
71
+ Summary: summary,
72
+ UpdatedAt: time.Now(),
73
+ }
74
+
75
+ return ctx, nil
76
+ }
77
+
78
+ func (b *Builder) getRelevantFiles(maxFiles int) ([]FileInfo, error) {
79
+ var files []FileInfo
80
+
81
+ err := filepath.Walk(b.rootDir, func(path string, info os.FileInfo, err error) error {
82
+ if err != nil {
83
+ return nil // Skip errors
84
+ }
85
+
86
+ if info.IsDir() {
87
+ // Skip common directories
88
+ name := info.Name()
89
+ if name == ".git" || name == "node_modules" || name == "vendor" ||
90
+ name == "__pycache__" || name == "dist" || name == "build" {
91
+ return filepath.SkipDir
92
+ }
93
+ return nil
94
+ }
95
+
96
+ // Check if file is relevant
97
+ if detector.IsRelevantFile(path) {
98
+ relPath, _ := filepath.Rel(b.rootDir, path)
99
+ files = append(files, FileInfo{
100
+ Path: path,
101
+ RelPath: relPath,
102
+ Size: info.Size(),
103
+ ModTime: info.ModTime(),
104
+ Language: detectLanguage(path),
105
+ Relevance: 0.5, // Default relevance
106
+ })
107
+
108
+ // Limit number of files
109
+ if maxFiles > 0 && len(files) >= maxFiles {
110
+ return filepath.SkipDir
111
+ }
112
+ }
113
+
114
+ return nil
115
+ })
116
+
117
+ if err != nil && err != filepath.SkipDir {
118
+ return nil, err
119
+ }
120
+
121
+ return files, nil
122
+ }
123
+
124
+ func (b *Builder) scoreFiles(files []FileInfo, projectType *detector.ProjectType) {
125
+ for i := range files {
126
+ score := 0.5 // Base score
127
+
128
+ path := files[i].RelPath
129
+ base := filepath.Base(path)
130
+
131
+ // Entry points get highest priority
132
+ for _, ep := range projectType.EntryPoints {
133
+ if base == ep {
134
+ score = 1.0
135
+ break
136
+ }
137
+ }
138
+
139
+ // Config files are important
140
+ for _, cf := range projectType.ConfigFiles {
141
+ if base == cf {
142
+ score = 0.9
143
+ break
144
+ }
145
+ }
146
+
147
+ // Files in root directory are more relevant
148
+ if !strings.Contains(path, string(filepath.Separator)) {
149
+ score += 0.2
150
+ }
151
+
152
+ // Language-specific scoring
153
+ if files[i].Language == projectType.Language {
154
+ score += 0.1
155
+ }
156
+
157
+ // Common important files
158
+ switch base {
159
+ case "README.md", "README.txt", "README":
160
+ score = 0.95
161
+ case "Makefile", "Dockerfile", ".env":
162
+ score = 0.85
163
+ }
164
+
165
+ // Boost test files slightly
166
+ if strings.Contains(base, "_test") || strings.Contains(base, ".test.") {
167
+ score += 0.05
168
+ }
169
+
170
+ // Penalize very large files
171
+ if files[i].Size > 100*1024 { // > 100KB
172
+ score -= 0.1
173
+ }
174
+
175
+ // Penalize generated files
176
+ if strings.Contains(path, "generated") || strings.Contains(path, "gen") {
177
+ score -= 0.2
178
+ }
179
+
180
+ // Ensure score is in valid range
181
+ if score < 0 {
182
+ score = 0
183
+ }
184
+ if score > 1 {
185
+ score = 1
186
+ }
187
+
188
+ files[i].Relevance = score
189
+ }
190
+ }
191
+
192
+ func (b *Builder) generateSummary(projectType *detector.ProjectType, files []FileInfo) string {
193
+ var sb strings.Builder
194
+
195
+ sb.WriteString("# Project Overview\n\n")
196
+
197
+ // Project type info
198
+ if projectType.Language != "" {
199
+ sb.WriteString(fmt.Sprintf("**Language:** %s\n", projectType.Language))
200
+ }
201
+ if projectType.Framework != "" {
202
+ sb.WriteString(fmt.Sprintf("**Framework:** %s\n", projectType.Framework))
203
+ }
204
+ if projectType.BuildSystem != "" {
205
+ sb.WriteString(fmt.Sprintf("**Build System:** %s\n", projectType.BuildSystem))
206
+ }
207
+ sb.WriteString(fmt.Sprintf("**Total Files:** %d\n\n", len(files)))
208
+
209
+ // Entry points
210
+ if len(projectType.EntryPoints) > 0 {
211
+ sb.WriteString("**Entry Points:**\n")
212
+ for _, ep := range projectType.EntryPoints {
213
+ sb.WriteString(fmt.Sprintf("- %s\n", ep))
214
+ }
215
+ sb.WriteString("\n")
216
+ }
217
+
218
+ // Top relevant files
219
+ sb.WriteString("**Key Files (by relevance):**\n")
220
+ count := 0
221
+ for _, f := range files {
222
+ if count >= 10 { // Show top 10
223
+ break
224
+ }
225
+ sb.WriteString(fmt.Sprintf("- %s (%.1f%%)\n", f.RelPath, f.Relevance*100))
226
+ count++
227
+ }
228
+
229
+ // File type breakdown
230
+ langCount := make(map[string]int)
231
+ for _, f := range files {
232
+ if f.Language != "" {
233
+ langCount[f.Language]++
234
+ }
235
+ }
236
+ if len(langCount) > 0 {
237
+ sb.WriteString("\n**File Types:**\n")
238
+ for lang, count := range langCount {
239
+ sb.WriteString(fmt.Sprintf("- %s: %d files\n", lang, count))
240
+ }
241
+ }
242
+
243
+ return sb.String()
244
+ }
245
+
246
+ func detectLanguage(path string) string {
247
+ ext := filepath.Ext(path)
248
+ switch ext {
249
+ case ".go":
250
+ return "go"
251
+ case ".py":
252
+ return "python"
253
+ case ".js":
254
+ return "javascript"
255
+ case ".ts":
256
+ return "typescript"
257
+ case ".tsx":
258
+ return "tsx"
259
+ case ".jsx":
260
+ return "jsx"
261
+ case ".java":
262
+ return "java"
263
+ case ".rs":
264
+ return "rust"
265
+ case ".c", ".h":
266
+ return "c"
267
+ case ".cpp", ".hpp":
268
+ return "cpp"
269
+ case ".rb":
270
+ return "ruby"
271
+ case ".php":
272
+ return "php"
273
+ case ".cs":
274
+ return "csharp"
275
+ case ".md":
276
+ return "markdown"
277
+ case ".json":
278
+ return "json"
279
+ case ".yaml", ".yml":
280
+ return "yaml"
281
+ case ".toml":
282
+ return "toml"
283
+ case ".sql":
284
+ return "sql"
285
+ default:
286
+ return ""
287
+ }
288
+ }
289
+
290
+ // GetTopFiles returns the top N most relevant files
291
+ func (ctx *ProjectContext) GetTopFiles(n int) []FileInfo {
292
+ if n > len(ctx.RelevantFiles) {
293
+ n = len(ctx.RelevantFiles)
294
+ }
295
+ return ctx.RelevantFiles[:n]
296
+ }
297
+
298
+ // ReadTopFiles reads content of top N files
299
+ func (ctx *ProjectContext) ReadTopFiles(n int) (map[string]string, error) {
300
+ topFiles := ctx.GetTopFiles(n)
301
+ contents := make(map[string]string)
302
+
303
+ for _, f := range topFiles {
304
+ content, err := fsutil.ReadFile(f.Path)
305
+ if err != nil {
306
+ continue // Skip files we can't read
307
+ }
308
+ contents[f.RelPath] = content
309
+ }
310
+
311
+ return contents, nil
312
+ }
@@ -0,0 +1,204 @@
1
+ package detector
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "strings"
7
+ )
8
+
9
+ // ProjectType represents the detected project type
10
+ type ProjectType struct {
11
+ Language string // go, python, javascript, typescript, etc.
12
+ Framework string // gin, django, react, nextjs, etc.
13
+ BuildSystem string // go.mod, package.json, requirements.txt, etc.
14
+ EntryPoints []string // main files
15
+ ConfigFiles []string // important config files
16
+ }
17
+
18
+ // Detect analyzes a directory and determines project type
19
+ func Detect(dir string) (*ProjectType, error) {
20
+ pt := &ProjectType{
21
+ EntryPoints: make([]string, 0),
22
+ ConfigFiles: make([]string, 0),
23
+ }
24
+
25
+ // Check for various project indicators
26
+ files, err := os.ReadDir(dir)
27
+ if err != nil {
28
+ return nil, err
29
+ }
30
+
31
+ for _, file := range files {
32
+ name := file.Name()
33
+
34
+ // Skip hidden files/directories
35
+ if strings.HasPrefix(name, ".") && name != ".env" {
36
+ continue
37
+ }
38
+
39
+ switch name {
40
+ // Go
41
+ case "go.mod":
42
+ pt.Language = "go"
43
+ pt.BuildSystem = "go.mod"
44
+ pt.ConfigFiles = append(pt.ConfigFiles, name)
45
+
46
+ // Python
47
+ case "requirements.txt", "pyproject.toml", "setup.py", "Pipfile":
48
+ pt.Language = "python"
49
+ pt.BuildSystem = name
50
+ pt.ConfigFiles = append(pt.ConfigFiles, name)
51
+
52
+ // Node.js
53
+ case "package.json":
54
+ pt.Language = "javascript"
55
+ pt.BuildSystem = "npm"
56
+ pt.ConfigFiles = append(pt.ConfigFiles, name)
57
+
58
+ // Rust
59
+ case "Cargo.toml":
60
+ pt.Language = "rust"
61
+ pt.BuildSystem = "cargo"
62
+ pt.ConfigFiles = append(pt.ConfigFiles, name)
63
+
64
+ // Java
65
+ case "pom.xml":
66
+ pt.Language = "java"
67
+ pt.BuildSystem = "maven"
68
+ pt.ConfigFiles = append(pt.ConfigFiles, name)
69
+ case "build.gradle":
70
+ pt.Language = "java"
71
+ pt.BuildSystem = "gradle"
72
+ pt.ConfigFiles = append(pt.ConfigFiles, name)
73
+
74
+ // Common entry points
75
+ case "main.go":
76
+ pt.EntryPoints = append(pt.EntryPoints, name)
77
+ case "main.py", "app.py", "manage.py":
78
+ pt.EntryPoints = append(pt.EntryPoints, name)
79
+ case "index.js", "index.ts", "app.js", "app.ts":
80
+ pt.EntryPoints = append(pt.EntryPoints, name)
81
+
82
+ // Config files
83
+ case ".env", "Dockerfile", "docker-compose.yml", "Makefile":
84
+ pt.ConfigFiles = append(pt.ConfigFiles, name)
85
+ }
86
+ }
87
+
88
+ // Detect framework based on package.json, go.mod, etc.
89
+ pt.Framework = detectFramework(dir, pt.Language)
90
+
91
+ return pt, nil
92
+ }
93
+
94
+ func detectFramework(dir, lang string) string {
95
+ switch lang {
96
+ case "go":
97
+ // Check go.mod for common frameworks
98
+ if gomod, err := os.ReadFile(filepath.Join(dir, "go.mod")); err == nil {
99
+ content := string(gomod)
100
+ if strings.Contains(content, "github.com/gin-gonic/gin") {
101
+ return "gin"
102
+ }
103
+ if strings.Contains(content, "github.com/labstack/echo") {
104
+ return "echo"
105
+ }
106
+ if strings.Contains(content, "github.com/gofiber/fiber") {
107
+ return "fiber"
108
+ }
109
+ if strings.Contains(content, "github.com/spf13/cobra") {
110
+ return "cobra-cli"
111
+ }
112
+ }
113
+
114
+ case "python":
115
+ // Check requirements.txt
116
+ if req, err := os.ReadFile(filepath.Join(dir, "requirements.txt")); err == nil {
117
+ content := strings.ToLower(string(req))
118
+ if strings.Contains(content, "django") {
119
+ return "django"
120
+ }
121
+ if strings.Contains(content, "flask") {
122
+ return "flask"
123
+ }
124
+ if strings.Contains(content, "fastapi") {
125
+ return "fastapi"
126
+ }
127
+ }
128
+
129
+ case "javascript":
130
+ // Check package.json
131
+ if pkg, err := os.ReadFile(filepath.Join(dir, "package.json")); err == nil {
132
+ content := string(pkg)
133
+ if strings.Contains(content, "\"react\"") {
134
+ if strings.Contains(content, "\"next\"") {
135
+ return "nextjs"
136
+ }
137
+ return "react"
138
+ }
139
+ if strings.Contains(content, "\"vue\"") {
140
+ return "vue"
141
+ }
142
+ if strings.Contains(content, "\"express\"") {
143
+ return "express"
144
+ }
145
+ if strings.Contains(content, "\"@nestjs") {
146
+ return "nestjs"
147
+ }
148
+ }
149
+ }
150
+
151
+ return ""
152
+ }
153
+
154
+ // IsRelevantFile determines if a file is relevant for AI context
155
+ func IsRelevantFile(path string) bool {
156
+ // Skip common non-relevant patterns
157
+ skipPatterns := []string{
158
+ "node_modules/", "vendor/", ".git/", "__pycache__/",
159
+ ".cache/", "dist/", "build/", ".next/", "target/",
160
+ }
161
+
162
+ for _, pattern := range skipPatterns {
163
+ if strings.Contains(path, pattern) {
164
+ return false
165
+ }
166
+ }
167
+
168
+ base := filepath.Base(path)
169
+ // Skip binary/media/lock files
170
+ skipSuffixes := []string{
171
+ ".min.js", ".map", ".png", ".jpg", ".jpeg", ".gif",
172
+ ".ico", ".woff", ".woff2", ".ttf", ".eot",
173
+ ".lock", "go.sum", "package-lock.json", "yarn.lock",
174
+ }
175
+
176
+ for _, suffix := range skipSuffixes {
177
+ if strings.HasSuffix(base, suffix) {
178
+ return false
179
+ }
180
+ }
181
+
182
+ // Include common source code extensions
183
+ ext := filepath.Ext(path)
184
+ relevantExts := []string{
185
+ ".go", ".py", ".js", ".ts", ".tsx", ".jsx",
186
+ ".java", ".c", ".cpp", ".h", ".hpp",
187
+ ".rs", ".rb", ".php", ".cs",
188
+ ".sql", ".md", ".txt", ".yml", ".yaml",
189
+ ".json", ".toml", ".xml", ".html", ".css", ".scss",
190
+ }
191
+
192
+ for _, e := range relevantExts {
193
+ if ext == e {
194
+ return true
195
+ }
196
+ }
197
+
198
+ // Include files without extensions (likely scripts or configs)
199
+ if ext == "" && !strings.HasPrefix(base, ".") {
200
+ return true
201
+ }
202
+
203
+ return false
204
+ }
@@ -0,0 +1,30 @@
1
+ package diffutil
2
+
3
+ import (
4
+ "strings"
5
+
6
+ "github.com/pmezard/go-difflib/difflib"
7
+ )
8
+
9
+ func Unified(fromName, toName, fromContent, toContent string) string {
10
+ u := difflib.UnifiedDiff{
11
+ A: difflib.SplitLines(normalizeNewline(fromContent)),
12
+ B: difflib.SplitLines(normalizeNewline(toContent)),
13
+ FromFile: fromName,
14
+ ToFile: toName,
15
+ Context: 3,
16
+ }
17
+ text, err := difflib.GetUnifiedDiffString(u)
18
+ if err != nil {
19
+ return "(diff unavailable)"
20
+ }
21
+ if strings.TrimSpace(text) == "" {
22
+ return "(no changes)"
23
+ }
24
+ return text
25
+ }
26
+
27
+ func normalizeNewline(s string) string {
28
+ // avoid CRLF noise
29
+ return strings.ReplaceAll(s, "\r\n", "\n")
30
+ }
@@ -0,0 +1,35 @@
1
+ package fsutil
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "path/filepath"
7
+ "time"
8
+ )
9
+
10
+ func ReadFile(path string) (string, error) {
11
+ b, err := os.ReadFile(path)
12
+ if err != nil {
13
+ return "", err
14
+ }
15
+ return string(b), nil
16
+ }
17
+
18
+ // WriteFileAtomic writes content to path using a temp file + rename.
19
+ func WriteFileAtomic(path string, content []byte, perm os.FileMode) error {
20
+ dir := filepath.Dir(path)
21
+ base := filepath.Base(path)
22
+ tmp := filepath.Join(dir, fmt.Sprintf(".%s.%d.tmp", base, time.Now().UnixNano()))
23
+ if err := os.WriteFile(tmp, content, perm); err != nil {
24
+ return err
25
+ }
26
+ return os.Rename(tmp, path)
27
+ }
28
+
29
+ func FilePermOrDefault(path string, def os.FileMode) os.FileMode {
30
+ st, err := os.Stat(path)
31
+ if err != nil {
32
+ return def
33
+ }
34
+ return st.Mode()
35
+ }