@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
package/cmd/auth.go ADDED
@@ -0,0 +1,338 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "os"
7
+ "time"
8
+
9
+ "github.com/ddhanush1/dcode/internal/auth"
10
+ "github.com/ddhanush1/dcode/internal/config"
11
+ "github.com/spf13/cobra"
12
+ )
13
+
14
+ var authCmd = &cobra.Command{
15
+ Use: "auth",
16
+ Short: "Authentication management",
17
+ Long: `Manage authentication credentials for various AI providers`,
18
+ }
19
+
20
+ var loginCmd = &cobra.Command{
21
+ Use: "login",
22
+ Short: "Login to an AI provider",
23
+ Long: `Authenticate with an AI provider using OAuth2 flow`,
24
+ RunE: runLogin,
25
+ }
26
+
27
+ var logoutCmd = &cobra.Command{
28
+ Use: "logout",
29
+ Short: "Logout from an AI provider",
30
+ Long: `Remove stored credentials for an AI provider`,
31
+ RunE: runLogout,
32
+ }
33
+
34
+ var statusCmd = &cobra.Command{
35
+ Use: "status",
36
+ Short: "Show authentication status",
37
+ Long: `Display current authentication status for all providers`,
38
+ RunE: runStatus,
39
+ }
40
+
41
+ func init() {
42
+ rootCmd.AddCommand(authCmd)
43
+ authCmd.AddCommand(loginCmd)
44
+ authCmd.AddCommand(logoutCmd)
45
+ authCmd.AddCommand(statusCmd)
46
+
47
+ loginCmd.Flags().String("provider", "gemini", "AI provider (openai, gemini, claude)")
48
+ loginCmd.Flags().String("type", "oauth2", "Auth type (oauth2, adc, api_key)")
49
+
50
+ logoutCmd.Flags().String("provider", "", "AI provider to logout from (default: all)")
51
+ }
52
+
53
+ func runLogin(cmd *cobra.Command, args []string) error {
54
+ provider, _ := cmd.Flags().GetString("provider")
55
+ authType, _ := cmd.Flags().GetString("type")
56
+
57
+ fmt.Printf("🔐 Logging in to %s using %s...\n\n", provider, authType)
58
+
59
+ store := auth.GetDefaultStore()
60
+
61
+ cfg, err := config.LoadConfig()
62
+ if err != nil {
63
+ return fmt.Errorf("failed to load config: %w", err)
64
+ }
65
+
66
+ // Override with flags
67
+ cfg.Auth.Provider = provider
68
+ cfg.Auth.Type = authType
69
+
70
+ ctx := context.Background()
71
+
72
+ switch auth.AuthType(authType) {
73
+ case auth.AuthTypeOAuth2:
74
+ return runOAuth2Login(ctx, provider, store)
75
+
76
+ case auth.AuthTypeADC:
77
+ return runADCLogin(ctx, provider)
78
+
79
+ case auth.AuthTypeAPIKey:
80
+ return runAPIKeyLogin(provider, store)
81
+
82
+ default:
83
+ return fmt.Errorf("unsupported auth type: %s", authType)
84
+ }
85
+ }
86
+
87
+ func runOAuth2Login(ctx context.Context, provider string, store auth.CredentialStore) error {
88
+ clientID := os.Getenv("GOOGLE_CLIENT_ID")
89
+ clientSecret := os.Getenv("GOOGLE_CLIENT_SECRET")
90
+
91
+ if clientID == "" || clientSecret == "" {
92
+ fmt.Println("❌ OAuth2 requires GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET environment variables")
93
+ fmt.Println("\nTo set up OAuth2:")
94
+ fmt.Println("1. Go to https://console.cloud.google.com/apis/credentials")
95
+ fmt.Println("2. Create OAuth 2.0 Client ID")
96
+ fmt.Println("3. Set redirect URI to: http://localhost:8085/callback")
97
+ fmt.Println("4. Export credentials:")
98
+ fmt.Println(" export GOOGLE_CLIENT_ID=your-client-id")
99
+ fmt.Println(" export GOOGLE_CLIENT_SECRET=your-client-secret")
100
+ return fmt.Errorf("OAuth2 credentials not configured")
101
+ }
102
+
103
+ scopes := []string{
104
+ "https://www.googleapis.com/auth/userinfo.email",
105
+ "https://www.googleapis.com/auth/cloud-platform",
106
+ "https://www.googleapis.com/auth/generative-language",
107
+ }
108
+
109
+ authenticator := auth.NewOAuth2Auth(provider, clientID, clientSecret, scopes, store)
110
+
111
+ // Start auth flow
112
+ authURL, err := authenticator.StartAuthFlow(ctx)
113
+ if err != nil {
114
+ return fmt.Errorf("failed to start auth flow: %w", err)
115
+ }
116
+
117
+ fmt.Println("📱 Opening browser for authentication...")
118
+ fmt.Printf("\nIf browser doesn't open, visit:\n%s\n\n", authURL)
119
+
120
+ // TODO: Implement OAuth callback server
121
+ fmt.Println("⚠️ OAuth2 callback server not yet implemented")
122
+ fmt.Println("For now, use API key authentication with: dcode auth login --type api_key")
123
+
124
+ return nil
125
+ }
126
+
127
+ func runADCLogin(ctx context.Context, provider string) error {
128
+ fmt.Println("🔍 Checking for Application Default Credentials...")
129
+
130
+ if !auth.IsADCAvailable(ctx) {
131
+ fmt.Println("❌ ADC not available")
132
+ fmt.Println("\nTo set up ADC:")
133
+ fmt.Println("1. Install gcloud CLI: https://cloud.google.com/sdk/docs/install")
134
+ fmt.Println("2. Run: gcloud auth application-default login")
135
+ return fmt.Errorf("ADC not configured")
136
+ }
137
+
138
+ authenticator := auth.NewADCAuth(provider, []string{"https://www.googleapis.com/auth/cloud-platform"})
139
+
140
+ if err := authenticator.Validate(ctx); err != nil {
141
+ return fmt.Errorf("ADC validation failed: %w", err)
142
+ }
143
+
144
+ fmt.Println("✅ ADC authentication successful!")
145
+ fmt.Printf("Provider: %s\n", provider)
146
+
147
+ return nil
148
+ }
149
+
150
+ func runAPIKeyLogin(provider string, store auth.CredentialStore) error {
151
+ var envVar string
152
+ switch provider {
153
+ case "openai":
154
+ envVar = "OPENAI_API_KEY"
155
+ case "gemini":
156
+ envVar = "GEMINI_API_KEY or GOOGLE_API_KEY"
157
+ case "claude", "anthropic":
158
+ envVar = "ANTHROPIC_API_KEY"
159
+ default:
160
+ return fmt.Errorf("unknown provider: %s", provider)
161
+ }
162
+
163
+ fmt.Printf("🔑 API Key authentication for %s\n", provider)
164
+ fmt.Printf("Set environment variable: %s\n\n", envVar)
165
+
166
+ apiKey := os.Getenv(envVar)
167
+ if apiKey == "" && provider == "gemini" {
168
+ apiKey = os.Getenv("GOOGLE_API_KEY")
169
+ }
170
+
171
+ if apiKey == "" {
172
+ fmt.Println("❌ API key not found in environment")
173
+ fmt.Printf("\nTo set up API key:\n")
174
+ fmt.Printf("export %s=your-api-key\n", envVar)
175
+ return fmt.Errorf("API key not configured")
176
+ }
177
+
178
+ // Store credentials
179
+ creds := &auth.Credentials{
180
+ Type: auth.AuthTypeAPIKey,
181
+ Provider: provider,
182
+ Token: apiKey,
183
+ Metadata: make(map[string]string),
184
+ }
185
+
186
+ if err := store.Save(provider, creds); err != nil {
187
+ fmt.Printf("⚠️ Failed to store credentials: %v\n", err)
188
+ fmt.Println("Credentials are still available from environment variable")
189
+ }
190
+
191
+ fmt.Println("✅ API key authentication successful!")
192
+ fmt.Printf("Provider: %s\n", provider)
193
+
194
+ return nil
195
+ }
196
+
197
+ func runLogout(cmd *cobra.Command, args []string) error {
198
+ provider, _ := cmd.Flags().GetString("provider")
199
+
200
+ store := auth.GetDefaultStore()
201
+
202
+ if provider == "" {
203
+ // Logout from all providers
204
+ providers, err := store.List()
205
+ if err != nil {
206
+ return fmt.Errorf("failed to list providers: %w", err)
207
+ }
208
+
209
+ if len(providers) == 0 {
210
+ fmt.Println("No stored credentials found")
211
+ return nil
212
+ }
213
+
214
+ fmt.Printf("Logging out from %d provider(s)...\n", len(providers))
215
+ for _, p := range providers {
216
+ if err := store.Delete(p); err != nil {
217
+ fmt.Printf("⚠️ Failed to remove %s: %v\n", p, err)
218
+ } else {
219
+ fmt.Printf("✅ Removed credentials for %s\n", p)
220
+ }
221
+ }
222
+ } else {
223
+ // Logout from specific provider
224
+ if err := store.Delete(provider); err != nil {
225
+ return fmt.Errorf("failed to remove credentials: %w", err)
226
+ }
227
+ fmt.Printf("✅ Logged out from %s\n", provider)
228
+ }
229
+
230
+ return nil
231
+ }
232
+
233
+ func runStatus(cmd *cobra.Command, args []string) error {
234
+ fmt.Println("🔐 Authentication Status\n")
235
+
236
+ cfg, err := config.LoadConfig()
237
+ if err != nil {
238
+ return fmt.Errorf("failed to load config: %w", err)
239
+ }
240
+
241
+ factory := auth.NewFactory(auth.GetDefaultStore())
242
+ ctx := context.Background()
243
+
244
+ // Check configured provider
245
+ fmt.Printf("Configured Provider: %s\n", cfg.Auth.Provider)
246
+ fmt.Printf("Auth Type: %s\n\n", cfg.Auth.Type)
247
+
248
+ // Check stored credentials
249
+ store := auth.GetDefaultStore()
250
+ providers, err := store.List()
251
+ if err != nil {
252
+ fmt.Printf("⚠️ Failed to list stored credentials: %v\n", err)
253
+ } else if len(providers) > 0 {
254
+ fmt.Println("Stored Credentials:")
255
+ for _, provider := range providers {
256
+ authenticator, err := factory.CreateFromProvider(provider)
257
+ if err != nil {
258
+ fmt.Printf(" • %s: ❌ Invalid\n", provider)
259
+ continue
260
+ }
261
+
262
+ info, err := getTokenInfo(ctx, authenticator)
263
+ if err != nil {
264
+ fmt.Printf(" • %s: ❌ Error: %v\n", provider, err)
265
+ continue
266
+ }
267
+
268
+ status := "✅ Valid"
269
+ if !info.Valid {
270
+ status = "❌ Invalid"
271
+ } else if !info.ExpiresAt.IsZero() && info.ExpiresAt.Before(time.Now()) {
272
+ status = "⚠️ Expired"
273
+ }
274
+
275
+ fmt.Printf(" • %s: %s", provider, status)
276
+ if !info.ExpiresAt.IsZero() {
277
+ fmt.Printf(" (expires in %s)", time.Until(info.ExpiresAt).Round(time.Minute))
278
+ }
279
+ fmt.Println()
280
+ }
281
+ fmt.Println()
282
+ }
283
+
284
+ // Check environment variables
285
+ fmt.Println("Environment Variables:")
286
+ checkEnvVar("OPENAI_API_KEY")
287
+ checkEnvVar("GEMINI_API_KEY")
288
+ checkEnvVar("GOOGLE_API_KEY")
289
+ checkEnvVar("ANTHROPIC_API_KEY")
290
+ fmt.Println()
291
+
292
+ // Check ADC
293
+ fmt.Print("Application Default Credentials (ADC): ")
294
+ if auth.IsADCAvailable(ctx) {
295
+ fmt.Println("✅ Available")
296
+ } else {
297
+ fmt.Println("❌ Not available")
298
+ }
299
+
300
+ return nil
301
+ }
302
+
303
+ func checkEnvVar(name string) {
304
+ value := os.Getenv(name)
305
+ if value != "" {
306
+ masked := value[:minInt(8, len(value))] + "..."
307
+ fmt.Printf(" • %s: ✅ Set (%s)\n", name, masked)
308
+ } else {
309
+ fmt.Printf(" • %s: ❌ Not set\n", name)
310
+ }
311
+ }
312
+
313
+ func getTokenInfo(ctx context.Context, authenticator auth.Authenticator) (*auth.TokenInfo, error) {
314
+ // Try type assertion for method that returns TokenInfo
315
+ switch a := authenticator.(type) {
316
+ case *auth.APIKeyAuth:
317
+ return a.GetTokenInfo(ctx)
318
+ case *auth.OAuth2Auth:
319
+ return a.GetTokenInfo(ctx)
320
+ case *auth.ADCAuth:
321
+ return a.GetTokenInfo(ctx)
322
+ case *auth.VertexAIAuth:
323
+ return a.GetTokenInfo(ctx)
324
+ default:
325
+ // Fallback: just validate
326
+ if err := authenticator.Validate(ctx); err != nil {
327
+ return &auth.TokenInfo{Valid: false}, nil
328
+ }
329
+ return &auth.TokenInfo{Valid: true, Provider: authenticator.GetProvider()}, nil
330
+ }
331
+ }
332
+
333
+ func minInt(a, b int) int {
334
+ if a < b {
335
+ return a
336
+ }
337
+ return b
338
+ }
package/cmd/compose.go ADDED
@@ -0,0 +1,284 @@
1
+ package cmd
2
+
3
+ import (
4
+ "bufio"
5
+ "context"
6
+ "fmt"
7
+ "os"
8
+ "path/filepath"
9
+ "strings"
10
+ "time"
11
+
12
+ "github.com/ddhanush1/dcode/internal/agent"
13
+ "github.com/ddhanush1/dcode/internal/diffutil"
14
+ "github.com/ddhanush1/dcode/internal/fsutil"
15
+ "github.com/spf13/cobra"
16
+ )
17
+
18
+ var (
19
+ composeFiles []string
20
+ composeAuto bool
21
+ composeModel string
22
+ composeProvider string
23
+ composeWeb bool
24
+ )
25
+
26
+ var composeCmd = &cobra.Command{
27
+ Use: "compose [prompt]",
28
+ Short: "Cursor-like composer for multi-file edits",
29
+ Long: `Composer Mode - Multi-file AI-powered code generation and editing
30
+
31
+ Like Cursor Composer, this mode allows you to:
32
+ • Generate multiple files at once
33
+ • Edit across multiple files simultaneously
34
+ • Create entire features or components
35
+ • Refactor with context awareness
36
+
37
+ Examples:
38
+ dcode compose "Create a REST API with user CRUD operations"
39
+ dcode compose "Add authentication to the project"
40
+ dcode compose --files "*.go" "Add logging to all functions"
41
+ dcode compose --auto "Refactor database layer to use repository pattern"
42
+
43
+ The composer will:
44
+ 1. Analyze your request and codebase
45
+ 2. Determine which files need changes
46
+ 3. Generate or edit multiple files
47
+ 4. Show you a preview of all changes
48
+ 5. Apply changes after confirmation (or --auto)
49
+ `,
50
+ RunE: runCompose,
51
+ }
52
+
53
+ func runCompose(cmd *cobra.Command, args []string) error {
54
+ if len(args) == 0 {
55
+ return fmt.Errorf("prompt required")
56
+ }
57
+
58
+ prompt := strings.Join(args, " ")
59
+
60
+ if composeProvider != "" {
61
+ _ = os.Setenv("AI_PROVIDER", composeProvider)
62
+ }
63
+ if composeModel != "" {
64
+ _ = os.Setenv("AI_MODEL", composeModel)
65
+ }
66
+
67
+ ag, err := agent.New()
68
+ if err != nil {
69
+ return err
70
+ }
71
+
72
+ wd, _ := os.Getwd()
73
+
74
+ fmt.Println("\033[1;35m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m")
75
+ fmt.Println("\033[1;36m🎼 DCode Composer Mode\033[0m")
76
+ fmt.Printf("\033[90mWorking directory: %s\033[0m\n", wd)
77
+ fmt.Println("\033[1;35m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m")
78
+ fmt.Println()
79
+
80
+ // Gather context files
81
+ contextFiles, err := gatherContextFiles(wd, composeFiles)
82
+ if err != nil {
83
+ return fmt.Errorf("gathering context: %w", err)
84
+ }
85
+
86
+ if len(contextFiles) > 0 {
87
+ fmt.Printf("\033[90m📁 Context files (%d):\033[0m\n", len(contextFiles))
88
+ for _, f := range contextFiles {
89
+ fmt.Printf(" • %s\n", f.Path)
90
+ }
91
+ fmt.Println()
92
+ }
93
+
94
+ // Build prompt with context
95
+ fullPrompt := buildComposePrompt(prompt, contextFiles)
96
+
97
+ fmt.Println("\033[1;33m🤔 Analyzing request...\033[0m")
98
+ fmt.Println()
99
+
100
+ // Use the agent's edit capability for multi-file operations
101
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
102
+ defer cancel()
103
+
104
+ resp, err := ag.Edit(ctx, agent.EditRequest{
105
+ Prompt: fullPrompt,
106
+ Files: contextFiles,
107
+ WebSearch: agent.WebSearchRequest{Enabled: composeWeb},
108
+ })
109
+ if err != nil {
110
+ return fmt.Errorf("composer failed: %w", err)
111
+ }
112
+
113
+ if len(resp.Edits) == 0 {
114
+ fmt.Println("\033[33m⚠ No changes generated\033[0m")
115
+ return nil
116
+ }
117
+
118
+ // Display results
119
+ fmt.Println("\033[1;32m✨ Generated changes:\033[0m")
120
+ fmt.Println()
121
+
122
+ for i, edit := range resp.Edits {
123
+ fmt.Printf("\033[1;36m%d. %s\033[0m\n", i+1, edit.Path)
124
+
125
+ // Check if file exists
126
+ existingContent := ""
127
+ fullPath := filepath.Join(wd, edit.Path)
128
+ if _, err := os.Stat(fullPath); err == nil {
129
+ existingContent, _ = fsutil.ReadFile(fullPath)
130
+ }
131
+
132
+ if existingContent == "" {
133
+ fmt.Println("\033[90m (new file)\033[0m")
134
+ // Show preview of new file (first 10 lines)
135
+ lines := strings.Split(edit.NewContent, "\n")
136
+ preview := strings.Join(lines[:min(10, len(lines))], "\n")
137
+ fmt.Println("\033[90m" + preview + "\033[0m")
138
+ if len(lines) > 10 {
139
+ fmt.Printf("\033[90m ... (%d more lines)\033[0m\n", len(lines)-10)
140
+ }
141
+ } else {
142
+ // Show diff
143
+ diff := diffutil.Unified("a/"+edit.Path, "b/"+edit.Path, existingContent, edit.NewContent)
144
+ fmt.Println(diff)
145
+ }
146
+ fmt.Println()
147
+ }
148
+
149
+ if resp.Explanation != "" {
150
+ fmt.Println("\033[1;34m💡 Explanation:\033[0m")
151
+ fmt.Println(resp.Explanation)
152
+ fmt.Println()
153
+ }
154
+
155
+ // Auto-apply or ask for confirmation
156
+ apply := composeAuto
157
+ if !apply {
158
+ fmt.Print("\033[1;33m📝 Apply these changes? [y/N] \033[0m")
159
+ reader := bufio.NewReader(os.Stdin)
160
+ line, _ := reader.ReadString('\n')
161
+ apply = strings.TrimSpace(strings.ToLower(line)) == "y" || strings.TrimSpace(strings.ToLower(line)) == "yes"
162
+ }
163
+
164
+ if !apply {
165
+ fmt.Println("\033[90m✗ Changes not applied\033[0m")
166
+ return nil
167
+ }
168
+
169
+ // Apply changes
170
+ fmt.Println("\033[1;32m✓ Applying changes...\033[0m")
171
+ for _, edit := range resp.Edits {
172
+ fullPath := filepath.Join(wd, edit.Path)
173
+
174
+ // Ensure directory exists
175
+ dir := filepath.Dir(fullPath)
176
+ if err := os.MkdirAll(dir, 0o755); err != nil {
177
+ return fmt.Errorf("creating directory %s: %w", dir, err)
178
+ }
179
+
180
+ // Write file
181
+ perm := fsutil.FilePermOrDefault(fullPath, 0o644)
182
+ if err := fsutil.WriteFileAtomic(fullPath, []byte(edit.NewContent), perm); err != nil {
183
+ return fmt.Errorf("writing %s: %w", fullPath, err)
184
+ }
185
+
186
+ fmt.Printf(" \033[32m✓\033[0m %s\n", edit.Path)
187
+ }
188
+
189
+ fmt.Println()
190
+ fmt.Println("\033[1;32m🎉 All changes applied successfully!\033[0m")
191
+
192
+ return nil
193
+ }
194
+
195
+ func gatherContextFiles(wd string, patterns []string) ([]agent.FileInput, error) {
196
+ if len(patterns) == 0 {
197
+ // Auto-detect relevant files based on common patterns
198
+ patterns = []string{"*.go", "*.ts", "*.tsx", "*.js", "*.jsx", "*.py", "*.java", "*.rs"}
199
+ }
200
+
201
+ var files []agent.FileInput
202
+ seen := make(map[string]bool)
203
+
204
+ for _, pattern := range patterns {
205
+ matches, err := filepath.Glob(filepath.Join(wd, pattern))
206
+ if err != nil {
207
+ continue
208
+ }
209
+
210
+ for _, match := range matches {
211
+ relPath, _ := filepath.Rel(wd, match)
212
+ if seen[relPath] {
213
+ continue
214
+ }
215
+ seen[relPath] = true
216
+
217
+ // Skip large files and binaries
218
+ info, err := os.Stat(match)
219
+ if err != nil || info.IsDir() || info.Size() > 100*1024 {
220
+ continue
221
+ }
222
+
223
+ content, err := fsutil.ReadFile(match)
224
+ if err != nil {
225
+ continue
226
+ }
227
+
228
+ files = append(files, agent.FileInput{
229
+ Path: filepath.ToSlash(relPath),
230
+ Content: content,
231
+ })
232
+
233
+ // Limit context size
234
+ if len(files) >= 20 {
235
+ break
236
+ }
237
+ }
238
+ }
239
+
240
+ return files, nil
241
+ }
242
+
243
+ func buildComposePrompt(userPrompt string, contextFiles []agent.FileInput) string {
244
+ var sb strings.Builder
245
+
246
+ sb.WriteString("You are a Cursor-like AI composer assistant. ")
247
+ sb.WriteString("Generate or edit multiple files as needed to fulfill the user's request.\n\n")
248
+
249
+ sb.WriteString("User Request:\n")
250
+ sb.WriteString(userPrompt)
251
+ sb.WriteString("\n\n")
252
+
253
+ if len(contextFiles) > 0 {
254
+ sb.WriteString("Existing Codebase Context:\n")
255
+ sb.WriteString("Analyze these files to understand the project structure, patterns, and style.\n")
256
+ sb.WriteString("Maintain consistency with existing code.\n\n")
257
+ }
258
+
259
+ sb.WriteString("Instructions:\n")
260
+ sb.WriteString("1. Create new files or edit existing ones as needed\n")
261
+ sb.WriteString("2. Follow best practices and existing code patterns\n")
262
+ sb.WriteString("3. Add comments where helpful\n")
263
+ sb.WriteString("4. Ensure code is production-ready\n")
264
+ sb.WriteString("5. Generate complete, runnable code\n")
265
+
266
+ return sb.String()
267
+ }
268
+
269
+ func min(a, b int) int {
270
+ if a < b {
271
+ return a
272
+ }
273
+ return b
274
+ }
275
+
276
+ func init() {
277
+ composeCmd.Flags().StringSliceVarP(&composeFiles, "files", "f", nil, "File patterns for context (e.g., *.go, src/*.ts)")
278
+ composeCmd.Flags().BoolVarP(&composeAuto, "auto", "a", false, "Auto-apply changes without confirmation")
279
+ composeCmd.Flags().StringVar(&composeModel, "model", "", "AI model to use")
280
+ composeCmd.Flags().StringVar(&composeProvider, "provider", "", "AI provider (openai, claude, gemini)")
281
+ composeCmd.Flags().BoolVar(&composeWeb, "web", false, "Enable web search for additional context")
282
+
283
+ rootCmd.AddCommand(composeCmd)
284
+ }