Commit 9788712
Changed files (5)
lib
elelem
spec
elelem
lib/elelem/agent.rb
@@ -91,24 +91,24 @@ module Elelem
end
def format_tool_result(name, result)
- text = result["stdout"] || result["stderr"] || result[:content] || result[:error] || ""
+ text = result[:output] || result[:content] || result[:error] || ""
return nil if text.strip.empty?
result[:error] ? " ! #{text.lines.first&.strip}" : text
end
def truncate(result)
- %w[stdout stderr].each do |k|
- next unless result[k].is_a?(String) && result[k].lines.size > MAX_LINES
- result[k] = result[k].lines.first(MAX_LINES).join + "… (truncated)"
- end
+ return result unless result[:output].is_a?(String) && result[:output].lines.size > MAX_LINES
+
+ result[:output] = result[:output].lines.first(MAX_LINES).join + "… (truncated)"
result
end
def system_prompt
<<~PROMPT.strip
- Terminal agent. Be concise. Act directly, verify your work. Stay grounded - only respond to what is asked.
+ Terminal agent. Be concise. Act directly, verify your work.
pwd: #{Dir.pwd}
+ Use `which` or `compgen -c | grep` to discover available tools.
PROMPT
end
end
lib/elelem/terminal.rb
@@ -74,7 +74,7 @@ module Elelem
def complete_files(target)
result = Elelem.sh("bash", args: ["-c", "compgen -f #{target}"])
- result["stdout"].lines.map(&:strip).first(20)
+ result[:output].lines.map(&:strip).first(20)
end
end
end
lib/elelem/toolbox.rb
@@ -15,27 +15,15 @@ module Elelem
required: ["path", "content"],
fn: ->(a) { p = Pathname.new(a["path"]).expand_path; FileUtils.mkdir_p(p.dirname); { bytes: p.write(a["content"]) } }
},
- "exec" => {
- desc: "Run shell command",
- params: { cmd: { type: "string" }, args: { type: "array", items: { type: "string" } }, stdin: { type: "string" } },
- required: ["cmd"],
- fn: ->(a) { Elelem.sh(a["cmd"], args: a["args"] || [], stdin: a["stdin"]) }
- },
- "grep" => {
- desc: "Search git-tracked files",
- params: { query: { type: "string" } },
- required: ["query"],
- fn: ->(a) { Elelem.sh("git", args: ["grep", "-nI", a["query"]]) }
- },
- "list" => {
- desc: "List git-tracked files",
- params: { path: { type: "string" } },
- required: [],
- fn: ->(a) { Elelem.sh("git", args: a["path"] ? ["ls-files", "--", a["path"]] : ["ls-files"]) }
+ "execute" => {
+ desc: "Run shell command (supports pipes and redirections)",
+ params: { command: { type: "string" } },
+ required: ["command"],
+ fn: ->(a) { Elelem.sh("bash", args: ["-c", a["command"]]) { |x| $stdout.print(x) } }
}
}.freeze
- ALIASES = { "bash" => "exec", "sh" => "exec", "open" => "read" }.freeze
+ ALIASES = { "bash" => "execute", "sh" => "execute", "exec" => "execute", "open" => "read" }.freeze
attr_reader :tools
lib/elelem.rb
@@ -5,6 +5,7 @@ require "json"
require "net/llm"
require "open3"
require "pathname"
+require "stringio"
require "reline"
require_relative "elelem/agent"
@@ -12,15 +13,17 @@ require_relative "elelem/terminal"
require_relative "elelem/toolbox"
require_relative "elelem/version"
-Reline.input = $stdin
-Reline.output = $stdout
-
module Elelem
- class Error < StandardError; end
-
- def self.sh(cmd, args: [], env: {}, cwd: Dir.pwd, stdin: nil)
- stdout, stderr, status = Open3.capture3(env, cmd, *args, chdir: cwd, stdin_data: stdin)
- { "exit_status" => status.exitstatus, "stdout" => stdout, "stderr" => stderr }
+ def self.sh(cmd, args: [], cwd: Dir.pwd)
+ output = StringIO.new
+ Open3.popen2e(cmd, *args, chdir: cwd) do |stdin, out, wait_thr|
+ stdin.close
+ out.each_line do |l|
+ yield l if block_given?
+ output.write(l)
+ end
+ { exit_status: wait_thr.value.exitstatus, output: output.string }
+ end
end
def self.start(client)
spec/elelem/toolbox_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Elelem::Toolbox do
describe "#to_h" do
it "returns all tools in API format" do
tool_names = subject.to_h.map { |t| t.dig(:function, :name) }
- expect(tool_names).to include("read", "write", "exec", "grep", "list")
+ expect(tool_names).to include("read", "write", "execute")
end
end
@@ -25,10 +25,5 @@ RSpec.describe Elelem::Toolbox do
result = subject.run("nonexistent", {})
expect(result[:error]).to include("unknown tool")
end
-
- it "executes grep tool" do
- result = subject.run("grep", { "query" => "RSpec.describe" })
- expect(result["stdout"]).to include("toolbox_spec.rb")
- end
end
end