Commit 3c793a0

mo khan <mo@mokhan.ca>
2026-01-29 23:10:52
refactor: demote plugins to project local until they are mature enough to be promoted to the gem
1 parent 78c3505
lib/elelem/builtins/builtins.rb
@@ -8,88 +8,6 @@ Elelem::Plugins.register(:builtins) do |agent|
     agent.terminal.say "  → context cleared"
   end
 
-  agent.commands.register("context", description: "Show conversation context") do |args|
-    messages = agent.context
-
-    case args
-    when nil, ""
-      messages.each_with_index do |msg, i|
-        role = msg[:role]
-        preview = msg[:content].to_s.lines.first&.strip&.slice(0, 60) || ""
-        preview += "..." if msg[:content].to_s.length > 60
-        agent.terminal.say "  #{i + 1}. #{role}: #{preview}"
-      end
-    when "json"
-      agent.terminal.say JSON.pretty_generate(messages)
-    when /^\d+$/
-      index = args.to_i - 1
-      if index >= 0 && index < messages.length
-        content = messages[index][:content].to_s
-        agent.terminal.say(agent.terminal.markdown(content))
-      else
-        agent.terminal.say "  Invalid index: #{args}"
-      end
-    else
-      agent.terminal.say "  Usage: /context [json|<number>]"
-    end
-  end
-
-  strip_ansi = ->(text) do
-    text
-      .gsub(/^Script started.*?\n/, "")
-      .gsub(/\nScript done.*$/, "")
-      .gsub(/\e\].*?(?:\a|\e\\)/, "")
-      .gsub(/\e\[[0-9;?]*[A-Za-z]/, "")
-      .gsub(/\e[PX^_].*?\e\\/, "")
-      .gsub(/\e./, "")
-      .gsub(/[\b]/, "")
-      .gsub(/\r/, "")
-  end
-
-  agent.commands.register("shell", description: "Start interactive shell") do
-    transcript = Tempfile.create do |file|
-      system("script", "-q", file.path, chdir: Dir.pwd)
-      strip_ansi.call(File.read(file.path))
-    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
-
-    agent.fork(system_prompt: system_prompt).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
lib/elelem/builtins/context.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+Elelem::Plugins.register(:context) do |agent|
+  agent.commands.register("context", description: "Show conversation context") do |args|
+    messages = agent.context
+
+    case args
+    when nil, ""
+      messages.each_with_index do |msg, i|
+        role = msg[:role]
+        preview = msg[:content].to_s.lines.first&.strip&.slice(0, 60) || ""
+        preview += "..." if msg[:content].to_s.length > 60
+        agent.terminal.say "  #{i + 1}. #{role}: #{preview}"
+      end
+    when "json"
+      agent.terminal.say JSON.pretty_generate(messages)
+    when /^\d+$/
+      index = args.to_i - 1
+      if index >= 0 && index < messages.length
+        content = messages[index][:content].to_s
+        agent.terminal.say(agent.terminal.markdown(content))
+      else
+        agent.terminal.say "  Invalid index: #{args}"
+      end
+    else
+      agent.terminal.say "  Usage: /context [json|<number>]"
+    end
+  end
+end
lib/elelem/builtins/eval.rb
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-Elelem::Plugins.register(:eval) do |agent|
-  description = <<~'DESC'
-    Evaluate Ruby code. Available API:
-
-    name = "search"
-    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
-
-  agent.toolbox.add("eval",
-    description: description,
-    params: { ruby: { type: "string" } },
-    required: ["ruby"]
-  ) do |args|
-    { result: binding.eval(args["ruby"]) }
-  end
-end
lib/elelem/builtins/git.rb
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-Elelem::Plugins.register(:git) do |agent|
-  allowed = %w[status diff log show branch checkout add reset stash].freeze
-
-  agent.toolbox.add("git",
-    description: "Run git command",
-    params: { command: { type: "string" }, args: { type: "array" } },
-    required: ["command"]
-  ) do |a|
-    cmd = a["command"]
-    next { error: "not allowed: #{cmd}" } unless allowed.include?(cmd)
-
-    agent.toolbox.exec("git", cmd, *(a["args"] || []))
-  end
-
-  agent.toolbox.after("git") do |_, result|
-    agent.terminal.say "  ! #{result[:error]}" if result[:error]
-  end
-end
lib/elelem/builtins/glob.rb
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-Elelem::Plugins.register(:glob) do |agent|
-  agent.toolbox.add("glob",
-    description: "Find files matching pattern",
-    params: { pattern: { type: "string" }, path: { type: "string" } },
-    required: ["pattern"]
-  ) do |a|
-    path = a["path"] || "."
-    result = agent.toolbox.exec("fd", "--glob", a["pattern"], path)
-    result[:ok] ? result : agent.toolbox.exec("find", path, "-name", a["pattern"])
-  end
-end
lib/elelem/builtins/grep.rb
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-Elelem::Plugins.register(:grep) do |agent|
-  agent.toolbox.add("grep",
-    description: "Search file contents",
-    params: { pattern: { type: "string" }, path: { type: "string" }, glob: { type: "string" } },
-    required: ["pattern"]
-  ) do |a|
-    path = a["path"] || "."
-    glob = a["glob"]
-    rg_args = ["rg", "-n", a["pattern"], path]
-    rg_args += ["-g", glob] if glob
-    result = agent.toolbox.exec(*rg_args)
-    next result if result[:ok]
-
-    grep_args = ["grep", "-rn"]
-    grep_args += ["--include", glob] if glob
-    grep_args += [a["pattern"], path]
-    agent.toolbox.exec(*grep_args)
-  end
-end
lib/elelem/builtins/interview.rb
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-Elelem::Plugins.register(:interview) do |agent|
-  agent.toolbox.add("interview",
-    description: "Ask the user a question and wait for their response",
-    params: {
-      question: { type: "string", description: "The question to ask the user" },
-    },
-    required: ["question"]
-  ) do |args|
-    agent.terminal.say(agent.terminal.markdown(args["question"]))
-    { answer: agent.terminal.ask("> ") }
-  end
-end
lib/elelem/builtins/list.rb
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-Elelem::Plugins.register(:list) do |agent|
-  agent.toolbox.add("list",
-    description: "List directory contents",
-    params: { path: { type: "string" }, recursive: { type: "boolean" } },
-    required: [],
-    aliases: ["ls"]
-  ) do |a|
-    path = a["path"] || "."
-    flags = a["recursive"] ? "-laR" : "-la"
-    agent.toolbox.exec("ls", flags, path)
-  end
-end
lib/elelem/builtins/mode.rb
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-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
-
-  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
lib/elelem/builtins/task.rb
@@ -1,14 +0,0 @@
-# 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/verify.rb
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-module Elelem
-  module Verifiers
-    SYNTAX = {
-      ".rb" => "ruby -c %{path}",
-      ".erb" => "erb -x %{path} | ruby -c",
-      ".py" => "python -m py_compile %{path}",
-      ".go" => "go vet %{path}",
-      ".rs" => "cargo check --quiet",
-      ".ts" => "npx tsc --noEmit %{path}",
-      ".js" => "node --check %{path}",
-    }.freeze
-
-    def self.for(path)
-      return [] unless 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