console_agent 0.0.1 → 0.1.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/LICENSE +21 -0
- data/README.md +235 -0
- data/lib/console_agent/configuration.rb +58 -0
- data/lib/console_agent/console_methods.rb +88 -0
- data/lib/console_agent/context_builder.rb +200 -0
- data/lib/console_agent/executor.rb +131 -0
- data/lib/console_agent/providers/anthropic.rb +112 -0
- data/lib/console_agent/providers/base.rb +106 -0
- data/lib/console_agent/providers/openai.rb +114 -0
- data/lib/console_agent/railtie.rb +30 -0
- data/lib/console_agent/repl.rb +286 -0
- data/lib/console_agent/tools/code_tools.rb +114 -0
- data/lib/console_agent/tools/model_tools.rb +95 -0
- data/lib/console_agent/tools/registry.rb +181 -0
- data/lib/console_agent/tools/schema_tools.rb +60 -0
- data/lib/console_agent/version.rb +3 -0
- data/lib/console_agent.rb +58 -1
- data/lib/generators/console_agent/install_generator.rb +26 -0
- data/lib/generators/console_agent/templates/initializer.rb +33 -0
- metadata +92 -3
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
module ConsoleAgent
|
|
2
|
+
class Executor
|
|
3
|
+
CODE_REGEX = /```ruby\s*\n(.*?)```/m
|
|
4
|
+
|
|
5
|
+
attr_reader :binding_context
|
|
6
|
+
|
|
7
|
+
def initialize(binding_context)
|
|
8
|
+
@binding_context = binding_context
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def extract_code(response)
|
|
12
|
+
match = response.match(CODE_REGEX)
|
|
13
|
+
match ? match[1].strip : ''
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def display_response(response)
|
|
17
|
+
code = extract_code(response)
|
|
18
|
+
explanation = response.gsub(CODE_REGEX, '').strip
|
|
19
|
+
|
|
20
|
+
$stdout.puts
|
|
21
|
+
$stdout.puts colorize(explanation, :cyan) unless explanation.empty?
|
|
22
|
+
|
|
23
|
+
unless code.empty?
|
|
24
|
+
$stdout.puts
|
|
25
|
+
$stdout.puts colorize("# Generated code:", :yellow)
|
|
26
|
+
$stdout.puts highlight_code(code)
|
|
27
|
+
$stdout.puts
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
code
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def execute(code)
|
|
34
|
+
return nil if code.nil? || code.strip.empty?
|
|
35
|
+
|
|
36
|
+
result = binding_context.eval(code, "(console_agent)", 1)
|
|
37
|
+
$stdout.puts colorize("=> #{result.inspect}", :green)
|
|
38
|
+
result
|
|
39
|
+
rescue SyntaxError => e
|
|
40
|
+
$stderr.puts colorize("SyntaxError: #{e.message}", :red)
|
|
41
|
+
nil
|
|
42
|
+
rescue => e
|
|
43
|
+
$stderr.puts colorize("Error: #{e.class}: #{e.message}", :red)
|
|
44
|
+
e.backtrace.first(3).each { |line| $stderr.puts colorize(" #{line}", :red) }
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def confirm_and_execute(code)
|
|
49
|
+
return nil if code.nil? || code.strip.empty?
|
|
50
|
+
|
|
51
|
+
$stdout.print colorize("Execute? [y/N/edit] ", :yellow)
|
|
52
|
+
answer = $stdin.gets.to_s.strip.downcase
|
|
53
|
+
|
|
54
|
+
case answer
|
|
55
|
+
when 'y', 'yes'
|
|
56
|
+
execute(code)
|
|
57
|
+
when 'e', 'edit'
|
|
58
|
+
edited = open_in_editor(code)
|
|
59
|
+
if edited && edited != code
|
|
60
|
+
$stdout.puts colorize("# Edited code:", :yellow)
|
|
61
|
+
$stdout.puts highlight_code(edited)
|
|
62
|
+
$stdout.print colorize("Execute edited code? [y/N] ", :yellow)
|
|
63
|
+
if $stdin.gets.to_s.strip.downcase == 'y'
|
|
64
|
+
execute(edited)
|
|
65
|
+
else
|
|
66
|
+
$stdout.puts colorize("Cancelled.", :yellow)
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
else
|
|
70
|
+
execute(code)
|
|
71
|
+
end
|
|
72
|
+
else
|
|
73
|
+
$stdout.puts colorize("Cancelled.", :yellow)
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def open_in_editor(code)
|
|
81
|
+
require 'tempfile'
|
|
82
|
+
editor = ENV['EDITOR'] || 'vi'
|
|
83
|
+
tmpfile = Tempfile.new(['console_agent', '.rb'])
|
|
84
|
+
tmpfile.write(code)
|
|
85
|
+
tmpfile.flush
|
|
86
|
+
|
|
87
|
+
system("#{editor} #{tmpfile.path}")
|
|
88
|
+
File.read(tmpfile.path)
|
|
89
|
+
rescue => e
|
|
90
|
+
$stderr.puts colorize("Editor error: #{e.message}", :red)
|
|
91
|
+
code
|
|
92
|
+
ensure
|
|
93
|
+
tmpfile.close! if tmpfile
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def highlight_code(code)
|
|
97
|
+
if coderay_available?
|
|
98
|
+
CodeRay.scan(code, :ruby).terminal
|
|
99
|
+
else
|
|
100
|
+
colorize(code, :white)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def coderay_available?
|
|
105
|
+
return @coderay_available unless @coderay_available.nil?
|
|
106
|
+
@coderay_available = begin
|
|
107
|
+
require 'coderay'
|
|
108
|
+
true
|
|
109
|
+
rescue LoadError
|
|
110
|
+
false
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
COLORS = {
|
|
115
|
+
red: "\e[31m",
|
|
116
|
+
green: "\e[32m",
|
|
117
|
+
yellow: "\e[33m",
|
|
118
|
+
cyan: "\e[36m",
|
|
119
|
+
white: "\e[37m",
|
|
120
|
+
reset: "\e[0m"
|
|
121
|
+
}.freeze
|
|
122
|
+
|
|
123
|
+
def colorize(text, color)
|
|
124
|
+
if $stdout.respond_to?(:tty?) && $stdout.tty?
|
|
125
|
+
"#{COLORS[color]}#{text}#{COLORS[:reset]}"
|
|
126
|
+
else
|
|
127
|
+
text
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module ConsoleAgent
|
|
2
|
+
module Providers
|
|
3
|
+
class Anthropic < Base
|
|
4
|
+
API_URL = 'https://api.anthropic.com'.freeze
|
|
5
|
+
|
|
6
|
+
def chat(messages, system_prompt: nil)
|
|
7
|
+
result = call_api(messages, system_prompt: system_prompt)
|
|
8
|
+
result
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def chat_with_tools(messages, tools:, system_prompt: nil)
|
|
12
|
+
call_api(messages, system_prompt: system_prompt, tools: tools)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def format_assistant_message(result)
|
|
16
|
+
# Rebuild the assistant content blocks from the raw response
|
|
17
|
+
content_blocks = []
|
|
18
|
+
content_blocks << { 'type' => 'text', 'text' => result.text } if result.text && !result.text.empty?
|
|
19
|
+
(result.tool_calls || []).each do |tc|
|
|
20
|
+
content_blocks << {
|
|
21
|
+
'type' => 'tool_use',
|
|
22
|
+
'id' => tc[:id],
|
|
23
|
+
'name' => tc[:name],
|
|
24
|
+
'input' => tc[:arguments]
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
{ role: 'assistant', content: content_blocks }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def format_tool_result(tool_call_id, result_string)
|
|
31
|
+
{
|
|
32
|
+
role: 'user',
|
|
33
|
+
content: [
|
|
34
|
+
{
|
|
35
|
+
'type' => 'tool_result',
|
|
36
|
+
'tool_use_id' => tool_call_id,
|
|
37
|
+
'content' => result_string.to_s
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def call_api(messages, system_prompt: nil, tools: nil)
|
|
46
|
+
conn = build_connection(API_URL, {
|
|
47
|
+
'x-api-key' => config.resolved_api_key,
|
|
48
|
+
'anthropic-version' => '2023-06-01'
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
body = {
|
|
52
|
+
model: config.resolved_model,
|
|
53
|
+
max_tokens: config.max_tokens,
|
|
54
|
+
temperature: config.temperature,
|
|
55
|
+
messages: format_messages(messages)
|
|
56
|
+
}
|
|
57
|
+
body[:system] = system_prompt if system_prompt
|
|
58
|
+
body[:tools] = tools.to_anthropic_format if tools
|
|
59
|
+
|
|
60
|
+
json_body = JSON.generate(body)
|
|
61
|
+
debug_request("#{API_URL}/v1/messages", body)
|
|
62
|
+
response = conn.post('/v1/messages', json_body)
|
|
63
|
+
debug_response(response.body)
|
|
64
|
+
data = parse_response(response)
|
|
65
|
+
usage = data['usage'] || {}
|
|
66
|
+
|
|
67
|
+
tool_calls = extract_tool_calls(data)
|
|
68
|
+
stop = data['stop_reason'] == 'tool_use' ? :tool_use : :end_turn
|
|
69
|
+
|
|
70
|
+
ChatResult.new(
|
|
71
|
+
text: extract_text(data),
|
|
72
|
+
input_tokens: usage['input_tokens'],
|
|
73
|
+
output_tokens: usage['output_tokens'],
|
|
74
|
+
tool_calls: tool_calls,
|
|
75
|
+
stop_reason: stop
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def format_messages(messages)
|
|
80
|
+
messages.map do |msg|
|
|
81
|
+
if msg[:content].is_a?(Array)
|
|
82
|
+
{ role: msg[:role].to_s, content: msg[:content] }
|
|
83
|
+
else
|
|
84
|
+
{ role: msg[:role].to_s, content: msg[:content].to_s }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def extract_text(data)
|
|
90
|
+
content = data['content']
|
|
91
|
+
return '' unless content.is_a?(Array)
|
|
92
|
+
|
|
93
|
+
content.select { |c| c['type'] == 'text' }
|
|
94
|
+
.map { |c| c['text'] }
|
|
95
|
+
.join("\n")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def extract_tool_calls(data)
|
|
99
|
+
content = data['content']
|
|
100
|
+
return [] unless content.is_a?(Array)
|
|
101
|
+
|
|
102
|
+
content.select { |c| c['type'] == 'tool_use' }.map do |c|
|
|
103
|
+
{
|
|
104
|
+
id: c['id'],
|
|
105
|
+
name: c['name'],
|
|
106
|
+
arguments: c['input'] || {}
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
require 'faraday'
|
|
2
|
+
require 'json'
|
|
3
|
+
|
|
4
|
+
module ConsoleAgent
|
|
5
|
+
module Providers
|
|
6
|
+
class Base
|
|
7
|
+
attr_reader :config
|
|
8
|
+
|
|
9
|
+
def initialize(config = ConsoleAgent.configuration)
|
|
10
|
+
@config = config
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def chat(messages, system_prompt: nil)
|
|
14
|
+
raise NotImplementedError, "#{self.class}#chat must be implemented"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def chat_with_tools(messages, tools:, system_prompt: nil)
|
|
18
|
+
raise NotImplementedError, "#{self.class}#chat_with_tools must be implemented"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def format_assistant_message(_result)
|
|
22
|
+
raise NotImplementedError, "#{self.class}#format_assistant_message must be implemented"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def format_tool_result(_tool_call_id, _result_string)
|
|
26
|
+
raise NotImplementedError, "#{self.class}#format_tool_result must be implemented"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def build_connection(url, headers = {})
|
|
32
|
+
Faraday.new(url: url) do |f|
|
|
33
|
+
f.options.timeout = config.timeout
|
|
34
|
+
f.options.open_timeout = config.timeout
|
|
35
|
+
f.headers.update(headers)
|
|
36
|
+
f.headers['Content-Type'] = 'application/json'
|
|
37
|
+
f.adapter Faraday.default_adapter
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def debug_request(url, body)
|
|
42
|
+
return unless config.debug
|
|
43
|
+
|
|
44
|
+
$stderr.puts "\e[33m--- ConsoleAgent DEBUG: REQUEST ---\e[0m"
|
|
45
|
+
$stderr.puts "\e[33mURL: #{url}\e[0m"
|
|
46
|
+
parsed = body.is_a?(String) ? JSON.parse(body) : body
|
|
47
|
+
$stderr.puts "\e[33m#{JSON.pretty_generate(parsed)}\e[0m"
|
|
48
|
+
$stderr.puts "\e[33m--- END REQUEST ---\e[0m"
|
|
49
|
+
rescue => e
|
|
50
|
+
$stderr.puts "\e[33m[debug] #{body}\e[0m"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def debug_response(body)
|
|
54
|
+
return unless config.debug
|
|
55
|
+
|
|
56
|
+
$stderr.puts "\e[36m--- ConsoleAgent DEBUG: RESPONSE ---\e[0m"
|
|
57
|
+
parsed = body.is_a?(String) ? JSON.parse(body) : body
|
|
58
|
+
$stderr.puts "\e[36m#{JSON.pretty_generate(parsed)}\e[0m"
|
|
59
|
+
$stderr.puts "\e[36m--- END RESPONSE ---\e[0m"
|
|
60
|
+
rescue => e
|
|
61
|
+
$stderr.puts "\e[36m[debug] #{body}\e[0m"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def parse_response(response)
|
|
65
|
+
unless response.success?
|
|
66
|
+
body = begin
|
|
67
|
+
JSON.parse(response.body)
|
|
68
|
+
rescue
|
|
69
|
+
{ 'error' => response.body }
|
|
70
|
+
end
|
|
71
|
+
error_msg = body.dig('error', 'message') || body['error'] || response.body
|
|
72
|
+
raise ProviderError, "API error (#{response.status}): #{error_msg}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
JSON.parse(response.body)
|
|
76
|
+
rescue JSON::ParserError => e
|
|
77
|
+
raise ProviderError, "Failed to parse response: #{e.message}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class ProviderError < StandardError; end
|
|
82
|
+
|
|
83
|
+
ChatResult = Struct.new(:text, :input_tokens, :output_tokens, :tool_calls, :stop_reason, keyword_init: true) do
|
|
84
|
+
def total_tokens
|
|
85
|
+
(input_tokens || 0) + (output_tokens || 0)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def tool_use?
|
|
89
|
+
stop_reason == :tool_use && tool_calls && !tool_calls.empty?
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def self.build(config = ConsoleAgent.configuration)
|
|
94
|
+
case config.provider
|
|
95
|
+
when :anthropic
|
|
96
|
+
require 'console_agent/providers/anthropic'
|
|
97
|
+
Anthropic.new(config)
|
|
98
|
+
when :openai
|
|
99
|
+
require 'console_agent/providers/openai'
|
|
100
|
+
OpenAI.new(config)
|
|
101
|
+
else
|
|
102
|
+
raise ConfigurationError, "Unknown provider: #{config.provider}"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
module ConsoleAgent
|
|
2
|
+
module Providers
|
|
3
|
+
class OpenAI < Base
|
|
4
|
+
API_URL = 'https://api.openai.com'.freeze
|
|
5
|
+
|
|
6
|
+
def chat(messages, system_prompt: nil)
|
|
7
|
+
call_api(messages, system_prompt: system_prompt)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def chat_with_tools(messages, tools:, system_prompt: nil)
|
|
11
|
+
call_api(messages, system_prompt: system_prompt, tools: tools)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def format_assistant_message(result)
|
|
15
|
+
msg = { role: 'assistant' }
|
|
16
|
+
msg[:content] = result.text if result.text && !result.text.empty?
|
|
17
|
+
if result.tool_calls && !result.tool_calls.empty?
|
|
18
|
+
msg[:tool_calls] = result.tool_calls.map do |tc|
|
|
19
|
+
{
|
|
20
|
+
'id' => tc[:id],
|
|
21
|
+
'type' => 'function',
|
|
22
|
+
'function' => {
|
|
23
|
+
'name' => tc[:name],
|
|
24
|
+
'arguments' => JSON.generate(tc[:arguments] || {})
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
msg
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def format_tool_result(tool_call_id, result_string)
|
|
33
|
+
{
|
|
34
|
+
role: 'tool',
|
|
35
|
+
tool_call_id: tool_call_id,
|
|
36
|
+
content: result_string.to_s
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def call_api(messages, system_prompt: nil, tools: nil)
|
|
43
|
+
conn = build_connection(API_URL, {
|
|
44
|
+
'Authorization' => "Bearer #{config.resolved_api_key}"
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
formatted = []
|
|
48
|
+
formatted << { role: 'system', content: system_prompt } if system_prompt
|
|
49
|
+
formatted.concat(format_messages(messages))
|
|
50
|
+
|
|
51
|
+
body = {
|
|
52
|
+
model: config.resolved_model,
|
|
53
|
+
max_tokens: config.max_tokens,
|
|
54
|
+
temperature: config.temperature,
|
|
55
|
+
messages: formatted
|
|
56
|
+
}
|
|
57
|
+
body[:tools] = tools.to_openai_format if tools
|
|
58
|
+
|
|
59
|
+
json_body = JSON.generate(body)
|
|
60
|
+
debug_request("#{API_URL}/v1/chat/completions", body)
|
|
61
|
+
response = conn.post('/v1/chat/completions', json_body)
|
|
62
|
+
debug_response(response.body)
|
|
63
|
+
data = parse_response(response)
|
|
64
|
+
usage = data['usage'] || {}
|
|
65
|
+
|
|
66
|
+
choice = (data['choices'] || []).first || {}
|
|
67
|
+
message = choice['message'] || {}
|
|
68
|
+
finish_reason = choice['finish_reason']
|
|
69
|
+
|
|
70
|
+
tool_calls = extract_tool_calls(message)
|
|
71
|
+
stop = finish_reason == 'tool_calls' ? :tool_use : :end_turn
|
|
72
|
+
|
|
73
|
+
ChatResult.new(
|
|
74
|
+
text: message['content'] || '',
|
|
75
|
+
input_tokens: usage['prompt_tokens'],
|
|
76
|
+
output_tokens: usage['completion_tokens'],
|
|
77
|
+
tool_calls: tool_calls,
|
|
78
|
+
stop_reason: stop
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def format_messages(messages)
|
|
83
|
+
messages.map do |msg|
|
|
84
|
+
base = { role: msg[:role].to_s }
|
|
85
|
+
if msg[:content]
|
|
86
|
+
base[:content] = msg[:content].is_a?(Array) ? JSON.generate(msg[:content]) : msg[:content].to_s
|
|
87
|
+
end
|
|
88
|
+
base[:tool_calls] = msg[:tool_calls] if msg[:tool_calls]
|
|
89
|
+
base[:tool_call_id] = msg[:tool_call_id] if msg[:tool_call_id]
|
|
90
|
+
base
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def extract_tool_calls(message)
|
|
95
|
+
calls = message['tool_calls']
|
|
96
|
+
return [] unless calls.is_a?(Array)
|
|
97
|
+
|
|
98
|
+
calls.map do |tc|
|
|
99
|
+
func = tc['function'] || {}
|
|
100
|
+
args = begin
|
|
101
|
+
JSON.parse(func['arguments'] || '{}')
|
|
102
|
+
rescue
|
|
103
|
+
{}
|
|
104
|
+
end
|
|
105
|
+
{
|
|
106
|
+
id: tc['id'],
|
|
107
|
+
name: func['name'],
|
|
108
|
+
arguments: args
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'console_agent'
|
|
2
|
+
|
|
3
|
+
module ConsoleAgent
|
|
4
|
+
class Railtie < Rails::Railtie
|
|
5
|
+
console do
|
|
6
|
+
require 'console_agent/console_methods'
|
|
7
|
+
|
|
8
|
+
# Inject into IRB if available
|
|
9
|
+
if defined?(IRB::ExtendCommandBundle)
|
|
10
|
+
IRB::ExtendCommandBundle.include(ConsoleAgent::ConsoleMethods)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Extend TOPLEVEL_BINDING's receiver as well
|
|
14
|
+
TOPLEVEL_BINDING.eval('self').extend(ConsoleAgent::ConsoleMethods)
|
|
15
|
+
|
|
16
|
+
# Welcome message
|
|
17
|
+
if $stdout.respond_to?(:tty?) && $stdout.tty?
|
|
18
|
+
$stdout.puts "\e[36m[ConsoleAgent] AI assistant loaded. Try: ai \"show me all tables\"\e[0m"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Pre-build context in background
|
|
22
|
+
Thread.new do
|
|
23
|
+
require 'console_agent/context_builder'
|
|
24
|
+
ConsoleAgent::ContextBuilder.new.build
|
|
25
|
+
rescue => e
|
|
26
|
+
ConsoleAgent.logger.debug("ConsoleAgent: background context build failed: #{e.message}")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|