Commit fe04748
Changed files (7)
lib/elelem/toolbox/bash.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module Elelem
+ module Toolbox
+ class Bash < ::Elelem::Tool
+ attr_reader :tui
+
+ def initialize(configuration)
+ @tui = configuration.tui
+ super("bash", "Execute a shell command.", {
+ type: "object",
+ properties: {
+ command: { type: "string" }
+ },
+ required: ["command"]
+ })
+ end
+
+ def call(args)
+ command = args["command"]
+ output_buffer = []
+
+ Open3.popen3("/bin/sh", "-c", command) do |stdin, stdout, stderr, wait_thread|
+ stdin.close
+ streams = [stdout, stderr]
+
+ until streams.empty?
+ ready = IO.select(streams, nil, nil, 0.1)
+
+ if ready
+ ready[0].each do |io|
+ data = io.read_nonblock(4096)
+ output_buffer << data
+
+ if io == stderr
+ tui.say(data, colour: :red, newline: false)
+ else
+ tui.say(data, newline: false)
+ end
+ rescue IO::WaitReadable
+ next
+ rescue EOFError
+ streams.delete(io)
+ end
+ elsif !wait_thread.alive?
+ break
+ end
+ end
+
+ wait_thread.value
+ end
+
+ output_buffer.join
+ end
+ end
+ end
+end
lib/elelem/toolbox/mcp.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Elelem
+ module Toolbox
+ class MCP < ::Elelem::Tool
+ attr_reader :client, :tui
+
+ def initialize(client, tui, tool)
+ @client = client
+ @tui = tui
+ super(tool["name"], tool["description"], tool["inputSchema"] || {})
+ end
+
+ def call(args)
+ unless client.connected?
+ tui.say("MCP connection lost", colour: :red)
+ return ""
+ end
+
+ result = client.call(name, args)
+ tui.say(result)
+
+ if result.nil? || result.empty?
+ tui.say("Tool call failed: no response from MCP server", colour: :red)
+ return result
+ end
+
+ if result["error"]
+ tui.say(result["error"], colour: :red)
+ return result
+ end
+
+ result.dig("content", 0, "text") || result.to_s
+ end
+ end
+ end
+end
lib/elelem/configuration.rb
@@ -41,7 +41,7 @@ module Elelem
end
def tools
- @tools ||= Tools.new(self, [BashTool.new(self)] + mcp_tools)
+ @tools ||= Tools.new(self, [Toolbox::Bash.new(self)] + mcp_tools)
end
private
@@ -53,7 +53,7 @@ module Elelem
def mcp_tools(clients = [serena_client])
return [] if ENV["SMALL"]
- @mcp_tools ||= clients.map { |client| client.tools.map { |tool| MCPTool.new(client, tui, tool) } }.flatten
+ @mcp_tools ||= clients.map { |client| client.tools.map { |tool| Toolbox::MCP.new(client, tui, tool) } }.flatten
end
def serena_client
lib/elelem/tool.rb
@@ -29,88 +29,4 @@ module Elelem
}
end
end
-
- class BashTool < Tool
- attr_reader :tui
-
- def initialize(configuration)
- @tui = configuration.tui
- super("bash", "Execute a shell command.", {
- type: "object",
- properties: {
- command: { type: "string" }
- },
- required: ["command"]
- })
- end
-
- def call(args)
- command = args["command"]
- output_buffer = []
-
- Open3.popen3("/bin/sh", "-c", command) do |stdin, stdout, stderr, wait_thread|
- stdin.close
- streams = [stdout, stderr]
-
- until streams.empty?
- ready = IO.select(streams, nil, nil, 0.1)
-
- if ready
- ready[0].each do |io|
- data = io.read_nonblock(4096)
- output_buffer << data
-
- if io == stderr
- tui.say(data, colour: :red, newline: false)
- else
- tui.say(data, newline: false)
- end
- rescue IO::WaitReadable
- next
- rescue EOFError
- streams.delete(io)
- end
- elsif !wait_thread.alive?
- break
- end
- end
-
- wait_thread.value
- end
-
- output_buffer.join
- end
- end
-
- class MCPTool < Tool
- attr_reader :client, :tui
-
- def initialize(client, tui, tool)
- @client = client
- @tui = tui
- super(tool["name"], tool["description"], tool["inputSchema"] || {})
- end
-
- def call(args)
- unless client.connected?
- tui.say("MCP connection lost", colour: :red)
- return ""
- end
-
- result = client.call(name, args)
- tui.say(result)
-
- if result.nil? || result.empty?
- tui.say("Tool call failed: no response from MCP server", colour: :red)
- return result
- end
-
- if result["error"]
- tui.say(result["error"], colour: :red)
- return result
- end
-
- result.dig("content", 0, "text") || result.to_s
- end
- end
end
lib/elelem/tui.rb
@@ -24,7 +24,7 @@ module Elelem
def show_progress(message, icon = ".", colour: :gray)
timestamp = Time.now.strftime("%H:%M:%S")
- say("[#{icon}] #{timestamp} #{message}", colour: colour, newline: false)
+ say("[#{icon}] #{timestamp} #{message}", colour: colour, newline: true)
end
def clear_line
lib/elelem.rb
@@ -18,6 +18,8 @@ require_relative "elelem/conversation"
require_relative "elelem/mcp_client"
require_relative "elelem/state"
require_relative "elelem/tool"
+require_relative "elelem/toolbox/bash"
+require_relative "elelem/toolbox/mcp"
require_relative "elelem/tools"
require_relative "elelem/tui"
require_relative "elelem/version"
elelem.gemspec
@@ -43,6 +43,8 @@ Gem::Specification.new do |spec|
"lib/elelem/state.rb",
"lib/elelem/system_prompt.erb",
"lib/elelem/tool.rb",
+ "lib/elelem/toolbox/bash.rb",
+ "lib/elelem/toolbox/mcp.rb",
"lib/elelem/tools.rb",
"lib/elelem/tui.rb",
"lib/elelem/version.rb",