@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/agent_v2.go
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"os"
|
|
7
|
+
"strings"
|
|
8
|
+
"time"
|
|
9
|
+
|
|
10
|
+
"github.com/ddhanush1/dcode/internal/agents"
|
|
11
|
+
"github.com/ddhanush1/dcode/internal/ai"
|
|
12
|
+
"github.com/ddhanush1/dcode/internal/auth"
|
|
13
|
+
"github.com/ddhanush1/dcode/internal/config"
|
|
14
|
+
"github.com/ddhanush1/dcode/internal/registry"
|
|
15
|
+
"github.com/ddhanush1/dcode/internal/scheduler"
|
|
16
|
+
"github.com/ddhanush1/dcode/internal/tools"
|
|
17
|
+
"github.com/peterh/liner"
|
|
18
|
+
"github.com/spf13/cobra"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
var agentV2Cmd = &cobra.Command{
|
|
22
|
+
Use: "agent-v2",
|
|
23
|
+
Short: "Start the new agent system (v2 architecture)",
|
|
24
|
+
Long: `Interactive AI agent using the new architecture with agent registry and tool system`,
|
|
25
|
+
RunE: runAgentV2,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func init() {
|
|
29
|
+
rootCmd.AddCommand(agentV2Cmd)
|
|
30
|
+
|
|
31
|
+
// Add flags to both root and agent-v2 commands
|
|
32
|
+
for _, cmd := range []*cobra.Command{rootCmd, agentV2Cmd} {
|
|
33
|
+
cmd.Flags().String("agent", "generalist", "Agent to use (generalist, codebase_investigator)")
|
|
34
|
+
cmd.Flags().String("provider", "", "AI provider (openai, gemini, claude)")
|
|
35
|
+
cmd.Flags().String("model", "", "AI model to use")
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func runAgentV2(cmd *cobra.Command, args []string) error {
|
|
40
|
+
// Load config
|
|
41
|
+
cfg, err := config.LoadConfig()
|
|
42
|
+
if err != nil {
|
|
43
|
+
return fmt.Errorf("failed to load config: %w", err)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Override with flags
|
|
47
|
+
if provider, _ := cmd.Flags().GetString("provider"); provider != "" {
|
|
48
|
+
cfg.Model.Provider = provider
|
|
49
|
+
cfg.Auth.Provider = provider
|
|
50
|
+
}
|
|
51
|
+
if model, _ := cmd.Flags().GetString("model"); model != "" {
|
|
52
|
+
cfg.Model.Model = model
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Interactive provider selection if not set
|
|
56
|
+
if cfg.Model.Provider == "" {
|
|
57
|
+
fmt.Println("š¤ Welcome to DCode!")
|
|
58
|
+
fmt.Println("\nSelect your AI provider:")
|
|
59
|
+
fmt.Println(" 1. Gemini (Google)")
|
|
60
|
+
fmt.Println(" 2. OpenAI (ChatGPT)")
|
|
61
|
+
fmt.Println(" 3. GitHub Copilot")
|
|
62
|
+
fmt.Println(" 4. Claude (Anthropic)")
|
|
63
|
+
fmt.Print("\nEnter choice (1-4): ")
|
|
64
|
+
|
|
65
|
+
var choice int
|
|
66
|
+
fmt.Scanf("%d", &choice)
|
|
67
|
+
|
|
68
|
+
providers := map[int]string{
|
|
69
|
+
1: "gemini",
|
|
70
|
+
2: "openai",
|
|
71
|
+
3: "copilot",
|
|
72
|
+
4: "claude",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if provider, ok := providers[choice]; ok {
|
|
76
|
+
cfg.Model.Provider = provider
|
|
77
|
+
cfg.Auth.Provider = provider
|
|
78
|
+
|
|
79
|
+
// Set default models
|
|
80
|
+
switch provider {
|
|
81
|
+
case "gemini":
|
|
82
|
+
cfg.Model.Model = "gemini-2.0-flash-exp"
|
|
83
|
+
case "openai":
|
|
84
|
+
cfg.Model.Model = "gpt-4"
|
|
85
|
+
case "copilot":
|
|
86
|
+
cfg.Model.Model = "gpt-4"
|
|
87
|
+
case "claude":
|
|
88
|
+
cfg.Model.Model = "claude-3-5-sonnet-20241022"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
config.SaveConfig(cfg)
|
|
92
|
+
fmt.Printf("\nā
Set provider to: %s\n\n", provider)
|
|
93
|
+
} else {
|
|
94
|
+
return fmt.Errorf("invalid choice. Use: dcode --provider gemini|openai|copilot|claude")
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Create authenticator
|
|
99
|
+
authFactory := auth.NewFactory(auth.GetDefaultStore())
|
|
100
|
+
authenticator, err := authFactory.CreateFromConfig(cfg)
|
|
101
|
+
if err != nil {
|
|
102
|
+
fmt.Printf("ā ļø Warning: Auth setup failed: %v\n", err)
|
|
103
|
+
fmt.Println("Falling back to environment variables...")
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Get API key
|
|
107
|
+
var apiKey string
|
|
108
|
+
if authenticator != nil {
|
|
109
|
+
ctx := context.Background()
|
|
110
|
+
apiKey, err = authenticator.GetToken(ctx)
|
|
111
|
+
if err != nil {
|
|
112
|
+
return fmt.Errorf("failed to get API key: %w", err)
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
// Fallback to env
|
|
116
|
+
switch cfg.Model.Provider {
|
|
117
|
+
case "openai":
|
|
118
|
+
apiKey = os.Getenv("OPENAI_API_KEY")
|
|
119
|
+
case "gemini":
|
|
120
|
+
apiKey = os.Getenv("GEMINI_API_KEY")
|
|
121
|
+
if apiKey == "" {
|
|
122
|
+
apiKey = os.Getenv("GOOGLE_API_KEY")
|
|
123
|
+
}
|
|
124
|
+
case "github":
|
|
125
|
+
apiKey = os.Getenv("GITHUB_TOKEN")
|
|
126
|
+
case "claude":
|
|
127
|
+
apiKey = os.Getenv("ANTHROPIC_API_KEY")
|
|
128
|
+
case "copilot":
|
|
129
|
+
apiKey = os.Getenv("COPILOT_TOKEN")
|
|
130
|
+
if apiKey == "" {
|
|
131
|
+
// Try to get token from Copilot CLI config
|
|
132
|
+
apiKey = ai.GetCopilotToken()
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if apiKey == "" {
|
|
137
|
+
return fmt.Errorf("API key not found for provider: %s", cfg.Model.Provider)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Create AI client
|
|
142
|
+
aiClient := &ai.Client{
|
|
143
|
+
Provider: ai.Provider(cfg.Model.Provider),
|
|
144
|
+
Model: cfg.Model.Model,
|
|
145
|
+
APIKey: apiKey,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Create registries
|
|
149
|
+
agentRegistry := registry.NewAgentRegistry()
|
|
150
|
+
toolRegistry := registry.NewToolRegistry()
|
|
151
|
+
|
|
152
|
+
// Register tools
|
|
153
|
+
if err := tools.RegisterBuiltinTools(toolRegistry); err != nil {
|
|
154
|
+
return fmt.Errorf("failed to register tools: %w", err)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Register agents
|
|
158
|
+
if err := agents.RegisterAllAgents(agentRegistry, cfg, aiClient); err != nil {
|
|
159
|
+
return fmt.Errorf("failed to register agents: %w", err)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Create policy
|
|
163
|
+
policyFile, err := config.LoadPolicyFile()
|
|
164
|
+
if err != nil {
|
|
165
|
+
return fmt.Errorf("failed to load policy: %w", err)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Create scheduler
|
|
169
|
+
sched := scheduler.NewScheduler(toolRegistry, policyFile)
|
|
170
|
+
|
|
171
|
+
// Create agent manager
|
|
172
|
+
agentManager := agents.NewAgentManager(agentRegistry, toolRegistry)
|
|
173
|
+
|
|
174
|
+
// Switch to requested agent
|
|
175
|
+
agentID, _ := cmd.Flags().GetString("agent")
|
|
176
|
+
if err := agentManager.SwitchAgent(agentID); err != nil {
|
|
177
|
+
return err
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
currentAgent, _ := agentManager.GetCurrentAgent()
|
|
181
|
+
|
|
182
|
+
// Simple welcome message (like gemini-cli)
|
|
183
|
+
fmt.Println()
|
|
184
|
+
fmt.Printf("āā DCode v2.0 ā %s ā %d tools\n", cfg.Model.Model, len(toolRegistry.List()))
|
|
185
|
+
fmt.Println("ā°ā Type your message or /help for commands")
|
|
186
|
+
fmt.Println()
|
|
187
|
+
|
|
188
|
+
// Start REPL
|
|
189
|
+
line := liner.NewLiner()
|
|
190
|
+
defer line.Close()
|
|
191
|
+
|
|
192
|
+
line.SetCtrlCAborts(true)
|
|
193
|
+
|
|
194
|
+
// Load history
|
|
195
|
+
historyFile := os.ExpandEnv("$HOME/.dcode_history")
|
|
196
|
+
if f, err := os.Open(historyFile); err == nil {
|
|
197
|
+
line.ReadHistory(f)
|
|
198
|
+
f.Close()
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
ctx := context.Background()
|
|
202
|
+
|
|
203
|
+
for {
|
|
204
|
+
// Simple prompt like gemini-cli
|
|
205
|
+
prompt := "ā°ā "
|
|
206
|
+
input, err := line.Prompt(prompt)
|
|
207
|
+
if err != nil {
|
|
208
|
+
break
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
input = strings.TrimSpace(input)
|
|
212
|
+
if input == "" {
|
|
213
|
+
continue
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
line.AppendHistory(input)
|
|
217
|
+
|
|
218
|
+
// Handle commands
|
|
219
|
+
if strings.HasPrefix(input, "/") {
|
|
220
|
+
if handled, shouldContinue := handleCommand(input, agentManager, toolRegistry, agentRegistry, line, historyFile); !shouldContinue {
|
|
221
|
+
break
|
|
222
|
+
} else if handled {
|
|
223
|
+
currentAgent, _ = agentManager.GetCurrentAgent()
|
|
224
|
+
continue
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Get current working directory
|
|
229
|
+
cwd, err := os.Getwd()
|
|
230
|
+
if err != nil {
|
|
231
|
+
cwd = "unknown"
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Execute agent with streaming and working directory context
|
|
235
|
+
agentInput := ®istry.AgentInput{
|
|
236
|
+
Query: input,
|
|
237
|
+
Parameters: make(map[string]interface{}),
|
|
238
|
+
History: agentManager.GetHistory(currentAgent.ID),
|
|
239
|
+
Tools: currentAgent.Tools,
|
|
240
|
+
ToolRegistry: toolRegistry,
|
|
241
|
+
SystemPrompt: currentAgent.SystemPrompt + fmt.Sprintf("\n\nCURRENT WORKING DIRECTORY: %s\nIMPORTANT: When creating files, use relative paths from the current working directory unless absolute paths are explicitly requested.", cwd),
|
|
242
|
+
Context: make(map[string]interface{}),
|
|
243
|
+
Config: currentAgent.Config,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
fmt.Println("\nš¤ Thinking...")
|
|
247
|
+
|
|
248
|
+
// Try to execute with streaming if possible
|
|
249
|
+
var output *registry.AgentOutput
|
|
250
|
+
var execErr error
|
|
251
|
+
|
|
252
|
+
// Check if we can use streaming (only for GeneralistAgent currently)
|
|
253
|
+
if genAgent, ok := currentAgent.Instance.(*agents.GeneralistAgent); ok {
|
|
254
|
+
// Stream with typewriter effect
|
|
255
|
+
fmt.Print("\nš¬ ")
|
|
256
|
+
output, execErr = genAgent.ExecuteStream(agentInput, func(chunk string) {
|
|
257
|
+
for _, char := range chunk {
|
|
258
|
+
fmt.Print(string(char))
|
|
259
|
+
time.Sleep(20 * time.Millisecond) // Typewriter speed
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
fmt.Println() // Newline after streaming
|
|
263
|
+
} else {
|
|
264
|
+
// Fallback to non-streaming
|
|
265
|
+
output, execErr = agentManager.Execute(ctx, agentInput)
|
|
266
|
+
if execErr == nil && output.Message != "" {
|
|
267
|
+
fmt.Printf("\nš¬ %s\n", output.Message)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if execErr != nil {
|
|
272
|
+
fmt.Printf("\nā Error: %v\n\n", execErr)
|
|
273
|
+
continue
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Execute tool calls if any and continue conversation
|
|
277
|
+
if len(output.ToolCalls) > 0 {
|
|
278
|
+
fmt.Printf("\nš§ Executing %d tool(s)...\n", len(output.ToolCalls))
|
|
279
|
+
|
|
280
|
+
toolResults := []string{}
|
|
281
|
+
for _, toolCall := range output.ToolCalls {
|
|
282
|
+
fmt.Printf(" ⢠%s\n", toolCall.ToolName)
|
|
283
|
+
|
|
284
|
+
req := &scheduler.ExecutionRequest{
|
|
285
|
+
ToolID: toolCall.ToolName,
|
|
286
|
+
Parameters: toolCall.Parameters,
|
|
287
|
+
Context: make(map[string]interface{}),
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
confirmHandler := func(req *scheduler.ConfirmationRequest) (bool, error) {
|
|
291
|
+
// Use liner.Prompt for input (works with liner's terminal mode)
|
|
292
|
+
response, err := line.Prompt(fmt.Sprintf("ā ļø Confirm %s? (y/n): ", req.ToolName))
|
|
293
|
+
if err != nil {
|
|
294
|
+
return false, err
|
|
295
|
+
}
|
|
296
|
+
response = strings.ToLower(strings.TrimSpace(response))
|
|
297
|
+
return response == "y" || response == "yes", nil
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
result, err := sched.Execute(ctx, req, confirmHandler)
|
|
301
|
+
if err != nil {
|
|
302
|
+
fmt.Printf(" ā Failed: %v\n", err)
|
|
303
|
+
toolResults = append(toolResults, fmt.Sprintf("Tool '%s' failed: %v", toolCall.ToolName, err))
|
|
304
|
+
} else if result.Success {
|
|
305
|
+
fmt.Printf(" ā
Success\n")
|
|
306
|
+
// Format tool result as string
|
|
307
|
+
resultStr := fmt.Sprintf("Tool '%s' result: %v", toolCall.ToolName, result.Data)
|
|
308
|
+
toolResults = append(toolResults, resultStr)
|
|
309
|
+
} else {
|
|
310
|
+
fmt.Printf(" ā ļø %s\n", result.Error)
|
|
311
|
+
toolResults = append(toolResults, fmt.Sprintf("Tool '%s' error: %s", toolCall.ToolName, result.Error))
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Continue conversation with tool results
|
|
316
|
+
if len(toolResults) > 0 {
|
|
317
|
+
fmt.Println("\nš¤ Processing results...")
|
|
318
|
+
|
|
319
|
+
// Only add model's response if it has content
|
|
320
|
+
if output.Message != "" {
|
|
321
|
+
agentManager.AddMessage(currentAgent.ID, "model", output.Message, make(map[string]interface{}))
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Add function results as special "function" message
|
|
325
|
+
toolResultsStr := strings.Join(toolResults, "\n\n")
|
|
326
|
+
agentManager.AddMessage(currentAgent.ID, "function", toolResultsStr, make(map[string]interface{}))
|
|
327
|
+
|
|
328
|
+
// Get continuation from AI
|
|
329
|
+
continuationInput := ®istry.AgentInput{
|
|
330
|
+
Query: "", // Empty query, AI should process function results
|
|
331
|
+
Parameters: make(map[string]interface{}),
|
|
332
|
+
History: agentManager.GetHistory(currentAgent.ID),
|
|
333
|
+
Tools: currentAgent.Tools,
|
|
334
|
+
ToolRegistry: toolRegistry,
|
|
335
|
+
SystemPrompt: currentAgent.SystemPrompt,
|
|
336
|
+
Context: make(map[string]interface{}),
|
|
337
|
+
Config: currentAgent.Config,
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
continuationOutput, err := agentManager.Execute(ctx, continuationInput)
|
|
341
|
+
if err != nil {
|
|
342
|
+
fmt.Printf("\nā Error processing results: %v\n\n", err)
|
|
343
|
+
} else {
|
|
344
|
+
fmt.Printf("\nš¬ %s\n", continuationOutput.Message)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
fmt.Println()
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return nil
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
func handleCommand(input string, agentManager *agents.AgentManager, toolRegistry *registry.ToolRegistry, agentRegistry *registry.AgentRegistry, linerInst *liner.State, historyFile string) (handled bool, shouldContinue bool) {
|
|
356
|
+
parts := strings.Fields(input)
|
|
357
|
+
if len(parts) == 0 {
|
|
358
|
+
return true, true
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
command := parts[0]
|
|
362
|
+
|
|
363
|
+
switch command {
|
|
364
|
+
case "/help", "/h":
|
|
365
|
+
fmt.Println("\nAvailable Commands:")
|
|
366
|
+
fmt.Println(" /help, /h - Show this help")
|
|
367
|
+
fmt.Println(" /agent - List and switch agents")
|
|
368
|
+
fmt.Println(" /tools - List available tools")
|
|
369
|
+
fmt.Println(" /auth - Show authentication status")
|
|
370
|
+
fmt.Println(" /clear - Clear conversation history")
|
|
371
|
+
fmt.Println(" /exit, /quit - Exit the agent")
|
|
372
|
+
fmt.Println()
|
|
373
|
+
return true, true
|
|
374
|
+
|
|
375
|
+
case "/agent", "/agents":
|
|
376
|
+
if len(parts) > 1 {
|
|
377
|
+
// Switch agent
|
|
378
|
+
agentID := parts[1]
|
|
379
|
+
if err := agentManager.SwitchAgent(agentID); err != nil {
|
|
380
|
+
fmt.Printf("ā Error: %v\n", err)
|
|
381
|
+
} else {
|
|
382
|
+
agent, _ := agentManager.GetCurrentAgent()
|
|
383
|
+
fmt.Printf("ā
Switched to: %s\n", agent.Name)
|
|
384
|
+
}
|
|
385
|
+
} else {
|
|
386
|
+
// List agents
|
|
387
|
+
agents := agentRegistry.List()
|
|
388
|
+
fmt.Println("\nAvailable Agents:")
|
|
389
|
+
for _, agent := range agents {
|
|
390
|
+
fmt.Printf(" ⢠%s - %s\n", agent.ID, agent.Description)
|
|
391
|
+
}
|
|
392
|
+
fmt.Println("\nUse '/agent <id>' to switch")
|
|
393
|
+
fmt.Println()
|
|
394
|
+
}
|
|
395
|
+
return true, true
|
|
396
|
+
|
|
397
|
+
case "/tools":
|
|
398
|
+
tools := toolRegistry.List()
|
|
399
|
+
fmt.Println("\nAvailable Tools:")
|
|
400
|
+
for _, tool := range tools {
|
|
401
|
+
fmt.Printf(" ⢠%s (%s) - %s\n", tool.Name, tool.RiskLevel, tool.Description)
|
|
402
|
+
}
|
|
403
|
+
fmt.Println()
|
|
404
|
+
return true, true
|
|
405
|
+
|
|
406
|
+
case "/clear":
|
|
407
|
+
agent, _ := agentManager.GetCurrentAgent()
|
|
408
|
+
agentManager.ClearHistory(agent.ID)
|
|
409
|
+
fmt.Println("ā
Conversation history cleared")
|
|
410
|
+
return true, true
|
|
411
|
+
|
|
412
|
+
case "/auth":
|
|
413
|
+
// Show auth status
|
|
414
|
+
cfg, err := config.LoadConfig()
|
|
415
|
+
if err != nil {
|
|
416
|
+
fmt.Printf("ā Error loading config: %v\n", err)
|
|
417
|
+
return true, true
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
fmt.Println("\nš Authentication Status:")
|
|
421
|
+
fmt.Printf(" Provider: %s\n", cfg.Auth.Provider)
|
|
422
|
+
fmt.Printf(" Auth Type: %s\n", cfg.Auth.Type)
|
|
423
|
+
|
|
424
|
+
if cfg.Auth.APIKey != "" {
|
|
425
|
+
fmt.Printf(" Status: ā
Authenticated (API Key)\n")
|
|
426
|
+
keyLen := len(cfg.Auth.APIKey)
|
|
427
|
+
if keyLen > 12 {
|
|
428
|
+
fmt.Printf(" Key: %s...%s\n", cfg.Auth.APIKey[:8], cfg.Auth.APIKey[keyLen-4:])
|
|
429
|
+
}
|
|
430
|
+
} else {
|
|
431
|
+
fmt.Printf(" Status: ā Not authenticated\n")
|
|
432
|
+
fmt.Println("\n Run: ./dcode auth login --type api_key")
|
|
433
|
+
}
|
|
434
|
+
fmt.Println()
|
|
435
|
+
return true, true
|
|
436
|
+
|
|
437
|
+
case "/exit", "/quit", "/q":
|
|
438
|
+
if f, err := os.Create(historyFile); err == nil {
|
|
439
|
+
linerInst.WriteHistory(f)
|
|
440
|
+
f.Close()
|
|
441
|
+
}
|
|
442
|
+
fmt.Println("\nš Goodbye!")
|
|
443
|
+
return true, false
|
|
444
|
+
|
|
445
|
+
default:
|
|
446
|
+
return false, true
|
|
447
|
+
}
|
|
448
|
+
}
|
package/cmd/analyze.go
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"io/fs"
|
|
6
|
+
"os"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"sort"
|
|
9
|
+
"strings"
|
|
10
|
+
|
|
11
|
+
"github.com/spf13/cobra"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
var analyzeCmd = &cobra.Command{
|
|
15
|
+
Use: "analyze [path]",
|
|
16
|
+
Short: "Analyze project structure",
|
|
17
|
+
RunE: func(cmd *cobra.Command, args []string) error {
|
|
18
|
+
path := "."
|
|
19
|
+
if len(args) > 0 {
|
|
20
|
+
path = args[0]
|
|
21
|
+
}
|
|
22
|
+
fmt.Printf("Analyzing: %s\n\n", path)
|
|
23
|
+
|
|
24
|
+
var total int
|
|
25
|
+
byExt := map[string]int{}
|
|
26
|
+
markers := map[string]bool{}
|
|
27
|
+
ignoreDirs := map[string]bool{".git": true, "node_modules": true, ".next": true, "dist": true, "build": true, ".venv": true}
|
|
28
|
+
|
|
29
|
+
err := filepath.WalkDir(path, func(p string, d fs.DirEntry, err error) error {
|
|
30
|
+
if err != nil {
|
|
31
|
+
return err
|
|
32
|
+
}
|
|
33
|
+
name := d.Name()
|
|
34
|
+
if d.IsDir() {
|
|
35
|
+
if ignoreDirs[name] {
|
|
36
|
+
return filepath.SkipDir
|
|
37
|
+
}
|
|
38
|
+
return nil
|
|
39
|
+
}
|
|
40
|
+
total++
|
|
41
|
+
ext := strings.ToLower(filepath.Ext(name))
|
|
42
|
+
if ext == "" {
|
|
43
|
+
ext = "(noext)"
|
|
44
|
+
}
|
|
45
|
+
byExt[ext]++
|
|
46
|
+
base := strings.ToLower(name)
|
|
47
|
+
switch base {
|
|
48
|
+
case "go.mod", "package.json", "pyproject.toml", "requirements.txt", "pom.xml", "build.gradle", "cargo.toml":
|
|
49
|
+
markers[base] = true
|
|
50
|
+
}
|
|
51
|
+
return nil
|
|
52
|
+
})
|
|
53
|
+
if err != nil {
|
|
54
|
+
return err
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fmt.Printf("Files: %d\n", total)
|
|
58
|
+
if len(markers) > 0 {
|
|
59
|
+
fmt.Println("Detected:")
|
|
60
|
+
keys := make([]string, 0, len(markers))
|
|
61
|
+
for k := range markers {
|
|
62
|
+
keys = append(keys, k)
|
|
63
|
+
}
|
|
64
|
+
sort.Strings(keys)
|
|
65
|
+
for _, k := range keys {
|
|
66
|
+
fmt.Printf(" - %s\n", k)
|
|
67
|
+
}
|
|
68
|
+
fmt.Println()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type kv struct {
|
|
72
|
+
k string
|
|
73
|
+
v int
|
|
74
|
+
}
|
|
75
|
+
items := make([]kv, 0, len(byExt))
|
|
76
|
+
for k, v := range byExt {
|
|
77
|
+
items = append(items, kv{k: k, v: v})
|
|
78
|
+
}
|
|
79
|
+
sort.Slice(items, func(i, j int) bool { return items[i].v > items[j].v })
|
|
80
|
+
|
|
81
|
+
fmt.Println("Top file types:")
|
|
82
|
+
for i := 0; i < len(items) && i < 10; i++ {
|
|
83
|
+
fmt.Printf(" %6d %s\n", items[i].v, items[i].k)
|
|
84
|
+
}
|
|
85
|
+
fmt.Println()
|
|
86
|
+
|
|
87
|
+
if _, err := os.Stat(filepath.Join(path, "README.md")); err == nil {
|
|
88
|
+
fmt.Println("Tip: run `dcode edit --file README.md --prompt \"...\"` to update docs.")
|
|
89
|
+
}
|
|
90
|
+
fmt.Println("Analysis complete.")
|
|
91
|
+
return nil
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func init() {
|
|
96
|
+
rootCmd.AddCommand(analyzeCmd)
|
|
97
|
+
}
|