askcii 0.3.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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/CLAUDE.md +131 -0
- data/README.md +348 -109
- data/completions/README.md +64 -0
- data/completions/askcii.bash +29 -0
- data/completions/askcii.zsh +27 -0
- data/lib/askcii/application.rb +81 -48
- data/lib/askcii/chat_factory.rb +80 -0
- data/lib/askcii/chat_session.rb +71 -44
- data/lib/askcii/cli.rb +45 -1
- data/lib/askcii/commands/base_command.rb +45 -0
- data/lib/askcii/commands/chat_command.rb +56 -0
- data/lib/askcii/commands/clear_history_command.rb +31 -0
- data/lib/askcii/commands/configure_command.rb +17 -0
- data/lib/askcii/commands/help_command.rb +52 -0
- data/lib/askcii/commands/history_command.rb +46 -0
- data/lib/askcii/commands/last_response_command.rb +31 -0
- data/lib/askcii/commands/list_sessions_command.rb +36 -0
- data/lib/askcii/config_validator.rb +90 -0
- data/lib/askcii/configuration_manager.rb +170 -151
- data/lib/askcii/errors.rb +15 -0
- data/lib/askcii/models/chat.rb +6 -3
- data/lib/askcii/models/config.rb +12 -4
- data/lib/askcii/provider_config.rb +116 -0
- data/lib/askcii/version.rb +1 -1
- data/lib/askcii.rb +72 -34
- metadata +17 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Askcii
|
|
4
|
+
# Base error class for all Askcii errors
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Raised when configuration is invalid or missing
|
|
8
|
+
class ConfigurationError < Error; end
|
|
9
|
+
|
|
10
|
+
# Raised when session operations fail
|
|
11
|
+
class SessionError < Error; end
|
|
12
|
+
|
|
13
|
+
# Raised when validation fails
|
|
14
|
+
class ValidationError < Error; end
|
|
15
|
+
end
|
data/lib/askcii/models/chat.rb
CHANGED
|
@@ -4,9 +4,12 @@ module Askcii
|
|
|
4
4
|
class Chat < Sequel::Model(Askcii.database[:chats])
|
|
5
5
|
one_to_many :messages, class: 'Askcii::Message', key: :chat_id
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
# Converts this Chat to a RubyLLM::Chat object with message history
|
|
8
|
+
# @param config [Hash] Configuration hash with provider and model settings
|
|
9
|
+
# @return [RubyLLM::Chat]
|
|
10
|
+
def to_llm(config)
|
|
11
|
+
provider = config[:provider] || config['provider'] || 'openai'
|
|
12
|
+
provider_symbol = provider.to_sym
|
|
10
13
|
|
|
11
14
|
@chat = RubyLLM.chat(
|
|
12
15
|
model: model_id,
|
data/lib/askcii/models/config.rb
CHANGED
|
@@ -37,13 +37,14 @@ module Askcii
|
|
|
37
37
|
[]
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
def self.add_configuration(name
|
|
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
|
|
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
|
data/lib/askcii/version.rb
CHANGED
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Roel Bondoc
|
|
@@ -116,6 +116,7 @@ extensions: []
|
|
|
116
116
|
extra_rdoc_files: []
|
|
117
117
|
files:
|
|
118
118
|
- ".gitignore"
|
|
119
|
+
- CLAUDE.md
|
|
119
120
|
- Gemfile
|
|
120
121
|
- Gemfile.lock
|
|
121
122
|
- LICENSE.txt
|
|
@@ -125,14 +126,29 @@ files:
|
|
|
125
126
|
- bin/askcii
|
|
126
127
|
- bin/console
|
|
127
128
|
- bin/setup
|
|
129
|
+
- completions/README.md
|
|
130
|
+
- completions/askcii.bash
|
|
131
|
+
- completions/askcii.zsh
|
|
128
132
|
- lib/askcii.rb
|
|
129
133
|
- lib/askcii/application.rb
|
|
134
|
+
- lib/askcii/chat_factory.rb
|
|
130
135
|
- lib/askcii/chat_session.rb
|
|
131
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
|
|
132
146
|
- lib/askcii/configuration_manager.rb
|
|
147
|
+
- lib/askcii/errors.rb
|
|
133
148
|
- lib/askcii/models/chat.rb
|
|
134
149
|
- lib/askcii/models/config.rb
|
|
135
150
|
- lib/askcii/models/message.rb
|
|
151
|
+
- lib/askcii/provider_config.rb
|
|
136
152
|
- lib/askcii/version.rb
|
|
137
153
|
homepage: https://github.com/roelbondoc/askcii
|
|
138
154
|
licenses:
|