Commit fe04748

mo khan <mo@mokhan.ca>
2025-08-14 18:35:35
refactor: move tools into toolbox
1 parent c34aa3e
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",