Skip to content

Commit d4c2a38

Browse files
Adding new CLIs
1 parent ef2725b commit d4c2a38

9 files changed

Lines changed: 568 additions & 0 deletions

File tree

docs/CLI.md

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,247 @@ Priority (highest to lowest):
236236
3. Config file (`~/.bpsa.yaml`)
237237
4. Built-in defaults
238238

239+
## Utility CLIs
240+
241+
Beyond the main `bpsa` REPL, the project ships lightweight CLI wrappers around commonly used tools from `bp_tools.py`. All are installed automatically via `pip install` and support `--help`.
242+
243+
### Quick Reference
244+
245+
| Command | Description |
246+
|---------|-------------|
247+
| `bptree` | Directory tree with line counts and optional function signatures |
248+
| `bpgrep` | Search for text patterns in files with extension filtering |
249+
| `bppack` | Pack a folder's source code into a single tagged string |
250+
| `bpunpack` | Reconstruct source files from a packed string |
251+
| `bploc` | Lines-of-code summary broken down by file type |
252+
| `bpdiff` | Compare two files or two folders (unified diff) |
253+
| `bpsig` | Extract function/class signatures from a source file |
254+
| `bppas` | Extract Pascal unit interface sections from a folder |
255+
256+
Entry points in `pyproject.toml`:
257+
```toml
258+
[project.scripts]
259+
bptree = "smolagents.bp_tree_cli:main"
260+
bpgrep = "smolagents.bp_grep_cli:main"
261+
bppack = "smolagents.bp_pack_cli:main"
262+
bpunpack = "smolagents.bp_unpack_cli:main"
263+
bploc = "smolagents.bp_loc_cli:main"
264+
bpdiff = "smolagents.bp_diff_cli:main"
265+
bpsig = "smolagents.bp_sig_cli:main"
266+
bppas = "smolagents.bp_pas_cli:main"
267+
```
268+
269+
---
270+
271+
### `bptree` — Directory Tree
272+
273+
Displays a tree view of a directory with source-file line counts and optional function signatures. Skips common build/artifact folders by default.
274+
275+
```bash
276+
bptree [FOLDER] [-d DEPTH] [--no-files] [-s] [--skip-dirs DIRS]
277+
```
278+
279+
| Flag | Description |
280+
|------|-------------|
281+
| `FOLDER` | Root folder (default: `.`) |
282+
| `-d`, `--depth N` | Max depth to traverse (default: 6) |
283+
| `--no-files` | Show only directories, hide files |
284+
| `-s`, `--signatures` | Show function/class signatures inline |
285+
| `--skip-dirs d1,d2` | Comma-separated dirs to show but not traverse |
286+
287+
```bash
288+
# Basic tree of src/ with depth 2
289+
bptree src/ -d 2
290+
291+
# Tree with function signatures
292+
bptree . -s
293+
294+
# Directories only, skip vendor
295+
bptree . --no-files --skip-dirs vendor,tmp
296+
```
297+
298+
---
299+
300+
### `bpgrep` — Search in Files
301+
302+
Searches for a text pattern in files within a folder (or a single file). Returns matching lines with file paths and line numbers.
303+
304+
```bash
305+
bpgrep PATTERN [PATH] [-e EXTS] [-c] [-m MAX]
306+
```
307+
308+
| Flag | Description |
309+
|------|-------------|
310+
| `PATTERN` | Text pattern to search for |
311+
| `PATH` | Folder or file to search (default: `.`) |
312+
| `-e`, `--extensions` | Comma-separated extensions to filter (e.g. `py,js,ts`) |
313+
| `-c`, `--case-sensitive` | Case-sensitive search (default: case-insensitive) |
314+
| `-m`, `--max-results N` | Maximum results to return (default: 50) |
315+
316+
```bash
317+
# Search for "def main" in Python files
318+
bpgrep "def main" src/ -e py
319+
320+
# Case-sensitive search with limit
321+
bpgrep "TODO" . -c -m 20
322+
```
323+
324+
---
325+
326+
### `bppack` — Pack Source Code
327+
328+
Packs all source files in a folder into a single string with `<file filename="...">...</file>` XML-like tags. Useful for feeding entire projects to LLMs or creating context dumps.
329+
330+
```bash
331+
bppack FOLDER [-e EXTS] [--strip-pascal-comments] [--exclude ITEMS] [-o FILE]
332+
```
333+
334+
| Flag | Description |
335+
|------|-------------|
336+
| `FOLDER` | Root folder to scan |
337+
| `-e`, `--extensions` | Comma-separated extensions to include (default: common source extensions) |
338+
| `--strip-pascal-comments` | Remove Pascal comments from `.pas`/`.inc` files |
339+
| `--exclude` | Comma-separated files/folders to exclude |
340+
| `-o`, `--output FILE` | Write to a file instead of stdout |
341+
342+
```bash
343+
# Pack src/ to a file
344+
bppack src/ -o context.txt
345+
346+
# Pack only Python and JS files
347+
bppack . -e py,js --exclude __pycache__,node_modules
348+
```
349+
350+
---
351+
352+
### `bpunpack` — Unpack Source Code
353+
354+
Reconstructs source files from a packed string produced by `bppack`. Reads from a file or stdin.
355+
356+
```bash
357+
bpunpack [INPUT] [-o DIR] [--no-overwrite] [-q]
358+
```
359+
360+
| Flag | Description |
361+
|------|-------------|
362+
| `INPUT` | Packed file to read (default: stdin) |
363+
| `-o`, `--output-dir DIR` | Base directory to write into (default: `.`) |
364+
| `--no-overwrite` | Skip files that already exist |
365+
| `-q`, `--quiet` | Suppress status messages |
366+
367+
```bash
368+
# Unpack from file into output/
369+
bpunpack context.txt -o output/
370+
371+
# Pipe from bppack
372+
bppack src/ | bpunpack -o copy/
373+
374+
# Unpack without overwriting existing files
375+
bpunpack context.txt --no-overwrite
376+
```
377+
378+
---
379+
380+
### `bploc` — Lines of Code
381+
382+
Counts lines of code broken down by file extension. Skips hidden files and directories.
383+
384+
```bash
385+
bploc [FOLDER] [-e EXTS]
386+
```
387+
388+
| Flag | Description |
389+
|------|-------------|
390+
| `FOLDER` | Root folder to analyze (default: `.`) |
391+
| `-e`, `--extensions` | Comma-separated extensions to count (default: `py,js,java,cpp,c,php,rb`) |
392+
393+
```bash
394+
# Count lines in current project
395+
bploc
396+
397+
# Count only Python and TypeScript
398+
bploc src/ -e py,ts
399+
```
400+
401+
Example output:
402+
```
403+
.py 23985 lines
404+
total 23985 lines
405+
```
406+
407+
---
408+
409+
### `bpdiff` — Compare Files or Folders
410+
411+
Compares two files or two folders and shows differences in unified diff format. For folders, only source code files are compared.
412+
413+
```bash
414+
bpdiff PATH1 PATH2 [-c CONTEXT]
415+
```
416+
417+
| Flag | Description |
418+
|------|-------------|
419+
| `PATH1` | First file or folder |
420+
| `PATH2` | Second file or folder |
421+
| `-c`, `--context N` | Context lines around differences (default: 3) |
422+
423+
```bash
424+
# Compare two files
425+
bpdiff old_version.py new_version.py
426+
427+
# Compare two folders with more context
428+
bpdiff project_v1/ project_v2/ -c 5
429+
```
430+
431+
---
432+
433+
### `bpsig` — Function Signatures
434+
435+
Extracts function and class signatures from a source file without showing the implementation. Supports Python, JavaScript/TypeScript, Java, PHP, Pascal, C/C++, Markdown (section headers), and a generic fallback.
436+
437+
```bash
438+
bpsig FILE [-l LANGUAGE]
439+
```
440+
441+
| Flag | Description |
442+
|------|-------------|
443+
| `FILE` | Source file to extract signatures from |
444+
| `-l`, `--language` | Programming language (auto-detected from extension if omitted) |
445+
446+
```bash
447+
# Auto-detect language
448+
bpsig src/main.py
449+
450+
# Explicit language
451+
bpsig utils.inc -l pascal
452+
```
453+
454+
---
455+
456+
### `bppas` — Pascal Interfaces
457+
458+
Extracts the interface sections (between `interface` and `implementation` keywords) from Pascal source files in a folder. Output uses `<pascal_interface filename="...">` tags.
459+
460+
```bash
461+
bppas FOLDER [--strip-comments] [-o FILE]
462+
```
463+
464+
| Flag | Description |
465+
|------|-------------|
466+
| `FOLDER` | Root folder to scan for `.pas`, `.inc`, `.pp`, `.lpr`, `.dpr` files |
467+
| `--strip-comments` | Remove Pascal comments from extracted interfaces |
468+
| `-o`, `--output FILE` | Write to a file instead of stdout |
469+
470+
```bash
471+
# Extract interfaces
472+
bppas pascal_src/
473+
474+
# Strip comments and save to file
475+
bppas lib/ --strip-comments -o interfaces.txt
476+
```
477+
478+
---
479+
239480
## Dependencies
240481

241482
- `prompt_toolkit` - REPL input handling (optional, falls back to basic `input()`)

pyproject.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,5 +165,12 @@ lines-after-imports = 2
165165
smolagent = "smolagents.cli:main"
166166
bpsa = "smolagents.bp_cli:main"
167167
bptree = "smolagents.bp_tree_cli:main"
168+
bpgrep = "smolagents.bp_grep_cli:main"
169+
bppack = "smolagents.bp_pack_cli:main"
170+
bpunpack = "smolagents.bp_unpack_cli:main"
171+
bploc = "smolagents.bp_loc_cli:main"
172+
bpdiff = "smolagents.bp_diff_cli:main"
173+
bpsig = "smolagents.bp_sig_cli:main"
174+
bppas = "smolagents.bp_pas_cli:main"
168175
webagent = "smolagents.vision_web_browser:main"
169176
ad-infinitum = "smolagents.bp_ad_infinitum:main"

src/smolagents/bp_diff_cli.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""CLI wrapper for compare_files and compare_folders."""
2+
3+
import argparse
4+
import os
5+
6+
from smolagents.bp_tools import compare_files, compare_folders
7+
8+
9+
def main():
10+
parser = argparse.ArgumentParser(
11+
prog="bpdiff",
12+
description="Compare two files or two folders and show differences in unified diff format.",
13+
)
14+
parser.add_argument("path1", help="first file or folder")
15+
parser.add_argument("path2", help="second file or folder")
16+
parser.add_argument(
17+
"-c", "--context",
18+
type=int,
19+
default=3,
20+
help="number of context lines around differences (default: 3)",
21+
)
22+
23+
args = parser.parse_args()
24+
25+
if os.path.isfile(args.path1) and os.path.isfile(args.path2):
26+
result = compare_files(args.path1, args.path2, context_lines=args.context)
27+
elif os.path.isdir(args.path1) and os.path.isdir(args.path2):
28+
result = compare_folders(args.path1, args.path2, context_lines=args.context)
29+
else:
30+
result = "Error: both paths must be files or both must be directories"
31+
32+
print(result)
33+
34+
35+
if __name__ == "__main__":
36+
main()

src/smolagents/bp_grep_cli.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""CLI wrapper for search_in_files."""
2+
3+
import argparse
4+
5+
from smolagents.bp_tools import search_in_files
6+
7+
8+
def main():
9+
parser = argparse.ArgumentParser(
10+
prog="bpgrep",
11+
description="Search for a text pattern in files within a folder or a single file.",
12+
)
13+
parser.add_argument("pattern", help="text pattern to search for")
14+
parser.add_argument(
15+
"path",
16+
nargs="?",
17+
default=".",
18+
help="folder or file to search in (default: current directory)",
19+
)
20+
parser.add_argument(
21+
"-e", "--extensions",
22+
type=lambda s: tuple(ext if ext.startswith(".") else f".{ext}" for ext in s.split(",")),
23+
default=None,
24+
help="comma-separated file extensions to search (e.g. py,js,ts)",
25+
)
26+
parser.add_argument(
27+
"-c", "--case-sensitive",
28+
action="store_true",
29+
help="make the search case-sensitive (default: case-insensitive)",
30+
)
31+
parser.add_argument(
32+
"-m", "--max-results",
33+
type=int,
34+
default=50,
35+
help="maximum number of results (default: 50)",
36+
)
37+
38+
args = parser.parse_args()
39+
40+
result = search_in_files(
41+
folder_path=args.path,
42+
search_pattern=args.pattern,
43+
file_extensions=args.extensions,
44+
case_sensitive=args.case_sensitive,
45+
max_results=args.max_results,
46+
)
47+
print(result)
48+
49+
50+
if __name__ == "__main__":
51+
main()

0 commit comments

Comments
 (0)