opencode-bettergrep
v1.1.0
Published
Standalone OpenCode plugin that ports the advanced local grep implementation from oh-my-opencode-slim.
Maintainers
Readme
⚡ opencode-bettergrep
An OpenCode plugin that replaces the built-in grep tool. It registers under the
same tool ID, searches with ripgrep, and adds the filters, limits and timeout
handling an agent needs when it explores a large repository.
It is part of Better OpenCode Tools,
next to opencode-betterglob and opencode-betterread.
🚀 Install
npm install opencode-bettergrep{
"plugin": ["opencode-bettergrep"]
}To run it from a local checkout, clone the repository, run bun install and
bun run build, and point the plugin entry at
file:///path/to/better-opencode-tools/packages/opencode-bettergrep.
🔎 What you can ask for
| Area | Options |
| --- | --- |
| Output | output_mode: content, files_with_matches or count. |
| Matching | fixed_strings, case_sensitive, smart_case, word_regexp, invert_match, multiline, multiline_dotall, pcre2. |
| Context | context, before_context, after_context (0 to 20 lines). |
| Scope | path or paths, include, globs, exclude_globs, file_type, file_types, exclude_file_types, max_filesize, hidden, follow_symlinks. |
| Limits | max_results (500 by default, 5,000 at most), max_count_per_file, timeout_ms (80 s by default, 140 s at most). |
| Order | sort_by: none, path or mtime, with sort_order. |
Results list file paths, line numbers and context. When a search was cut short by a limit, a timeout or a cancellation, the output says so, so the agent knows whether the result was exhaustive.
🛡️ How it handles processes
- Every search runs under a deadline. On timeout or cancellation the process gets SIGTERM and then SIGKILL, so a stubborn child does not outlive the call.
- The probe that checks the ripgrep binary competes against process termination. A probe that times out resolves even if a child process keeps stdio open.
- When results tie on modification time, they are ordered by raw path bytes. The output does not depend on which worker finished first or on the system locale.
🧭 Finding ripgrep
The plugin uses a ripgrep it finds on PATH, and installs a managed copy when it
finds none. If ripgrep cannot be used, it falls back to GNU grep. The fallback
runs with LC_ALL=C.UTF-8 and an empty LANGUAGE, so its version output parses
in English even on a machine with a translated locale.
The fallback choice is remembered for 10 minutes. After that the plugin tries
ripgrep again, so a single failed probe does not keep the session on GNU grep. It
also tries again as soon as the ripgrep binary on PATH changes.
⏱️ Benchmark against the built-in grep
Times in milliseconds, lower is better, for the built-in tool from the OpenCode
1.18.32 source and this plugin at 1.1.0. Both were called in process with the same
arguments, on a 4-core Arm Neoverse-N1 VM with ripgrep 15.2.0, over the OpenCode
repository (about 6,600 files) and a generated 13 MB log of 200,000 lines. The
built-in tool returns at most 100 results, so the plugin ran with
max_results: 100. Each figure is a median across separate runs of the suite: 5 for the
built-in tool and 4 for the plugin (Bun 1.4.2 and Bun 1.3.14, two runs each). The
two Bun versions differ by less than 2 ms on every row.
| Case | Built-in | Plugin | Ratio |
| --- | ---: | ---: | ---: |
| Rare literal, whole repository | 42 | 36 | 1.2× |
| Common word in *.ts | 12 | 6.6 | 1.9× |
| Regex in *.ts | 15 | 7.4 | 2.1× |
| No match, whole repository | 41 | 34 | 1.2× |
| 13 MB log, many hits | 20 | 7.1 | 2.8× |
| 13 MB log, one hit | 24 | 7.6 | 3.2× |
Both sides run ripgrep. A search that walks the whole repository takes 34 to 42 ms
either way. On searches limited to *.ts the plugin is 1.9× to 2.1× faster, and
on the 13 MB log 2.8× to 3.2×.
The full method, and the read and glob results, are in the
repository README.
⚠️ Known limitations
- The GNU grep fallback is slower and does not support every ripgrep feature.
- An
includeorglobsentry that matches every path (*,**,**/*) is not passed to ripgrep, so ignore files such as.gitignorestill apply. Any other positive glob is a ripgrep override: it also searches the ignored or hidden files it names. To search an ignored directory, pass it aspath. .gitdirectories are skipped even withhiddenor a glob such as**/.git/**, unless a search path is inside one (path: '.git'), as in OpenCode's native grep.- A very large output can still be expensive for the host UI and the model, even
when the search process exits quickly. Use
max_resultsand the filters to keep results small.
🧪 Development
bun run typecheck
bun test
bun run build
bun run check