rlmgrep 0.1.4__py3-none-any.whl → 0.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rlmgrep/__init__.py +1 -1
- rlmgrep/cli.py +60 -14
- rlmgrep/config.py +1 -1
- rlmgrep/render.py +5 -5
- {rlmgrep-0.1.4.dist-info → rlmgrep-0.1.6.dist-info}/METADATA +9 -23
- rlmgrep-0.1.6.dist-info/RECORD +14 -0
- rlmgrep-0.1.4.dist-info/RECORD +0 -14
- {rlmgrep-0.1.4.dist-info → rlmgrep-0.1.6.dist-info}/WHEEL +0 -0
- {rlmgrep-0.1.4.dist-info → rlmgrep-0.1.6.dist-info}/entry_points.txt +0 -0
- {rlmgrep-0.1.4.dist-info → rlmgrep-0.1.6.dist-info}/top_level.txt +0 -0
rlmgrep/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.6"
|
rlmgrep/cli.py
CHANGED
|
@@ -73,8 +73,6 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
|
|
|
73
73
|
parser.add_argument("paths", nargs="*", help="Files or directories")
|
|
74
74
|
|
|
75
75
|
parser.add_argument("-n", dest="line_numbers", action="store_true", help="Show line numbers (default)")
|
|
76
|
-
parser.add_argument("--no-line-number", dest="line_numbers", action="store_false", help="Hide line numbers")
|
|
77
|
-
parser.add_argument("-H", dest="with_filename", action="store_true", help="Always show filenames (default)")
|
|
78
76
|
parser.add_argument("-r", dest="recursive", action="store_true", help="Recursive (directories are searched recursively by default)")
|
|
79
77
|
parser.add_argument("--no-recursive", dest="recursive", action="store_false", help="Do not recurse directories")
|
|
80
78
|
parser.set_defaults(recursive=True, line_numbers=True)
|
|
@@ -86,10 +84,22 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
|
|
|
86
84
|
parser.add_argument("-a", "--text", dest="binary_as_text", action="store_true", help="Search binary files as text")
|
|
87
85
|
parser.add_argument("--answer", action="store_true", help="Print a narrative answer before grep output")
|
|
88
86
|
parser.add_argument("-y", "--yes", action="store_true", help="Skip file count confirmation")
|
|
87
|
+
parser.add_argument(
|
|
88
|
+
"--files-from-stdin",
|
|
89
|
+
action="store_true",
|
|
90
|
+
help="Treat stdin as newline-delimited file paths (e.g., rg -l)",
|
|
91
|
+
)
|
|
92
|
+
parser.add_argument(
|
|
93
|
+
"--files-from-rg",
|
|
94
|
+
dest="stdin_files",
|
|
95
|
+
action="store_true",
|
|
96
|
+
help="Alias for --files-from-stdin",
|
|
97
|
+
)
|
|
89
98
|
parser.add_argument(
|
|
90
99
|
"--stdin-files",
|
|
100
|
+
dest="stdin_files",
|
|
91
101
|
action="store_true",
|
|
92
|
-
help="
|
|
102
|
+
help="Deprecated: use --files-from-stdin",
|
|
93
103
|
)
|
|
94
104
|
|
|
95
105
|
parser.add_argument("-g", "--glob", dest="globs", action="append", default=[], help="Include files matching glob (may repeat)")
|
|
@@ -240,6 +250,33 @@ def _build_markitdown(config: dict, warnings: list[str]):
|
|
|
240
250
|
kwargs["base_url"] = api_base
|
|
241
251
|
return OpenAI(**kwargs)
|
|
242
252
|
|
|
253
|
+
class _LiteLLMClient:
|
|
254
|
+
def __init__(self, api_key: str | None, api_base: str | None):
|
|
255
|
+
try:
|
|
256
|
+
import litellm # type: ignore
|
|
257
|
+
except Exception as exc:
|
|
258
|
+
raise RuntimeError("litellm not available") from exc
|
|
259
|
+
self._litellm = litellm
|
|
260
|
+
self._api_key = api_key
|
|
261
|
+
self._api_base = api_base
|
|
262
|
+
self.chat = self._Chat(self)
|
|
263
|
+
|
|
264
|
+
class _Chat:
|
|
265
|
+
def __init__(self, parent):
|
|
266
|
+
self.completions = parent._Completions(parent)
|
|
267
|
+
|
|
268
|
+
class _Completions:
|
|
269
|
+
def __init__(self, parent):
|
|
270
|
+
self._parent = parent
|
|
271
|
+
|
|
272
|
+
def create(self, model: str, messages):
|
|
273
|
+
kwargs = {"model": model, "messages": messages}
|
|
274
|
+
if self._parent._api_key:
|
|
275
|
+
kwargs["api_key"] = self._parent._api_key
|
|
276
|
+
if self._parent._api_base:
|
|
277
|
+
kwargs["api_base"] = self._parent._api_base
|
|
278
|
+
return self._parent._litellm.completion(**kwargs)
|
|
279
|
+
|
|
243
280
|
llm_client = None
|
|
244
281
|
if enable_images:
|
|
245
282
|
if not llm_model:
|
|
@@ -247,18 +284,27 @@ def _build_markitdown(config: dict, warnings: list[str]):
|
|
|
247
284
|
"markitdown_enable_images set but markitdown_image_llm_model missing; skipping images"
|
|
248
285
|
)
|
|
249
286
|
enable_images = False
|
|
250
|
-
elif llm_provider != "openai":
|
|
251
|
-
warnings.append(
|
|
252
|
-
f"markitdown image LLM provider '{llm_provider}' not supported; skipping images"
|
|
253
|
-
)
|
|
254
|
-
enable_images = False
|
|
255
287
|
else:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
288
|
+
if llm_provider == "openai":
|
|
289
|
+
llm_client = _openai_client(
|
|
290
|
+
llm_api_key,
|
|
291
|
+
llm_api_base,
|
|
292
|
+
"openai package missing; skipping image conversion",
|
|
293
|
+
)
|
|
294
|
+
if llm_client is None:
|
|
295
|
+
enable_images = False
|
|
296
|
+
elif llm_provider in {"gemini", "anthropic"}:
|
|
297
|
+
try:
|
|
298
|
+
llm_client = _LiteLLMClient(llm_api_key, llm_api_base)
|
|
299
|
+
except RuntimeError:
|
|
300
|
+
warnings.append(
|
|
301
|
+
"litellm not available; skipping image conversion"
|
|
302
|
+
)
|
|
303
|
+
enable_images = False
|
|
304
|
+
else:
|
|
305
|
+
warnings.append(
|
|
306
|
+
f"markitdown image LLM provider '{llm_provider}' not supported; skipping images"
|
|
307
|
+
)
|
|
262
308
|
enable_images = False
|
|
263
309
|
|
|
264
310
|
md_kwargs: dict[str, object] = {"enable_plugins": False}
|
rlmgrep/config.py
CHANGED
|
@@ -25,7 +25,7 @@ DEFAULT_CONFIG_TEXT = "\n".join(
|
|
|
25
25
|
"# markitdown_image_llm_api_base = \"\"",
|
|
26
26
|
"# markitdown_image_llm_prompt = \"\"",
|
|
27
27
|
"# markitdown_enable_audio = false",
|
|
28
|
-
"# markitdown_audio_model = \"gpt-4o-mini-transcribe\"",
|
|
28
|
+
"# markitdown_audio_model = \"gpt-4o-mini-transcribe-2025-12-15\"",
|
|
29
29
|
"# markitdown_audio_provider = \"openai\"",
|
|
30
30
|
"# markitdown_audio_api_key = \"\"",
|
|
31
31
|
"# markitdown_audio_api_base = \"\"",
|
rlmgrep/render.py
CHANGED
|
@@ -28,11 +28,11 @@ def _format_line(
|
|
|
28
28
|
heading: bool,
|
|
29
29
|
) -> str:
|
|
30
30
|
delim = ":" if is_match else "-"
|
|
31
|
-
if show_line_numbers:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return text
|
|
31
|
+
if not show_line_numbers:
|
|
32
|
+
return text
|
|
33
|
+
prefix = _colorize(str(line_no), COLOR_LINE_NO, use_color)
|
|
34
|
+
sep = "\t" if heading else ""
|
|
35
|
+
return f"{prefix}{delim}{sep}{text}"
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
def _merge_ranges(ranges: list[tuple[int, int]]) -> list[tuple[int, int]]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rlmgrep
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: Grep-shaped CLI search powered by DSPy RLM
|
|
5
5
|
Author: rlmgrep
|
|
6
6
|
License: MIT
|
|
@@ -61,9 +61,6 @@ rlmgrep [options] "query" [paths...]
|
|
|
61
61
|
|
|
62
62
|
Common options:
|
|
63
63
|
|
|
64
|
-
- `-n` show line numbers (default)
|
|
65
|
-
- `--no-line-number` hide line numbers
|
|
66
|
-
- `-H` always show filenames (default)
|
|
67
64
|
- `-C N` context lines before/after (grep-style)
|
|
68
65
|
- `-A N` context lines after
|
|
69
66
|
- `-B N` context lines before
|
|
@@ -83,19 +80,19 @@ Examples:
|
|
|
83
80
|
|
|
84
81
|
```sh
|
|
85
82
|
# Natural-language query over a repo
|
|
86
|
-
rlmgrep -n -C 2 "
|
|
83
|
+
rlmgrep -n -C 2 "Where is retry/backoff configured and what are the defaults?" .
|
|
87
84
|
|
|
88
85
|
# Restrict to Python files
|
|
89
|
-
rlmgrep "
|
|
86
|
+
rlmgrep "Where do we parse JWTs and enforce expiration?" --type py .
|
|
90
87
|
|
|
91
88
|
# Glob filters (repeatable or comma-separated)
|
|
92
|
-
rlmgrep "error
|
|
89
|
+
rlmgrep "How do we map external API errors into internal error codes?" -g "**/*.py" -g "**/*.ts" .
|
|
93
90
|
|
|
94
|
-
#
|
|
95
|
-
cat README.md | rlmgrep "
|
|
91
|
+
# Single-file semantic question (when you already have the file)
|
|
92
|
+
cat README.md | rlmgrep --answer "What is this tool for and how is it used?"
|
|
96
93
|
|
|
97
94
|
# Use rg/grep to find candidate files, then rlmgrep over that list
|
|
98
|
-
rg -l "token" . | rlmgrep --stdin
|
|
95
|
+
rg -l "token" . | rlmgrep --files-from-stdin --answer "What does this token control and where is it validated?"
|
|
99
96
|
```
|
|
100
97
|
|
|
101
98
|
## Input selection
|
|
@@ -120,7 +117,6 @@ rg -l "token" . | rlmgrep --stdin-files --answer "what does this token control?"
|
|
|
120
117
|
- `1` = no matches
|
|
121
118
|
- `2` = usage/config/error
|
|
122
119
|
|
|
123
|
-
Agent tip: use `-n -H` and no context for parse-friendly output, then key off exit codes.
|
|
124
120
|
|
|
125
121
|
## Regex-style queries (best effort)
|
|
126
122
|
|
|
@@ -160,7 +156,7 @@ file_hard_max = 1000
|
|
|
160
156
|
# markitdown_image_llm_api_base = ""
|
|
161
157
|
# markitdown_image_llm_prompt = ""
|
|
162
158
|
# markitdown_enable_audio = false
|
|
163
|
-
# markitdown_audio_model = "gpt-4o-mini-transcribe"
|
|
159
|
+
# markitdown_audio_model = "gpt-4o-mini-transcribe-2025-12-15"
|
|
164
160
|
# markitdown_audio_provider = "openai"
|
|
165
161
|
# markitdown_audio_api_key = ""
|
|
166
162
|
# markitdown_audio_api_base = ""
|
|
@@ -180,22 +176,12 @@ If more than one provider key is set and the model does not make the provider ob
|
|
|
180
176
|
## Non-text files (PDF, images, audio)
|
|
181
177
|
|
|
182
178
|
- PDF files are parsed with `pypdf`. Each page gets a marker line `===== Page N =====`, and output lines include a `page=N` suffix.
|
|
183
|
-
- Images and audio are converted via `markitdown` when enabled in config.
|
|
179
|
+
- Images and audio are converted via `markitdown` when enabled in config. Image conversion supports `openai`, `anthropic`, and `gemini` providers; audio conversion currently supports `openai` only.
|
|
184
180
|
- Converted image/audio text is cached in sidecar files named `<original>.<ext>.md` next to the original file and reused on subsequent runs.
|
|
185
181
|
- Use `-a/--text` to force binary files to be read as text (UTF-8 with replacement).
|
|
186
182
|
|
|
187
|
-
## Agent usage notes
|
|
188
|
-
|
|
189
|
-
- Prefer narrow corpora (globs/types) to reduce token usage.
|
|
190
|
-
- Use `--max-llm-calls` to cap costs; combine with small `--max-iterations` for safety.
|
|
191
|
-
- For reproducible parsing, use `-n -H` and avoid context (`-C/-A/-B`).
|
|
192
|
-
|
|
193
183
|
## Development
|
|
194
184
|
|
|
195
185
|
- Install locally: `pip install -e .` or `uv tool install .`
|
|
196
186
|
- Run: `rlmgrep "query" .`
|
|
197
187
|
- No test suite is configured yet.
|
|
198
|
-
|
|
199
|
-
## Security
|
|
200
|
-
|
|
201
|
-
Do not commit API keys. Use environment variables or `~/.rlmgrep/config.toml`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
rlmgrep/__init__.py,sha256=uEY-o5UpO0zjCdZ1SCWaClP-xXaDZDIXmwIfxAtFffI,48
|
|
2
|
+
rlmgrep/__main__.py,sha256=MHKZ_ae3fSLGTLUUMOx15fWdeOnJSHhq-zslRP5F5Lc,79
|
|
3
|
+
rlmgrep/cli.py,sha256=eUdM9aC0znZFzjqj-4YmT0_ymisSc_lUwDVjRi4ZU-s,20589
|
|
4
|
+
rlmgrep/config.py,sha256=u1iz-nI8dj-dZETbpIki3RQefHJEyi5oE5zE4_IR8kg,2399
|
|
5
|
+
rlmgrep/file_map.py,sha256=x2Ri1wzK8_87GUorsAV01K_nYLZcv30yIquDeTCcdEw,876
|
|
6
|
+
rlmgrep/ingest.py,sha256=uCz2el9B-RIT9umFo-gFEdAsmWPP1IJOArFFQY0D_1A,9127
|
|
7
|
+
rlmgrep/interpreter.py,sha256=s_nMRxLlAU9C0JmUzUBW5NbVbuH67doVWF54K54STlA,2478
|
|
8
|
+
rlmgrep/render.py,sha256=OYZy7BuJJe-KsDhEGAz6JA5RGd65ZInPWf9wLDJE0ag,3554
|
|
9
|
+
rlmgrep/rlm.py,sha256=i3rCTp8OABByF60Un5gO7265gaW4spwU0OFKIz4surg,5750
|
|
10
|
+
rlmgrep-0.1.6.dist-info/METADATA,sha256=g9zfDLY_x-QCcMzB0pcyXWZ6FoXr_c67mLiVRrOcHyI,6318
|
|
11
|
+
rlmgrep-0.1.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
12
|
+
rlmgrep-0.1.6.dist-info/entry_points.txt,sha256=UV6QkEbkwBO1JJ53mm84_n35tVyOczPvOQ14ga7vrCI,45
|
|
13
|
+
rlmgrep-0.1.6.dist-info/top_level.txt,sha256=gTujSRsO58c80eN7aRH2cfe51FHxx8LJ1w1Y2YlHti0,8
|
|
14
|
+
rlmgrep-0.1.6.dist-info/RECORD,,
|
rlmgrep-0.1.4.dist-info/RECORD
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
rlmgrep/__init__.py,sha256=V2k5h8cZ-5Dt0onxqKP5wn7Rn2cmXmsSVcJMQZ9X1sY,48
|
|
2
|
-
rlmgrep/__main__.py,sha256=MHKZ_ae3fSLGTLUUMOx15fWdeOnJSHhq-zslRP5F5Lc,79
|
|
3
|
-
rlmgrep/cli.py,sha256=4qwR5cdQV0H40XZhLXdAO9tzlubeQCgA2sepWrXzKWI,18996
|
|
4
|
-
rlmgrep/config.py,sha256=A6VLuuXSgQ1vM207CP0G92Mg3et93dGSmkkLQ0IOfwk,2388
|
|
5
|
-
rlmgrep/file_map.py,sha256=x2Ri1wzK8_87GUorsAV01K_nYLZcv30yIquDeTCcdEw,876
|
|
6
|
-
rlmgrep/ingest.py,sha256=uCz2el9B-RIT9umFo-gFEdAsmWPP1IJOArFFQY0D_1A,9127
|
|
7
|
-
rlmgrep/interpreter.py,sha256=s_nMRxLlAU9C0JmUzUBW5NbVbuH67doVWF54K54STlA,2478
|
|
8
|
-
rlmgrep/render.py,sha256=mxU7ByarJ6FU8ERt7biPVIOmpZQdKRrdyoZqlngploQ,3558
|
|
9
|
-
rlmgrep/rlm.py,sha256=i3rCTp8OABByF60Un5gO7265gaW4spwU0OFKIz4surg,5750
|
|
10
|
-
rlmgrep-0.1.4.dist-info/METADATA,sha256=s8iJbqKgLZSiUv7q4dkun4f40uDTQOYom3qjE1OMaks,6600
|
|
11
|
-
rlmgrep-0.1.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
12
|
-
rlmgrep-0.1.4.dist-info/entry_points.txt,sha256=UV6QkEbkwBO1JJ53mm84_n35tVyOczPvOQ14ga7vrCI,45
|
|
13
|
-
rlmgrep-0.1.4.dist-info/top_level.txt,sha256=gTujSRsO58c80eN7aRH2cfe51FHxx8LJ1w1Y2YlHti0,8
|
|
14
|
-
rlmgrep-0.1.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|