Commit c6af4df

mo khan <mo@mokhan.ca>
2026-01-22 00:26:54
feat: add a default edit tool
1 parent 027ec87
lib/elelem/plugins/edit.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+Elelem::Plugins.register(:edit) do |toolbox|
+  toolbox.add("edit",
+    description: "Replace first occurrence of text in file",
+    params: { path: { type: "string" }, old: { type: "string" }, new: { type: "string" } },
+    required: ["path", "old", "new"]
+  ) do |a|
+    path = Pathname.new(a["path"]).expand_path
+    content = path.read
+    if content.include?(a["old"])
+      path.write(content.sub(a["old"], a["new"]))
+      { path: a["path"], bytes: path.write(content.sub(a["old"], a["new"])) }
+    else
+      { error: "text not found", content: content.lines.first(20).join }
+    end
+  end
+
+  toolbox.after("edit") do |_, result|
+    if result[:error]
+      $stdout.puts "  ! #{result[:error]}"
+    elsif !system("bat", "--paging=never", result[:path])
+      $stdout.puts "  -> #{result[:path]}"
+    end
+  end
+end
lib/elelem/plugins/execute.rb
@@ -5,7 +5,7 @@ Elelem::Plugins.register(:execute) do |toolbox|
     description: "Run shell command (supports pipes and redirections)",
     params: { command: { type: "string" } },
     required: ["command"],
-    aliases: ["bash", "sh", "exec"]
+    aliases: ["bash", "sh", "exec", "execute<|channel|>"]
   ) do |a|
     Elelem.sh("bash", args: ["-c", a["command"]]) { |x| $stdout.print(x) }
   end
lib/elelem/plugins/read.rb
@@ -14,7 +14,7 @@ Elelem::Plugins.register(:read) do |toolbox|
   toolbox.after("read") do |_, result|
     if result[:error]
       $stdout.puts "  ! #{result[:error]}"
-    elsif !system("bat", "--style=plain", "--paging=never", result[:path])
+    elsif !system("bat", "--paging=never", result[:path])
       $stdout.puts result[:content]
     end
   end
lib/elelem/plugins/verify.rb
@@ -27,7 +27,7 @@ module Elelem
   end
 
   Plugins.register(:verify) do |toolbox|
-    toolbox.after("write") do |_, result|
+    toolbox.after("write") do |_args, result|
       next if result[:error]
 
       Verifiers.for(result[:path]).each do |cmd|
lib/elelem/agent.rb
@@ -133,19 +133,19 @@ module Elelem
         Terminal coding agent. Be concise. Verify your work.
 
         # Tools
-        - read: file contents
-        - write: create/overwrite file
-        - execute: shell command
+        - read(path): file contents
+        - write(path, content): create/overwrite file
+        - execute(command): shell command
 
         # Editing
-        Use `patch -p1` for multi-line changes: `echo "DIFF" | patch -p1`
-        Use sed for single-line changes: `sed -i'' 's/old/new/' file`
+        Use execute(`patch -p1`) for multi-line changes: `echo "DIFF" | patch -p1`
+        Use execute(`sed`) for single-line changes: `sed -i'' 's/old/new/' file`
         Use write for new files or full rewrites
 
         # Search
-        Use `rg` for text search: `rg -n "pattern" .`
-        Use `fd` for file discovery: `fd -e rb .`
-        Use `sg` (ast-grep) for structural search: `sg -p 'def $NAME' -l ruby`
+        Use execute(`rg`) for text search: `rg -n "pattern" .`
+        Use execute(`fd`) for file discovery: `fd -e rb .`
+        Use execute(`sg`) (ast-grep) for structural search: `sg -p 'def $NAME' -l ruby`
 
         # Task Management
         For complex tasks:
lib/elelem/toolbox.rb
@@ -32,7 +32,7 @@ module Elelem
     def run(name, args)
       name = @aliases.fetch(name, name)
       tool = tools[name]
-      return { error: "unknown tool: #{name}", tools: to_a } unless tool
+      return { error: "unknown tool: #{name}. Use 'execute' to run shell commands like rg, fd, git.", tools: to_a } unless tool
 
       errors = tool.validate(args)
       return { error: errors.join(", ") } if errors.any?
elelem.gemspec
@@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
     "lib/elelem/net/openai.rb",
     "lib/elelem/plugins.rb",
     "lib/elelem/plugins/confirm.rb",
+    "lib/elelem/plugins/edit.rb",
     "lib/elelem/plugins/execute.rb",
     "lib/elelem/plugins/mcp.rb",
     "lib/elelem/plugins/read.rb",