Packages

An Elixir framework for building AI agents from OTP primitives. Weaves conversations, tool calls, and reasoning into coherent agents using GenServer, Task.Supervisor, and ETS.

Current section

Files

Jump to
loom_ex lib loom_ex tools grep.ex
Raw

lib/loom_ex/tools/grep.ex

defmodule LoomEx.Tools.Grep do
use LoomEx.Tool
@max_results 100
@impl true
def name, do: "grep"
@impl true
def description,
do:
"Search file contents for a pattern. Returns matching lines with file paths and line numbers."
@impl true
def parameters do
%{
type: "object",
properties: %{
pattern: %{type: "string", description: "Search pattern (regex supported)"},
path: %{
type: "string",
description: "Directory or file to search in (default: current directory)"
},
glob: %{
type: "string",
description: "File glob pattern to filter, e.g. '*.ex' (optional)"
},
case_insensitive: %{
type: "boolean",
description: "Case insensitive search (default: false)"
}
},
required: ["pattern"]
}
end
@impl true
def execute(%{"pattern" => pattern} = args, ctx) do
search_path = args["path"] || "."
full_path = resolve_path(search_path, ctx)
case_insensitive = args["case_insensitive"] || false
# Build grep arguments
grep_args = ["-rn"]
grep_args = if case_insensitive, do: grep_args ++ ["-i"], else: grep_args
grep_args = if glob = args["glob"], do: grep_args ++ ["--include", glob], else: grep_args
grep_args = grep_args ++ [pattern, full_path]
try do
{output, exit_code} = System.cmd("grep", grep_args, stderr_to_stdout: true)
case exit_code do
0 ->
lines = String.split(output, "\n", trim: true)
total = length(lines)
truncated = Enum.take(lines, @max_results)
result = Enum.join(truncated, "\n")
result =
if total > @max_results do
result <> "\n... [#{total - @max_results} more matches truncated]"
else
result
end
{:ok, %{"matches" => result, "count" => min(total, @max_results), "total" => total}}
1 ->
{:ok, %{"matches" => "", "count" => 0, "total" => 0}}
_ ->
{:error, "grep failed (exit #{exit_code}): #{String.trim(output)}"}
end
rescue
e -> {:error, "grep error: #{Exception.message(e)}"}
end
end
defp resolve_path(path, ctx) do
if Path.type(path) == :absolute do
path
else
Path.join(ctx[:cwd] || ".", path)
end
end
end