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,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Askcii
|
|
4
|
+
module Commands
|
|
5
|
+
# Displays help information
|
|
6
|
+
class HelpCommand < BaseCommand
|
|
7
|
+
def execute
|
|
8
|
+
puts <<~HELP
|
|
9
|
+
askcii - Command-line interface for multiple LLM providers
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
askcii [options] "Your prompt here"
|
|
13
|
+
echo "input" | askcii [options] "Your prompt"
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
-p, --private Create a private session (no history saved)
|
|
17
|
+
-r, --last-response Retrieve the last assistant response from current session
|
|
18
|
+
-c, --configure Open configuration management interface
|
|
19
|
+
-m, --model ID Use specific configuration by ID
|
|
20
|
+
-v, --verbose Show detailed information during execution
|
|
21
|
+
--session NAME Use specific session name (alternative to ASKCII_SESSION)
|
|
22
|
+
--list-sessions List all available sessions
|
|
23
|
+
--history Show current session history
|
|
24
|
+
--clear-history Clear current session history
|
|
25
|
+
-h, --help Show this help message
|
|
26
|
+
|
|
27
|
+
Environment Variables:
|
|
28
|
+
ASKCII_SESSION Session identifier for conversation persistence
|
|
29
|
+
ASKCII_API_KEY API key (legacy, use -c to configure)
|
|
30
|
+
ASKCII_API_ENDPOINT API endpoint (legacy)
|
|
31
|
+
ASKCII_MODEL_ID Model ID (legacy)
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
askcii "Explain what a Ruby block is"
|
|
35
|
+
askcii -p "What is the capital of France?"
|
|
36
|
+
echo "def hello\nputs 'hi'\nend" | askcii "Explain this Ruby code"
|
|
37
|
+
askcii -m 2 "Use configuration #2"
|
|
38
|
+
askcii --session my-project "Continue conversation in my-project session"
|
|
39
|
+
ASKCII_SESSION=work askcii "Use work session"
|
|
40
|
+
|
|
41
|
+
Configuration:
|
|
42
|
+
Run 'askcii -c' to configure providers interactively.
|
|
43
|
+
Supports: OpenAI, Anthropic, Gemini, DeepSeek, OpenRouter, Ollama
|
|
44
|
+
|
|
45
|
+
For more information, visit: https://github.com/yourusername/askcii
|
|
46
|
+
HELP
|
|
47
|
+
|
|
48
|
+
exit 0
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Askcii
|
|
4
|
+
module Commands
|
|
5
|
+
# Displays conversation history for the current session
|
|
6
|
+
class HistoryCommand < BaseCommand
|
|
7
|
+
def execute
|
|
8
|
+
verbose "Retrieving history for session: #{session_context}"
|
|
9
|
+
|
|
10
|
+
chat = Chat.find(context: session_context)
|
|
11
|
+
|
|
12
|
+
unless chat
|
|
13
|
+
puts "No history found for this session."
|
|
14
|
+
exit 0
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
messages = chat.messages_dataset.order(:created_at).all
|
|
18
|
+
|
|
19
|
+
if messages.empty?
|
|
20
|
+
puts "No messages in this session."
|
|
21
|
+
exit 0
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
puts "Session: #{chat.context}"
|
|
25
|
+
puts "Model: #{chat.model_id}"
|
|
26
|
+
puts "Messages: #{messages.count}"
|
|
27
|
+
puts
|
|
28
|
+
puts "=" * 80
|
|
29
|
+
puts
|
|
30
|
+
|
|
31
|
+
messages.each do |msg|
|
|
32
|
+
role_label = msg.role.to_s.capitalize
|
|
33
|
+
timestamp = msg.created_at.strftime('%Y-%m-%d %H:%M:%S')
|
|
34
|
+
|
|
35
|
+
puts "#{role_label} (#{timestamp}):"
|
|
36
|
+
puts msg.content
|
|
37
|
+
puts
|
|
38
|
+
puts "-" * 80
|
|
39
|
+
puts
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
exit 0
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Askcii
|
|
4
|
+
module Commands
|
|
5
|
+
# Retrieves and displays the last assistant response from the current session
|
|
6
|
+
class LastResponseCommand < BaseCommand
|
|
7
|
+
def execute
|
|
8
|
+
verbose "Retrieving last response from session: #{session_context}"
|
|
9
|
+
|
|
10
|
+
chat = Chat.find(context: session_context)
|
|
11
|
+
|
|
12
|
+
unless chat
|
|
13
|
+
error! "No chat session found for context: #{session_context}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
last_message = chat.messages_dataset
|
|
17
|
+
.where(role: 'assistant')
|
|
18
|
+
.order(Sequel.desc(:created_at))
|
|
19
|
+
.first
|
|
20
|
+
|
|
21
|
+
unless last_message
|
|
22
|
+
error! "No assistant messages found in this session"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
puts last_message.content
|
|
26
|
+
|
|
27
|
+
exit 0
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Askcii
|
|
4
|
+
module Commands
|
|
5
|
+
# Lists all available chat sessions
|
|
6
|
+
class ListSessionsCommand < BaseCommand
|
|
7
|
+
def execute
|
|
8
|
+
verbose "Listing all sessions..."
|
|
9
|
+
|
|
10
|
+
chats = Chat.order(Sequel.desc(:created_at)).all
|
|
11
|
+
|
|
12
|
+
if chats.empty?
|
|
13
|
+
puts "No sessions found."
|
|
14
|
+
exit 0
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
puts "Available sessions:"
|
|
18
|
+
puts
|
|
19
|
+
|
|
20
|
+
chats.each do |chat|
|
|
21
|
+
message_count = chat.messages.count
|
|
22
|
+
last_message = chat.messages_dataset.order(Sequel.desc(:created_at)).first
|
|
23
|
+
last_updated = last_message&.created_at || chat.created_at
|
|
24
|
+
|
|
25
|
+
puts "Session: #{chat.context}"
|
|
26
|
+
puts " Model: #{chat.model_id}"
|
|
27
|
+
puts " Messages: #{message_count}"
|
|
28
|
+
puts " Last updated: #{last_updated}"
|
|
29
|
+
puts
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
exit 0
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Askcii
|
|
4
|
+
# Validates configuration settings before use
|
|
5
|
+
class ConfigValidator
|
|
6
|
+
VALID_PROVIDERS = %w[openai anthropic gemini deepseek openrouter ollama].freeze
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
# Validates a configuration hash
|
|
10
|
+
# @param config [Hash] Configuration to validate
|
|
11
|
+
# @raise [ConfigurationError] if configuration is invalid
|
|
12
|
+
# @return [Hash] The validated configuration
|
|
13
|
+
def validate!(config)
|
|
14
|
+
validate_presence!(config)
|
|
15
|
+
validate_provider!(config)
|
|
16
|
+
validate_api_key!(config)
|
|
17
|
+
validate_model_id!(config)
|
|
18
|
+
validate_api_endpoint!(config)
|
|
19
|
+
config
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Checks if a provider name is valid
|
|
23
|
+
# @param name [String] Provider name
|
|
24
|
+
# @return [Boolean]
|
|
25
|
+
def valid_provider?(name)
|
|
26
|
+
VALID_PROVIDERS.include?(name.to_s.downcase)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Lists all valid provider names
|
|
30
|
+
# @return [Array<String>]
|
|
31
|
+
def valid_providers
|
|
32
|
+
VALID_PROVIDERS
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def validate_presence!(config)
|
|
38
|
+
raise ConfigurationError, "Configuration cannot be nil" if config.nil?
|
|
39
|
+
raise ConfigurationError, "Configuration cannot be empty" if config.empty?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def validate_provider!(config)
|
|
43
|
+
provider = config[:provider].to_s.strip
|
|
44
|
+
if provider.empty?
|
|
45
|
+
raise ConfigurationError, "Provider is required"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
unless valid_provider?(provider)
|
|
49
|
+
raise ConfigurationError,
|
|
50
|
+
"Invalid provider '#{provider}'. Must be one of: #{VALID_PROVIDERS.join(', ')}"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def validate_api_key!(config)
|
|
55
|
+
provider = config[:provider].to_s.downcase
|
|
56
|
+
|
|
57
|
+
# Ollama doesn't require an API key
|
|
58
|
+
return if provider == 'ollama'
|
|
59
|
+
|
|
60
|
+
api_key = config[:api_key].to_s.strip
|
|
61
|
+
if api_key.empty?
|
|
62
|
+
raise ConfigurationError, "API key is required for #{provider}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if api_key == 'blank'
|
|
66
|
+
raise ConfigurationError, "API key cannot be 'blank'"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def validate_model_id!(config)
|
|
71
|
+
model_id = config[:model_id].to_s.strip
|
|
72
|
+
if model_id.empty?
|
|
73
|
+
raise ConfigurationError, "Model ID is required"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def validate_api_endpoint!(config)
|
|
78
|
+
endpoint = config[:api_endpoint].to_s.strip
|
|
79
|
+
if endpoint.empty?
|
|
80
|
+
raise ConfigurationError, "API endpoint is required"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Basic URL format validation
|
|
84
|
+
unless endpoint.start_with?('http://', 'https://')
|
|
85
|
+
raise ConfigurationError, "API endpoint must start with http:// or https://"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Askcii
|
|
4
|
+
# Interactive terminal UI for managing provider configurations
|
|
4
5
|
class ConfigurationManager
|
|
5
|
-
|
|
6
|
+
# Maps user selection numbers to provider identifiers
|
|
7
|
+
PROVIDER_MENU = {
|
|
6
8
|
'1' => 'openai',
|
|
7
9
|
'2' => 'anthropic',
|
|
8
10
|
'3' => 'gemini',
|
|
@@ -11,102 +13,50 @@ module Askcii
|
|
|
11
13
|
'6' => 'ollama'
|
|
12
14
|
}.freeze
|
|
13
15
|
|
|
14
|
-
DEFAULT_ENDPOINTS = {
|
|
15
|
-
'openai' => 'https://api.openai.com/v1',
|
|
16
|
-
'anthropic' => 'https://api.anthropic.com',
|
|
17
|
-
'gemini' => 'https://generativelanguage.googleapis.com/v1',
|
|
18
|
-
'deepseek' => 'https://api.deepseek.com/v1',
|
|
19
|
-
'openrouter' => 'https://openrouter.ai/api/v1',
|
|
20
|
-
'ollama' => 'http://localhost:11434/v1'
|
|
21
|
-
}.freeze
|
|
22
|
-
|
|
23
|
-
PROVIDER_MODELS = {
|
|
24
|
-
'openai' => {
|
|
25
|
-
default: 'gpt-4o',
|
|
26
|
-
models: [
|
|
27
|
-
'gpt-4o',
|
|
28
|
-
'gpt-4o-mini',
|
|
29
|
-
'gpt-4-turbo',
|
|
30
|
-
'gpt-4',
|
|
31
|
-
'gpt-3.5-turbo'
|
|
32
|
-
]
|
|
33
|
-
},
|
|
34
|
-
'anthropic' => {
|
|
35
|
-
default: 'claude-3-5-sonnet-20241022',
|
|
36
|
-
models: [
|
|
37
|
-
'claude-3-5-sonnet-20241022',
|
|
38
|
-
'claude-3-5-haiku-20241022',
|
|
39
|
-
'claude-3-opus-20240229',
|
|
40
|
-
'claude-3-sonnet-20240229',
|
|
41
|
-
'claude-3-haiku-20240307'
|
|
42
|
-
]
|
|
43
|
-
},
|
|
44
|
-
'gemini' => {
|
|
45
|
-
default: 'gemini-pro',
|
|
46
|
-
models: [
|
|
47
|
-
'gemini-pro',
|
|
48
|
-
'gemini-pro-vision',
|
|
49
|
-
'gemini-1.5-pro',
|
|
50
|
-
'gemini-1.5-flash'
|
|
51
|
-
]
|
|
52
|
-
},
|
|
53
|
-
'deepseek' => {
|
|
54
|
-
default: 'deepseek-chat',
|
|
55
|
-
models: %w[
|
|
56
|
-
deepseek-chat
|
|
57
|
-
deepseek-coder
|
|
58
|
-
]
|
|
59
|
-
},
|
|
60
|
-
'openrouter' => {
|
|
61
|
-
default: 'anthropic/claude-3.5-sonnet',
|
|
62
|
-
models: [
|
|
63
|
-
'anthropic/claude-3.5-sonnet',
|
|
64
|
-
'openai/gpt-4o',
|
|
65
|
-
'google/gemini-pro',
|
|
66
|
-
'meta-llama/llama-3.1-405b-instruct',
|
|
67
|
-
'anthropic/claude-3-opus',
|
|
68
|
-
'openai/gpt-4-turbo'
|
|
69
|
-
]
|
|
70
|
-
},
|
|
71
|
-
'ollama' => {
|
|
72
|
-
default: 'llama3.2',
|
|
73
|
-
models: [
|
|
74
|
-
'llama3.2',
|
|
75
|
-
'llama3.1',
|
|
76
|
-
'mistral',
|
|
77
|
-
'codellama',
|
|
78
|
-
'phi3',
|
|
79
|
-
'gemma2'
|
|
80
|
-
]
|
|
81
|
-
}
|
|
82
|
-
}.freeze
|
|
83
|
-
|
|
84
16
|
def run
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
17
|
+
loop do
|
|
18
|
+
show_current_configurations
|
|
19
|
+
show_menu
|
|
20
|
+
choice = handle_user_choice
|
|
21
|
+
break if choice == :exit
|
|
22
|
+
end
|
|
88
23
|
end
|
|
89
24
|
|
|
90
25
|
private
|
|
91
26
|
|
|
92
27
|
def show_current_configurations
|
|
28
|
+
puts
|
|
93
29
|
puts 'Configuration Management'
|
|
94
|
-
puts '
|
|
30
|
+
puts '=' * 50
|
|
31
|
+
puts
|
|
95
32
|
|
|
96
33
|
configs = Askcii::Config.configurations
|
|
97
34
|
default_id = Askcii::Config.default_configuration_id
|
|
98
35
|
|
|
99
36
|
if configs.empty?
|
|
100
37
|
puts 'No configurations found.'
|
|
38
|
+
puts 'Add a configuration to get started.'
|
|
101
39
|
else
|
|
102
40
|
puts 'Current configurations:'
|
|
41
|
+
puts
|
|
103
42
|
configs.each do |config|
|
|
104
|
-
|
|
105
|
-
provider_info = config['provider'] ? " [#{config['provider']}]" : ''
|
|
106
|
-
puts " #{config['id']}. #{config['name']}#{provider_info}#{marker}"
|
|
43
|
+
show_configuration_summary(config, default_id)
|
|
107
44
|
end
|
|
108
|
-
puts
|
|
109
45
|
end
|
|
46
|
+
puts
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def show_configuration_summary(config, default_id)
|
|
50
|
+
is_default = config['id'] == default_id
|
|
51
|
+
marker = is_default ? ' ★ (default)' : ''
|
|
52
|
+
provider_name = ProviderConfig.display_name(config['provider'])
|
|
53
|
+
|
|
54
|
+
puts " #{config['id']}. #{config['name']}"
|
|
55
|
+
puts " Provider: #{provider_name}"
|
|
56
|
+
puts " Model: #{config['model_id']}"
|
|
57
|
+
puts " Endpoint: #{config['api_endpoint']}"
|
|
58
|
+
puts "#{marker}"
|
|
59
|
+
puts
|
|
110
60
|
end
|
|
111
61
|
|
|
112
62
|
def show_menu
|
|
@@ -114,8 +64,9 @@ module Askcii
|
|
|
114
64
|
puts ' 1. Add new configuration'
|
|
115
65
|
puts ' 2. Set default configuration'
|
|
116
66
|
puts ' 3. Delete configuration'
|
|
117
|
-
puts ' 4.
|
|
118
|
-
|
|
67
|
+
puts ' 4. Test configuration'
|
|
68
|
+
puts ' 5. Exit'
|
|
69
|
+
print 'Select option (1-5): '
|
|
119
70
|
end
|
|
120
71
|
|
|
121
72
|
def handle_user_choice
|
|
@@ -124,20 +75,32 @@ module Askcii
|
|
|
124
75
|
case choice
|
|
125
76
|
when '1'
|
|
126
77
|
add_new_configuration
|
|
78
|
+
:continue
|
|
127
79
|
when '2'
|
|
128
80
|
set_default_configuration
|
|
81
|
+
:continue
|
|
129
82
|
when '3'
|
|
130
83
|
delete_configuration
|
|
84
|
+
:continue
|
|
131
85
|
when '4'
|
|
132
|
-
|
|
86
|
+
test_configuration
|
|
87
|
+
:continue
|
|
88
|
+
when '5'
|
|
89
|
+
puts 'Exiting configuration manager.'
|
|
90
|
+
:exit
|
|
133
91
|
else
|
|
134
|
-
puts 'Invalid option.'
|
|
92
|
+
puts 'Invalid option. Please select 1-5.'
|
|
93
|
+
:continue
|
|
135
94
|
end
|
|
136
95
|
end
|
|
137
96
|
|
|
138
97
|
def add_new_configuration
|
|
98
|
+
puts
|
|
99
|
+
puts 'Add New Configuration'
|
|
100
|
+
puts '-' * 50
|
|
101
|
+
|
|
139
102
|
print 'Enter configuration name: '
|
|
140
|
-
name = $stdin.gets.chomp
|
|
103
|
+
name = $stdin.gets.chomp.strip
|
|
141
104
|
|
|
142
105
|
provider = select_provider
|
|
143
106
|
return unless provider
|
|
@@ -147,28 +110,65 @@ module Askcii
|
|
|
147
110
|
|
|
148
111
|
endpoint = get_api_endpoint(provider)
|
|
149
112
|
model_id = get_model_id(provider)
|
|
150
|
-
|
|
151
113
|
return unless model_id
|
|
152
114
|
|
|
115
|
+
# Use model_id as name if no name provided
|
|
153
116
|
name = model_id if name.empty?
|
|
154
|
-
|
|
155
|
-
|
|
117
|
+
|
|
118
|
+
# Build configuration hash
|
|
119
|
+
config = {
|
|
120
|
+
name: name,
|
|
121
|
+
api_key: api_key || '',
|
|
122
|
+
api_endpoint: endpoint,
|
|
123
|
+
model_id: model_id,
|
|
124
|
+
provider: provider
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Validate configuration
|
|
128
|
+
begin
|
|
129
|
+
ConfigValidator.validate!(config)
|
|
130
|
+
rescue ConfigurationError => e
|
|
131
|
+
puts "Validation failed: #{e.message}"
|
|
132
|
+
return
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Save configuration
|
|
136
|
+
Askcii::Config.add_configuration(
|
|
137
|
+
name: name,
|
|
138
|
+
api_key: api_key || '',
|
|
139
|
+
api_endpoint: endpoint,
|
|
140
|
+
model_id: model_id,
|
|
141
|
+
provider: provider
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
puts
|
|
145
|
+
puts '✓ Configuration added successfully!'
|
|
146
|
+
puts
|
|
147
|
+
|
|
148
|
+
# Offer to test the configuration
|
|
149
|
+
print 'Test this configuration now? (y/n): '
|
|
150
|
+
if $stdin.gets.chomp.downcase == 'y'
|
|
151
|
+
# Get the newly added configuration ID
|
|
152
|
+
configs = Askcii::Config.configurations
|
|
153
|
+
new_config = configs.last
|
|
154
|
+
test_specific_configuration(new_config) if new_config
|
|
155
|
+
end
|
|
156
156
|
end
|
|
157
157
|
|
|
158
158
|
def select_provider
|
|
159
|
+
puts
|
|
159
160
|
puts 'Select provider:'
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if provider.nil?
|
|
161
|
+
ProviderConfig.list.each_with_index do |provider_id, index|
|
|
162
|
+
provider_meta = ProviderConfig.get(provider_id)
|
|
163
|
+
key_info = provider_meta[:requires_key] ? '' : ' (no API key needed)'
|
|
164
|
+
puts " #{index + 1}. #{provider_meta[:name]}#{key_info}"
|
|
165
|
+
end
|
|
166
|
+
print "Provider (1-#{ProviderConfig.list.size}): "
|
|
167
|
+
|
|
168
|
+
choice = $stdin.gets.chomp
|
|
169
|
+
provider = PROVIDER_MENU[choice]
|
|
170
|
+
|
|
171
|
+
if provider.nil? || !ProviderConfig.valid?(provider)
|
|
172
172
|
puts 'Invalid provider selection.'
|
|
173
173
|
return nil
|
|
174
174
|
end
|
|
@@ -179,11 +179,12 @@ module Askcii
|
|
|
179
179
|
def get_api_key(provider)
|
|
180
180
|
return '' if provider == 'ollama'
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
provider_name = ProviderConfig.display_name(provider)
|
|
183
|
+
print "Enter #{provider_name} API key: "
|
|
184
|
+
api_key = $stdin.gets.chomp.strip
|
|
184
185
|
|
|
185
|
-
if api_key.empty?
|
|
186
|
-
puts
|
|
186
|
+
if api_key.empty? && ProviderConfig.requires_key?(provider)
|
|
187
|
+
puts "API key is required for #{provider_name}."
|
|
187
188
|
return nil
|
|
188
189
|
end
|
|
189
190
|
|
|
@@ -191,54 +192,24 @@ module Askcii
|
|
|
191
192
|
end
|
|
192
193
|
|
|
193
194
|
def get_api_endpoint(provider)
|
|
194
|
-
default_endpoint =
|
|
195
|
+
default_endpoint = ProviderConfig.default_endpoint(provider)
|
|
195
196
|
print "Enter API endpoint (default: #{default_endpoint}): "
|
|
196
|
-
api_endpoint = $stdin.gets.chomp
|
|
197
|
+
api_endpoint = $stdin.gets.chomp.strip
|
|
197
198
|
api_endpoint.empty? ? default_endpoint : api_endpoint
|
|
198
199
|
end
|
|
199
200
|
|
|
200
201
|
def get_model_id(provider)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
available_models = provider_config[:models]
|
|
206
|
-
|
|
207
|
-
puts "\nAvailable models for #{provider.capitalize}:"
|
|
208
|
-
available_models.each_with_index do |model, index|
|
|
209
|
-
marker = model == default_model ? ' (recommended)' : ''
|
|
210
|
-
puts " #{index + 1}. #{model}#{marker}"
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
puts " #{available_models.length + 1}. Enter custom model ID"
|
|
214
|
-
print "\nSelect model (1-#{available_models.length + 1}) or press Enter for default [#{default_model}]: "
|
|
202
|
+
examples = ProviderConfig.example_models(provider)
|
|
203
|
+
puts "Example models for #{ProviderConfig.display_name(provider)}: #{examples.join(', ')}"
|
|
204
|
+
print 'Enter model ID: '
|
|
205
|
+
model_id = $stdin.gets.chomp.strip
|
|
215
206
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
default_model
|
|
220
|
-
elsif choice.to_i.between?(1, available_models.length)
|
|
221
|
-
available_models[choice.to_i - 1]
|
|
222
|
-
elsif choice.to_i == available_models.length + 1
|
|
223
|
-
print 'Enter custom model ID: '
|
|
224
|
-
custom_model = $stdin.gets.chomp
|
|
225
|
-
custom_model.empty? ? nil : custom_model
|
|
226
|
-
else
|
|
227
|
-
puts 'Invalid selection.'
|
|
228
|
-
nil
|
|
229
|
-
end
|
|
230
|
-
else
|
|
231
|
-
# Fallback for unknown providers
|
|
232
|
-
print 'Enter model ID: '
|
|
233
|
-
model_id = $stdin.gets.chomp
|
|
234
|
-
|
|
235
|
-
if model_id.empty?
|
|
236
|
-
puts 'Model ID is required.'
|
|
237
|
-
return nil
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
model_id
|
|
207
|
+
if model_id.empty?
|
|
208
|
+
puts 'Model ID is required.'
|
|
209
|
+
return nil
|
|
241
210
|
end
|
|
211
|
+
|
|
212
|
+
model_id
|
|
242
213
|
end
|
|
243
214
|
|
|
244
215
|
def set_default_configuration
|
|
@@ -249,14 +220,15 @@ module Askcii
|
|
|
249
220
|
return
|
|
250
221
|
end
|
|
251
222
|
|
|
223
|
+
puts
|
|
252
224
|
print 'Enter configuration ID to set as default: '
|
|
253
|
-
new_default = $stdin.gets.chomp
|
|
225
|
+
new_default = $stdin.gets.chomp.strip
|
|
254
226
|
|
|
255
227
|
if configs.any? { |c| c['id'] == new_default }
|
|
256
228
|
Askcii::Config.set_default_configuration(new_default)
|
|
257
|
-
puts "Configuration #{new_default} set as default."
|
|
229
|
+
puts "✓ Configuration #{new_default} set as default."
|
|
258
230
|
else
|
|
259
|
-
puts 'Invalid configuration ID.'
|
|
231
|
+
puts '✗ Invalid configuration ID.'
|
|
260
232
|
end
|
|
261
233
|
end
|
|
262
234
|
|
|
@@ -268,18 +240,65 @@ module Askcii
|
|
|
268
240
|
return
|
|
269
241
|
end
|
|
270
242
|
|
|
243
|
+
puts
|
|
271
244
|
print 'Enter configuration ID to delete: '
|
|
272
|
-
delete_id = $stdin.gets.chomp
|
|
245
|
+
delete_id = $stdin.gets.chomp.strip
|
|
273
246
|
|
|
274
247
|
if configs.any? { |c| c['id'] == delete_id }
|
|
248
|
+
print "Are you sure you want to delete configuration #{delete_id}? (y/n): "
|
|
249
|
+
return unless $stdin.gets.chomp.downcase == 'y'
|
|
250
|
+
|
|
275
251
|
if Askcii::Config.delete_configuration(delete_id)
|
|
276
|
-
puts "Configuration #{delete_id} deleted successfully."
|
|
252
|
+
puts "✓ Configuration #{delete_id} deleted successfully."
|
|
277
253
|
else
|
|
278
|
-
puts 'Failed to delete configuration.'
|
|
254
|
+
puts '✗ Failed to delete configuration.'
|
|
279
255
|
end
|
|
280
256
|
else
|
|
281
|
-
puts 'Invalid configuration ID.'
|
|
257
|
+
puts '✗ Invalid configuration ID.'
|
|
282
258
|
end
|
|
283
259
|
end
|
|
260
|
+
|
|
261
|
+
def test_configuration
|
|
262
|
+
configs = Askcii::Config.configurations
|
|
263
|
+
|
|
264
|
+
if configs.empty?
|
|
265
|
+
puts 'No configurations available to test.'
|
|
266
|
+
return
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
puts
|
|
270
|
+
print 'Enter configuration ID to test: '
|
|
271
|
+
config_id = $stdin.gets.chomp.strip
|
|
272
|
+
|
|
273
|
+
config = configs.find { |c| c['id'] == config_id }
|
|
274
|
+
|
|
275
|
+
if config
|
|
276
|
+
test_specific_configuration(config)
|
|
277
|
+
else
|
|
278
|
+
puts '✗ Invalid configuration ID.'
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def test_specific_configuration(config)
|
|
283
|
+
puts
|
|
284
|
+
puts "Testing configuration: #{config['name']}"
|
|
285
|
+
puts '-' * 50
|
|
286
|
+
|
|
287
|
+
# Validate first
|
|
288
|
+
begin
|
|
289
|
+
symbolized_config = config.transform_keys(&:to_sym)
|
|
290
|
+
ConfigValidator.validate!(symbolized_config)
|
|
291
|
+
puts '✓ Configuration validation passed'
|
|
292
|
+
rescue ConfigurationError => e
|
|
293
|
+
puts "✗ Validation failed: #{e.message}"
|
|
294
|
+
return
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# TODO: Add actual connectivity test with minimal LLM call
|
|
298
|
+
# For now, just validate the configuration structure
|
|
299
|
+
puts '✓ Configuration appears valid'
|
|
300
|
+
puts
|
|
301
|
+
puts 'Note: Actual connectivity testing will be added in a future update.'
|
|
302
|
+
end
|
|
284
303
|
end
|
|
285
304
|
end
|