Commit f8c4ca7

mo khan <mo@mokhan.ca>
2025-08-09 01:44:54
style: use consistent formatting
1 parent 00ec2d3
exe/elelem
@@ -3,7 +3,7 @@
 
 require "elelem"
 
-Signal.trap('INT') do
+Signal.trap("INT") do
   exit(1)
 end
 
lib/elelem/agent.rb
@@ -13,17 +13,18 @@ module Elelem
     def repl
       loop do
         print "\n> "
-        user = STDIN.gets&.chomp
-        break if user.nil? || user.empty? || user == 'exit'
+        user = $stdin.gets&.chomp
+        break if user.nil? || user.empty? || user == "exit"
+
         process_input(user)
-        puts("\u001b[32mDone!\u001b[0m")
+        puts "\u001b[32mDone!\u001b[0m"
       end
     end
 
     private
 
     def process_input(text)
-      conversation.add(role: 'user', content: text)
+      conversation.add(role: "user", content: text)
 
       done = false
       loop do
@@ -31,18 +32,25 @@ module Elelem
           debug_print(chunk)
 
           response = JSON.parse(chunk)
-          done = response['done']
-          message = response['message'] || {}
-
-          if message['thinking']
-            print("\u001b[90m#{message['thinking']}\u001b[0m")
-          elsif message['tool_calls']&.any?
-            message['tool_calls'].each do |t|
-              conversation.add(role: 'tool', content: tools.execute(t))
+          done = response["done"]
+          message = response["message"] || {}
+
+          if message["thinking"]
+            print message["thinking"]
+            $stdout.flush
+          elsif message["tool_calls"]&.any?
+            puts
+            message["tool_calls"].each do |t|
+              command = extract_command_from_tool_call(t)
+              puts "Running: #{command}"
+              tool_output = tools.execute(t)
+              puts tool_output
+              conversation.add(role: "tool", content: tool_output)
             end
             done = false
-          elsif message['content'].to_s.strip
-            print message['content'].to_s.strip
+          elsif message["content"].to_s.strip
+            print message["content"]
+            $stdout.flush
           else
             raise chunk.inspect
           end
@@ -50,24 +58,26 @@ module Elelem
 
         break if done
       end
+
+      puts
     end
 
     def call_api(messages)
       body = {
-        messages:   messages,
-        model:      configuration.model,
-        stream:     true,
-        keep_alive: '5m',
-        options:      { temperature: 0.1 },
-        tools:       tools.to_h
+        messages: messages,
+        model: configuration.model,
+        stream: true,
+        keep_alive: "5m",
+        options: { temperature: 0.1 },
+        tools: tools.to_h
       }
       json_body = body.to_json
       debug_print(json_body)
 
       req = Net::HTTP::Post.new(configuration.uri)
-      req['Content-Type'] = 'application/json'
+      req["Content-Type"] = "application/json"
       req.body = json_body
-      req['Authorization'] = "Bearer #{configuration.token}" if configuration.token
+      req["Authorization"] = "Bearer #{configuration.token}" if configuration.token
 
       configuration.http.request(req) do |response|
         raise response.inspect unless response.code == "200"
@@ -83,5 +93,19 @@ module Elelem
     def debug_print(body = nil)
       configuration.logger.debug(body) if configuration.debug && body
     end
+
+    def extract_command_from_tool_call(tool_call)
+      function_name = tool_call.dig("function", "name")
+      args = tool_call.dig("function", "arguments")
+
+      case function_name
+      when "execute_command"
+        args["command"]
+      when "ask_user"
+        "ask user: #{args["question"]}"
+      else
+        function_name
+      end
+    end
   end
 end
lib/elelem/application.rb
@@ -2,21 +2,24 @@
 
 module Elelem
   class Application < Thor
-    desc 'chat', 'Start the REPL'
-    method_option :help, aliases: '-h', type: :boolean, desc: 'Display usage information'
-    method_option :host, aliases: '--host', type: :string, desc: 'Ollama host', default: ENV.fetch('OLLAMA_HOST', 'localhost:11434')
-    method_option :model, aliases: '--model', type: :string, desc: 'Ollama model', default: ENV.fetch('OLLAMA_MODEL', 'gpt-oss')
-    method_option :token, aliases: '--token', type: :string, desc: 'Ollama token', default: ENV.fetch('OLLAMA_API_KEY', nil)
-    method_option :debug, aliases: '--debug', type: :boolean, desc: 'Debug mode', default: false
+    desc "chat", "Start the REPL"
+    method_option :help, aliases: "-h", type: :boolean, desc: "Display usage information"
+    method_option :host, aliases: "--host", type: :string, desc: "Ollama host",
+                         default: ENV.fetch("OLLAMA_HOST", "localhost:11434")
+    method_option :model, aliases: "--model", type: :string, desc: "Ollama model",
+                          default: ENV.fetch("OLLAMA_MODEL", "gpt-oss")
+    method_option :token, aliases: "--token", type: :string, desc: "Ollama token",
+                          default: ENV.fetch("OLLAMA_API_KEY", nil)
+    method_option :debug, aliases: "--debug", type: :boolean, desc: "Debug mode", default: false
     def chat(*)
       if options[:help]
-        invoke :help, ['chat']
+        invoke :help, ["chat"]
       else
         configuration = Configuration.new(
           host: options[:host],
           model: options[:model],
           token: options[:token],
-          debug: options[:debug],
+          debug: options[:debug]
         )
         say "Ollama Agent (#{configuration.model})", :green
         say "Tools:\n  #{configuration.tools.banner}", :green
@@ -26,7 +29,7 @@ module Elelem
       end
     end
 
-    desc 'version', 'spandx version'
+    desc "version", "spandx version"
     def version
       puts "v#{Spandx::VERSION}"
     end
lib/elelem/configuration.rb
@@ -19,10 +19,8 @@ module Elelem
     end
 
     def logger
-      @logger ||= begin
-        Logger.new(debug ? $stderr : "/dev/null").tap do |logger|
-          logger.formatter = ->(_, _, _, msg) { msg }
-        end
+      @logger ||= Logger.new(debug ? $stderr : "/dev/null").tap do |logger|
+        logger.formatter = ->(_, _, _, msg) { msg }
       end
     end
 
@@ -41,7 +39,7 @@ module Elelem
     private
 
     def scheme
-      host.match?(/\A(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(:\d+)?\z/) ? 'http' : 'https'
+      host.match?(/\A(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(:\d+)?\z/) ? "http" : "https"
     end
   end
 end
lib/elelem/conversation.rb
@@ -4,14 +4,14 @@ module Elelem
   class Conversation
     SYSTEM_MESSAGE = <<~SYS
       You are ChatGPT, a helpful assistant with reasoning capabilities.
-      Current date: #{Time.now.strftime('%Y-%m-%d')}.
+      Current date: #{Time.now.strftime("%Y-%m-%d")}.
       System info: `uname -a` output: #{`uname -a`.strip}
       Reasoning: high
     SYS
 
-    ROLES = ['system', 'user', 'tool'].freeze
+    ROLES = %w[system user tool].freeze
 
-    def initialize(items = [{ role: 'system', content: SYSTEM_MESSAGE }])
+    def initialize(items = [{ role: "system", content: SYSTEM_MESSAGE }])
       @items = items
     end
 
@@ -20,7 +20,7 @@ module Elelem
     end
 
     # :TODO truncate conversation history
-    def add(role: 'user', content: '')
+    def add(role: "user", content: "")
       raise "unknown role: #{role}" unless ROLES.include?(role)
 
       @items << { role: role, content: content }
lib/elelem/tools.rb
@@ -4,36 +4,36 @@ module Elelem
   class Tools
     DEFAULT_TOOLS = [
       {
-        type: 'function',
+        type: "function",
         function: {
-          name:        'execute_command',
-          description: 'Execute a shell command.',
+          name: "execute_command",
+          description: "Execute a shell command.",
           parameters: {
-            type:       'object',
-            properties: { command: { type: 'string' } },
-            required:   ['command']
+            type: "object",
+            properties: { command: { type: "string" } },
+            required: ["command"]
           }
         },
-        handler: -> (args) {
-          stdout, stderr, _status = Open3.capture3('/bin/sh', '-c', args['command'])
+        handler: lambda { |args|
+          stdout, stderr, _status = Open3.capture3("/bin/sh", "-c", args["command"])
           stdout + stderr
         }
       },
       {
-        type: 'function',
+        type: "function",
         function: {
-          name:        'ask_user',
-          description: 'Ask the user to answer a question.',
+          name: "ask_user",
+          description: "Ask the user to answer a question.",
           parameters: {
-            type:       'object',
-            properties: { question: { type: 'string' } },
-            required:   ['question']
+            type: "object",
+            properties: { question: { type: "string" } },
+            required: ["question"]
           }
         },
-        handler: ->(args) {
-          puts("\u001b[35m#{args['question']}\u001b[0m")
+        handler: lambda { |args|
+          puts("\u001b[35m#{args["question"]}\u001b[0m")
           print "> "
-          STDIN.gets&.chomp
+          $stdin.gets&.chomp
         }
       }
     ]
@@ -52,8 +52,8 @@ module Elelem
     end
 
     def execute(tool_call)
-      name = tool_call.dig('function', 'name')
-      args = tool_call.dig('function', 'arguments')
+      name = tool_call.dig("function", "name")
+      args = tool_call.dig("function", "arguments")
 
       tool = @tools.find do |tool|
         tool.dig(:function, :name) == name
elelem.gemspec
@@ -33,10 +33,10 @@ Gem::Specification.new do |spec|
   spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
   spec.require_paths = ["lib"]
 
-  spec.add_dependency 'json'
-  spec.add_dependency 'logger'
-  spec.add_dependency 'net-http'
-  spec.add_dependency 'open3'
-  spec.add_dependency 'thor'
-  spec.add_dependency 'uri'
+  spec.add_dependency "json"
+  spec.add_dependency "logger"
+  spec.add_dependency "net-http"
+  spec.add_dependency "open3"
+  spec.add_dependency "thor"
+  spec.add_dependency "uri"
 end