Class: N2B::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/n2b/base.rb

Direct Known Subclasses

CLI, MergeCLI

Constant Summary collapse

CONFIG_FILE =
ENV['N2B_CONFIG_FILE'] || File.expand_path('~/.n2b/config.yml')
HISTORY_FILE =
ENV['N2B_HISTORY_FILE'] || File.expand_path('~/.n2b/history')

Instance Method Summary collapse

Instance Method Details

#get_config(reconfigure: false, advanced_flow: false) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/n2b/base.rb', line 17

def get_config(reconfigure: false, advanced_flow: false)
  config = load_config
  api_key = ENV['CLAUDE_API_KEY'] || config['access_key'] # This will be used as default or for existing configs
  _model = config['model'] # Unused but kept for potential future use # Model will be handled per LLM

  # Determine if it's effectively a first-time setup for core LLM details
  is_first_time_core_setup = config['llm'].nil?

  if api_key.nil? || api_key == '' || config['llm'].nil? || reconfigure
    puts "--- N2B Core LLM Configuration ---"
    print "Choose a language model to use (1:claude, 2:openai, 3:gemini, 4:openrouter, 5:ollama) [current: #{config['llm']}]: "
    llm_choice = $stdin.gets.chomp
    llm_choice = config['llm'] if llm_choice.empty? && !config['llm'].nil? # Keep current if input is empty

    unless ['claude', 'openai', 'gemini', 'openrouter', 'ollama', '1', '2', '3', '4', '5'].include?(llm_choice)
      puts "Invalid language model selection. Defaulting to 'claude' or previous if available."
      llm_choice = config['llm'] || 'claude' # Fallback
    end

    selected_llm = case llm_choice
                   when '1', 'claude' then 'claude'
                   when '2', 'openai' then 'openai'
                   when '3', 'gemini' then 'gemini'
                   when '4', 'openrouter' then 'openrouter'
                   when '5', 'ollama' then 'ollama'
                   else config['llm'] || 'claude' # Should not happen due to validation
                   end
    config['llm'] = selected_llm

    if selected_llm == 'ollama'
      puts "\n--- Ollama Specific Configuration ---"
      puts "Ollama typically runs locally and doesn't require an API key."

      # Use new model configuration system
      current_model = config['model']
      model_choice = N2B::ModelConfig.get_model_choice(selected_llm, current_model) # Renamed to model_choice to avoid conflict
      config['model'] = model_choice

      current_ollama_api_url = config['ollama_api_url'] || N2M::Llm::Ollama::DEFAULT_OLLAMA_API_URI
      print "Enter Ollama API base URL [current: #{current_ollama_api_url}]: "
      ollama_api_url_input = $stdin.gets.chomp
      config['ollama_api_url'] = ollama_api_url_input.empty? ? current_ollama_api_url : ollama_api_url_input
      config.delete('access_key') # Remove access_key if switching to ollama
    else
      # Configuration for API key based LLMs
      puts "\n--- #{selected_llm.capitalize} Specific Configuration ---"
      current_api_key = config['access_key']
      print "Enter your #{selected_llm} API key #{ current_api_key.nil? || current_api_key.empty? ? '' : '[leave blank to keep current]' }: "
      api_key_input = $stdin.gets.chomp
      config['access_key'] = api_key_input if !api_key_input.empty?
      config['access_key'] = current_api_key if api_key_input.empty? && !current_api_key.nil? && !current_api_key.empty?

      if config['access_key'].nil? || config['access_key'].empty?
        puts "API key is required for #{selected_llm}."
        exit 1
      end

      current_model = config['model']
      model_choice = N2B::ModelConfig.get_model_choice(selected_llm, current_model) # Renamed
      config['model'] = model_choice

      if selected_llm == 'openrouter'
        current_site_url = config['openrouter_site_url'] || ""
        print "Enter your OpenRouter Site URL (optional, for HTTP-Referer) [current: #{current_site_url}]: "
        openrouter_site_url_input = $stdin.gets.chomp
        config['openrouter_site_url'] = openrouter_site_url_input.empty? ? current_site_url : openrouter_site_url_input

        current_site_name = config['openrouter_site_name'] || ""
        print "Enter your OpenRouter Site Name (optional, for X-Title) [current: #{current_site_name}]: "
        openrouter_site_name_input = $stdin.gets.chomp
        config['openrouter_site_name'] = openrouter_site_name_input.empty? ? current_site_name : openrouter_site_name_input
      end
    end

    # --- Advanced Configuration Flow ---
    # Prompt for advanced settings if advanced_flow is true or if it's the first time setting up core LLM.
    prompt_for_advanced = advanced_flow || is_first_time_core_setup

    if prompt_for_advanced
      puts "\n--- Advanced Settings ---"
      print "Would you like to configure advanced settings (e.g., Jira integration, privacy)? (y/n) [default: n]: "
      choice = $stdin.gets.chomp.downcase

      if choice == 'y'
        # Jira Configuration
        puts "\n--- Jira Integration ---"
        puts "You can generate a Jira API token here: https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/"
        config['jira'] ||= {}
        print "Jira Domain (e.g., your-company.atlassian.net) [current: #{config['jira']['domain']}]: "
        config['jira']['domain'] = $stdin.gets.chomp.then { |val| val.empty? ? config['jira']['domain'] : val }

        print "Jira Email Address [current: #{config['jira']['email']}]: "
        config['jira']['email'] = $stdin.gets.chomp.then { |val| val.empty? ? config['jira']['email'] : val }

        print "Jira API Token #{config['jira']['api_key'] ? '[leave blank to keep current]' : ''}: "
        api_token_input = $stdin.gets.chomp
        config['jira']['api_key'] = api_token_input if !api_token_input.empty?

        print "Default Jira Project Key (optional, e.g., MYPROJ) [current: #{config['jira']['default_project']}]: "
        config['jira']['default_project'] = $stdin.gets.chomp.then { |val| val.empty? ? config['jira']['default_project'] : val }

        # Privacy Settings
        puts "\n--- Privacy Settings ---"
        config['privacy'] ||= {}
        puts "Allow sending shell command history to the LLM? (true/false) [current: #{config['privacy']['send_shell_history']}]"
        config['privacy']['send_shell_history'] = $stdin.gets.chomp.then { |val| val.empty? ? config['privacy']['send_shell_history'] : (val.downcase == 'true') }

        puts "Allow sending LLM interaction history (your prompts and LLM responses) to the LLM? (true/false) [current: #{config['privacy']['send_llm_history']}]"
        config['privacy']['send_llm_history'] = $stdin.gets.chomp.then { |val| val.empty? ? config['privacy']['send_llm_history'] : (val.downcase == 'true') }

        puts "Allow sending current directory to the LLM? (true/false) [current: #{config['privacy']['send_current_directory']}]"
        config['privacy']['send_current_directory'] = $stdin.gets.chomp.then { |val| val.empty? ? config['privacy']['send_current_directory'] : (val.downcase == 'true') }

        puts "Append n2b generated commands to your shell history file? (true/false) [current: #{config['append_to_shell_history']}]"
        # Note: append_to_shell_history was previously outside 'privacy' hash. Standardizing it inside.
        config['append_to_shell_history'] = $stdin.gets.chomp.then { |val| val.empty? ? config['append_to_shell_history'] : (val.downcase == 'true') }
        config['privacy']['append_to_shell_history'] = config['append_to_shell_history'] # Also place under privacy for consistency

      else # User chose 'n' for advanced settings
        # Ensure Jira config is empty or defaults are cleared if user opts out of advanced
        config['jira'] ||= {} # Ensure it exists
        # If they opt out, we don't clear existing, just don't prompt.
        # If it's a fresh setup and they opt out, these will remain empty/nil.

        # Ensure privacy settings are initialized to defaults if not already set by advanced flow
        config['privacy'] ||= {}
        config['privacy']['send_shell_history'] = config['privacy']['send_shell_history'] || false
        config['privacy']['send_llm_history'] = config['privacy']['send_llm_history'] || true # Default true
        config['privacy']['send_current_directory'] = config['privacy']['send_current_directory'] || true # Default true
        config['append_to_shell_history'] = config['append_to_shell_history'] || false
        config['privacy']['append_to_shell_history'] = config['append_to_shell_history']
      end
    else # Not prompting for advanced (neither advanced_flow nor first_time_core_setup)
      # Ensure defaults for privacy if they don't exist from a previous config
      config['jira'] ||= {} # Ensure Jira key exists
      config['privacy'] ||= {}
      config['privacy']['send_shell_history'] = config['privacy']['send_shell_history'] || false
      config['privacy']['send_llm_history'] = config['privacy']['send_llm_history'] || true
      config['privacy']['send_current_directory'] = config['privacy']['send_current_directory'] || true
      config['append_to_shell_history'] = config['append_to_shell_history'] || false
      config['privacy']['append_to_shell_history'] = config['append_to_shell_history']
    end

    # Validate configuration before saving
    validation_errors = validate_config(config)
    if validation_errors.any?
      puts "\n⚠️  Configuration validation warnings:"
      validation_errors.each { |error| puts "  - #{error}" }
      puts "Configuration saved with warnings."
    end

    puts "\nConfiguration saved to #{CONFIG_FILE}"
    FileUtils.mkdir_p(File.dirname(CONFIG_FILE)) unless File.exist?(File.dirname(CONFIG_FILE))
    File.write(CONFIG_FILE, config.to_yaml)
  else
    # If not reconfiguring, still ensure privacy and jira keys exist with defaults if missing
    # This handles configs from before these settings were introduced
    config['jira'] ||= {}
    config['privacy'] ||= {}
    config['privacy']['send_shell_history'] = config['privacy']['send_shell_history'] || false
    config['privacy']['send_llm_history'] = config['privacy']['send_llm_history'] || true
    config['privacy']['send_current_directory'] = config['privacy']['send_current_directory'] || true
    # append_to_shell_history was outside 'privacy' before, ensure it's correctly defaulted
    # and also placed under 'privacy' for future consistency.
    current_append_setting = config.key?('append_to_shell_history') ? config['append_to_shell_history'] : false
    config['append_to_shell_history'] = current_append_setting
    config['privacy']['append_to_shell_history'] = config['privacy']['append_to_shell_history'] || current_append_setting
  end
  config
end

#load_configObject



9
10
11
12
13
14
15
# File 'lib/n2b/base.rb', line 9

def load_config
  if File.exist?(CONFIG_FILE)
    YAML.load_file(CONFIG_FILE)
  else
    { }
  end
end