@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
package/cmd/providers.go
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
"github.com/spf13/cobra"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
var listCmd = &cobra.Command{
|
|
10
|
+
Use: "providers",
|
|
11
|
+
Short: "List available AI providers and models",
|
|
12
|
+
Long: `Display all available AI providers, their models, and configuration.`,
|
|
13
|
+
Run: func(cmd *cobra.Command, args []string) {
|
|
14
|
+
fmt.Println(" Available AI Providers:")
|
|
15
|
+
fmt.Println()
|
|
16
|
+
|
|
17
|
+
// OpenAI
|
|
18
|
+
fmt.Println(" OpenAI")
|
|
19
|
+
fmt.Println(" Models: gpt-4, gpt-4-turbo-preview, gpt-3.5-turbo")
|
|
20
|
+
fmt.Println(" API Key: OPENAI_API_KEY")
|
|
21
|
+
if os.Getenv("OPENAI_API_KEY") != "" {
|
|
22
|
+
fmt.Println(" Status: Configured")
|
|
23
|
+
} else {
|
|
24
|
+
fmt.Println(" Status: Not configured")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Gemini
|
|
28
|
+
fmt.Println("\n Google Gemini")
|
|
29
|
+
fmt.Println(" Models: gemini-pro, gemini-pro-vision")
|
|
30
|
+
fmt.Println(" API Key: GEMINI_API_KEY")
|
|
31
|
+
if os.Getenv("GEMINI_API_KEY") != "" {
|
|
32
|
+
fmt.Println(" Status: Configured")
|
|
33
|
+
} else {
|
|
34
|
+
fmt.Println(" Status: Not configured")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Claude
|
|
38
|
+
fmt.Println("\n Anthropic Claude")
|
|
39
|
+
fmt.Println(" Models: claude-3-opus-20240229, claude-3-sonnet-20240229, claude-3-haiku-20240307")
|
|
40
|
+
fmt.Println(" API Key: CLAUDE_API_KEY")
|
|
41
|
+
if os.Getenv("CLAUDE_API_KEY") != "" {
|
|
42
|
+
fmt.Println(" Status: Configured")
|
|
43
|
+
} else {
|
|
44
|
+
fmt.Println(" Status: Not configured")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Current selection
|
|
48
|
+
currentProvider := os.Getenv("AI_PROVIDER")
|
|
49
|
+
currentModel := os.Getenv("AI_MODEL")
|
|
50
|
+
if currentProvider == "" {
|
|
51
|
+
currentProvider = "openai"
|
|
52
|
+
}
|
|
53
|
+
if currentModel == "" {
|
|
54
|
+
currentModel = "gpt-4"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fmt.Printf("\n Current Configuration:\n")
|
|
58
|
+
fmt.Printf(" Provider: %s\n", currentProvider)
|
|
59
|
+
fmt.Printf(" Model: %s\n", currentModel)
|
|
60
|
+
|
|
61
|
+
fmt.Println("\n To change provider, edit .env file or use --provider flag")
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
func init() {
|
|
66
|
+
rootCmd.AddCommand(listCmd)
|
|
67
|
+
}
|
package/cmd/root.go
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"github.com/spf13/cobra"
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
var (
|
|
8
|
+
cfgFile string
|
|
9
|
+
verbose bool
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
var rootCmd = &cobra.Command{
|
|
13
|
+
Use: "dcode",
|
|
14
|
+
Short: "DCode - AI-powered coding assistant",
|
|
15
|
+
Long: `DCode - Intelligent AI Coding Agent
|
|
16
|
+
|
|
17
|
+
Supports multiple AI providers:
|
|
18
|
+
• OpenAI (GPT-5, GPT-4, GPT-3.5)
|
|
19
|
+
• Google Gemini (Gemini 3, Gemini 2.5)
|
|
20
|
+
• Anthropic Claude (Claude 3.5)
|
|
21
|
+
|
|
22
|
+
Features:
|
|
23
|
+
• Code analysis and understanding
|
|
24
|
+
• AI-powered code editing
|
|
25
|
+
• Error detection and fixing
|
|
26
|
+
• Web search integration
|
|
27
|
+
• Code generation
|
|
28
|
+
• Patch management
|
|
29
|
+
|
|
30
|
+
Configure your provider in .env file or use --provider flag.`,
|
|
31
|
+
RunE: runAgentV2, // Use RunE directly
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func Execute() error {
|
|
35
|
+
return rootCmd.Execute()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func init() {
|
|
39
|
+
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.dcode.yaml)")
|
|
40
|
+
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
|
|
41
|
+
}
|
package/cmd/search.go
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"time"
|
|
7
|
+
|
|
8
|
+
"github.com/ddhanush1/dcode/internal/websearch"
|
|
9
|
+
"github.com/spf13/cobra"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
var (
|
|
13
|
+
searchQuery string
|
|
14
|
+
searchType string
|
|
15
|
+
searchEngine string
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
var searchCmd = &cobra.Command{
|
|
19
|
+
Use: "search",
|
|
20
|
+
Short: "Search the web for coding solutions",
|
|
21
|
+
Long: `Search documentation, Stack Overflow, and the web for coding help.
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
dcode search --query "Go error handling best practices"
|
|
25
|
+
dcode search --query "React hooks" --type docs
|
|
26
|
+
dcode search --query "Python async await" --type stackoverflow
|
|
27
|
+
dcode search --query "JWT authentication" --engine google`,
|
|
28
|
+
RunE: func(cmd *cobra.Command, args []string) error {
|
|
29
|
+
if searchQuery == "" {
|
|
30
|
+
return fmt.Errorf("query is required (use --query)")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
searcher := websearch.NewFromEnv()
|
|
34
|
+
if searcher == nil {
|
|
35
|
+
return fmt.Errorf("web search not configured. Set WEB_SEARCH_PROVIDER=tavily and TAVILY_API_KEY in .env")
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
39
|
+
defer cancel()
|
|
40
|
+
|
|
41
|
+
results, err := searcher.Search(ctx, searchQuery)
|
|
42
|
+
if err != nil {
|
|
43
|
+
return err
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fmt.Printf("Query: %s\n\n", searchQuery)
|
|
47
|
+
for i, r := range results {
|
|
48
|
+
fmt.Printf("%d. %s\n %s\n %s\n\n", i+1, r.Title, r.URL, r.Snippet)
|
|
49
|
+
}
|
|
50
|
+
return nil
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
func init() {
|
|
55
|
+
searchCmd.Flags().StringVarP(&searchQuery, "query", "q", "", "Search query (required)")
|
|
56
|
+
searchCmd.Flags().StringVarP(&searchType, "type", "t", "general", "Search type (reserved for future): general, docs, stackoverflow, github")
|
|
57
|
+
searchCmd.Flags().StringVar(&searchEngine, "engine", "tavily", "Search engine/provider (reserved for future)")
|
|
58
|
+
|
|
59
|
+
searchCmd.MarkFlagRequired("query")
|
|
60
|
+
rootCmd.AddCommand(searchCmd)
|
|
61
|
+
}
|
package/cmd/server.go
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"os"
|
|
7
|
+
"os/signal"
|
|
8
|
+
"syscall"
|
|
9
|
+
|
|
10
|
+
"github.com/ddhanush1/dcode/internal/agent"
|
|
11
|
+
"github.com/ddhanush1/dcode/internal/server"
|
|
12
|
+
"github.com/spf13/cobra"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
var serverCmd = &cobra.Command{
|
|
16
|
+
Use: "server",
|
|
17
|
+
Short: "Start DCode server",
|
|
18
|
+
RunE: func(cmd *cobra.Command, args []string) error {
|
|
19
|
+
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
20
|
+
defer cancel()
|
|
21
|
+
|
|
22
|
+
ag, err := agent.New()
|
|
23
|
+
if err != nil {
|
|
24
|
+
return err
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
cfg := server.ConfigFromEnv()
|
|
28
|
+
fmt.Printf("Starting server on port %d...\n", cfg.Port)
|
|
29
|
+
api := server.New(ag)
|
|
30
|
+
return api.ListenAndServe(ctx, cfg)
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func init() {
|
|
35
|
+
rootCmd.AddCommand(serverCmd)
|
|
36
|
+
}
|
package/cmd/switch.go
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
|
|
7
|
+
"github.com/ddhanush1/dcode/internal/config"
|
|
8
|
+
"github.com/spf13/cobra"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
var switchCmd = &cobra.Command{
|
|
12
|
+
Use: "switch [provider]",
|
|
13
|
+
Short: "Switch AI provider",
|
|
14
|
+
Long: `Switch between AI providers easily.
|
|
15
|
+
|
|
16
|
+
Available providers:
|
|
17
|
+
• gemini - Google Gemini (requires GEMINI_API_KEY)
|
|
18
|
+
• openai - OpenAI GPT (requires OPENAI_API_KEY)
|
|
19
|
+
• copilot - GitHub Copilot (requires GitHub CLI login)
|
|
20
|
+
• claude - Anthropic Claude (requires ANTHROPIC_API_KEY)
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
dcode switch gemini
|
|
24
|
+
dcode switch openai
|
|
25
|
+
dcode switch copilot`,
|
|
26
|
+
Args: cobra.ExactArgs(1),
|
|
27
|
+
RunE: switchProvider,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func init() {
|
|
31
|
+
rootCmd.AddCommand(switchCmd)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func switchProvider(cmd *cobra.Command, args []string) error {
|
|
35
|
+
provider := args[0]
|
|
36
|
+
|
|
37
|
+
// Validate provider
|
|
38
|
+
validProviders := map[string]string{
|
|
39
|
+
"gemini": "GEMINI_API_KEY or GOOGLE_API_KEY",
|
|
40
|
+
"openai": "OPENAI_API_KEY",
|
|
41
|
+
"copilot": "GitHub CLI authenticated",
|
|
42
|
+
"claude": "ANTHROPIC_API_KEY",
|
|
43
|
+
"github": "GITHUB_TOKEN",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
envVar, valid := validProviders[provider]
|
|
47
|
+
if !valid {
|
|
48
|
+
return fmt.Errorf("invalid provider: %s\nValid providers: gemini, openai, copilot, claude", provider)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check if API key exists
|
|
52
|
+
hasKey := false
|
|
53
|
+
switch provider {
|
|
54
|
+
case "gemini":
|
|
55
|
+
hasKey = os.Getenv("GEMINI_API_KEY") != "" || os.Getenv("GOOGLE_API_KEY") != ""
|
|
56
|
+
case "openai":
|
|
57
|
+
hasKey = os.Getenv("OPENAI_API_KEY") != ""
|
|
58
|
+
case "claude":
|
|
59
|
+
hasKey = os.Getenv("ANTHROPIC_API_KEY") != ""
|
|
60
|
+
case "github":
|
|
61
|
+
hasKey = os.Getenv("GITHUB_TOKEN") != ""
|
|
62
|
+
case "copilot":
|
|
63
|
+
hasKey = true // Will check later via gh auth
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if !hasKey && provider != "copilot" {
|
|
67
|
+
fmt.Printf("⚠️ Warning: %s not found in environment\n", envVar)
|
|
68
|
+
fmt.Printf("Set it with: export %s=your-api-key\n\n", envVar)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Load or create config
|
|
72
|
+
cfg, err := config.LoadConfig()
|
|
73
|
+
if err != nil {
|
|
74
|
+
// Create default config
|
|
75
|
+
cfg = &config.Config{
|
|
76
|
+
Model: config.ModelConfig{
|
|
77
|
+
Provider: provider,
|
|
78
|
+
},
|
|
79
|
+
Auth: config.AuthConfig{
|
|
80
|
+
Provider: provider,
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
// Update existing config
|
|
85
|
+
cfg.Model.Provider = provider
|
|
86
|
+
cfg.Auth.Provider = provider
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Set default models
|
|
90
|
+
switch provider {
|
|
91
|
+
case "gemini":
|
|
92
|
+
if cfg.Model.Model == "" {
|
|
93
|
+
cfg.Model.Model = "gemini-2.0-flash-exp"
|
|
94
|
+
}
|
|
95
|
+
case "openai":
|
|
96
|
+
if cfg.Model.Model == "" {
|
|
97
|
+
cfg.Model.Model = "gpt-4"
|
|
98
|
+
}
|
|
99
|
+
case "claude":
|
|
100
|
+
if cfg.Model.Model == "" {
|
|
101
|
+
cfg.Model.Model = "claude-3-5-sonnet-20241022"
|
|
102
|
+
}
|
|
103
|
+
case "copilot":
|
|
104
|
+
if cfg.Model.Model == "" {
|
|
105
|
+
cfg.Model.Model = "gpt-4"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Save config
|
|
110
|
+
if err := config.SaveConfig(cfg); err != nil {
|
|
111
|
+
return fmt.Errorf("failed to save config: %w", err)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fmt.Printf("✅ Switched to %s provider\n", provider)
|
|
115
|
+
fmt.Printf(" Model: %s\n", cfg.Model.Model)
|
|
116
|
+
if provider != "copilot" {
|
|
117
|
+
fmt.Printf(" Env: %s\n", envVar)
|
|
118
|
+
}
|
|
119
|
+
fmt.Println("\nYou can now run: dcode")
|
|
120
|
+
|
|
121
|
+
return nil
|
|
122
|
+
}
|
package/cmd/terminal.go
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bufio"
|
|
5
|
+
"context"
|
|
6
|
+
"fmt"
|
|
7
|
+
"os"
|
|
8
|
+
"os/exec"
|
|
9
|
+
"strings"
|
|
10
|
+
"time"
|
|
11
|
+
|
|
12
|
+
"github.com/ddhanush1/dcode/internal/agent"
|
|
13
|
+
"github.com/ddhanush1/dcode/internal/ai"
|
|
14
|
+
"github.com/spf13/cobra"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
var (
|
|
18
|
+
terminalDryRun bool
|
|
19
|
+
terminalShell string
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
var terminalCmd = &cobra.Command{
|
|
23
|
+
Use: "terminal [command description]",
|
|
24
|
+
Aliases: []string{"cmd", "run"},
|
|
25
|
+
Short: "AI-powered terminal command generation and execution",
|
|
26
|
+
Long: `Generate and execute shell commands using AI
|
|
27
|
+
|
|
28
|
+
Like Cursor's terminal integration, this allows you to:
|
|
29
|
+
• Describe what you want to do in natural language
|
|
30
|
+
• Get the appropriate shell command
|
|
31
|
+
• Review and execute the command
|
|
32
|
+
• Get help with command errors
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
dcode terminal "list all go files modified in the last week"
|
|
36
|
+
dcode terminal "install dependencies for a React project"
|
|
37
|
+
dcode terminal "find large files over 100MB"
|
|
38
|
+
dcode cmd "create a new git branch called feature-auth"
|
|
39
|
+
dcode run "run the tests for the api package"
|
|
40
|
+
|
|
41
|
+
Options:
|
|
42
|
+
--dry-run Show command without executing
|
|
43
|
+
--shell Specify shell (bash, zsh, fish, powershell)
|
|
44
|
+
`,
|
|
45
|
+
RunE: runTerminal,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func runTerminal(cmd *cobra.Command, args []string) error {
|
|
49
|
+
if len(args) == 0 {
|
|
50
|
+
return fmt.Errorf("command description required")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
description := strings.Join(args, " ")
|
|
54
|
+
|
|
55
|
+
ag, err := agent.New()
|
|
56
|
+
if err != nil {
|
|
57
|
+
return err
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
wd, _ := os.Getwd()
|
|
61
|
+
shell := terminalShell
|
|
62
|
+
if shell == "" {
|
|
63
|
+
shell = detectShell()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fmt.Println("\033[1;36m🖥️ Terminal Command Generator\033[0m")
|
|
67
|
+
fmt.Printf("\033[90mDirectory: %s\033[0m\n", wd)
|
|
68
|
+
fmt.Printf("\033[90mShell: %s\033[0m\n\n", shell)
|
|
69
|
+
|
|
70
|
+
// Build prompt for command generation
|
|
71
|
+
prompt := buildTerminalPrompt(description, shell, wd)
|
|
72
|
+
|
|
73
|
+
fmt.Println("\033[33m⚙️ Generating command...\033[0m\n")
|
|
74
|
+
|
|
75
|
+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
76
|
+
defer cancel()
|
|
77
|
+
|
|
78
|
+
resp, err := ag.Chat(ctx, agent.ChatRequest{
|
|
79
|
+
Messages: []ai.Message{
|
|
80
|
+
{Role: "user", Content: prompt},
|
|
81
|
+
},
|
|
82
|
+
WebSearch: agent.WebSearchRequest{Enabled: false},
|
|
83
|
+
})
|
|
84
|
+
if err != nil {
|
|
85
|
+
return fmt.Errorf("generating command: %w", err)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Extract command from response
|
|
89
|
+
command := extractCommand(resp.Assistant)
|
|
90
|
+
explanation := extractExplanation(resp.Assistant, command)
|
|
91
|
+
|
|
92
|
+
if command == "" {
|
|
93
|
+
fmt.Println("\033[31m✗ Could not generate a valid command\033[0m")
|
|
94
|
+
fmt.Println("\033[90mResponse:\033[0m")
|
|
95
|
+
fmt.Println(resp.Assistant)
|
|
96
|
+
return nil
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Display the command
|
|
100
|
+
fmt.Println("\033[1;32m📝 Generated Command:\033[0m")
|
|
101
|
+
fmt.Printf("\033[1;37m %s\033[0m\n\n", command)
|
|
102
|
+
|
|
103
|
+
if explanation != "" {
|
|
104
|
+
fmt.Println("\033[1;34m💡 Explanation:\033[0m")
|
|
105
|
+
fmt.Println(explanation)
|
|
106
|
+
fmt.Println()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Dry run mode
|
|
110
|
+
if terminalDryRun {
|
|
111
|
+
fmt.Println("\033[90m(dry-run mode - not executing)\033[0m")
|
|
112
|
+
return nil
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Ask for confirmation
|
|
116
|
+
fmt.Print("\033[1;33m▶ Execute this command? [y/N] \033[0m")
|
|
117
|
+
reader := bufio.NewReader(os.Stdin)
|
|
118
|
+
line, _ := reader.ReadString('\n')
|
|
119
|
+
execute := strings.TrimSpace(strings.ToLower(line))
|
|
120
|
+
|
|
121
|
+
if execute != "y" && execute != "yes" {
|
|
122
|
+
fmt.Println("\033[90m✗ Command not executed\033[0m")
|
|
123
|
+
return nil
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Execute the command
|
|
127
|
+
fmt.Println("\n\033[1;36m━━━ Executing ━━━\033[0m\n")
|
|
128
|
+
|
|
129
|
+
shellCmd := exec.Command(shell, "-c", command)
|
|
130
|
+
shellCmd.Dir = wd
|
|
131
|
+
shellCmd.Stdout = os.Stdout
|
|
132
|
+
shellCmd.Stderr = os.Stderr
|
|
133
|
+
shellCmd.Stdin = os.Stdin
|
|
134
|
+
|
|
135
|
+
if err := shellCmd.Run(); err != nil {
|
|
136
|
+
exitCode := shellCmd.ProcessState.ExitCode()
|
|
137
|
+
fmt.Printf("\n\033[31m✗ Command failed with exit code %d\033[0m\n", exitCode)
|
|
138
|
+
|
|
139
|
+
// Offer to get help with the error
|
|
140
|
+
fmt.Print("\n\033[33mWould you like AI help debugging this error? [y/N] \033[0m")
|
|
141
|
+
line, _ = reader.ReadString('\n')
|
|
142
|
+
if strings.TrimSpace(strings.ToLower(line)) == "y" {
|
|
143
|
+
return helpWithError(ag, command, err)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return nil
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
fmt.Println("\n\033[1;32m✓ Command completed successfully\033[0m")
|
|
150
|
+
return nil
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
func buildTerminalPrompt(description, shell, wd string) string {
|
|
154
|
+
return fmt.Sprintf(`You are a terminal command generator. Generate a single shell command for the following request.
|
|
155
|
+
|
|
156
|
+
Request: %s
|
|
157
|
+
|
|
158
|
+
Context:
|
|
159
|
+
- Shell: %s
|
|
160
|
+
- Working Directory: %s
|
|
161
|
+
- OS: %s
|
|
162
|
+
|
|
163
|
+
Instructions:
|
|
164
|
+
1. Generate ONLY the command, no extra text
|
|
165
|
+
2. Make it safe - avoid destructive operations without confirmation flags
|
|
166
|
+
3. Use commonly available tools
|
|
167
|
+
4. Format: Just the command on a single line, or multiple lines if using &&
|
|
168
|
+
5. If you need to explain, add it AFTER the command with "Explanation:" prefix
|
|
169
|
+
|
|
170
|
+
Format your response like this:
|
|
171
|
+
[command here]
|
|
172
|
+
|
|
173
|
+
Explanation: [optional explanation here]`, description, shell, wd, detectOS())
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
func extractCommand(response string) string {
|
|
177
|
+
lines := strings.Split(response, "\n")
|
|
178
|
+
|
|
179
|
+
for _, line := range lines {
|
|
180
|
+
line = strings.TrimSpace(line)
|
|
181
|
+
|
|
182
|
+
// Skip empty lines and explanations
|
|
183
|
+
if line == "" || strings.HasPrefix(strings.ToLower(line), "explanation:") {
|
|
184
|
+
continue
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Skip markdown code blocks
|
|
188
|
+
if strings.HasPrefix(line, "```") {
|
|
189
|
+
continue
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// First non-empty, non-explanation line is likely the command
|
|
193
|
+
if !strings.HasPrefix(line, "#") && !strings.HasPrefix(line, "//") {
|
|
194
|
+
// Remove markdown inline code
|
|
195
|
+
line = strings.Trim(line, "`")
|
|
196
|
+
return line
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return ""
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
func extractExplanation(response, command string) string {
|
|
204
|
+
// Look for "Explanation:" in the response
|
|
205
|
+
idx := strings.Index(strings.ToLower(response), "explanation:")
|
|
206
|
+
if idx == -1 {
|
|
207
|
+
return ""
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
explanation := strings.TrimSpace(response[idx+12:]) // 12 = len("explanation:")
|
|
211
|
+
|
|
212
|
+
// Remove the command if it appears in the explanation
|
|
213
|
+
explanation = strings.ReplaceAll(explanation, command, "")
|
|
214
|
+
|
|
215
|
+
return strings.TrimSpace(explanation)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
func detectShell() string {
|
|
219
|
+
shell := os.Getenv("SHELL")
|
|
220
|
+
if shell != "" {
|
|
221
|
+
parts := strings.Split(shell, "/")
|
|
222
|
+
return parts[len(parts)-1]
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Default based on OS
|
|
226
|
+
if detectOS() == "windows" {
|
|
227
|
+
return "powershell"
|
|
228
|
+
}
|
|
229
|
+
return "bash"
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
func detectOS() string {
|
|
233
|
+
switch strings.ToLower(os.Getenv("OS")) {
|
|
234
|
+
case "windows_nt":
|
|
235
|
+
return "windows"
|
|
236
|
+
default:
|
|
237
|
+
// Check OSTYPE or use runtime
|
|
238
|
+
return "unix"
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
func helpWithError(ag *agent.Agent, command string, err error) error {
|
|
243
|
+
prompt := fmt.Sprintf(`The following command failed:
|
|
244
|
+
|
|
245
|
+
Command: %s
|
|
246
|
+
Error: %v
|
|
247
|
+
|
|
248
|
+
Please:
|
|
249
|
+
1. Explain what went wrong
|
|
250
|
+
2. Suggest how to fix it
|
|
251
|
+
3. Provide an alternative command if needed`, command, err)
|
|
252
|
+
|
|
253
|
+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
254
|
+
defer cancel()
|
|
255
|
+
|
|
256
|
+
resp, err := ag.Chat(ctx, agent.ChatRequest{
|
|
257
|
+
Messages: []ai.Message{
|
|
258
|
+
{Role: "user", Content: prompt},
|
|
259
|
+
},
|
|
260
|
+
WebSearch: agent.WebSearchRequest{Enabled: false},
|
|
261
|
+
})
|
|
262
|
+
if err != nil {
|
|
263
|
+
return err
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
fmt.Println("\n\033[1;34m💡 AI Assistant:\033[0m")
|
|
267
|
+
fmt.Println(resp.Assistant)
|
|
268
|
+
|
|
269
|
+
return nil
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
func init() {
|
|
273
|
+
terminalCmd.Flags().BoolVar(&terminalDryRun, "dry-run", false, "Show command without executing")
|
|
274
|
+
terminalCmd.Flags().StringVar(&terminalShell, "shell", "", "Shell to use (bash, zsh, fish, powershell)")
|
|
275
|
+
|
|
276
|
+
rootCmd.AddCommand(terminalCmd)
|
|
277
|
+
}
|
package/go.mod
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module github.com/ddhanush1/dcode
|
|
2
|
+
|
|
3
|
+
go 1.24.0
|
|
4
|
+
|
|
5
|
+
require (
|
|
6
|
+
cloud.google.com/go/compute/metadata v0.9.0
|
|
7
|
+
github.com/charmbracelet/bubbles v0.18.0
|
|
8
|
+
github.com/charmbracelet/bubbletea v0.25.0
|
|
9
|
+
github.com/charmbracelet/lipgloss v0.9.1
|
|
10
|
+
github.com/joho/godotenv v1.5.1
|
|
11
|
+
github.com/pelletier/go-toml/v2 v2.2.4
|
|
12
|
+
github.com/peterh/liner v1.2.2
|
|
13
|
+
github.com/pmezard/go-difflib v1.0.0
|
|
14
|
+
github.com/spf13/cobra v1.8.0
|
|
15
|
+
github.com/zalando/go-keyring v0.2.6
|
|
16
|
+
golang.org/x/oauth2 v0.35.0
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
require (
|
|
20
|
+
al.essio.dev/pkg/shellescape v1.5.1 // indirect
|
|
21
|
+
github.com/atotto/clipboard v0.1.4 // indirect
|
|
22
|
+
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
|
23
|
+
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
|
24
|
+
github.com/danieljoos/wincred v1.2.2 // indirect
|
|
25
|
+
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
|
26
|
+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
|
27
|
+
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
|
28
|
+
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
29
|
+
github.com/mattn/go-localereader v0.0.1 // indirect
|
|
30
|
+
github.com/mattn/go-runewidth v0.0.15 // indirect
|
|
31
|
+
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
|
32
|
+
github.com/muesli/cancelreader v0.2.2 // indirect
|
|
33
|
+
github.com/muesli/reflow v0.3.0 // indirect
|
|
34
|
+
github.com/muesli/termenv v0.15.2 // indirect
|
|
35
|
+
github.com/rivo/uniseg v0.4.7 // indirect
|
|
36
|
+
github.com/spf13/pflag v1.0.5 // indirect
|
|
37
|
+
github.com/stretchr/testify v1.11.1 // indirect
|
|
38
|
+
golang.org/x/sync v0.19.0 // indirect
|
|
39
|
+
golang.org/x/sys v0.40.0 // indirect
|
|
40
|
+
golang.org/x/term v0.39.0 // indirect
|
|
41
|
+
golang.org/x/text v0.33.0 // indirect
|
|
42
|
+
)
|