Commit cfb2b7b

mo khan <mo@mokhan.ca>
2025-11-05 19:43:22
refactor: remove old tools
1 parent fc01784
exe/llm-ollama
@@ -1,359 +0,0 @@
-#!/usr/bin/env ruby
-
-=begin
-Fast, correct, autonomous - Pick two
-
-PURPOSE:
-
-This script is a minimal coding agent written in Ruby. It is intended to
-assist me (a software engineer and computer science student) with writing,
-editing, and managing code and text files from the command line. It acts
-as a direct interface to an LLM, providing it with a simple text-based
-UI and access to the local filesystem.
-
-DESIGN PRINCIPLES:
-
-- Follows the Unix philosophy: simple, composable, minimal.
-- Convention over configuration.
-- Avoids unnecessary defensive checks, or complexity.
-- Assumes a mature and responsible LLM that behaves like a capable engineer.
-- Designed for my workflow and preferences.
-- Efficient and minimal like aider - https://aider.chat/
-- UX like Claude Code - https://docs.claude.com/en/docs/claude-code/overview
-
-SYSTEM ASSUMPTIONS:
-
-- This script is used on a Linux system with the following tools: Alacritty, tmux, Bash, and Vim.
-- It is always run inside a Git repository.
-- All project work is assumed to be version-controlled with Git.
-- Git is expected to be available and working; no checks are necessary.
-
-SCOPE:
-
-- This program operates only on code and plain-text files.
-- It does not need to support binary files.
-- The LLM has full access to execute system commands.
-- There are no sandboxing, permission, or validation layers.
-- Execution is not restricted or monitored — responsibility is delegated to the LLM.
-
-CONFIGURATION:
-
-- Avoid adding configuration options unless absolutely necessary.
-- Prefer hard-coded values that can be changed later if needed.
-- Only introduce environment variables after repeated usage proves them worthwhile.
-
-UI EXPECTATIONS:
-
-- The TUI must remain simple, fast, and predictable.
-- No mouse support or complex UI components are required.
-- Interaction is strictly keyboard-driven.
-
-CODING STANDARDS FOR LLM:
-
-- Do not add error handling or logging unless it is essential for functionality.
-- Keep methods short and single-purpose.
-- Use descriptive, conventional names.
-- Stick to Ruby's standard library whenever possible.
-
-HELPFUL LINKS:
-
-- https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents
-- https://www.anthropic.com/engineering/writing-tools-for-agents
-- https://simonwillison.net/2025/Sep/30/designing-agentic-loops/
-
-=end
-
-require "bundler/inline"
-
-gemfile do
-  source "https://rubygems.org"
-
-  gem "fileutils", "~> 1.0"
-  gem "json", "~> 2.0"
-  gem "net-llm", "~> 0.4"
-  gem "open3", "~> 0.1"
-  gem "ostruct", "~> 0.1"
-  gem "reline", "~> 0.1"
-  gem "set", "~> 1.0"
-  gem "uri", "~> 1.0"
-end
-
-STDOUT.set_encoding(Encoding::UTF_8)
-STDERR.set_encoding(Encoding::UTF_8)
-
-OLLAMA_HOST = ENV["OLLAMA_HOST"] || "localhost:11434"
-OLLAMA_MODEL = ENV["OLLAMA_MODEL"] || "gpt-oss:latest"
-SYSTEM_PROMPT="You are a reasoning coding and system agent."
-
-def build_tool(name, description, properties, required = [])
-  {
-    type: "function",
-    function: {
-      name: name,
-      description: description,
-      parameters: {
-        type: "object",
-        properties: properties,
-        required: required
-      }
-    }
-  }
-end
-
-EXEC_TOOL = build_tool("execute", "Execute shell commands. Returns stdout, stderr, and exit code. Use for: checking system state, running tests, managing services. Common Unix tools available: git, bash, grep, etc. Tip: Check exit_status in response to determine success.", { cmd: { type: "string" }, args: { type: "array", items: { type: "string" } }, env: { type: "object", additionalProperties: { type: "string" } }, cwd: { type: "string" }, stdin: { type: "string" } }, ["cmd"])
-GREP_TOOL = build_tool("grep", "Search all git-tracked files using git grep. Returns file paths with matching line numbers. Use this to discover where code/configuration exists before reading files. Examples: search 'def method_name' to find method definitions. Much faster than reading multiple files.", { query: { type: "string" } }, ["query"])
-LS_TOOL = build_tool("list", "List all git-tracked files in the repository, optionally filtered by path. Use this to explore project structure or find files in a directory. Returns relative paths from repo root. Tip: Use this before reading if you need to discover what files exist.", { path: { type: "string" } })
-PATCH_TOOL = build_tool("patch", "Apply a unified diff patch via 'git apply'. Use this for surgical edits to existing files rather than rewriting entire files. Generates proper git diffs. Format: standard unified diff with --- and +++ headers. Tip: More efficient than write for small changes to large files.", { diff: { type: "string" } }, ["diff"])
-READ_TOOL = build_tool("read", "Read complete contents of a file. Requires exact file path. Use grep or list first if you don't know the path. Best for: understanding existing code, reading config files, reviewing implementation details. Tip: For large files, grep first to confirm relevance.", { path: { type: "string" } }, ["path"])
-WRITE_TOOL = build_tool("write", "Write complete file contents (overwrites existing files). Creates parent directories automatically. Best for: creating new files, replacing entire file contents. For small edits to existing files, consider using patch instead.", { path: { type: "string" }, content: { type: "string" } }, ["path", "content"])
-
-TOOLS = {
-  read: [GREP_TOOL, LS_TOOL, READ_TOOL],
-  write: [PATCH_TOOL, WRITE_TOOL],
-  execute: [EXEC_TOOL]
-}
-
-trap("INT") do
-  puts "\nExiting."
-  exit
-end
-
-def run_exec(command, args: [], env: {}, cwd: Dir.pwd, stdin: nil)
-  stdout, stderr, status = Open3.capture3(env, command, *args, chdir: cwd, stdin_data: stdin)
-  {
-    "exit_status" => status.exitstatus,
-    "stdout" => stdout.to_s,
-    "stderr" => stderr.to_s
-  }
-end
-
-def expand_path(path)
-  Pathname.new(path).expand_path
-end
-
-def read_file(path)
-  full_path = expand_path(path)
-  full_path.exist? ? { content: full_path.read } : { error: "File not found: #{path}" }
-end
-
-def write_file(path, content)
-  full_path = expand_path(path)
-  FileUtils.mkdir_p(full_path.dirname)
-  { bytes_written: full_path.write(content) }
-end
-
-def run_tool(name, args)
-  case name
-  when "execute" then run_exec(args["cmd"], args: args["args"] || [], env: args["env"] || {}, cwd: args["cwd"] || Dir.pwd, stdin: args["stdin"])
-  when "grep" then run_exec("git", args: ["grep", "-nI", args["query"]])
-  when "list" then run_exec("git", args: args["path"] ? ["ls-files", "--", args["path"]] : ["ls-files"])
-  when "patch" then run_exec("git", args: ["apply", "--index", "--whitespace=nowarn", "-p1"], stdin: args["diff"])
-  when "read" then read_file(args["path"])
-  when "write" then write_file(args["path"], args["content"])
-  else
-    { error: "Unknown tool", name: name, args: args }
-  end
-end
-
-def format_tool_call(name, args)
-  case name
-  when "execute" then "execute(#{args["cmd"]})"
-  when "grep" then "grep(#{args["query"]})"
-  when "list" then "list(#{args["path"] || '.'})"
-  when "patch" then "patch(#{args["diff"].lines.count} lines)"
-  when "read" then "read(#{args["path"]})"
-  when "write" then "write(#{args["path"]})"
-  else
-    "▶ #{name}(#{args.to_s[0...70]})"
-  end
-end
-
-def system_prompt_for(mode)
-  base = "You are a reasoning coding and system agent."
-
-  case mode.sort
-  when [:read]
-    "#{base}\n\nRead and analyze. Understand before suggesting action."
-  when [:write]
-    "#{base}\n\nWrite clean, thoughtful code."
-  when [:execute]
-    "#{base}\n\nUse shell commands creatively to understand and manipulate the system."
-  when [:read, :write]
-    "#{base}\n\nFirst understand, then build solutions that integrate well."
-  when [:read, :execute]
-    "#{base}\n\nUse commands to deeply understand the system."
-  when [:write, :execute]
-    "#{base}\n\nCreate and execute freely. Have fun. Be kind."
-  when [:read, :write, :execute]
-    "#{base}\n\nYou have all tools. Use them wisely."
-  else
-    base
-  end
-end
-
-def tools_for(modes)
-  modes.map { |mode| TOOLS[mode] }.flatten
-end
-
-def prune_context(messages, keep_recent: 5)
-  return messages if messages.length <= keep_recent + 1
-
-  default_context + messages.last(keep_recent)
-end
-
-def execute_turn(client, messages, tools:)
-  turn_context = []
-
-  loop do
-    content = ""
-    tool_calls = nil
-    role = "assistant"
-    first_content = true
-
-    print "Thinking..."
-    client.chat(messages + turn_context, tools) do |chunk|
-      if chunk["message"]
-        msg = chunk["message"]
-        role = msg["role"] if msg["role"]
-
-        if msg["thinking"] && !msg["thinking"].empty?
-          print "."
-        end
-
-        if msg["content"] && !msg["content"].empty?
-          if first_content
-            print "\r\e[KAssistant> "
-            first_content = false
-          end
-          print msg["content"]
-          $stdout.flush
-          content += msg["content"]
-        end
-
-        tool_calls = msg["tool_calls"] if msg["tool_calls"]
-      end
-    end
-    puts
-
-    turn_context << { role: role, content: content, tool_calls: tool_calls }.compact
-
-    if tool_calls
-      tool_calls.each do |call|
-        name = call.dig("function", "name")
-        args_raw = call.dig("function", "arguments")
-
-        begin
-          args = args_raw.is_a?(String) ? JSON.parse(args_raw) : args_raw
-        rescue JSON::ParserError => e
-          turn_context << {
-            role: "tool",
-            content: JSON.dump({
-              error: "Invalid JSON in arguments: #{e.message}",
-              received: args_raw
-            })
-          }
-          next
-        end
-
-        puts "Tool> #{format_tool_call(name, args)}"
-        result = run_tool(name, args)
-        turn_context << { role: "tool", content: JSON.dump(result) }
-      end
-      next
-    end
-
-    return { role: "assistant", content: content } unless content.strip.empty?
-  end
-end
-
-def dump_context(messages)
-  puts JSON.pretty_generate(messages)
-end
-
-def print_status(mode, messages)
-  puts "Mode: #{mode.inspect}"
-  puts "Tools: #{tools_for(mode).map { |x| x.dig(:function, :name) }}"
-end
-
-def strip_ansi(text)
-  text.gsub(/^Script started.*?\n/, '')
-      .gsub(/\nScript done.*$/, '')
-      .gsub(/\e\[[0-9;]*[a-zA-Z]/, '')        # Standard ANSI codes
-      .gsub(/\e\[\?[0-9]+[hl]/, '')           # Bracketed paste mode
-      .gsub(/[\b]/, '')                       # Backspace chars
-      .gsub(/\r/, '')                         # Carriage returns
-end
-
-def start_shell
-  Tempfile.create do |file|
-    system("script -q #{file.path}")
-    { role: "user", content: strip_ansi(File.read(file.path)) }
-  end
-end
-
-def ask?(text)
-  input = Reline.readline(text, true)&.strip
-  exit if input.nil? || input.downcase == "exit"
-
-  input
-end
-
-def print_help
-  puts <<~HELP
-  /chmod - (+|-)rwx
-  /mode auto build plan verify
-  /clear
-  /context
-  /exit
-  /help
-  /shell
-  /status
-  HELP
-end
-
-def default_context
-  [{ role: "system", content: SYSTEM_PROMPT }]
-end
-
-def main
-  client = Net::Llm::Ollama.new(
-    host: OLLAMA_HOST,
-    model: OLLAMA_MODEL
-  )
-
-  messages = default_context
-  mode = Set.new([:read])
-
-  loop do
-    input = ask?("User> ")
-    if input.start_with?("/")
-      case input
-      when "/chmod +r" then mode.add(:read)
-      when "/chmod +w" then mode.add(:write)
-      when "/chmod +x" then mode.add(:execute)
-      when "/chmod -r" then mode.add(:read)
-      when "/chmod -w" then mode.add(:write)
-      when "/chmod -x" then mode.add(:execute)
-      when "/clear" then messages = default_context
-      when "/compact" then messages = prune_context(messages, keep_recent: 10)
-      when "/context" then dump_context(messages)
-      when "/exit" then exit
-      when "/help" then print_help
-      when "/mode auto" then mode = Set[:read, :write, :execute]
-      when "/mode build" then mode = Set[:read, :write]
-      when "/mode plan" then mode = Set[:read]
-      when "/mode verify" then mode = Set[:read, :execute]
-      when "/mode" then print_status(mode, messages)
-      when "/shell" then messages << start_shell
-      else
-        print_help
-      end
-    else
-      messages[0] = { role: "system", content: system_prompt_for(mode) }
-      messages << { role: "user", content: input }
-      messages << execute_turn(client, messages, tools: tools_for(mode))
-    end
-  end
-end
-
-main
exe/llm-openai
@@ -1,339 +0,0 @@
-#!/usr/bin/env ruby
-
-=begin
-Fast, correct, autonomous - Pick two
-
-PURPOSE:
-
-This script is a minimal coding agent written in Ruby. It is intended to
-assist me (a software engineer and computer science student) with writing,
-editing, and managing code and text files from the command line. It acts
-as a direct interface to an LLM, providing it with a simple text-based
-UI and access to the local filesystem.
-
-DESIGN PRINCIPLES:
-
-- Follows the Unix philosophy: simple, composable, minimal.
-- Convention over configuration.
-- Avoids unnecessary defensive checks, or complexity.
-- Assumes a mature and responsible LLM that behaves like a capable engineer.
-- Designed for my workflow and preferences.
-- Efficient and minimal like aider - https://aider.chat/
-- UX like Claude Code - https://docs.claude.com/en/docs/claude-code/overview
-
-SYSTEM ASSUMPTIONS:
-
-- This script is used on a Linux system with the following tools: Alacritty, tmux, Bash, and Vim.
-- It is always run inside a Git repository.
-- All project work is assumed to be version-controlled with Git.
-- Git is expected to be available and working; no checks are necessary.
-
-SCOPE:
-
-- This program operates only on code and plain-text files.
-- It does not need to support binary files.
-- The LLM has full access to execute system commands.
-- There are no sandboxing, permission, or validation layers.
-- Execution is not restricted or monitored — responsibility is delegated to the LLM.
-
-CONFIGURATION:
-
-- Avoid adding configuration options unless absolutely necessary.
-- Prefer hard-coded values that can be changed later if needed.
-- Only introduce environment variables after repeated usage proves them worthwhile.
-
-UI EXPECTATIONS:
-
-- The TUI must remain simple, fast, and predictable.
-- No mouse support or complex UI components are required.
-- Interaction is strictly keyboard-driven.
-
-CODING STANDARDS FOR LLM:
-
-- Do not add error handling or logging unless it is essential for functionality.
-- Keep methods short and single-purpose.
-- Use descriptive, conventional names.
-- Stick to Ruby's standard library whenever possible.
-
-HELPFUL LINKS:
-
-- https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents
-- https://www.anthropic.com/engineering/writing-tools-for-agents
-- https://simonwillison.net/2025/Sep/30/designing-agentic-loops/
-
-=end
-
-require "bundler/inline"
-
-gemfile do
-  source "https://rubygems.org"
-
-  gem "fileutils", "~> 1.0"
-  gem "json", "~> 2.0"
-  gem "net-llm", "0.3.1"
-  gem "open3", "~> 0.1"
-  gem "ostruct", "~> 0.1"
-  gem "reline", "~> 0.1"
-  gem "set", "~> 1.0"
-  gem "uri", "~> 1.0"
-end
-
-STDOUT.set_encoding(Encoding::UTF_8)
-STDERR.set_encoding(Encoding::UTF_8)
-
-API_KEY = ENV["OPENAI_API_KEY"] or abort("Set OPENAI_API_KEY")
-SYSTEM_PROMPT="You are a reasoning coding and system agent."
-
-def build_tool(name, description, properties, required = [])
-  {
-    type: "function",
-    function: {
-      name: name,
-      description: description,
-      parameters: {
-        type: "object",
-        properties: properties,
-        required: required
-      }
-    }
-  }
-end
-
-EXEC_TOOL = build_tool("execute", "Execute shell commands. Returns stdout, stderr, and exit code. Use for: checking system state, running tests, managing services. Common Unix tools available: git, bash, grep, etc. Tip: Check exit_status in response to determine success.", { cmd: { type: "string" }, args: { type: "array", items: { type: "string" } }, env: { type: "object", additionalProperties: { type: "string" } }, cwd: { type: "string" }, stdin: { type: "string" } }, ["cmd"])
-GREP_TOOL = build_tool("grep", "Search all git-tracked files using git grep. Returns file paths with matching line numbers. Use this to discover where code/configuration exists before reading files. Examples: search 'def method_name' to find method definitions. Much faster than reading multiple files.", { query: { type: "string" } }, ["query"])
-LS_TOOL = build_tool("list", "List all git-tracked files in the repository, optionally filtered by path. Use this to explore project structure or find files in a directory. Returns relative paths from repo root. Tip: Use this before reading if you need to discover what files exist.", { path: { type: "string" } })
-PATCH_TOOL = build_tool("patch", "Apply a unified diff patch via 'git apply'. Use this for surgical edits to existing files rather than rewriting entire files. Generates proper git diffs. Format: standard unified diff with --- and +++ headers. Tip: More efficient than write for small changes to large files.", { diff: { type: "string" } }, ["diff"])
-READ_TOOL = build_tool("read", "Read complete contents of a file. Requires exact file path. Use grep or list first if you don't know the path. Best for: understanding existing code, reading config files, reviewing implementation details. Tip: For large files, grep first to confirm relevance.", { path: { type: "string" } }, ["path"])
-WRITE_TOOL = build_tool("write", "Write complete file contents (overwrites existing files). Creates parent directories automatically. Best for: creating new files, replacing entire file contents. For small edits to existing files, consider using patch instead.", { path: { type: "string" }, content: { type: "string" } }, ["path", "content"])
-
-TOOLS = {
-  read: [GREP_TOOL, LS_TOOL, READ_TOOL],
-  write: [PATCH_TOOL, WRITE_TOOL],
-  execute: [EXEC_TOOL]
-}
-
-trap("INT") do
-  puts "\nExiting."
-  exit
-end
-
-def run_exec(command, args: [], env: {}, cwd: Dir.pwd, stdin: nil)
-  stdout, stderr, status = Open3.capture3(env, command, *args, chdir: cwd, stdin_data: stdin)
-  {
-    "exit_status" => status.exitstatus,
-    "stdout" => stdout.to_s,
-    "stderr" => stderr.to_s
-  }
-end
-
-def expand_path(path)
-  Pathname.new(path).expand_path
-end
-
-def read_file(path)
-  full_path = expand_path(path)
-  full_path.exist? ? { content: full_path.read } : { error: "File not found: #{path}" }
-end
-
-def write_file(path, content)
-  full_path = expand_path(path)
-  FileUtils.mkdir_p(full_path.dirname)
-  { bytes_written: full_path.write(content) }
-end
-
-def run_tool(name, args)
-  case name
-  when "execute" then run_exec(args["cmd"], args: args["args"] || [], env: args["env"] || {}, cwd: args["cwd"] || Dir.pwd, stdin: args["stdin"])
-  when "grep" then run_exec("git", args: ["grep", "-nI", args["query"]])
-  when "list" then run_exec("git", args: args["path"] ? ["ls-files", "--", args["path"]] : ["ls-files"])
-  when "patch" then run_exec("git", args: ["apply", "--index", "--whitespace=nowarn", "-p1"], stdin: args["diff"])
-  when "read" then read_file(args["path"])
-  when "write" then write_file(args["path"], args["content"])
-  else
-    { error: "Unknown tool", name: name, args: args }
-  end
-end
-
-def format_tool_call(name, args)
-  case name
-  when "execute" then "execute(#{args["cmd"]})"
-  when "grep" then "grep(#{args["query"]})"
-  when "list" then "list(#{args["path"] || '.'})"
-  when "patch" then "patch(#{args["diff"].lines.count} lines)"
-  when "read" then "read(#{args["path"]})"
-  when "write" then "write(#{args["path"]})"
-  else
-    "▶ #{name}(#{args.to_s[0...70]})"
-  end
-end
-
-def system_prompt_for(mode)
-  base = "You are a reasoning coding and system agent."
-
-  case mode.sort
-  when [:read]
-    "#{base}\n\nRead and analyze. Understand before suggesting action."
-  when [:write]
-    "#{base}\n\nWrite clean, thoughtful code."
-  when [:execute]
-    "#{base}\n\nUse shell commands creatively to understand and manipulate the system."
-  when [:read, :write]
-    "#{base}\n\nFirst understand, then build solutions that integrate well."
-  when [:read, :execute]
-    "#{base}\n\nUse commands to deeply understand the system."
-  when [:write, :execute]
-    "#{base}\n\nCreate and execute freely. Have fun. Be kind."
-  when [:read, :write, :execute]
-    "#{base}\n\nYou have all tools. Use them wisely."
-  else
-    base
-  end
-end
-
-def tools_for(modes)
-  modes.map { |mode| TOOLS[mode] }.flatten
-end
-
-def prune_context(messages, keep_recent: 5)
-  return messages if messages.length <= keep_recent + 1
-
-  default_context + messages.last(keep_recent)
-end
-
-def execute_turn(client, messages, tools:)
-  turn_context = []
-
-  loop do
-    puts "Thinking..."
-    response = client.chat(messages + turn_context, tools)
-    abort "API Error #{response['code']}: #{response['body']}" if response["code"]
-    message = response.dig("choices", 0, "message")
-    turn_context << message
-
-    if message["tool_calls"]
-      message["tool_calls"].each do |call|
-        name = call.dig("function", "name")
-        # args = JSON.parse(call.dig("function", "arguments"))
-        begin
-          args = JSON.parse(call.dig("function", "arguments"))
-        rescue JSON::ParserError => e
-          # Feed the error back to the LLM as a tool result
-          turn_context << {
-            role: "tool",
-            tool_call_id: call["id"],
-            content: JSON.dump({
-              error: "Invalid JSON in arguments: #{e.message}",
-              received: call.dig("function", "arguments")
-            })
-          }
-          next  # Continue the loop, giving the LLM a chance to correct itself
-        end
-
-        puts "Tool> #{format_tool_call(name, args)}"
-        result = run_tool(name, args)
-        turn_context << { role: "tool", tool_call_id: call["id"], content: JSON.dump(result) }
-      end
-      next
-    end
-
-    if message["content"] && !message["content"].strip.empty?
-      puts "\nAssistant>\n#{message['content']}"
-
-      unless message["tool_calls"]
-        return { role: "assistant", content: message["content"] }
-      end
-    end
-  end
-end
-
-def dump_context(messages)
-  puts JSON.pretty_generate(messages)
-end
-
-def print_status(mode, messages)
-  puts "Mode: #{mode.inspect}"
-  puts "Tools: #{tools_for(mode).map { |x| x.dig(:function, :name) }}"
-end
-
-def strip_ansi(text)
-  text.gsub(/^Script started.*?\n/, '')
-      .gsub(/\nScript done.*$/, '')
-      .gsub(/\e\[[0-9;]*[a-zA-Z]/, '')        # Standard ANSI codes
-      .gsub(/\e\[\?[0-9]+[hl]/, '')           # Bracketed paste mode
-      .gsub(/[\b]/, '')                       # Backspace chars
-      .gsub(/\r/, '')                         # Carriage returns
-end
-
-def start_shell
-  Tempfile.create do |file|
-    system("script -q #{file.path}")
-    { role: "user", content: strip_ansi(File.read(file.path)) }
-  end
-end
-
-def ask?(text)
-  input = Reline.readline(text, true)&.strip
-  exit if input.nil? || input.downcase == "exit"
-
-  input
-end
-
-def print_help
-  puts <<~HELP
-  /chmod - (+|-)rwx auto build plan
-  /clear
-  /context
-  /exit
-  /help
-  /shell
-  /status
-  HELP
-end
-
-def default_context
-  [{ role: "system", content: SYSTEM_PROMPT }]
-end
-
-def main
-  client = Net::Llm::OpenAI.new(
-    api_key: API_KEY,
-    base_url: ENV["BASE_URL"] || "https://api.openai.com/v1",
-    model: ENV["MODEL"] || "gpt-4o-mini"
-  )
-
-  messages = default_context
-  mode = Set.new([:read])
-
-  loop do
-    input = ask?("User> ")
-    if input.start_with?("/")
-      case input
-      when "/chmod +r" then mode.add(:read)
-      when "/chmod +w" then mode.add(:write)
-      when "/chmod +x" then mode.add(:execute)
-      when "/chmod -r" then mode.add(:read)
-      when "/chmod -w" then mode.add(:write)
-      when "/chmod -x" then mode.add(:execute)
-      when "/clear" then messages = default_context
-      when "/compact" then messages = prune_context(messages, keep_recent: 10)
-      when "/context" then dump_context(messages)
-      when "/exit" then exit
-      when "/help" then print_help
-      when "/mode auto" then mode = Set[:read, :write, :execute]
-      when "/mode build" then mode = Set[:read, :write]
-      when "/mode plan" then mode = Set[:read]
-      when "/mode verify" then mode = Set[:read, :execute]
-      when "/mode" then print_status(mode, messages)
-      when "/shell" then messages << start_shell
-      else
-        print_help
-      end
-    else
-      messages[0] = { role: "system", content: system_prompt_for(mode) }
-      messages << { role: "user", content: input }
-      messages << execute_turn(client, messages, tools: tools_for(mode))
-    end
-  end
-end
-
-main
lib/elelem/toolbox/exec.rb
@@ -10,9 +10,9 @@ module Elelem
         super("exec", "Execute shell commands with pipe support", {
           type: "object",
           properties: {
-            command: { 
-              type: "string", 
-              description: "Shell command to execute (supports pipes, redirects, etc.)" 
+            command: {
+              type: "string",
+              description: "Shell command to execute (supports pipes, redirects, etc.)"
             }
           },
           required: ["command"]
lib/elelem/toolbox/memory.rb
@@ -1,164 +0,0 @@
-# frozen_string_literal: true
-
-module Elelem
-  module Toolbox
-    class Memory < Tool
-      MEMORY_DIR = ".elelem_memory"
-      MAX_MEMORY_SIZE = 1_000_000
-      
-      def initialize(configuration)
-        @configuration = configuration
-        @tui = configuration.tui
-        
-        super("memory", "Persistent memory for learning and context retention", {
-          type: :object,
-          properties: {
-            action: {
-              type: :string,
-              enum: %w[store retrieve list search forget],
-              description: "Memory action: store, retrieve, list, search, forget"
-            },
-            key: {
-              type: :string,
-              description: "Unique key for storing/retrieving memory"
-            },
-            content: {
-              type: :string,
-              description: "Content to store (required for store action)"
-            },
-            query: {
-              type: :string,
-              description: "Search query for finding memories"
-            }
-          },
-          required: %w[action]
-        })
-        ensure_memory_dir
-      end
-
-      def call(args)
-        action = args["action"]
-        
-        case action
-        when "store"
-          store_memory(args["key"], args["content"])
-        when "retrieve"
-          retrieve_memory(args["key"])
-        when "list"
-          list_memories
-        when "search"
-          search_memories(args["query"])
-        when "forget"
-          forget_memory(args["key"])
-        else
-          "Invalid memory action: #{action}"
-        end
-      rescue StandardError => e
-        "Memory error: #{e.message}"
-      end
-
-      private
-
-      attr_reader :configuration, :tui
-
-      def ensure_memory_dir
-        Dir.mkdir(MEMORY_DIR) unless Dir.exist?(MEMORY_DIR)
-      end
-
-      def memory_path(key)
-        ::File.join(MEMORY_DIR, "#{sanitize_key(key)}.json")
-      end
-
-      def sanitize_key(key)
-        key.to_s.gsub(/[^a-zA-Z0-9_-]/, "_").slice(0, 100)
-      end
-
-      def store_memory(key, content)
-        return "Key and content required for storing" unless key && content
-        
-        total_size = Dir.glob("#{MEMORY_DIR}/*.json").sum { |f| ::File.size(f) }
-        return "Memory capacity exceeded" if total_size > MAX_MEMORY_SIZE
-
-        memory = {
-          key: key,
-          content: content,
-          timestamp: Time.now.iso8601,
-          access_count: 0
-        }
-
-        ::File.write(memory_path(key), JSON.pretty_generate(memory))
-        "Memory stored: #{key}"
-      end
-
-      def retrieve_memory(key)
-        return "Key required for retrieval" unless key
-        
-        path = memory_path(key)
-        return "Memory not found: #{key}" unless ::File.exist?(path)
-
-        memory = JSON.parse(::File.read(path))
-        memory["access_count"] += 1
-        memory["last_accessed"] = Time.now.iso8601
-        
-        ::File.write(path, JSON.pretty_generate(memory))
-        memory["content"]
-      end
-
-      def list_memories
-        memories = Dir.glob("#{MEMORY_DIR}/*.json").map do |file|
-          memory = JSON.parse(::File.read(file))
-          {
-            key: memory["key"],
-            timestamp: memory["timestamp"],
-            size: memory["content"].length,
-            access_count: memory["access_count"] || 0
-          }
-        end
-        
-        memories.sort_by { |m| m[:timestamp] }.reverse
-        JSON.pretty_generate(memories)
-      end
-
-      def search_memories(query)
-        return "Query required for search" unless query
-        
-        matches = Dir.glob("#{MEMORY_DIR}/*.json").filter_map do |file|
-          memory = JSON.parse(::File.read(file))
-          if memory["content"].downcase.include?(query.downcase) ||
-             memory["key"].downcase.include?(query.downcase)
-            {
-              key: memory["key"],
-              snippet: memory["content"][0, 200] + "...",
-              relevance: calculate_relevance(memory, query)
-            }
-          end
-        end
-        
-        matches.sort_by { |m| -m[:relevance] }
-        JSON.pretty_generate(matches)
-      end
-
-      def forget_memory(key)
-        return "Key required for forgetting" unless key
-        
-        path = memory_path(key)
-        return "Memory not found: #{key}" unless ::File.exist?(path)
-
-        ::File.delete(path)
-        "Memory forgotten: #{key}"
-      end
-
-      def calculate_relevance(memory, query)
-        content = memory["content"].downcase
-        key = memory["key"].downcase
-        query = query.downcase
-        
-        score = 0
-        score += 3 if key.include?(query)
-        score += content.scan(query).length
-        score += (memory["access_count"] || 0) * 0.1
-        score
-      end
-    end
-  end
-end
\ No newline at end of file
lib/elelem/toolbox/prompt.rb
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-module Elelem
-  module Toolbox
-    class Prompt < Tool
-      def initialize(configuration)
-        @configuration = configuration
-        super("prompt", "Ask the user a question and get their response.", {
-          type: :object,
-          properties: {
-            question: {
-              type: :string,
-              description: "The question to ask the user."
-            }
-          },
-          required: [:question]
-        })
-      end
-
-      def call(args)
-        @configuration.tui.prompt(args["question"])
-      end
-    end
-  end
-end
lib/elelem/toolbox/web.rb
@@ -1,126 +0,0 @@
-# frozen_string_literal: true
-
-module Elelem
-  module Toolbox
-    class Web < Tool
-      def initialize(configuration)
-        super("web", "Fetch web content and search the internet", {
-          type: :object,
-          properties: {
-            action: {
-              type: :string,
-              enum: ["fetch", "search"],
-              description: "Action to perform: fetch URL or search"
-            },
-            url: {
-              type: :string,
-              description: "URL to fetch (for fetch action)"
-            },
-            query: {
-              type: :string,
-              description: "Search query (for search action)"
-            }
-          },
-          required: [:action]
-        })
-      end
-
-      def call(args)
-        action = args["action"]
-        case action
-        when "fetch"
-          fetch_url(args["url"])
-        when "search"
-          search_web(args["query"])
-        else
-          "Invalid action: #{action}"
-        end
-      end
-
-      private
-
-      def fetch_url(url)
-        return "URL required for fetch action" unless url
-
-        uri = URI(url)
-        http = Net::HTTP.new(uri.host, uri.port)
-        http.use_ssl = uri.scheme == "https"
-        http.read_timeout = 10
-        http.open_timeout = 5
-
-        request = Net::HTTP::Get.new(uri)
-        request["User-Agent"] = "Elelem Agent/1.0"
-
-        response = http.request(request)
-
-        if response.is_a?(Net::HTTPSuccess)
-          content_type = response["content-type"] || ""
-          if content_type.include?("text/html")
-            extract_text_from_html(response.body)
-          else
-            response.body
-          end
-        else
-          "HTTP Error: #{response.code} #{response.message}"
-        end
-      end
-
-      def search_web(query)
-        return "Query required for search action" unless query
-
-        # Use DuckDuckGo instant answers API
-        search_url = "https://api.duckduckgo.com/?q=#{URI.encode_www_form_component(query)}&format=json&no_html=1"
-
-        result = fetch_url(search_url)
-        if result.start_with?("Error") || result.start_with?("HTTP Error")
-          result
-        else
-          format_search_results(JSON.parse(result), query)
-        end
-      end
-
-      def extract_text_from_html(html)
-        # Simple HTML tag stripping
-        text = html.gsub(/<script[^>]*>.*?<\/script>/im, "")
-                  .gsub(/<style[^>]*>.*?<\/style>/im, "")
-                  .gsub(/<[^>]*>/, " ")
-                  .gsub(/\s+/, " ")
-                  .strip
-
-        # Limit content length
-        text.length > 5000 ? text[0...5000] + "..." : text
-      end
-
-      def format_search_results(data, query)
-        results = []
-
-        # Instant answer
-        if data["Answer"] && !data["Answer"].empty?
-          results << "Answer: #{data["Answer"]}"
-        end
-
-        # Abstract
-        if data["Abstract"] && !data["Abstract"].empty?
-          results << "Summary: #{data["Abstract"]}"
-        end
-
-        # Related topics
-        if data["RelatedTopics"] && data["RelatedTopics"].any?
-          topics = data["RelatedTopics"].first(3).map do |topic|
-            topic["Text"] if topic["Text"]
-          end.compact
-
-          if topics.any?
-            results << "Related: #{topics.join("; ")}"
-          end
-        end
-
-        if results.empty?
-          "No direct results found for '#{query}'. Try a more specific search or use web fetch to access specific URLs."
-        else
-          results.join("\n\n")
-        end
-      end
-    end
-  end
-end
lib/elelem/api.rb
@@ -1,48 +0,0 @@
-# frozen_string_literal: true
-
-require "net/llm"
-
-module Elelem
-  class Api
-    attr_reader :configuration, :client
-
-    def initialize(configuration)
-      @configuration = configuration
-      @client = Net::Llm::Ollama.new(
-        host: configuration.host,
-        model: configuration.model
-      )
-    end
-
-    def chat(messages, &block)
-      tools = configuration.tools.to_h
-      client.chat(messages, tools) do |chunk|
-        normalized = normalize_ollama_response(chunk)
-        block.call(normalized) if normalized
-      end
-    end
-
-    private
-
-    def normalize_ollama_response(chunk)
-      return done_response(chunk) if chunk["done"]
-
-      normalize_message(chunk["message"])
-    end
-
-    def done_response(chunk)
-      { "done" => true, "finish_reason" => chunk["done_reason"] || "stop" }
-    end
-
-    def normalize_message(message)
-      return nil unless message
-
-      {}.tap do |result|
-        result["role"] = message["role"] if message["role"]
-        result["content"] = message["content"] if message["content"]
-        result["reasoning"] = message["thinking"] if message["thinking"]
-        result["tool_calls"] = message["tool_calls"] if message["tool_calls"]
-      end.then { |r| r.empty? ? nil : r }
-    end
-  end
-end
lib/elelem/configuration.rb
@@ -15,9 +15,6 @@ module Elelem
       @tools ||= Tools.new([
         Toolbox::Exec.new(self),
         Toolbox::File.new(self),
-        Toolbox::Web.new(self),
-        Toolbox::Prompt.new(self),
-        Toolbox::Memory.new(self),
       ] + mcp_tools)
     end
 
lib/elelem/toolbox.rb
@@ -2,7 +2,4 @@
 
 require_relative "toolbox/exec"
 require_relative "toolbox/file"
-require_relative "toolbox/web"
 require_relative "toolbox/mcp"
-require_relative "toolbox/prompt"
-require_relative "toolbox/memory"
lib/elelem.rb
@@ -12,7 +12,6 @@ require "thor"
 require "timeout"
 
 require_relative "elelem/agent"
-require_relative "elelem/api"
 require_relative "elelem/application"
 require_relative "elelem/configuration"
 require_relative "elelem/conversation"
elelem.gemspec
@@ -33,11 +33,8 @@ Gem::Specification.new do |spec|
     "README.md",
     "Rakefile",
     "exe/elelem",
-    "exe/llm-ollama",
-    "exe/llm-openai",
     "lib/elelem.rb",
     "lib/elelem/agent.rb",
-    "lib/elelem/api.rb",
     "lib/elelem/application.rb",
     "lib/elelem/configuration.rb",
     "lib/elelem/conversation.rb",
@@ -48,8 +45,6 @@ Gem::Specification.new do |spec|
     "lib/elelem/toolbox/exec.rb",
     "lib/elelem/toolbox/file.rb",
     "lib/elelem/toolbox/mcp.rb",
-    "lib/elelem/toolbox/memory.rb",
-    "lib/elelem/toolbox/prompt.rb",
     "lib/elelem/toolbox/web.rb",
     "lib/elelem/tools.rb",
     "lib/elelem/tui.rb",