Commit dadb2bc

mo khan <mo@mokhan.ca>
2025-08-18 19:08:45
feat: attempt to connect to ollama.com
1 parent aef6142
lib/elelem/api.rb
@@ -28,7 +28,7 @@ module Elelem
       {
         open_timeout: 10,
         read_timeout: 3_600,
-        use_ssl: uri.scheme == 'https',
+        use_ssl: uri.scheme == "https"
       }
     end
 
@@ -39,50 +39,49 @@ module Elelem
         # No scheme โ€“ decide which one to add.
         # * localhost or 127.0.0.1 โ†’ http
         # * anything else          โ†’ https
-        if raw_host.start_with?('localhost', '127.0.0.1')
-          scheme = 'http://'
-        else
-          scheme = 'https://'
-        end
+        scheme = raw_host.start_with?("localhost", "127.0.0.1") ? "http://" : "https://"
         host = scheme + raw_host
       end
 
-      endpoint = "#{host.sub(%r{/?$}, '')}/api/chat"
-      URI(endpoint)
+      URI("#{host.sub(%r{/?$}, "")}/api/chat")
     end
 
     def build_request(messages)
-      Net::HTTP::Post.new(uri).tap do |request|
+      timestamp = Time.now.to_i
+      request_uri = build_uri_with(timestamp)
+      Net::HTTP::Post.new(request_uri).tap do |request|
         request["Accept"] = "application/json"
         request["Content-Type"] = "application/json"
         request["User-Agent"] = "ollama/0.11.3 (amd64 linux) Go/go1.24.6"
-        build_token do |token|
+        build_token("POST", request_uri.path, timestamp) do |token|
           request["Authorization"] = token
         end
         request.body = build_payload(messages).to_json
       end
     end
 
-    def build_token
+    def build_uri_with(timestamp)
+      uri.dup.tap do |request_uri|
+        original_query = request_uri.query
+        request_uri.query = original_query ? "#{original_query}&ts=#{timestamp}" : "ts=#{timestamp}"
+      end
+    end
+
+    def build_token(method, path, timestamp)
       if uri.host == "ollama.com"
-        raise "Not Implemented"
+        private_key_path = File.expand_path("~/.ollama/id_ed25519")
+        raise "Ollama Ed25519 key not found at #{private_key_path}" unless File.exist?(private_key_path)
 
-        # if File.exist?("~/.ollama/id_ed25519")
-          # TODO:: return signature
-          # now := strconv.FormatInt(time.Now().Unix(), 10)
-          # challenge := fmt.Sprintf("%s,%s?ts=%s", method, path, now)
-          # token, err := auth.Sign(ctx, []byte(challenge))
-          # q := requestURL.Query()
-          # q.Set("ts", now)
-          # requestURL.RawQuery = q.Encode()
-          #
-          # request["Authorization"] = token
-        # end
+        challenge = "#{method},#{path}?ts=#{timestamp}"
+        private_key = load_ed25519_key(private_key_path)
+        signature = private_key.sign(challenge)
+        encoded_signature = Base64.strict_encode64(signature)
+        yield encoded_signature
       end
 
-      if configuration.token && !configuration.token.empty?
-        yield "Bearer #{configuration.token}"
-      end
+      return unless configuration.token && !configuration.token.empty?
+
+      yield "Bearer #{configuration.token}"
     end
 
     def build_payload(messages)
@@ -97,5 +96,10 @@ module Elelem
         configuration.logger.debug(JSON.pretty_generate(payload))
       end
     end
+
+    def load_ed25519_key(key_path)
+      ssh_key = Net::SSH::KeyFactory.load_private_key(key_path)
+      Ed25519::SigningKey.new(ssh_key.sign_key.seed)
+    end
   end
 end
lib/elelem.rb
@@ -1,11 +1,14 @@
 # frozen_string_literal: true
 
+require "base64"
 require "cli/ui"
+require "ed25519"
 require "erb"
 require "json"
 require "json-schema"
 require "logger"
 require "net/http"
+require "net/ssh"
 require "open3"
 require "reline"
 require "thor"
elelem.gemspec
@@ -61,12 +61,16 @@ Gem::Specification.new do |spec|
   spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
   spec.require_paths = ["lib"]
 
+  spec.add_dependency "base64"
+  spec.add_dependency "bcrypt_pbkdf"
   spec.add_dependency "cli-ui"
+  spec.add_dependency "ed25519"
   spec.add_dependency "erb"
   spec.add_dependency "json"
   spec.add_dependency "json-schema"
   spec.add_dependency "logger"
   spec.add_dependency "net-http"
+  spec.add_dependency "net-ssh"
   spec.add_dependency "open3"
   spec.add_dependency "reline"
   spec.add_dependency "thor"
Gemfile.lock
@@ -2,12 +2,16 @@ PATH
   remote: .
   specs:
     elelem (0.1.3)
+      base64
+      bcrypt_pbkdf
       cli-ui
+      ed25519
       erb
       json
       json-schema
       logger
       net-http
+      net-ssh
       open3
       reline
       thor
@@ -20,10 +24,13 @@ GEM
     addressable (2.8.7)
       public_suffix (>= 2.0.2, < 7.0)
     ast (2.4.3)
+    base64 (0.3.0)
+    bcrypt_pbkdf (1.1.1)
     bigdecimal (3.2.2)
     cli-ui (2.4.0)
     date (3.4.1)
     diff-lcs (1.6.2)
+    ed25519 (1.4.0)
     erb (5.0.2)
     io-console (0.8.1)
     irb (1.15.2)
@@ -39,6 +46,7 @@ GEM
     logger (1.7.0)
     net-http (0.6.0)
       uri
+    net-ssh (7.3.0)
     open3 (0.2.1)
     parallel (1.27.0)
     parser (3.3.9.0)