Skip to content

Commit acd78ac

Browse files
committed
wip
1 parent c4db2db commit acd78ac

16 files changed

+384
-39
lines changed

examples/docker_chat.rb

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
# Docker Chat - Interactive command line tool
5+
# A comprehensive chat interface with all Docker tools available
6+
#
7+
# Usage:
8+
# ruby examples/docker_chat.rb
9+
#
10+
# Commands:
11+
# /exit - Exit the chat
12+
# /help - Show available Docker tools
13+
# /tools - List all loaded tools
14+
# /clear - Clear the screen
15+
# anything else - Send to OpenAI with Docker tools available
16+
17+
require_relative '../lib/ruby_llm/docker'
18+
require 'io/console'
19+
20+
# rubocop:disable Metrics/ClassLength
21+
class DockerChat
22+
def initialize
23+
check_environment
24+
configure_ruby_llm
25+
setup_chat
26+
@running = true
27+
end
28+
29+
def start
30+
show_welcome
31+
main_loop
32+
show_goodbye
33+
end
34+
35+
private
36+
37+
def check_environment
38+
return if ENV['OPENAI_API_KEY']
39+
40+
puts '❌ Error: Please set OPENAI_API_KEY environment variable'
41+
puts "Example: export OPENAI_API_KEY='your-api-key-here'"
42+
exit 1
43+
end
44+
45+
def configure_ruby_llm
46+
RubyLLM.configure do |config|
47+
config.openai_api_key = ENV.fetch('OPENAI_API_KEY', nil)
48+
end
49+
end
50+
51+
def setup_chat
52+
@chat = RubyLLM.chat(model: 'gpt-4')
53+
54+
# Add all Docker tools to the chat
55+
RubyLLM::Docker.add_all_tools_to_chat(@chat)
56+
57+
puts '🔧 Loading Docker tools...'
58+
puts "✅ Loaded #{RubyLLM::Docker.all_tools.size} Docker tools"
59+
end
60+
61+
def show_welcome
62+
puts "\n#{'=' * 60}"
63+
puts '🐳 Welcome to Docker Chat!'
64+
puts ' Interactive CLI with all Docker tools available'
65+
puts '=' * 60
66+
puts
67+
puts '💡 You can ask questions about Docker containers, images, networks, and volumes.'
68+
puts ' OpenAI has access to all Docker management tools on this system.'
69+
puts
70+
puts 'Commands:'
71+
puts ' /exit - Exit the chat'
72+
puts ' /help - Show available Docker tools'
73+
puts ' /tools - List all loaded tools'
74+
puts ' /clear - Clear the screen'
75+
puts
76+
puts '🚀 Ready! Type your questions or commands...'
77+
puts
78+
end
79+
80+
def main_loop
81+
while @running
82+
print "\n🐳 > "
83+
84+
begin
85+
input = gets&.chomp
86+
87+
# Handle Ctrl+C or EOF
88+
if input.nil?
89+
@running = false
90+
break
91+
end
92+
93+
process_input(input.strip)
94+
rescue Interrupt
95+
puts "\n\n👋 Received interrupt signal. Use /exit to quit cleanly."
96+
rescue StandardError => e
97+
puts "❌ Error: #{e.message}"
98+
puts ' Please try again or type /exit to quit.'
99+
end
100+
end
101+
end
102+
103+
def process_input(input)
104+
return if input.empty?
105+
106+
case input.downcase
107+
when '/exit', '/quit', '/q'
108+
@running = false
109+
when '/help', '/h'
110+
show_help
111+
when '/tools', '/t'
112+
show_tools
113+
when '/clear', '/c'
114+
clear_screen
115+
when input.start_with?('/')
116+
puts "❓ Unknown command: #{input}"
117+
puts ' Type /help for available commands'
118+
else
119+
handle_chat_message(input)
120+
end
121+
end
122+
123+
def handle_chat_message(message)
124+
puts "\n🤔 Thinking..."
125+
126+
begin
127+
response = @chat.ask(message)
128+
129+
puts "\n🤖 OpenAI Response:"
130+
puts '─' * 50
131+
puts response.content
132+
puts '─' * 50
133+
rescue StandardError => e
134+
puts "\n❌ Error communicating with OpenAI:"
135+
puts " #{e.class}: #{e.message}"
136+
puts ' Please check your API key and network connection.'
137+
end
138+
end
139+
140+
def show_help
141+
puts "\n📚 Available Docker Tools:"
142+
puts '─' * 50
143+
144+
tools_by_category = {
145+
'Container Management' => [
146+
'ListContainers - List all Docker containers',
147+
'CreateContainer - Create new containers',
148+
'RunContainer - Create and start containers',
149+
'StartContainer - Start stopped containers',
150+
'StopContainer - Stop running containers',
151+
'RemoveContainer - Delete containers',
152+
'RecreateContainer - Recreate containers with same config',
153+
'ExecContainer - Execute commands inside containers',
154+
'CopyToContainer - Copy files to containers',
155+
'FetchContainerLogs - Get container logs'
156+
],
157+
'Image Management' => [
158+
'ListImages - List available Docker images',
159+
'PullImage - Download images from registries',
160+
'BuildImage - Build images from Dockerfile',
161+
'TagImage - Tag images with new names',
162+
'PushImage - Upload images to registries',
163+
'RemoveImage - Delete images'
164+
],
165+
'Network Management' => [
166+
'ListNetworks - List Docker networks',
167+
'CreateNetwork - Create custom networks',
168+
'RemoveNetwork - Delete networks'
169+
],
170+
'Volume Management' => [
171+
'ListVolumes - List Docker volumes',
172+
'CreateVolume - Create persistent volumes',
173+
'RemoveVolume - Delete volumes'
174+
]
175+
}
176+
177+
tools_by_category.each do |category, tools|
178+
puts "\n#{category}:"
179+
tools.each { |tool| puts " • #{tool}" }
180+
end
181+
182+
puts "\n💡 Example questions you can ask:"
183+
puts " • 'How many containers are running?'"
184+
puts " • 'Show me all Docker images'"
185+
puts " • 'Create a new nginx container named web-server'"
186+
puts " • 'What networks are available?'"
187+
puts " • 'Pull the latest Ubuntu image'"
188+
end
189+
190+
def show_tools
191+
puts "\n🔧 Loaded Tools (#{RubyLLM::Docker.all_tools.size}):"
192+
puts '─' * 30
193+
194+
RubyLLM::Docker.all_tools.each_with_index do |tool_class, index|
195+
tool_name = tool_class.name.split('::').last
196+
puts "#{(index + 1).to_s.rjust(2)}. #{tool_name}"
197+
end
198+
end
199+
200+
def clear_screen
201+
system('clear') || system('cls')
202+
puts '🐳 Docker Chat - Screen cleared'
203+
end
204+
205+
def show_goodbye
206+
puts "\n👋 Thanks for using Docker Chat!"
207+
puts ' Hope you found it helpful for managing your Docker environment.'
208+
puts
209+
end
210+
end
211+
# rubocop:enable Metrics/ClassLength
212+
213+
# Start the chat if this file is run directly
214+
if __FILE__ == $PROGRAM_NAME
215+
begin
216+
DockerChat.new.start
217+
rescue StandardError => e
218+
puts "\n💥 Fatal error: #{e.class} - #{e.message}"
219+
puts ' Please check your setup and try again.'
220+
exit 1
221+
end
222+
end

examples/test_chat.rb

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
# Test script for docker_chat.rb
5+
# This verifies the basic functionality without requiring OpenAI API
6+
7+
require_relative '../lib/ruby_llm/docker'
8+
9+
puts '🧪 Testing Docker Chat functionality...'
10+
11+
# Test 1: Check that all tools are available
12+
puts "\n1. Testing tool loading:"
13+
tools = RubyLLM::Docker.all_tools
14+
puts "✅ Found #{tools.size} Docker tools"
15+
expected_tools = 22
16+
if tools.size == expected_tools
17+
puts "✅ Expected number of tools loaded (#{expected_tools})"
18+
else
19+
puts "❌ Expected #{expected_tools} tools, but found #{tools.size}"
20+
exit 1
21+
end
22+
23+
# Test 2: Check that all tools are valid RubyLLM::Tool classes
24+
puts "\n2. Testing tool validity:"
25+
invalid_tools = tools.reject { |tool| tool < RubyLLM::Tool }
26+
if invalid_tools.empty?
27+
puts '✅ All tools inherit from RubyLLM::Tool'
28+
else
29+
puts "❌ Invalid tools found: #{invalid_tools.map(&:name)}"
30+
exit 1
31+
end
32+
33+
# Test 3: Test helper method (without actually creating a chat)
34+
puts "\n3. Testing helper methods:"
35+
begin
36+
# Create a mock chat object to test the method exists
37+
mock_chat = Object.new
38+
def mock_chat.with_tool(_tool_class)
39+
self
40+
end
41+
42+
result = RubyLLM::Docker.add_all_tools_to_chat(mock_chat)
43+
if result == mock_chat
44+
puts '✅ add_all_tools_to_chat method works correctly'
45+
else
46+
puts '❌ add_all_tools_to_chat method failed'
47+
exit 1
48+
end
49+
rescue StandardError => e
50+
puts "❌ Helper method test failed: #{e.message}"
51+
exit 1
52+
end
53+
54+
# Test 4: Check that docker_chat.rb file exists and is executable
55+
puts "\n4. Testing chat script availability:"
56+
chat_script = File.join(__dir__, 'docker_chat.rb')
57+
if File.exist?(chat_script)
58+
puts '✅ docker_chat.rb exists'
59+
60+
if File.executable?(chat_script)
61+
puts '✅ docker_chat.rb is executable'
62+
else
63+
puts '⚠️ docker_chat.rb is not executable (run: chmod +x examples/docker_chat.rb)'
64+
end
65+
else
66+
puts '❌ docker_chat.rb not found'
67+
exit 1
68+
end
69+
70+
puts "\n🎉 All tests passed!"
71+
puts "\n📝 Next steps:"
72+
puts " 1. Set your OpenAI API key: export OPENAI_API_KEY='your-key-here'"
73+
puts ' 2. Run the chat: ruby examples/docker_chat.rb'
74+
puts ' 3. Or run with: ./examples/docker_chat.rb'
75+
puts "\n💡 Example chat commands to try:"
76+
puts " • 'How many containers are running?'"
77+
puts " • 'Show me all Docker images'"
78+
puts " • 'List Docker networks'"
79+
puts " • '/help' - to see all available tools"
80+
puts " • '/exit' - to quit the chat"

lib/ruby_llm/docker.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,48 @@
1212
module RubyLLM
1313
module Docker
1414
class Error < StandardError; end
15+
16+
# Helper method to get all Docker tool classes
17+
def self.all_tools
18+
[
19+
# Container Management
20+
ListContainers,
21+
CreateContainer,
22+
RunContainer,
23+
StartContainer,
24+
StopContainer,
25+
RemoveContainer,
26+
RecreateContainer,
27+
ExecContainer,
28+
CopyToContainer,
29+
FetchContainerLogs,
30+
31+
# Image Management
32+
ListImages,
33+
PullImage,
34+
BuildImage,
35+
TagImage,
36+
PushImage,
37+
RemoveImage,
38+
39+
# Network Management
40+
ListNetworks,
41+
CreateNetwork,
42+
RemoveNetwork,
43+
44+
# Volume Management
45+
ListVolumes,
46+
CreateVolume,
47+
RemoveVolume
48+
]
49+
end
50+
51+
# Helper method to add all Docker tools to a RubyLLM chat instance
52+
def self.add_all_tools_to_chat(chat)
53+
all_tools.each do |tool_class|
54+
chat.with_tool(tool_class)
55+
end
56+
chat
57+
end
1558
end
1659
end

lib/ruby_llm/docker/build_image.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ module Docker
5454
class BuildImage < RubyLLM::Tool
5555
description 'Build a Docker image'
5656

57-
param :dockerfile, type: :string, description: 'Dockerfile content as a string'
58-
param :tag, type: :string, description: 'Tag for the built image (e.g., "myimage:latest")', required: false
57+
param :dockerfile, type: :string, desc: 'Dockerfile content as a string'
58+
param :tag, type: :string, desc: 'Tag for the built image (e.g., "myimage:latest")', required: false
5959

6060
# Build a Docker image from Dockerfile content.
6161
#

lib/ruby_llm/docker/copy_to_container.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,12 @@ class CopyToContainer < RubyLLM::Tool
5858
description 'Copy a file or directory from the local filesystem into a running Docker container. ' \
5959
'The source path is on the local machine, and the destination path is inside the container.'
6060

61-
param :id, type: :string, description: 'Container ID or name'
62-
param :source_path, type: :string, description: 'Path to the file or directory on the local filesystem to copy'
61+
param :id, type: :string, desc: 'Container ID or name'
62+
param :source_path, type: :string, desc: 'Path to the file or directory on the local filesystem to copy'
6363
param :destination_path, type: :string,
64-
description: 'Path inside the container where the file/directory should be copied'
64+
desc: 'Path inside the container where the file/directory should be copied'
6565
param :owner, type: :string,
66-
description: 'Owner for the copied files (optional, e.g., "1000:1000" or "username:group")',
66+
desc: 'Owner for the copied files (optional, e.g., "1000:1000" or "username:group")',
6767
required: false
6868

6969
# Copy files or directories from host filesystem to a Docker container.

lib/ruby_llm/docker/create_network.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ module Docker
7777
class CreateNetwork < RubyLLM::Tool
7878
description 'Create a Docker network'
7979

80-
param :name, type: :string, description: 'Name of the network'
81-
param :driver, type: :string, description: 'Driver to use (default: bridge)', required: false
82-
param :check_duplicate, type: :boolean, description: 'Check for networks with duplicate names (default: true)',
80+
param :name, type: :string, desc: 'Name of the network'
81+
param :driver, type: :string, desc: 'Driver to use (default: bridge)', required: false
82+
param :check_duplicate, type: :boolean, desc: 'Check for networks with duplicate names (default: true)',
8383
required: false
8484

8585
# Create a new Docker network.

0 commit comments

Comments
 (0)