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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/CLAUDE.md +131 -0
- data/Gemfile.lock +7 -3
- data/README.md +348 -109
- data/Rakefile +19 -3
- data/askcii.gemspec +4 -2
- data/completions/README.md +64 -0
- data/completions/askcii.bash +29 -0
- data/completions/askcii.zsh +27 -0
- data/lib/askcii/application.rb +80 -31
- data/lib/askcii/chat_factory.rb +80 -0
- data/lib/askcii/chat_session.rb +71 -44
- data/lib/askcii/cli.rb +49 -3
- 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 +166 -54
- 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 +48 -4
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
|
|
@@ -29,14 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - '='
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 1.
|
|
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.
|
|
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
|
-
-
|
|
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:
|