@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,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
+ }