main

Plugin Examples

Real plugins from the elelem codebase.

Tool with After Hook

The read plugin displays file contents after reading:

Elelem::Plugins.register(:read) do |agent|
  agent.toolbox.add("read",
    description: "Read file",
    params: { path: { type: "string" } },
    required: ["path"],
    aliases: ["open"]
  ) do |a|
    path = Pathname.new(a["path"]).expand_path
    path.exist? ? { content: path.read, path: a["path"] } : { error: "not found" }
  end

  agent.toolbox.after("read") do |_, result|
    if result[:error]
      agent.terminal.say "  ! #{result[:error]}"
    else
      agent.terminal.display_file(result[:path], fallback: result[:content])
    end
  end
end

Context Compaction Command

The compact command summarizes conversation history:

Elelem::Plugins.register(:compact) do |agent|
  agent.commands.register("compact", description: "Compress context") do
    response = agent.turn("Summarize: accomplishments, state, next steps. Brief.")
    agent.conversation.clear!
    agent.conversation.add(role: "user", content: "Context: #{response}")
    agent.terminal.say "  → compacted"
  end
end

Tool Chaining

The verify plugin runs syntax checks and tests by calling other tools:

module Elelem
  module Verifiers
    SYNTAX = {
      ".rb" => "ruby -c %{path}",
      ".py" => "python -m py_compile %{path}",
      ".go" => "go vet %{path}",
      ".ts" => "npx tsc --noEmit %{path}",
      ".js" => "node --check %{path}",
    }.freeze

    def self.for(path)
      cmds = []
      ext = File.extname(path)
      cmds << (SYNTAX[ext] % { path: path }) if SYNTAX[ext]
      cmds << test_runner
      cmds.compact
    end

    def self.test_runner
      %w[bin/test script/test].find { |s| File.executable?(s) }
    end
  end

  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|
        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
        memo
      end
    end
  end
end

Mode Switcher with Completion

Elelem::Plugins.register(:mode) do |agent|
  agent.commands.register("mode",
    description: "Switch system prompt mode",
    completions: -> { Elelem::SystemPrompt.available_modes }
  ) do |args|
    name = args&.strip
    if name.nil? || name.empty?
      current = agent.system_prompt.mode
      modes = Elelem::SystemPrompt.available_modes.map { |m| m == current ? "*#{m}" : m }
      agent.terminal.say modes.join(" ")
    else
      agent.system_prompt.switch(name)
      agent.terminal.say "mode: #{name}"
    end
  end
end

Provider Plugin

The Ollama provider:

Elelem::Providers.register(:ollama) do
  Elelem::Net::Ollama.new(
    model: ENV.fetch("OLLAMA_MODEL", "gpt-oss:latest"),
    host: ENV.fetch("OLLAMA_HOST", "localhost:11434")
  )
end

MCP Tool Output Formatting

Pretty-print JSON from MCP tools:

Elelem::Plugins.register(:gitlab) do |agent|
  agent.toolbox.after("gitlab_search") do |_args, result|
    IO.popen(["jq", "-C", "."], "r+") do |io|
      io.write(result.to_json)
      io.close_write
      agent.terminal.say(io.read)
    end
  end
end

Builtin Commands

Simple commands for common operations:

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("help", description: "Show available commands") do
    agent.terminal.say agent.commands.map { |name, desc| "#{name.ljust(12)} #{desc}" }.join("\n")
  end
end