askcii 0.2.0 → 0.4.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.
@@ -37,13 +37,14 @@ module Askcii
37
37
  []
38
38
  end
39
39
 
40
- def self.add_configuration(name, api_key, api_endpoint, model_id, provider)
40
+ def self.add_configuration(name:, api_key:, api_endpoint:, model_id:, provider:, system_instruction: nil)
41
41
  config_data = {
42
42
  'name' => name,
43
43
  'api_key' => api_key,
44
44
  'api_endpoint' => api_endpoint,
45
45
  'model_id' => model_id,
46
- 'provider' => provider
46
+ 'provider' => provider,
47
+ 'system_instruction' => system_instruction || default_system_instruction
47
48
  }
48
49
 
49
50
  # Find the next available ID
@@ -101,14 +102,21 @@ module Askcii
101
102
  'api_key' => api_key,
102
103
  'api_endpoint' => api_endpoint,
103
104
  'model_id' => model_id,
104
- 'provider' => 'openai'
105
+ 'provider' => 'openai',
106
+ 'system_instruction' => default_system_instruction
105
107
  }
106
108
  else
107
- # Ensure provider is set for backward compatibility
109
+ # Ensure provider and system_instruction are set for backward compatibility
108
110
  config ||= {}
109
111
  config['provider'] ||= 'openai'
112
+ config['system_instruction'] ||= default_system_instruction
110
113
  config
111
114
  end
112
115
  end
116
+
117
+ def self.default_system_instruction
118
+ 'You are a command line application. Provide concise, terminal-friendly responses. ' \
119
+ 'Assume technical proficiency. Minimize explanations unless requested.'
120
+ end
113
121
  end
114
122
  end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Askcii
4
+ # Manages provider-specific configuration metadata
5
+ class ProviderConfig
6
+ # Provider metadata including default endpoints and requirements
7
+ PROVIDERS = {
8
+ 'openai' => {
9
+ name: 'OpenAI',
10
+ endpoint: 'https://api.openai.com/v1',
11
+ requires_key: true,
12
+ example_models: ['gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo']
13
+ },
14
+ 'anthropic' => {
15
+ name: 'Anthropic',
16
+ endpoint: 'https://api.anthropic.com',
17
+ requires_key: true,
18
+ example_models: ['claude-3-5-sonnet-20241022', 'claude-3-opus-20240229']
19
+ },
20
+ 'gemini' => {
21
+ name: 'Google Gemini',
22
+ endpoint: 'https://generativelanguage.googleapis.com/v1',
23
+ requires_key: true,
24
+ example_models: ['gemini-pro', 'gemini-pro-vision']
25
+ },
26
+ 'deepseek' => {
27
+ name: 'DeepSeek',
28
+ endpoint: 'https://api.deepseek.com/v1',
29
+ requires_key: true,
30
+ example_models: ['deepseek-chat', 'deepseek-coder']
31
+ },
32
+ 'openrouter' => {
33
+ name: 'OpenRouter',
34
+ endpoint: 'https://openrouter.ai/api/v1',
35
+ requires_key: true,
36
+ example_models: ['openai/gpt-4', 'anthropic/claude-3-opus']
37
+ },
38
+ 'ollama' => {
39
+ name: 'Ollama',
40
+ endpoint: 'http://localhost:11434/v1',
41
+ requires_key: false,
42
+ example_models: ['llama2', 'mistral', 'codellama']
43
+ }
44
+ }.freeze
45
+
46
+ class << self
47
+ # Lists all available provider identifiers
48
+ # @return [Array<String>]
49
+ def list
50
+ PROVIDERS.keys
51
+ end
52
+
53
+ # Lists all provider names for display
54
+ # @return [Array<String>]
55
+ def names
56
+ PROVIDERS.values.map { |p| p[:name] }
57
+ end
58
+
59
+ # Gets provider metadata
60
+ # @param provider [String] Provider identifier
61
+ # @raise [ConfigurationError] if provider is unknown
62
+ # @return [Hash] Provider metadata
63
+ def get(provider)
64
+ PROVIDERS[provider.to_s.downcase] or
65
+ raise ConfigurationError, "Unknown provider: #{provider}"
66
+ end
67
+
68
+ # Gets default endpoint for a provider
69
+ # @param provider [String] Provider identifier
70
+ # @return [String] Default API endpoint URL
71
+ def default_endpoint(provider)
72
+ get(provider)[:endpoint]
73
+ end
74
+
75
+ # Gets display name for a provider
76
+ # @param provider [String] Provider identifier
77
+ # @return [String] Human-readable provider name
78
+ def display_name(provider)
79
+ get(provider)[:name]
80
+ end
81
+
82
+ # Checks if provider requires an API key
83
+ # @param provider [String] Provider identifier
84
+ # @return [Boolean]
85
+ def requires_key?(provider)
86
+ get(provider)[:requires_key]
87
+ end
88
+
89
+ # Gets example model IDs for a provider
90
+ # @param provider [String] Provider identifier
91
+ # @return [Array<String>] Example model identifiers
92
+ def example_models(provider)
93
+ get(provider)[:example_models]
94
+ end
95
+
96
+ # Checks if a provider identifier is valid
97
+ # @param provider [String] Provider identifier
98
+ # @return [Boolean]
99
+ def valid?(provider)
100
+ PROVIDERS.key?(provider.to_s.downcase)
101
+ end
102
+
103
+ # Gets provider metadata as a formatted string
104
+ # @param provider [String] Provider identifier
105
+ # @return [String] Formatted provider information
106
+ def info(provider)
107
+ metadata = get(provider)
108
+ info = "#{metadata[:name]}\n"
109
+ info += "Endpoint: #{metadata[:endpoint]}\n"
110
+ info += "API Key: #{metadata[:requires_key] ? 'Required' : 'Not required'}\n"
111
+ info += "Example models: #{metadata[:example_models].join(', ')}"
112
+ info
113
+ end
114
+ end
115
+ end
116
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Askcii
4
- VERSION = '0.2.0'
4
+ VERSION = '0.4.0'
5
5
  end
data/lib/askcii.rb CHANGED
@@ -4,9 +4,11 @@ require 'sequel'
4
4
  require 'fileutils'
5
5
  require 'ruby_llm'
6
6
  require_relative './askcii/version'
7
+ require_relative './askcii/errors'
8
+ require_relative './askcii/config_validator'
9
+ require_relative './askcii/provider_config'
7
10
 
8
11
  module Askcii
9
- class Error < StandardError; end
10
12
 
11
13
  def self.database
12
14
  @@database ||= Sequel.amalgalite(db_path)
@@ -53,44 +55,80 @@ module Askcii
53
55
  end
54
56
 
55
57
  def self.configure_llm(selected_config = nil)
58
+ # Validate configuration before attempting to use it
59
+ if selected_config
60
+ ConfigValidator.validate!(selected_config)
61
+ else
62
+ # Try legacy configuration with proper error handling
63
+ selected_config = load_legacy_config
64
+ ConfigValidator.validate!(selected_config) if selected_config
65
+ end
66
+
67
+ raise ConfigurationError, "No configuration available. Run 'askcii -c' to configure." unless selected_config
68
+
56
69
  RubyLLM.configure do |config|
57
70
  config.log_file = '/dev/null'
58
71
 
59
- if selected_config
60
- provider = selected_config['provider'] || 'openai'
61
- api_key = selected_config['api_key']
62
-
63
- # Set the appropriate API key based on provider
64
- case provider.downcase
65
- when 'openai'
66
- config.openai_api_key = api_key || 'blank'
67
- config.openai_api_base = selected_config['api_endpoint'] || 'https://api.openai.com/v1'
68
- when 'anthropic'
69
- config.anthropic_api_key = api_key || 'blank'
70
- when 'gemini'
71
- config.gemini_api_key = api_key || 'blank'
72
- when 'deepseek'
73
- config.deepseek_api_key = api_key || 'blank'
74
- when 'openrouter'
75
- config.openrouter_api_key = api_key || 'blank'
76
- when 'ollama'
77
- # Ollama doesn't need an API key
78
- config.openai_api_base = selected_config['api_endpoint'] || 'http://localhost:11434/v1'
79
- end
72
+ provider = selected_config[:provider] || selected_config['provider']
73
+ api_key = selected_config[:api_key] || selected_config['api_key']
74
+ api_endpoint = selected_config[:api_endpoint] || selected_config['api_endpoint']
75
+
76
+ # Set the appropriate API key based on provider
77
+ case provider.to_s.downcase
78
+ when 'openai'
79
+ config.openai_api_key = api_key
80
+ config.openai_api_base = api_endpoint
81
+ when 'anthropic'
82
+ config.anthropic_api_key = api_key
83
+ when 'gemini'
84
+ config.gemini_api_key = api_key
85
+ when 'deepseek'
86
+ config.deepseek_api_key = api_key
87
+ when 'openrouter'
88
+ config.openrouter_api_key = api_key
89
+ when 'ollama'
90
+ # Ollama doesn't need an API key, just endpoint
91
+ config.ollama_api_base = api_endpoint
80
92
  else
81
- # Legacy configuration fallback
82
- config.openai_api_key = begin
83
- Askcii::Config.api_key || ENV['ASKCII_API_KEY'] || 'blank'
84
- rescue StandardError
85
- ENV['ASKCII_API_KEY'] || 'blank'
86
- end
87
-
88
- config.openai_api_base = begin
89
- Askcii::Config.api_endpoint || ENV['ASKCII_API_ENDPOINT'] || 'http://localhost:11434/v1'
90
- rescue StandardError
91
- ENV['ASKCII_API_ENDPOINT'] || 'http://localhost:11434/v1'
92
- end
93
+ raise ConfigurationError, "Unknown provider: #{provider}"
94
+ end
95
+ end
96
+ end
97
+
98
+ # Loads legacy configuration from database or environment variables
99
+ # @return [Hash, nil] Configuration hash or nil if not available
100
+ def self.load_legacy_config
101
+ # Try loading from Config model (requires model to be loaded)
102
+ return nil unless defined?(Askcii::Config)
103
+
104
+ begin
105
+ api_key = Askcii::Config.api_key
106
+ api_endpoint = Askcii::Config.api_endpoint
107
+ model_id = Askcii::Config.model_id
108
+
109
+ # If we got values from the database, use them
110
+ if api_key || api_endpoint || model_id
111
+ return {
112
+ provider: 'openai', # Legacy default
113
+ api_key: api_key,
114
+ api_endpoint: api_endpoint || ProviderConfig.default_endpoint('openai'),
115
+ model_id: model_id || 'gpt-3.5-turbo'
116
+ }
93
117
  end
118
+ rescue Sequel::DatabaseError, NoMethodError => e
119
+ # Database or model not available, fall through to env vars
120
+ end
121
+
122
+ # Fall back to environment variables
123
+ if ENV['ASKCII_API_KEY']
124
+ {
125
+ provider: 'openai', # Legacy default
126
+ api_key: ENV['ASKCII_API_KEY'],
127
+ api_endpoint: ENV['ASKCII_API_ENDPOINT'] || ProviderConfig.default_endpoint('openai'),
128
+ model_id: ENV['ASKCII_MODEL_ID'] || 'gpt-3.5-turbo'
129
+ }
130
+ else
131
+ nil
94
132
  end
95
133
  end
96
134
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: askcii
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roel Bondoc
@@ -29,14 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - '='
31
31
  - !ruby/object:Gem::Version
32
- version: 1.3.0
32
+ version: 1.5.1
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - '='
38
38
  - !ruby/object:Gem::Version
39
- version: 1.3.0
39
+ version: 1.5.1
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: sequel
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -51,6 +51,20 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '5.92'
54
+ - !ruby/object:Gem::Dependency
55
+ name: csv
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.0'
54
68
  - !ruby/object:Gem::Dependency
55
69
  name: minitest
56
70
  requirement: !ruby/object:Gem::Requirement
@@ -65,6 +79,20 @@ dependencies:
65
79
  - - "~>"
66
80
  - !ruby/object:Gem::Version
67
81
  version: '5.25'
82
+ - !ruby/object:Gem::Dependency
83
+ name: ostruct
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.6'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.6'
68
96
  - !ruby/object:Gem::Dependency
69
97
  name: rake
70
98
  requirement: !ruby/object:Gem::Requirement
@@ -81,13 +109,14 @@ dependencies:
81
109
  version: '13.0'
82
110
  description: A terminal-friendly interface for interacting with LLM models
83
111
  email:
84
- - roelbondoc@example.com
112
+ - rsbondoc@gmail.com
85
113
  executables:
86
114
  - askcii
87
115
  extensions: []
88
116
  extra_rdoc_files: []
89
117
  files:
90
118
  - ".gitignore"
119
+ - CLAUDE.md
91
120
  - Gemfile
92
121
  - Gemfile.lock
93
122
  - LICENSE.txt
@@ -97,14 +126,29 @@ files:
97
126
  - bin/askcii
98
127
  - bin/console
99
128
  - bin/setup
129
+ - completions/README.md
130
+ - completions/askcii.bash
131
+ - completions/askcii.zsh
100
132
  - lib/askcii.rb
101
133
  - lib/askcii/application.rb
134
+ - lib/askcii/chat_factory.rb
102
135
  - lib/askcii/chat_session.rb
103
136
  - lib/askcii/cli.rb
137
+ - lib/askcii/commands/base_command.rb
138
+ - lib/askcii/commands/chat_command.rb
139
+ - lib/askcii/commands/clear_history_command.rb
140
+ - lib/askcii/commands/configure_command.rb
141
+ - lib/askcii/commands/help_command.rb
142
+ - lib/askcii/commands/history_command.rb
143
+ - lib/askcii/commands/last_response_command.rb
144
+ - lib/askcii/commands/list_sessions_command.rb
145
+ - lib/askcii/config_validator.rb
104
146
  - lib/askcii/configuration_manager.rb
147
+ - lib/askcii/errors.rb
105
148
  - lib/askcii/models/chat.rb
106
149
  - lib/askcii/models/config.rb
107
150
  - lib/askcii/models/message.rb
151
+ - lib/askcii/provider_config.rb
108
152
  - lib/askcii/version.rb
109
153
  homepage: https://github.com/roelbondoc/askcii
110
154
  licenses: