@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,332 @@
1
+ package ai
2
+
3
+ import (
4
+ "bufio"
5
+ "bytes"
6
+ "context"
7
+ "encoding/json"
8
+ "fmt"
9
+ "io"
10
+ "net/http"
11
+ "time"
12
+ )
13
+
14
+ // Tool represents a function/tool schema for Gemini
15
+ type Tool struct {
16
+ FunctionDeclarations []FunctionDeclaration `json:"functionDeclarations"`
17
+ }
18
+
19
+ // FunctionDeclaration defines a function schema
20
+ type FunctionDeclaration struct {
21
+ Name string `json:"name"`
22
+ Description string `json:"description"`
23
+ Parameters map[string]interface{} `json:"parameters"`
24
+ }
25
+
26
+ // FunctionCall represents a function call from the AI
27
+ type FunctionCall struct {
28
+ Name string `json:"name"`
29
+ Args map[string]interface{} `json:"args"`
30
+ }
31
+
32
+ // FunctionResponse represents the result of a function call
33
+ type FunctionResponse struct {
34
+ Name string `json:"name"`
35
+ Response map[string]interface{} `json:"response"`
36
+ }
37
+
38
+ // ChatWithTools calls Gemini with function calling support
39
+ func (c *Client) ChatWithTools(ctx context.Context, messages []Message, tools []Tool) (*ToolResponse, error) {
40
+ if c.Provider != ProviderGemini {
41
+ // Fallback for non-Gemini providers
42
+ resp, err := c.ChatWithContext(ctx, messages)
43
+ return &ToolResponse{
44
+ Text: resp,
45
+ FunctionCalls: nil,
46
+ }, err
47
+ }
48
+
49
+ type Part struct {
50
+ Text string `json:"text,omitempty"`
51
+ FunctionCall *FunctionCall `json:"functionCall,omitempty"`
52
+ }
53
+
54
+ type Content struct {
55
+ Parts []Part `json:"parts"`
56
+ Role string `json:"role,omitempty"`
57
+ }
58
+
59
+ type Request struct {
60
+ Contents []Content `json:"contents"`
61
+ Tools []Tool `json:"tools,omitempty"`
62
+ SystemInstruction *Content `json:"systemInstruction,omitempty"`
63
+ }
64
+
65
+ type Response struct {
66
+ Candidates []struct {
67
+ Content Content `json:"content"`
68
+ FinishReason string `json:"finishReason,omitempty"`
69
+ SafetyRatings []struct {
70
+ Category string `json:"category"`
71
+ Probability string `json:"probability"`
72
+ } `json:"safetyRatings,omitempty"`
73
+ } `json:"candidates"`
74
+ }
75
+
76
+ // Convert messages to Gemini format
77
+ var contents []Content
78
+ var systemInstruction *Content
79
+
80
+ for _, msg := range messages {
81
+ role := msg.Role
82
+ if role == "system" {
83
+ // System messages go in systemInstruction field
84
+ systemInstruction = &Content{
85
+ Parts: []Part{{Text: msg.Content}},
86
+ }
87
+ continue
88
+ }
89
+
90
+ if role == "assistant" {
91
+ role = "model"
92
+ } else if role == "tool" || role == "function" {
93
+ // Tool results become user role (Gemini doesn't accept "tool" or "function")
94
+ role = "user"
95
+ }
96
+
97
+ contents = append(contents, Content{
98
+ Parts: []Part{{Text: msg.Content}},
99
+ Role: role,
100
+ })
101
+ }
102
+
103
+ reqBody := Request{
104
+ Contents: contents,
105
+ Tools: tools,
106
+ SystemInstruction: systemInstruction,
107
+ }
108
+
109
+ jsonData, err := json.Marshal(reqBody)
110
+ if err != nil {
111
+ return nil, fmt.Errorf("failed to marshal request: %w", err)
112
+ }
113
+
114
+ url := fmt.Sprintf("https://generativelanguage.googleapis.com/v1beta/models/%s:generateContent?key=%s", c.Model, c.APIKey)
115
+ req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
116
+ if err != nil {
117
+ return nil, err
118
+ }
119
+
120
+ req.Header.Set("Content-Type", "application/json")
121
+
122
+ client := &http.Client{Timeout: 90 * time.Second}
123
+ resp, err := client.Do(req)
124
+ if err != nil {
125
+ return nil, err
126
+ }
127
+ defer resp.Body.Close()
128
+
129
+ body, err := io.ReadAll(resp.Body)
130
+ if err != nil {
131
+ return nil, err
132
+ }
133
+
134
+ if resp.StatusCode != http.StatusOK {
135
+ return nil, fmt.Errorf("API error (%d): %s", resp.StatusCode, string(body))
136
+ }
137
+
138
+ var response Response
139
+ if err := json.Unmarshal(body, &response); err != nil {
140
+ return nil, fmt.Errorf("failed to unmarshal response: %w", err)
141
+ }
142
+
143
+ if len(response.Candidates) == 0 {
144
+ return nil, fmt.Errorf("no response from API")
145
+ }
146
+
147
+ candidate := response.Candidates[0]
148
+
149
+ // Extract text and function calls
150
+ var text string
151
+ var functionCalls []FunctionCall
152
+
153
+ for _, part := range candidate.Content.Parts {
154
+ if part.Text != "" {
155
+ text += part.Text
156
+ }
157
+ if part.FunctionCall != nil {
158
+ functionCalls = append(functionCalls, *part.FunctionCall)
159
+ }
160
+ }
161
+
162
+ return &ToolResponse{
163
+ Text: text,
164
+ FunctionCalls: functionCalls,
165
+ FinishReason: candidate.FinishReason,
166
+ }, nil
167
+ }
168
+
169
+ // ToolResponse contains the response with potential function calls
170
+ type ToolResponse struct {
171
+ Text string
172
+ FunctionCalls []FunctionCall
173
+ FinishReason string
174
+ }
175
+
176
+ // ChatWithToolsStream calls Gemini with streaming and function calling
177
+ func (c *Client) ChatWithToolsStream(ctx context.Context, messages []Message, tools []Tool, onChunk func(string)) (*ToolResponse, error) {
178
+ if c.Provider != ProviderGemini {
179
+ // Fallback for non-Gemini providers
180
+ resp, err := c.ChatWithContext(ctx, messages)
181
+ if onChunk != nil && resp != "" {
182
+ onChunk(resp)
183
+ }
184
+ return &ToolResponse{
185
+ Text: resp,
186
+ FunctionCalls: nil,
187
+ }, err
188
+ }
189
+
190
+ type Part struct {
191
+ Text string `json:"text,omitempty"`
192
+ FunctionCall *FunctionCall `json:"functionCall,omitempty"`
193
+ }
194
+
195
+ type Content struct {
196
+ Parts []Part `json:"parts"`
197
+ Role string `json:"role,omitempty"`
198
+ }
199
+
200
+ type Request struct {
201
+ Contents []Content `json:"contents"`
202
+ Tools []Tool `json:"tools,omitempty"`
203
+ SystemInstruction *Content `json:"systemInstruction,omitempty"`
204
+ }
205
+
206
+ // Convert messages to Gemini format
207
+ var contents []Content
208
+ var systemInstruction *Content
209
+
210
+ for _, msg := range messages {
211
+ role := msg.Role
212
+ if role == "system" {
213
+ // System messages go in systemInstruction field
214
+ systemInstruction = &Content{
215
+ Parts: []Part{{Text: msg.Content}},
216
+ }
217
+ continue
218
+ }
219
+
220
+ if role == "assistant" {
221
+ role = "model"
222
+ } else if role == "tool" || role == "function" {
223
+ // Tool results become user role (Gemini doesn't accept "tool" or "function")
224
+ role = "user"
225
+ }
226
+
227
+ contents = append(contents, Content{
228
+ Parts: []Part{{Text: msg.Content}},
229
+ Role: role,
230
+ })
231
+ }
232
+
233
+ reqBody := Request{
234
+ Contents: contents,
235
+ Tools: tools,
236
+ SystemInstruction: systemInstruction,
237
+ }
238
+
239
+ jsonData, err := json.Marshal(reqBody)
240
+ if err != nil {
241
+ return nil, fmt.Errorf("failed to marshal request: %w", err)
242
+ }
243
+
244
+ // Use streaming endpoint
245
+ url := fmt.Sprintf("https://generativelanguage.googleapis.com/v1beta/models/%s:streamGenerateContent?key=%s&alt=sse", c.Model, c.APIKey)
246
+ req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
247
+ if err != nil {
248
+ return nil, err
249
+ }
250
+
251
+ req.Header.Set("Content-Type", "application/json")
252
+ req.Header.Set("Accept", "text/event-stream")
253
+
254
+ client := &http.Client{Timeout: 120 * time.Second}
255
+ resp, err := client.Do(req)
256
+ if err != nil {
257
+ return nil, err
258
+ }
259
+ defer resp.Body.Close()
260
+
261
+ if resp.StatusCode != http.StatusOK {
262
+ body, _ := io.ReadAll(resp.Body)
263
+ return nil, fmt.Errorf("API error (%d): %s", resp.StatusCode, string(body))
264
+ }
265
+
266
+ // Parse SSE stream
267
+ var fullText string
268
+ var functionCalls []FunctionCall
269
+ var finishReason string
270
+
271
+ scanner := bufio.NewScanner(resp.Body)
272
+ for scanner.Scan() {
273
+ line := scanner.Text()
274
+
275
+ // Skip empty lines and comments
276
+ if line == "" || line[0] == ':' {
277
+ continue
278
+ }
279
+
280
+ // Parse data: lines
281
+ if len(line) > 6 && line[:6] == "data: " {
282
+ data := line[6:]
283
+
284
+ // Parse JSON chunk
285
+ var chunk struct {
286
+ Candidates []struct {
287
+ Content struct {
288
+ Parts []Part `json:"parts"`
289
+ } `json:"content"`
290
+ FinishReason string `json:"finishReason,omitempty"`
291
+ } `json:"candidates"`
292
+ }
293
+
294
+ if err := json.Unmarshal([]byte(data), &chunk); err != nil {
295
+ continue
296
+ }
297
+
298
+ if len(chunk.Candidates) == 0 {
299
+ continue
300
+ }
301
+
302
+ candidate := chunk.Candidates[0]
303
+
304
+ // Extract text and function calls
305
+ for _, part := range candidate.Content.Parts {
306
+ if part.Text != "" {
307
+ fullText += part.Text
308
+ if onChunk != nil {
309
+ onChunk(part.Text)
310
+ }
311
+ }
312
+ if part.FunctionCall != nil {
313
+ functionCalls = append(functionCalls, *part.FunctionCall)
314
+ }
315
+ }
316
+
317
+ if candidate.FinishReason != "" {
318
+ finishReason = candidate.FinishReason
319
+ }
320
+ }
321
+ }
322
+
323
+ if err := scanner.Err(); err != nil {
324
+ return nil, fmt.Errorf("error reading stream: %w", err)
325
+ }
326
+
327
+ return &ToolResponse{
328
+ Text: fullText,
329
+ FunctionCalls: functionCalls,
330
+ FinishReason: finishReason,
331
+ }, nil
332
+ }
@@ -0,0 +1,108 @@
1
+ package auth
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+
7
+ "cloud.google.com/go/compute/metadata"
8
+ "golang.org/x/oauth2/google"
9
+ )
10
+
11
+ // ADCAuth implements Application Default Credentials authentication
12
+ type ADCAuth struct {
13
+ provider string
14
+ scopes []string
15
+ }
16
+
17
+ // NewADCAuth creates a new ADC authenticator
18
+ func NewADCAuth(provider string, scopes []string) *ADCAuth {
19
+ if len(scopes) == 0 {
20
+ scopes = []string{
21
+ "https://www.googleapis.com/auth/cloud-platform",
22
+ }
23
+ }
24
+
25
+ return &ADCAuth{
26
+ provider: provider,
27
+ scopes: scopes,
28
+ }
29
+ }
30
+
31
+ // GetToken returns a valid access token from ADC
32
+ func (a *ADCAuth) GetToken(ctx context.Context) (string, error) {
33
+ // Try to get credentials from ADC
34
+ creds, err := google.FindDefaultCredentials(ctx, a.scopes...)
35
+ if err != nil {
36
+ return "", fmt.Errorf("failed to find default credentials: %w", err)
37
+ }
38
+
39
+ // Get token
40
+ token, err := creds.TokenSource.Token()
41
+ if err != nil {
42
+ return "", fmt.Errorf("failed to get token: %w", err)
43
+ }
44
+
45
+ return token.AccessToken, nil
46
+ }
47
+
48
+ // Refresh is handled automatically by the Google libraries
49
+ func (a *ADCAuth) Refresh(ctx context.Context) error {
50
+ // ADC handles refresh automatically
51
+ return nil
52
+ }
53
+
54
+ // Validate checks if ADC credentials are available
55
+ func (a *ADCAuth) Validate(ctx context.Context) error {
56
+ _, err := google.FindDefaultCredentials(ctx, a.scopes...)
57
+ if err != nil {
58
+ return fmt.Errorf("ADC not available: %w", err)
59
+ }
60
+ return nil
61
+ }
62
+
63
+ // GetType returns the auth type
64
+ func (a *ADCAuth) GetType() AuthType {
65
+ return AuthTypeADC
66
+ }
67
+
68
+ // GetProvider returns the provider name
69
+ func (a *ADCAuth) GetProvider() string {
70
+ return a.provider
71
+ }
72
+
73
+ // Revoke is not applicable for ADC
74
+ func (a *ADCAuth) Revoke(ctx context.Context) error {
75
+ return fmt.Errorf("cannot revoke ADC credentials")
76
+ }
77
+
78
+ // GetTokenInfo returns token information
79
+ func (a *ADCAuth) GetTokenInfo(ctx context.Context) (*TokenInfo, error) {
80
+ creds, err := google.FindDefaultCredentials(ctx, a.scopes...)
81
+ if err != nil {
82
+ return &TokenInfo{Valid: false, Provider: a.provider}, nil
83
+ }
84
+
85
+ token, err := creds.TokenSource.Token()
86
+ if err != nil {
87
+ return &TokenInfo{Valid: false, Provider: a.provider}, nil
88
+ }
89
+
90
+ // Try to get user info from metadata server (if on GCE)
91
+ var user string
92
+ if metadata.OnGCE() {
93
+ user, _ = metadata.Email("")
94
+ }
95
+
96
+ return &TokenInfo{
97
+ Valid: token.Valid(),
98
+ ExpiresAt: token.Expiry,
99
+ Provider: a.provider,
100
+ User: user,
101
+ }, nil
102
+ }
103
+
104
+ // IsAvailable checks if ADC is available in the current environment
105
+ func IsADCAvailable(ctx context.Context) bool {
106
+ _, err := google.FindDefaultCredentials(ctx)
107
+ return err == nil
108
+ }
@@ -0,0 +1,67 @@
1
+ package auth
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "time"
7
+ )
8
+
9
+ // APIKeyAuth implements authentication using API keys
10
+ type APIKeyAuth struct {
11
+ apiKey string
12
+ provider string
13
+ }
14
+
15
+ // NewAPIKeyAuth creates a new API key authenticator
16
+ func NewAPIKeyAuth(provider, apiKey string) *APIKeyAuth {
17
+ return &APIKeyAuth{
18
+ provider: provider,
19
+ apiKey: apiKey,
20
+ }
21
+ }
22
+
23
+ // GetToken returns the API key
24
+ func (a *APIKeyAuth) GetToken(ctx context.Context) (string, error) {
25
+ if a.apiKey == "" {
26
+ return "", fmt.Errorf("API key not set for provider: %s", a.provider)
27
+ }
28
+ return a.apiKey, nil
29
+ }
30
+
31
+ // Refresh is a no-op for API keys
32
+ func (a *APIKeyAuth) Refresh(ctx context.Context) error {
33
+ return nil // API keys don't need refresh
34
+ }
35
+
36
+ // Validate checks if the API key is set
37
+ func (a *APIKeyAuth) Validate(ctx context.Context) error {
38
+ if a.apiKey == "" {
39
+ return fmt.Errorf("API key not set")
40
+ }
41
+ return nil
42
+ }
43
+
44
+ // GetType returns the auth type
45
+ func (a *APIKeyAuth) GetType() AuthType {
46
+ return AuthTypeAPIKey
47
+ }
48
+
49
+ // GetProvider returns the provider name
50
+ func (a *APIKeyAuth) GetProvider() string {
51
+ return a.provider
52
+ }
53
+
54
+ // Revoke is a no-op for API keys
55
+ func (a *APIKeyAuth) Revoke(ctx context.Context) error {
56
+ a.apiKey = ""
57
+ return nil
58
+ }
59
+
60
+ // GetTokenInfo returns token information
61
+ func (a *APIKeyAuth) GetTokenInfo(ctx context.Context) (*TokenInfo, error) {
62
+ return &TokenInfo{
63
+ Valid: a.apiKey != "",
64
+ Provider: a.provider,
65
+ ExpiresAt: time.Time{}, // API keys don't expire
66
+ }, nil
67
+ }
@@ -0,0 +1,145 @@
1
+ package auth
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "os"
7
+
8
+ "github.com/ddhanush1/dcode/internal/config"
9
+ )
10
+
11
+ // Factory creates authenticators based on configuration
12
+ type Factory struct {
13
+ store CredentialStore
14
+ }
15
+
16
+ // NewFactory creates a new auth factory
17
+ func NewFactory(store CredentialStore) *Factory {
18
+ if store == nil {
19
+ store = NewKeyringStore()
20
+ }
21
+ return &Factory{
22
+ store: store,
23
+ }
24
+ }
25
+
26
+ // CreateFromConfig creates an authenticator from config
27
+ func (f *Factory) CreateFromConfig(cfg *config.Config) (Authenticator, error) {
28
+ authType := AuthType(cfg.Auth.Type)
29
+ provider := cfg.Auth.Provider
30
+
31
+ switch authType {
32
+ case AuthTypeAPIKey:
33
+ apiKey := cfg.Auth.APIKey
34
+ if apiKey == "" {
35
+ // Try to get from environment
36
+ apiKey = getAPIKeyFromEnv(provider)
37
+ }
38
+
39
+ if apiKey == "" {
40
+ return nil, fmt.Errorf("API key not found for provider: %s", provider)
41
+ }
42
+
43
+ return NewAPIKeyAuth(provider, apiKey), nil
44
+
45
+ case AuthTypeOAuth2:
46
+ // For OAuth2, we need client credentials
47
+ clientID := os.Getenv("GOOGLE_CLIENT_ID")
48
+ clientSecret := os.Getenv("GOOGLE_CLIENT_SECRET")
49
+
50
+ if clientID == "" || clientSecret == "" {
51
+ return nil, fmt.Errorf("OAuth2 requires GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET")
52
+ }
53
+
54
+ scopes := []string{
55
+ "https://www.googleapis.com/auth/userinfo.email",
56
+ "https://www.googleapis.com/auth/cloud-platform",
57
+ }
58
+
59
+ return NewOAuth2Auth(provider, clientID, clientSecret, scopes, f.store), nil
60
+
61
+ case AuthTypeADC:
62
+ scopes := []string{
63
+ "https://www.googleapis.com/auth/cloud-platform",
64
+ }
65
+ return NewADCAuth(provider, scopes), nil
66
+
67
+ case AuthTypeVertexAI:
68
+ project := cfg.Auth.Project
69
+ if project == "" {
70
+ project = os.Getenv("GOOGLE_CLOUD_PROJECT")
71
+ }
72
+
73
+ location := cfg.Auth.Location
74
+ if location == "" {
75
+ location = os.Getenv("GOOGLE_CLOUD_LOCATION")
76
+ if location == "" {
77
+ location = "us-central1"
78
+ }
79
+ }
80
+
81
+ apiKey := os.Getenv("GOOGLE_API_KEY")
82
+
83
+ return NewVertexAIAuth(provider, project, location, apiKey, f.store), nil
84
+
85
+ default:
86
+ return nil, fmt.Errorf("unsupported auth type: %s", authType)
87
+ }
88
+ }
89
+
90
+ // CreateFromProvider creates an authenticator for a specific provider
91
+ func (f *Factory) CreateFromProvider(provider string) (Authenticator, error) {
92
+ // Try to load existing credentials
93
+ creds, err := f.store.Load(provider)
94
+ if err == nil {
95
+ // Create authenticator based on stored credentials
96
+ switch creds.Type {
97
+ case AuthTypeAPIKey:
98
+ return NewAPIKeyAuth(provider, creds.Token), nil
99
+ case AuthTypeOAuth2:
100
+ // OAuth2 will load token from store
101
+ clientID := os.Getenv("GOOGLE_CLIENT_ID")
102
+ clientSecret := os.Getenv("GOOGLE_CLIENT_SECRET")
103
+ scopes := []string{"https://www.googleapis.com/auth/cloud-platform"}
104
+ return NewOAuth2Auth(provider, clientID, clientSecret, scopes, f.store), nil
105
+ }
106
+ }
107
+
108
+ // Fall back to API key from environment
109
+ apiKey := getAPIKeyFromEnv(provider)
110
+ if apiKey != "" {
111
+ return NewAPIKeyAuth(provider, apiKey), nil
112
+ }
113
+
114
+ // Try ADC
115
+ ctx := context.Background()
116
+ if IsADCAvailable(ctx) {
117
+ return NewADCAuth(provider, []string{"https://www.googleapis.com/auth/cloud-platform"}), nil
118
+ }
119
+
120
+ return nil, fmt.Errorf("no credentials found for provider: %s", provider)
121
+ }
122
+
123
+ // getAPIKeyFromEnv gets API key from environment based on provider
124
+ func getAPIKeyFromEnv(provider string) string {
125
+ switch provider {
126
+ case "openai":
127
+ return os.Getenv("OPENAI_API_KEY")
128
+ case "gemini":
129
+ key := os.Getenv("GEMINI_API_KEY")
130
+ if key == "" {
131
+ key = os.Getenv("GOOGLE_API_KEY")
132
+ }
133
+ return key
134
+ case "claude", "anthropic":
135
+ return os.Getenv("ANTHROPIC_API_KEY")
136
+ default:
137
+ return ""
138
+ }
139
+ }
140
+
141
+ // GetDefaultStore returns the default credential store
142
+ func GetDefaultStore() CredentialStore {
143
+ // Try keyring first, fall back to file
144
+ return NewKeyringStore()
145
+ }