@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
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
package config
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"sync"
|
|
8
|
+
|
|
9
|
+
"github.com/pelletier/go-toml/v2"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// Config represents the global configuration for dcode
|
|
13
|
+
type Config struct {
|
|
14
|
+
// Auth configuration
|
|
15
|
+
Auth AuthConfig `toml:"auth"`
|
|
16
|
+
|
|
17
|
+
// Model configuration
|
|
18
|
+
Model ModelConfig `toml:"model"`
|
|
19
|
+
|
|
20
|
+
// Policy configuration
|
|
21
|
+
Policy PolicyConfig `toml:"policy"`
|
|
22
|
+
|
|
23
|
+
// Agent configuration
|
|
24
|
+
Agent AgentConfig `toml:"agent"`
|
|
25
|
+
|
|
26
|
+
// Tool configuration
|
|
27
|
+
Tool ToolConfig `toml:"tool"`
|
|
28
|
+
|
|
29
|
+
// Server configuration
|
|
30
|
+
Server ServerConfig `toml:"server"`
|
|
31
|
+
|
|
32
|
+
// Telemetry configuration
|
|
33
|
+
Telemetry TelemetryConfig `toml:"telemetry"`
|
|
34
|
+
|
|
35
|
+
// Debug mode
|
|
36
|
+
Debug bool `toml:"debug"`
|
|
37
|
+
|
|
38
|
+
mu sync.RWMutex
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// AuthConfig holds authentication settings
|
|
42
|
+
type AuthConfig struct {
|
|
43
|
+
Type string `toml:"type"` // "api_key", "oauth2", "adc", "vertex_ai"
|
|
44
|
+
Provider string `toml:"provider"` // "openai", "gemini", "claude"
|
|
45
|
+
APIKey string `toml:"api_key"` // API key (if type is api_key)
|
|
46
|
+
Project string `toml:"project"` // GCP project (for vertex_ai)
|
|
47
|
+
Location string `toml:"location"` // GCP location (for vertex_ai)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ModelConfig holds model settings
|
|
51
|
+
type ModelConfig struct {
|
|
52
|
+
Provider string `toml:"provider"` // "openai", "gemini", "claude"
|
|
53
|
+
Model string `toml:"model"` // Model name
|
|
54
|
+
Temperature float64 `toml:"temperature"` // 0.0 - 2.0
|
|
55
|
+
MaxTokens int `toml:"max_tokens"` // Max output tokens
|
|
56
|
+
TopP float64 `toml:"top_p"` // Nucleus sampling
|
|
57
|
+
SystemPrompt string `toml:"system_prompt"` // System prompt override
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// PolicyConfig holds confirmation policy settings
|
|
61
|
+
type PolicyConfig struct {
|
|
62
|
+
Mode string `toml:"mode"` // "always", "auto", "risk_based"
|
|
63
|
+
AutoApproveTools []string `toml:"auto_approve_tools"` // Tools that don't need confirmation
|
|
64
|
+
AlwaysConfirm []string `toml:"always_confirm"` // Tools that always need confirmation
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// AgentConfig holds agent system settings
|
|
68
|
+
type AgentConfig struct {
|
|
69
|
+
DefaultAgent string `toml:"default_agent"` // Default agent to use
|
|
70
|
+
EnabledAgents []string `toml:"enabled_agents"` // List of enabled agents
|
|
71
|
+
AgentDirectory string `toml:"agent_directory"` // Custom agent directory
|
|
72
|
+
MaxTurns int `toml:"max_turns"` // Max conversation turns
|
|
73
|
+
EnableSubAgents bool `toml:"enable_sub_agents"` // Allow sub-agent invocation
|
|
74
|
+
RemoteAgentURLs []string `toml:"remote_agent_urls"` // A2A server URLs
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ToolConfig holds tool system settings
|
|
78
|
+
type ToolConfig struct {
|
|
79
|
+
EnabledTools []string `toml:"enabled_tools"` // List of enabled tools
|
|
80
|
+
ToolDirectory string `toml:"tool_directory"` // Custom tool directory
|
|
81
|
+
ShellTimeout int `toml:"shell_timeout"` // Shell command timeout (seconds)
|
|
82
|
+
WebSearchEnabled bool `toml:"web_search_enabled"` // Enable web search
|
|
83
|
+
WebSearchEngine string `toml:"web_search_engine"` // "tavily", "google", "bing"
|
|
84
|
+
ToolSettings map[string]string `toml:"tool_settings"` // Per-tool settings
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ServerConfig holds server settings
|
|
88
|
+
type ServerConfig struct {
|
|
89
|
+
Host string `toml:"host"` // Server host
|
|
90
|
+
Port int `toml:"port"` // Server port
|
|
91
|
+
EnableA2A bool `toml:"enable_a2a"` // Enable A2A server
|
|
92
|
+
A2APort int `toml:"a2a_port"` // A2A server port
|
|
93
|
+
EnableTLS bool `toml:"enable_tls"` // Enable TLS
|
|
94
|
+
CertFile string `toml:"cert_file"` // TLS cert file
|
|
95
|
+
KeyFile string `toml:"key_file"` // TLS key file
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// TelemetryConfig holds telemetry settings
|
|
99
|
+
type TelemetryConfig struct {
|
|
100
|
+
Enabled bool `toml:"enabled"` // Enable telemetry
|
|
101
|
+
Provider string `toml:"provider"` // "opentelemetry", "prometheus"
|
|
102
|
+
Endpoint string `toml:"endpoint"` // Telemetry endpoint
|
|
103
|
+
SampleRate float64 `toml:"sample_rate"` // Sampling rate (0.0 - 1.0)
|
|
104
|
+
EnableLogging bool `toml:"enable_logging"` // Enable detailed logging
|
|
105
|
+
LogLevel string `toml:"log_level"` // "debug", "info", "warn", "error"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
var (
|
|
109
|
+
globalConfig *Config
|
|
110
|
+
globalConfigOnce sync.Once
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
// DefaultConfig returns a config with sensible defaults
|
|
114
|
+
func DefaultConfig() *Config {
|
|
115
|
+
homeDir, _ := os.UserHomeDir()
|
|
116
|
+
|
|
117
|
+
// Detect provider from environment
|
|
118
|
+
provider := "openai"
|
|
119
|
+
model := "gpt-4o-mini"
|
|
120
|
+
|
|
121
|
+
// Check which API keys are available
|
|
122
|
+
if os.Getenv("GEMINI_API_KEY") != "" || os.Getenv("GOOGLE_API_KEY") != "" {
|
|
123
|
+
provider = "gemini"
|
|
124
|
+
model = "gemini-2.5-flash" // Stable fast model
|
|
125
|
+
} else if os.Getenv("ANTHROPIC_API_KEY") != "" {
|
|
126
|
+
provider = "claude"
|
|
127
|
+
model = "claude-3-5-sonnet-20241022"
|
|
128
|
+
} else if os.Getenv("OPENAI_API_KEY") != "" {
|
|
129
|
+
provider = "openai"
|
|
130
|
+
model = "gpt-4o-mini"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return &Config{
|
|
134
|
+
Auth: AuthConfig{
|
|
135
|
+
Type: "api_key",
|
|
136
|
+
Provider: provider,
|
|
137
|
+
},
|
|
138
|
+
Model: ModelConfig{
|
|
139
|
+
Provider: provider,
|
|
140
|
+
Model: model,
|
|
141
|
+
Temperature: 0.7,
|
|
142
|
+
MaxTokens: 4096,
|
|
143
|
+
TopP: 1.0,
|
|
144
|
+
},
|
|
145
|
+
Policy: PolicyConfig{
|
|
146
|
+
Mode: "always",
|
|
147
|
+
AutoApproveTools: []string{
|
|
148
|
+
"read_file",
|
|
149
|
+
"grep",
|
|
150
|
+
"glob",
|
|
151
|
+
"web_fetch",
|
|
152
|
+
"analyze",
|
|
153
|
+
},
|
|
154
|
+
AlwaysConfirm: []string{
|
|
155
|
+
"shell",
|
|
156
|
+
"edit_file",
|
|
157
|
+
"write_file",
|
|
158
|
+
"delete_file",
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
Agent: AgentConfig{
|
|
162
|
+
DefaultAgent: "generalist",
|
|
163
|
+
EnabledAgents: []string{"generalist", "codebase_investigator", "editor", "analyzer"},
|
|
164
|
+
AgentDirectory: filepath.Join(homeDir, ".dcode", "agents"),
|
|
165
|
+
MaxTurns: 50,
|
|
166
|
+
EnableSubAgents: true,
|
|
167
|
+
},
|
|
168
|
+
Tool: ToolConfig{
|
|
169
|
+
EnabledTools: []string{}, // Empty means all
|
|
170
|
+
ToolDirectory: filepath.Join(homeDir, ".dcode", "tools"),
|
|
171
|
+
ShellTimeout: 300,
|
|
172
|
+
WebSearchEnabled: false,
|
|
173
|
+
WebSearchEngine: "tavily",
|
|
174
|
+
ToolSettings: make(map[string]string),
|
|
175
|
+
},
|
|
176
|
+
Server: ServerConfig{
|
|
177
|
+
Host: "localhost",
|
|
178
|
+
Port: 8080,
|
|
179
|
+
EnableA2A: false,
|
|
180
|
+
A2APort: 41242,
|
|
181
|
+
},
|
|
182
|
+
Telemetry: TelemetryConfig{
|
|
183
|
+
Enabled: false,
|
|
184
|
+
Provider: "opentelemetry",
|
|
185
|
+
SampleRate: 1.0,
|
|
186
|
+
EnableLogging: true,
|
|
187
|
+
LogLevel: "info",
|
|
188
|
+
},
|
|
189
|
+
Debug: false,
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// GetConfigDir returns the dcode config directory
|
|
194
|
+
func GetConfigDir() (string, error) {
|
|
195
|
+
homeDir, err := os.UserHomeDir()
|
|
196
|
+
if err != nil {
|
|
197
|
+
return "", fmt.Errorf("failed to get home directory: %w", err)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
configDir := filepath.Join(homeDir, ".dcode")
|
|
201
|
+
|
|
202
|
+
// Create directory if it doesn't exist
|
|
203
|
+
if err := os.MkdirAll(configDir, 0755); err != nil {
|
|
204
|
+
return "", fmt.Errorf("failed to create config directory: %w", err)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return configDir, nil
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// LoadConfig loads configuration from file and environment
|
|
211
|
+
func LoadConfig() (*Config, error) {
|
|
212
|
+
cfg := DefaultConfig()
|
|
213
|
+
|
|
214
|
+
// Check environment for provider override first
|
|
215
|
+
if provider := os.Getenv("AI_PROVIDER"); provider != "" {
|
|
216
|
+
cfg.Model.Provider = provider
|
|
217
|
+
cfg.Auth.Provider = provider
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Load from settings.toml if it exists
|
|
221
|
+
configDir, err := GetConfigDir()
|
|
222
|
+
if err != nil {
|
|
223
|
+
return nil, err
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
settingsPath := filepath.Join(configDir, "settings.toml")
|
|
227
|
+
if _, err := os.Stat(settingsPath); err == nil {
|
|
228
|
+
data, err := os.ReadFile(settingsPath)
|
|
229
|
+
if err != nil {
|
|
230
|
+
return nil, fmt.Errorf("failed to read settings file: %w", err)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if err := toml.Unmarshal(data, cfg); err != nil {
|
|
234
|
+
return nil, fmt.Errorf("failed to parse settings file: %w", err)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Override with environment variables
|
|
239
|
+
cfg.ApplyEnvironment()
|
|
240
|
+
|
|
241
|
+
// Auto-detect model if using default OpenAI model with non-OpenAI provider
|
|
242
|
+
if cfg.Model.Model == "gpt-4o-mini" && cfg.Model.Provider != "openai" {
|
|
243
|
+
cfg.Model.Model = GetDefaultModelForProvider(cfg.Model.Provider)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return cfg, nil
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// GetDefaultModelForProvider returns the default model for a given provider
|
|
250
|
+
func GetDefaultModelForProvider(provider string) string {
|
|
251
|
+
switch provider {
|
|
252
|
+
case "gemini":
|
|
253
|
+
return "gemini-2.5-flash" // Stable fast model
|
|
254
|
+
case "claude":
|
|
255
|
+
return "claude-3-5-sonnet-20241022"
|
|
256
|
+
case "openai":
|
|
257
|
+
return "gpt-4o-mini"
|
|
258
|
+
default:
|
|
259
|
+
return "gpt-4o-mini"
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ApplyEnvironment overrides config with environment variables
|
|
264
|
+
func (c *Config) ApplyEnvironment() {
|
|
265
|
+
c.mu.Lock()
|
|
266
|
+
defer c.mu.Unlock()
|
|
267
|
+
|
|
268
|
+
// Auth
|
|
269
|
+
if val := os.Getenv("AI_PROVIDER"); val != "" {
|
|
270
|
+
c.Auth.Provider = val
|
|
271
|
+
c.Model.Provider = val
|
|
272
|
+
}
|
|
273
|
+
if val := os.Getenv("OPENAI_API_KEY"); val != "" && c.Auth.Provider == "openai" {
|
|
274
|
+
c.Auth.APIKey = val
|
|
275
|
+
}
|
|
276
|
+
if val := os.Getenv("GEMINI_API_KEY"); val != "" && c.Auth.Provider == "gemini" {
|
|
277
|
+
c.Auth.APIKey = val
|
|
278
|
+
}
|
|
279
|
+
if val := os.Getenv("ANTHROPIC_API_KEY"); val != "" && c.Auth.Provider == "claude" {
|
|
280
|
+
c.Auth.APIKey = val
|
|
281
|
+
}
|
|
282
|
+
if val := os.Getenv("GOOGLE_CLOUD_PROJECT"); val != "" {
|
|
283
|
+
c.Auth.Project = val
|
|
284
|
+
}
|
|
285
|
+
if val := os.Getenv("GOOGLE_CLOUD_LOCATION"); val != "" {
|
|
286
|
+
c.Auth.Location = val
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Model
|
|
290
|
+
if val := os.Getenv("AI_MODEL"); val != "" {
|
|
291
|
+
c.Model.Model = val
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Debug
|
|
295
|
+
if os.Getenv("DEBUG") == "1" || os.Getenv("DEBUG") == "true" {
|
|
296
|
+
c.Debug = true
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Web search
|
|
300
|
+
if os.Getenv("WEB_SEARCH") == "true" {
|
|
301
|
+
c.Tool.WebSearchEnabled = true
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Save writes the config to settings.toml
|
|
306
|
+
func (c *Config) Save() error {
|
|
307
|
+
c.mu.RLock()
|
|
308
|
+
defer c.mu.RUnlock()
|
|
309
|
+
|
|
310
|
+
configDir, err := GetConfigDir()
|
|
311
|
+
if err != nil {
|
|
312
|
+
return err
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
settingsPath := filepath.Join(configDir, "settings.toml")
|
|
316
|
+
|
|
317
|
+
data, err := toml.Marshal(c)
|
|
318
|
+
if err != nil {
|
|
319
|
+
return fmt.Errorf("failed to marshal config: %w", err)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if err := os.WriteFile(settingsPath, data, 0644); err != nil {
|
|
323
|
+
return fmt.Errorf("failed to write settings file: %w", err)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return nil
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Validate checks if the configuration is valid
|
|
330
|
+
func (c *Config) Validate() error {
|
|
331
|
+
c.mu.RLock()
|
|
332
|
+
defer c.mu.RUnlock()
|
|
333
|
+
|
|
334
|
+
// Validate auth
|
|
335
|
+
if c.Auth.Type == "" {
|
|
336
|
+
return fmt.Errorf("auth.type is required")
|
|
337
|
+
}
|
|
338
|
+
if c.Auth.Provider == "" {
|
|
339
|
+
return fmt.Errorf("auth.provider is required")
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Validate model
|
|
343
|
+
if c.Model.Provider == "" {
|
|
344
|
+
return fmt.Errorf("model.provider is required")
|
|
345
|
+
}
|
|
346
|
+
if c.Model.Model == "" {
|
|
347
|
+
return fmt.Errorf("model.model is required")
|
|
348
|
+
}
|
|
349
|
+
if c.Model.Temperature < 0 || c.Model.Temperature > 2 {
|
|
350
|
+
return fmt.Errorf("model.temperature must be between 0 and 2")
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Validate policy
|
|
354
|
+
validModes := map[string]bool{"always": true, "auto": true, "risk_based": true}
|
|
355
|
+
if !validModes[c.Policy.Mode] {
|
|
356
|
+
return fmt.Errorf("policy.mode must be one of: always, auto, risk_based")
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return nil
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Global returns the global config instance
|
|
363
|
+
func Global() *Config {
|
|
364
|
+
globalConfigOnce.Do(func() {
|
|
365
|
+
cfg, err := LoadConfig()
|
|
366
|
+
if err != nil {
|
|
367
|
+
// Fallback to defaults if loading fails
|
|
368
|
+
cfg = DefaultConfig()
|
|
369
|
+
}
|
|
370
|
+
globalConfig = cfg
|
|
371
|
+
})
|
|
372
|
+
return globalConfig
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Set updates a config value by path
|
|
376
|
+
func (c *Config) Set(key string, value interface{}) error {
|
|
377
|
+
c.mu.Lock()
|
|
378
|
+
defer c.mu.Unlock()
|
|
379
|
+
|
|
380
|
+
// Simple key-value setting
|
|
381
|
+
// In a real implementation, you'd parse the key path and set nested values
|
|
382
|
+
switch key {
|
|
383
|
+
case "auth.provider":
|
|
384
|
+
if v, ok := value.(string); ok {
|
|
385
|
+
c.Auth.Provider = v
|
|
386
|
+
}
|
|
387
|
+
case "model.provider":
|
|
388
|
+
if v, ok := value.(string); ok {
|
|
389
|
+
c.Model.Provider = v
|
|
390
|
+
}
|
|
391
|
+
case "model.model":
|
|
392
|
+
if v, ok := value.(string); ok {
|
|
393
|
+
c.Model.Model = v
|
|
394
|
+
}
|
|
395
|
+
case "debug":
|
|
396
|
+
if v, ok := value.(bool); ok {
|
|
397
|
+
c.Debug = v
|
|
398
|
+
}
|
|
399
|
+
default:
|
|
400
|
+
return fmt.Errorf("unknown config key: %s", key)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return nil
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Get retrieves a config value by path
|
|
407
|
+
func (c *Config) Get(key string) interface{} {
|
|
408
|
+
c.mu.RLock()
|
|
409
|
+
defer c.mu.RUnlock()
|
|
410
|
+
|
|
411
|
+
switch key {
|
|
412
|
+
case "auth.provider":
|
|
413
|
+
return c.Auth.Provider
|
|
414
|
+
case "model.provider":
|
|
415
|
+
return c.Model.Provider
|
|
416
|
+
case "model.model":
|
|
417
|
+
return c.Model.Model
|
|
418
|
+
case "debug":
|
|
419
|
+
return c.Debug
|
|
420
|
+
default:
|
|
421
|
+
return nil
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// SaveConfig is a convenience function to save a config
|
|
426
|
+
func SaveConfig(cfg *Config) error {
|
|
427
|
+
return cfg.Save()
|
|
428
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
package config
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
|
|
8
|
+
"github.com/pelletier/go-toml/v2"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// PolicyFile represents the policy.toml configuration
|
|
12
|
+
type PolicyFile struct {
|
|
13
|
+
// Confirmation mode: "always", "auto", "risk_based"
|
|
14
|
+
Mode string `toml:"mode"`
|
|
15
|
+
|
|
16
|
+
// Tools that don't require confirmation
|
|
17
|
+
AutoApprove AutoApprovePolicy `toml:"auto_approve"`
|
|
18
|
+
|
|
19
|
+
// Tools that always require confirmation
|
|
20
|
+
AlwaysConfirm AlwaysConfirmPolicy `toml:"always_confirm"`
|
|
21
|
+
|
|
22
|
+
// Risk-based policy settings
|
|
23
|
+
RiskBased RiskBasedPolicy `toml:"risk_based"`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// AutoApprovePolicy defines tools that auto-approve
|
|
27
|
+
type AutoApprovePolicy struct {
|
|
28
|
+
Enabled bool `toml:"enabled"`
|
|
29
|
+
Tools []string `toml:"tools"`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// AlwaysConfirmPolicy defines tools that always confirm
|
|
33
|
+
type AlwaysConfirmPolicy struct {
|
|
34
|
+
Enabled bool `toml:"enabled"`
|
|
35
|
+
Tools []string `toml:"tools"`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// RiskBasedPolicy defines risk-based confirmation rules
|
|
39
|
+
type RiskBasedPolicy struct {
|
|
40
|
+
Enabled bool `toml:"enabled"`
|
|
41
|
+
LowRiskTools []string `toml:"low_risk_tools"`
|
|
42
|
+
MediumRiskTools []string `toml:"medium_risk_tools"`
|
|
43
|
+
HighRiskTools []string `toml:"high_risk_tools"`
|
|
44
|
+
AutoApproveLowRisk bool `toml:"auto_approve_low_risk"`
|
|
45
|
+
AutoApproveMediumRisk bool `toml:"auto_approve_medium_risk"`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// DefaultPolicyFile returns a policy file with sensible defaults
|
|
49
|
+
func DefaultPolicyFile() *PolicyFile {
|
|
50
|
+
return &PolicyFile{
|
|
51
|
+
Mode: "always",
|
|
52
|
+
AutoApprove: AutoApprovePolicy{
|
|
53
|
+
Enabled: true,
|
|
54
|
+
Tools: []string{
|
|
55
|
+
"read_file",
|
|
56
|
+
"grep",
|
|
57
|
+
"glob",
|
|
58
|
+
"ls",
|
|
59
|
+
"web_fetch",
|
|
60
|
+
"analyze",
|
|
61
|
+
"get_project_context",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
AlwaysConfirm: AlwaysConfirmPolicy{
|
|
65
|
+
Enabled: true,
|
|
66
|
+
Tools: []string{
|
|
67
|
+
"shell",
|
|
68
|
+
"edit_file",
|
|
69
|
+
"write_file",
|
|
70
|
+
"delete_file",
|
|
71
|
+
"git_commit",
|
|
72
|
+
"git_push",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
RiskBased: RiskBasedPolicy{
|
|
76
|
+
Enabled: false,
|
|
77
|
+
LowRiskTools: []string{
|
|
78
|
+
"read_file",
|
|
79
|
+
"grep",
|
|
80
|
+
"glob",
|
|
81
|
+
"ls",
|
|
82
|
+
"web_fetch",
|
|
83
|
+
"web_search",
|
|
84
|
+
"analyze",
|
|
85
|
+
},
|
|
86
|
+
MediumRiskTools: []string{
|
|
87
|
+
"edit_file",
|
|
88
|
+
"write_file",
|
|
89
|
+
"shell",
|
|
90
|
+
},
|
|
91
|
+
HighRiskTools: []string{
|
|
92
|
+
"delete_file",
|
|
93
|
+
"git_push",
|
|
94
|
+
"git_force_push",
|
|
95
|
+
},
|
|
96
|
+
AutoApproveLowRisk: true,
|
|
97
|
+
AutoApproveMediumRisk: false,
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// LoadPolicyFile loads the policy.toml file
|
|
103
|
+
func LoadPolicyFile() (*PolicyFile, error) {
|
|
104
|
+
configDir, err := GetConfigDir()
|
|
105
|
+
if err != nil {
|
|
106
|
+
return nil, err
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
policyPath := filepath.Join(configDir, "policy.toml")
|
|
110
|
+
|
|
111
|
+
// If file doesn't exist, create it with defaults
|
|
112
|
+
if _, err := os.Stat(policyPath); os.IsNotExist(err) {
|
|
113
|
+
policy := DefaultPolicyFile()
|
|
114
|
+
if err := policy.Save(); err != nil {
|
|
115
|
+
return nil, err
|
|
116
|
+
}
|
|
117
|
+
return policy, nil
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Read and parse existing file
|
|
121
|
+
data, err := os.ReadFile(policyPath)
|
|
122
|
+
if err != nil {
|
|
123
|
+
return nil, fmt.Errorf("failed to read policy file: %w", err)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
policy := &PolicyFile{}
|
|
127
|
+
if err := toml.Unmarshal(data, policy); err != nil {
|
|
128
|
+
return nil, fmt.Errorf("failed to parse policy file: %w", err)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return policy, nil
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Save writes the policy to policy.toml
|
|
135
|
+
func (p *PolicyFile) Save() error {
|
|
136
|
+
configDir, err := GetConfigDir()
|
|
137
|
+
if err != nil {
|
|
138
|
+
return err
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
policyPath := filepath.Join(configDir, "policy.toml")
|
|
142
|
+
|
|
143
|
+
data, err := toml.Marshal(p)
|
|
144
|
+
if err != nil {
|
|
145
|
+
return fmt.Errorf("failed to marshal policy: %w", err)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if err := os.WriteFile(policyPath, data, 0644); err != nil {
|
|
149
|
+
return fmt.Errorf("failed to write policy file: %w", err)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return nil
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ShouldAutoApprove checks if a tool should be auto-approved
|
|
156
|
+
func (p *PolicyFile) ShouldAutoApprove(toolName string) bool {
|
|
157
|
+
switch p.Mode {
|
|
158
|
+
case "auto":
|
|
159
|
+
// In auto mode, check if tool is in auto-approve list
|
|
160
|
+
if p.AutoApprove.Enabled {
|
|
161
|
+
for _, tool := range p.AutoApprove.Tools {
|
|
162
|
+
if tool == toolName {
|
|
163
|
+
return true
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return false
|
|
168
|
+
|
|
169
|
+
case "always":
|
|
170
|
+
// In always mode, never auto-approve unless explicitly listed
|
|
171
|
+
if p.AutoApprove.Enabled {
|
|
172
|
+
for _, tool := range p.AutoApprove.Tools {
|
|
173
|
+
if tool == toolName {
|
|
174
|
+
return true
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return false
|
|
179
|
+
|
|
180
|
+
case "risk_based":
|
|
181
|
+
// In risk-based mode, check risk level
|
|
182
|
+
if !p.RiskBased.Enabled {
|
|
183
|
+
return false
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check if low risk and auto-approve is enabled
|
|
187
|
+
if p.RiskBased.AutoApproveLowRisk {
|
|
188
|
+
for _, tool := range p.RiskBased.LowRiskTools {
|
|
189
|
+
if tool == toolName {
|
|
190
|
+
return true
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check if medium risk and auto-approve is enabled
|
|
196
|
+
if p.RiskBased.AutoApproveMediumRisk {
|
|
197
|
+
for _, tool := range p.RiskBased.MediumRiskTools {
|
|
198
|
+
if tool == toolName {
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return false
|
|
205
|
+
|
|
206
|
+
default:
|
|
207
|
+
return false
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ShouldAlwaysConfirm checks if a tool should always require confirmation
|
|
212
|
+
func (p *PolicyFile) ShouldAlwaysConfirm(toolName string) bool {
|
|
213
|
+
if !p.AlwaysConfirm.Enabled {
|
|
214
|
+
return false
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
for _, tool := range p.AlwaysConfirm.Tools {
|
|
218
|
+
if tool == toolName {
|
|
219
|
+
return true
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return false
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// GetRiskLevel returns the risk level for a tool
|
|
227
|
+
func (p *PolicyFile) GetRiskLevel(toolName string) string {
|
|
228
|
+
if !p.RiskBased.Enabled {
|
|
229
|
+
return "unknown"
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
for _, tool := range p.RiskBased.LowRiskTools {
|
|
233
|
+
if tool == toolName {
|
|
234
|
+
return "low"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
for _, tool := range p.RiskBased.MediumRiskTools {
|
|
239
|
+
if tool == toolName {
|
|
240
|
+
return "medium"
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
for _, tool := range p.RiskBased.HighRiskTools {
|
|
245
|
+
if tool == toolName {
|
|
246
|
+
return "high"
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return "unknown"
|
|
251
|
+
}
|