Commit e99a023
Changed files (11)
lib
elelem
lib/elelem/builtins/builtins.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+Elelem::Plugins.register(:builtins) do |agent|
+ agent.commands.register("exit", description: "Exit elelem") { exit(0) }
+
+ agent.commands.register("clear", description: "Clear conversation history") do
+ agent.conversation.clear!
+ agent.terminal.say " → context cleared"
+ end
+
+ agent.commands.register("context", description: "Show conversation context") do
+ agent.terminal.say JSON.pretty_generate(agent.context)
+ end
+
+ agent.commands.register("shell", description: "Start interactive shell") do
+ transcript = Tempfile.create do |file|
+ system("script", "-q", file.path, chdir: Dir.pwd)
+ File.read(file.path)
+ .gsub(/^Script started.*?\n/, "")
+ .gsub(/\nScript done.*$/, "")
+ .gsub(/\e\[[0-9;]*[a-zA-Z]/, "")
+ .gsub(/\e\[\?[0-9]+[hl]/, "")
+ .gsub(/[\b]/, "")
+ .gsub(/\r/, "")
+ end
+ agent.conversation.add(role: "user", content: transcript) unless transcript.strip.empty?
+ end
+
+ agent.commands.register("init", description: "Generate AGENTS.md") do
+ system_prompt = <<~PROMPT
+ AGENTS.md generator. Analyze codebase and write AGENTS.md to project root.
+
+ # AGENTS.md Spec (https://agents.md/)
+ A file providing context and instructions for AI coding agents.
+
+ ## Recommended Sections
+ - Commands: build, test, lint commands
+ - Code Style: conventions, patterns
+ - Architecture: key components and flow
+ - Testing: how to run tests
+
+ ## Process
+ 1. Read README.md if present
+ 2. Identify language (Gemfile, package.json, go.mod)
+ 3. Find test scripts (bin/test, npm test)
+ 4. Check linter configs
+ 5. Write concise AGENTS.md
+
+ Keep it minimal. No fluff.
+ PROMPT
+
+ sub = Agent.new(agent.client, toolbox: agent.toolbox, terminal: agent.terminal, system_prompt: system_prompt)
+ sub.turn("Generate AGENTS.md for this project")
+ end
+
+ agent.commands.register("reload", description: "Reload plugins and source") do
+ lib_dir = File.expand_path("../..", __dir__)
+ original_verbose, $VERBOSE = $VERBOSE, nil
+ Dir["#{lib_dir}/**/*.rb"].sort.each { |f| load(f) }
+ $VERBOSE = original_verbose
+ agent.toolbox = Elelem::Toolbox.new
+ agent.commands = Elelem::Commands.new
+ Elelem::Plugins.reload!(agent)
+ end
+
+ agent.commands.register("help", description: "Show available commands") do
+ agent.terminal.say agent.commands.names.join(" ")
+ end
+end
lib/elelem/builtins/confirm.rb
@@ -1,12 +1,11 @@
# frozen_string_literal: true
-Elelem::Plugins.register(:confirm) do |toolbox|
- toolbox.before("execute") do |args|
+Elelem::Plugins.register(:confirm) do |agent|
+ agent.toolbox.before("execute") do |args|
next unless $stdin.tty?
cmd = args["command"]
- $stdout.print " Allow? [Y/n] > "
- answer = $stdin.gets&.strip&.downcase
+ answer = agent.terminal.ask(" Allow? [Y/n] > ")&.downcase
raise "User denied permission to execute: #{cmd}" if answer == "n"
end
end
lib/elelem/builtins/edit.rb
@@ -1,14 +1,14 @@
# frozen_string_literal: true
-Elelem::Plugins.register(:edit) do |toolbox|
- toolbox.add("edit",
+Elelem::Plugins.register(:edit) do |agent|
+ agent.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
- toolbox
+ agent.toolbox
.run("write", { "path" => a["path"], "content" => content.sub(a["old"], a["new"]) })
.merge(replaced: a["old"], with: a["new"])
end
lib/elelem/builtins/eval.rb
@@ -1,16 +1,16 @@
# frozen_string_literal: true
-Elelem::Plugins.register(:eval) do |toolbox|
+Elelem::Plugins.register(:eval) do |agent|
description = <<~'DESC'
Evaluate Ruby code. Available API:
name = "search"
- toolbox.add(name, description: "Search using rg", params: { query: { type: "string" } }, required: ["query"], aliases: []) do |args|
- toolbox.run("execute", { "command" => "rg --json -nI -F #{args["query"]}" })
+ agent.toolbox.add(name, description: "Search using rg", params: { query: { type: "string" } }, required: ["query"], aliases: []) do |args|
+ agent.toolbox.run("execute", { "command" => "rg --json -nI -F #{args["query"]}" })
end
DESC
- toolbox.add("eval",
+ agent.toolbox.add("eval",
description: description,
params: { ruby: { type: "string" } },
required: ["ruby"]
lib/elelem/builtins/execute.rb
@@ -1,18 +1,18 @@
# frozen_string_literal: true
-Elelem::Plugins.register(:execute) do |toolbox|
- toolbox.add("execute",
+Elelem::Plugins.register(:execute) do |agent|
+ agent.toolbox.add("execute",
description: "Run shell command (supports pipes and redirections)",
params: { command: { type: "string" } },
required: ["command"],
aliases: ["bash", "sh", "exec", "execute<|channel|>"]
) do |a|
- Elelem.sh("bash", args: ["-c", a["command"]]) { |x| $stdout.print(x) }
+ Elelem.sh("bash", args: ["-c", a["command"]]) { |x| agent.terminal.print(x) }
end
- toolbox.after("execute") do |args, result|
+ agent.toolbox.after("execute") do |args, result|
next if result[:exit_status] == 0
- $stdout.puts toolbox.header("execute", args, state: "x")
+ agent.terminal.say agent.toolbox.header("execute", args, state: "x")
end
end
lib/elelem/builtins/mcp.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-Elelem::Plugins.register(:mcp) do |toolbox|
+Elelem::Plugins.register(:mcp) do |agent|
mcp = Elelem::MCP.new
at_exit { mcp.close }
mcp.tools.each do |name, tool|
- toolbox.add(name,
+ agent.toolbox.add(name,
description: tool[:description],
params: tool[:params],
required: tool[:required],
lib/elelem/builtins/read.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-Elelem::Plugins.register(:read) do |toolbox|
- toolbox.add("read",
+Elelem::Plugins.register(:read) do |agent|
+ agent.toolbox.add("read",
description: "Read file",
params: { path: { type: "string" } },
required: ["path"],
@@ -11,11 +11,11 @@ Elelem::Plugins.register(:read) do |toolbox|
path.exist? ? { content: path.read, path: a["path"] } : { error: "not found" }
end
- toolbox.after("read") do |_, result|
+ agent.toolbox.after("read") do |_, result|
if result[:error]
- $stdout.puts " ! #{result[:error]}"
- elsif !system("bat", "--paging=never", result[:path])
- $stdout.puts result[:content]
+ agent.terminal.say " ! #{result[:error]}"
+ else
+ agent.terminal.display_file(result[:path], fallback: result[:content])
end
end
end
lib/elelem/builtins/task.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+Elelem::Plugins.register(:task) do |agent|
+ agent.toolbox.add("task",
+ description: "Delegate subtask to focused agent (complex searches, multi-file analysis)",
+ params: { prompt: { type: "string" } },
+ required: ["prompt"]
+ ) do |a|
+ sub = Elelem::Agent.new(agent.client, toolbox: agent.toolbox, terminal: agent.terminal,
+ system_prompt: "Research agent. Search, analyze, report. Be concise.")
+ sub.turn(a["prompt"])
+ { result: sub.conversation.last[:content] }
+ end
+end
lib/elelem/builtins/tools.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+Elelem::Plugins.register(:tools) do |agent|
+ agent.commands.register("tools", description: "List available tools") do
+ agent.toolbox.tools.each_value do |tool|
+ agent.terminal.say ""
+ agent.terminal.say " #{tool.name}"
+ agent.terminal.say " #{tool.description}"
+ tool.params.each { |k, v| agent.terminal.say " #{k}: #{v[:type] || v["type"]}" }
+ agent.terminal.say " aliases: #{tool.aliases.join(", ")}" if tool.aliases.any?
+ end
+ end
+end
lib/elelem/builtins/verify.rb
@@ -27,16 +27,16 @@ module Elelem
end
end
- Plugins.register(:verify) do |toolbox|
- toolbox.add("verify",
+ Plugins.register(:verify) do |agent|
+ agent.toolbox.add("verify",
description: "Verify file syntax and run tests",
params: { path: { type: "string" } },
required: ["path"]
) do |a|
path = a["path"]
Verifiers.for(path).inject({verified: []}) do |memo, cmd|
- $stdout.puts toolbox.header("execute", { "command" => cmd })
- v = toolbox.run("execute", { "command" => cmd })
+ agent.terminal.say agent.toolbox.header("execute", { "command" => cmd })
+ v = agent.toolbox.run("execute", { "command" => cmd })
break v.merge(path: path, command: cmd) if v[:exit_status] != 0
memo[:verified] << cmd
lib/elelem/builtins/write.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-Elelem::Plugins.register(:write) do |toolbox|
- toolbox.add("write",
+Elelem::Plugins.register(:write) do |agent|
+ agent.toolbox.add("write",
description: "Write file",
params: { path: { type: "string" }, content: { type: "string" } },
required: ["path", "content"],
@@ -12,12 +12,12 @@ Elelem::Plugins.register(:write) do |toolbox|
{ bytes: path.write(a["content"]), path: a["path"] }
end
- toolbox.after("write") do |_, result|
+ agent.toolbox.after("write") do |_, result|
if result[:error]
- $stdout.puts " ! #{result[:error]}"
+ agent.terminal.say " ! #{result[:error]}"
else
- system("bat", "--paging=never", result[:path]) || $stdout.puts(" -> #{result[:path]}")
- toolbox.run("verify", { "path" => result[:path] })
+ agent.terminal.display_file(result[:path], fallback: " -> #{result[:path]}")
+ agent.toolbox.run("verify", { "path" => result[:path] })
end
end
end