Comparing changes

v0.7.0 v0.8.0
3 commits 7 files changed

Commits

56190e3 chore: release v0.8.0 mo khan 2026-01-14 22:58:32
6f9a474 feat: add fetch and search_engine tools mo khan 2026-01-14 22:24:30
lib/elelem/toolbox.rb
@@ -2,7 +2,6 @@
 
 module Elelem
   class Toolbox
-
     READ_TOOL = Tool.build("read", "Read complete contents of a file. Requires exact file path.", { path: { type: "string" } }, ["path"]) do |args|
       path = args["path"]
       full_path = Pathname.new(path).expand_path
@@ -37,12 +36,30 @@ module Elelem
       { bytes_written: full_path.write(args["content"]) }
     end
 
+    FETCH_TOOL = Tool.build("fetch", "Fetch content from a URL. Returns status, headers, and body.", { url: { type: "string", description: "The URL to fetch" } }, ["url"]) do |args|
+      client = Net::Hippie::Client.new
+      response = client.get(args["url"])
+      { status: response.code.to_i, body: response.body }
+    end
+
+    WEB_SEARCH_TOOL = Tool.build("search_engine", "Search the web using DuckDuckGo. Returns raw API response.", { query: { type: "string", description: "The search query" } }, ["query"]) do |args|
+      query = CGI.escape(args["query"])
+      url = "https://api.duckduckgo.com/?q=#{query}&format=json&no_html=1"
+      client = Net::Hippie::Client.new
+      response = client.get(url)
+      JSON.parse(response.body)
+    end
+
     TOOL_ALIASES = {
       "bash" => "exec",
+      "duckduckgo" => "search_engine",
+      "ddg" => "search_engine",
       "execute" => "exec",
+      "get" => "fetch",
       "open" => "read",
       "search" => "grep",
       "sh" => "exec",
+      "web" => "fetch",
     }
 
     attr_reader :tools
@@ -52,7 +69,9 @@ module Elelem
       @tool_permissions = {}
       @tools = { read: [], write: [], execute: [] }
       add_tool(eval_tool(binding), :execute)
+      add_tool(WEB_SEARCH_TOOL, :read)
       add_tool(EXEC_TOOL, :execute)
+      add_tool(FETCH_TOOL, :read)
       add_tool(GREP_TOOL, :read)
       add_tool(LIST_TOOL, :read)
       add_tool(PATCH_TOOL, :write)
lib/elelem/version.rb
@@ -1,5 +1,5 @@
 # frozen_string_literal: true
 
 module Elelem
-  VERSION = "0.7.0"
+  VERSION = "0.8.0"
 end
\ No newline at end of file
lib/elelem.rb
@@ -1,11 +1,13 @@
 # frozen_string_literal: true
 
+require "cgi"
 require "cli/ui"
 require "erb"
 require "fileutils"
 require "json"
 require "json-schema"
 require "logger"
+require "net/hippie"
 require "net/llm"
 require "open3"
 require "pathname"
spec/elelem/toolbox_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Elelem::Toolbox do
       tools = subject.tools_for(mode)
 
       tool_names = tools.map { |t| t.dig(:function, :name) }
-      expect(tool_names).to include("grep", "list", "read")
+      expect(tool_names).to include("grep", "list", "read", "fetch", "search_engine")
       expect(tool_names).not_to include("write", "patch", "exec")
     end
 
@@ -36,7 +36,7 @@ RSpec.describe Elelem::Toolbox do
       tools = subject.tools_for(mode)
 
       tool_names = tools.map { |t| t.dig(:function, :name) }
-      expect(tool_names).to include("grep", "list", "read", "patch", "write", "exec")
+      expect(tool_names).to include("grep", "list", "read", "patch", "write", "exec", "fetch", "search_engine")
     end
 
     it "returns combined tools for build mode" do
@@ -44,11 +44,28 @@ RSpec.describe Elelem::Toolbox do
       tools = subject.tools_for(mode)
 
       tool_names = tools.map { |t| t.dig(:function, :name) }
-      expect(tool_names).to include("grep", "read", "write", "patch")
+      expect(tool_names).to include("grep", "read", "write", "patch", "fetch", "search_engine")
       expect(tool_names).not_to include("exec")
     end
   end
 
+  describe "web tools" do
+    it "includes fetch and search_engine in read permissions" do
+      tools = subject.tools_for([:read])
+      names = tools.map { |t| t.dig(:function, :name) }
+      expect(names).to include("fetch", "search_engine")
+    end
+
+    it "resolves web and get aliases to fetch" do
+      expect(Elelem::Toolbox::TOOL_ALIASES["web"]).to eq("fetch")
+      expect(Elelem::Toolbox::TOOL_ALIASES["get"]).to eq("fetch")
+    end
+
+    it "resolves duckduckgo alias to search_engine" do
+      expect(Elelem::Toolbox::TOOL_ALIASES["duckduckgo"]).to eq("search_engine")
+    end
+  end
+
   describe "#run_tool mode enforcement" do
     it "allows tool execution when mode matches" do
       result = subject.run_tool("read", { "path" => __FILE__ }, permissions: [:read])
CHANGELOG.md
@@ -1,5 +1,13 @@
 ## [Unreleased]
 
+## [0.8.0] - 2026-01-14
+
+### Added
+- `fetch` tool for HTTP GET requests (returns status and body)
+- `search_engine` tool for DuckDuckGo Instant Answer API searches
+- Tool aliases: `get`/`web` → `fetch`, `ddg`/`duckduckgo` → `search_engine`
+- `net-hippie` and `cgi` dependencies for HTTP requests
+
 ## [0.7.0] - 2026-01-14
 
 ### Added
elelem.gemspec
@@ -40,12 +40,14 @@ Gem::Specification.new do |spec|
   spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
   spec.require_paths = ["lib"]
 
+  spec.add_dependency "cgi", "~> 0.1"
   spec.add_dependency "cli-ui", "~> 2.0"
   spec.add_dependency "erb", "~> 6.0"
   spec.add_dependency "fileutils", "~> 1.0"
   spec.add_dependency "json", "~> 2.0"
   spec.add_dependency "json-schema", "~> 6.0"
   spec.add_dependency "logger", "~> 1.0"
+  spec.add_dependency "net-hippie", "~> 1.0"
   spec.add_dependency "net-llm", "~> 0.5", ">= 0.5.0"
   spec.add_dependency "open3", "~> 0.1"
   spec.add_dependency "pathname", "~> 0.1"
Gemfile.lock
@@ -1,13 +1,15 @@
 PATH
   remote: .
   specs:
-    elelem (0.7.0)
+    elelem (0.8.0)
+      cgi (~> 0.1)
       cli-ui (~> 2.0)
       erb (~> 6.0)
       fileutils (~> 1.0)
       json (~> 2.0)
       json-schema (~> 6.0)
       logger (~> 1.0)
+      net-hippie (~> 1.0)
       net-llm (~> 0.5, >= 0.5.0)
       open3 (~> 0.1)
       pathname (~> 0.1)
@@ -23,6 +25,7 @@ GEM
       public_suffix (>= 2.0.2, < 8.0)
     base64 (0.3.0)
     bigdecimal (4.0.1)
+    cgi (0.4.2)
     cli-ui (2.7.0)
     date (3.5.1)
     diff-lcs (1.6.2)