Skip to content

Commit f9b66f4

Browse files
authored
Merge pull request #6 from epilande/max-file-size
feat: Add max file size limit option
2 parents 89e9a93 + adddceb commit f9b66f4

File tree

12 files changed

+467
-150
lines changed

12 files changed

+467
-150
lines changed

README.md

Lines changed: 43 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ to your clipboard, ready for LLM processing.
2020

2121
- 🎮 **Interactive Mode**: Navigate your project structure with vim-like keybindings in a
2222
TUI environment
23-
- 🧹 **Filtering Options**: Respect `.gitignore` rules, handle hidden files, and apply customizable glob
24-
patterns
23+
- 🧹 **Filtering Options**: Respect `.gitignore` rules, handle hidden files, apply customizable glob
24+
patterns, and skip large files
2525
- 🔍 **Fuzzy Search**: Quickly find files across your project
2626
-**File Selection**: Toggle files or entire directories (with child items) for inclusion or exclusion
2727
- 📄 **Multiple Output Formats**: Generate Markdown, Plain Text, or XML output
@@ -59,7 +59,7 @@ cd codegrab
5959
go build ./cmd/grab
6060
```
6161

62-
Then move the binary to your `PATH`
62+
Then move the binary to your `PATH`.
6363

6464
## 🚀 Quick Start
6565

@@ -69,9 +69,9 @@ Then move the binary to your `PATH`
6969
grab
7070
```
7171

72-
2. Navigate with <kbd>h</kbd>/<kbd>j</kbd>/<kbd>k</kbd>/<kbd>l</kbd>
73-
3. Select files using the <kbd>Space</kbd> or <kbd>Tab</kbd> key
74-
4. Press <kbd>g</kbd> to generate output file or <kbd>y</kbd> to copy contents to clipboard
72+
2. Navigate with arrow keys or <kbd>h</kbd>/<kbd>j</kbd>/<kbd>k</kbd>/<kbd>l</kbd>.
73+
3. Select files using the <kbd>Space</kbd> or <kbd>Tab</kbd> key.
74+
4. Press <kbd>g</kbd> to generate output file or <kbd>y</kbd> to copy contents to clipboard.
7575

7676
CodeGrab will generate `codegrab-output.md` in your current working directory (on macOS this file is automatically copied to your clipboard), which you can immediately send to an AI assistant for better context-aware coding assistance.
7777

@@ -85,25 +85,26 @@ grab [options] [directory]
8585

8686
### Arguments
8787

88-
| Argument | Description |
89-
| :---------- | :----------------------------------------------------------------- |
90-
| `directory` | Path to the project directory (default: current working directory) |
88+
| Argument | Description |
89+
| :---------- | :---------------------------------------------------- |
90+
| `directory` | Optional path to the project directory (default: ".") |
9191

9292
### Options
9393

94-
| Option | Description |
95-
| :---------------------- | :-------------------------------------------------------------------------------------- |
96-
| `-h, --help` | Display help information |
97-
| `-v, --version` | Display version information |
98-
| `-n, --non-interactive` | Run in non-interactive mode (grabs all files) |
99-
| `-o, --output file` | Output file path (default: `./codegrab-output.<format>`) |
100-
| `-t, --temp` | Use system temporary directory for output file |
101-
| `-g, --glob pattern` | Include/exclude files and directories (e.g., `--glob="*.{ts,tsx}" --glob="\!*.spec.ts"`) |
102-
| `-f, --format format` | Output format (available: markdown, text, xml) |
103-
| `-S, --skip-redaction` | Skip automatic secret redaction (WARNING: This may expose sensitive information) |
104-
| `--deps` | Automatically include direct dependencies (Go, JS/TS) |
105-
| `--max-depth depth` | Maximum depth for dependency resolution (-1 for unlimited, default: 1) |
106-
| `--theme` | Set the UI theme |
94+
| Option | Description |
95+
| :----------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
96+
| `-h, --help` | Display help information. |
97+
| `-v, --version` | Display version information. |
98+
| `-n, --non-interactive` | Run in non-interactive mode (selects all valid files respecting filters). |
99+
| `-o, --output <file>` | Output file path (default: `./codegrab-output.<format>`). |
100+
| `-t, --temp` | Use system temporary directory for output file. |
101+
| `-g, --glob <pattern>` | Include/exclude files and directories using glob patterns. Can be used multiple times. Prefix with '!' to exclude (e.g., `--glob="*.{ts,tsx}" --glob="\!*.spec.ts"`). |
102+
| `-f, --format <format>` | Output format. Available: `markdown`, `text`, `xml` (default: `"markdown"`). |
103+
| `-S, --skip-redaction` | Skip automatic secret redaction via gitleaks (Default: false). WARNING: Disabling this may expose sensitive information! |
104+
| `--deps` | Automatically include direct dependencies for selected files (Go, JS/TS). |
105+
| `--max-depth <depth>` | Maximum depth for dependency resolution (`-1` for unlimited, default: `1`). Only effective with `--deps`. |
106+
| `--max-file-size <size>` | Maximum file size to include (e.g., `"100kb"`, `"2MB"`). No limit by default. Files exceeding the specified size will be skipped. |
107+
| `--theme <name>` | Set the UI theme. Available: catppuccin-latte, catppuccin-frappe, catppuccin-macchiato, catppuccin-mocha, dracula, nord. (default: `"catppuccin-mocha"`). |
107108

108109
### 📖 Examples
109110

@@ -113,22 +114,22 @@ grab [options] [directory]
113114
grab
114115
```
115116

116-
2. Grab all files in current directory (non-interactive):
117+
2. Grab all files in current directory (non-interactive), skipping files > 50kb:
117118

118119
```bash
119-
grab -n
120+
grab -n --max-file-size 50kb
120121
```
121122

122-
3. Grab a specific directory interactively:
123+
3. Grab a specific directory interactively including dependencies:
123124

124125
```bash
125-
grab /path/to/project
126+
grab --deps /path/to/project
126127
```
127128

128129
4. Grab a specific directory non-interactively including all dependencies (unlimited depth):
129130

130131
```bash
131-
grab -n --deps --max-depth -1
132+
grab -n --deps --max-depth -1 /path/to/project
132133
```
133134

134135
5. Specify custom output file:
@@ -171,13 +172,13 @@ grab [options] [directory]
171172

172173
### Search
173174

174-
| Action | Key | Description |
175-
| :--------------------- | :-------------------------------- | :----------------------------------------------------- |
176-
| Start search | <kbd>/</kbd> | Begin searching for files |
177-
| Next search result | <kbd>ctrl+n</kbd> or <kbd>↓</kbd> | Navigate to the next search result |
178-
| Previous search result | <kbd>ctrl+p</kbd> or <kbd>↑</kbd> | Navigate to the previous search result |
179-
| Select/deselect file | <kbd>tab</kbd> | Toggle selection of the current file in search results |
180-
| Exit search | <kbd>esc</kbd> | Exit search mode and return to normal navigation |
175+
| Action | Key | Description |
176+
| :--------------------- | :-------------------------------- | :---------------------------------------------------------- |
177+
| Start search | <kbd>/</kbd> | Begin fuzzy searching for files |
178+
| Next search result | <kbd>ctrl+n</kbd> or <kbd>↓</kbd> | Navigate to the next search result |
179+
| Previous search result | <kbd>ctrl+p</kbd> or <kbd>↑</kbd> | Navigate to the previous search result |
180+
| Select/deselect item | <kbd>tab</kbd> / <kbd>enter</kbd> | Toggle selection of the item under cursor in search results |
181+
| Exit search | <kbd>esc</kbd> | Exit search mode and return to normal navigation |
181182

182183
### Selection & Output
183184

@@ -192,19 +193,19 @@ grab [options] [directory]
192193

193194
### View Options
194195

195-
| Action | Key | Description |
196-
| :------------------------- | :----------- | :------------------------------------------------ |
197-
| Toggle `.gitignore` filter | <kbd>i</kbd> | Toggle whether to respect `.gitignore` rules |
198-
| Toggle hidden files | <kbd>.</kbd> | Toggle visibility of hidden files and directories |
199-
| Refresh files & folders | <kbd>r</kbd> | Reload directory tree and reset selections |
200-
| Toggle help screen | <kbd>?</kbd> | Show or hide the help screen |
201-
| Quit | <kbd>q</kbd> | Exit the application |
196+
| Action | Key | Description |
197+
| :------------------------- | :------------------------------- | :------------------------------------------- |
198+
| Toggle `.gitignore` filter | <kbd>i</kbd> | Toggle whether to respect `.gitignore` rules |
199+
| Toggle hidden files | <kbd>.</kbd> | Toggle visibility of hidden files |
200+
| Refresh files & folders | <kbd>r</kbd> | Reload directory tree and reset selections |
201+
| Toggle help screen | <kbd>?</kbd> | Show or hide the help screen |
202+
| Quit | <kbd>q</kbd> / <kbd>ctrl+c</kbd> | Exit the application |
202203

203204
## 🔗 Automatic Dependency Resolution
204205

205206
CodeGrab can automatically include dependencies for selected files, making it easier to share complete code snippets with LLMs.
206207

207-
- **How it works**: When enabled, CodeGrab utilizes [tree-sitter](https://tree-sitter.github.io/tree-sitter/) to parse selected source files, identifying language-specific dependency declarations (like `import` or `require`). It then attempts to resolve these dependencies within your project and automatically includes the necessary files.
208+
- **How it works**: When enabled, CodeGrab utilizes [tree-sitter](https://tree-sitter.github.io/tree-sitter/) to parse selected source files, identifying language-specific dependency declarations (like `import` or `require`). It then attempts to resolve these dependencies within your project and automatically includes the necessary files (respecting `.gitignore`, hidden, size, and glob filters).
208209
- **Supported Languages**:
209210
- **Go**: Resolves relative imports and project-local module imports (if `go.mod` is present).
210211
- **JavaScript/TypeScript**: Resolves relative imports/requires for `.js`, `.jsx`, `.ts`, and `.tsx` files, including directory `index` files.

cmd/grab/main.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func (s *stringSliceFlag) Set(value string) error {
3434
func main() {
3535
themes.Initialize()
3636

37+
var err error
3738
var globPatterns stringSliceFlag
3839
var showHelp bool
3940
var showVersion bool
@@ -45,6 +46,7 @@ func main() {
4546
var skipRedaction bool
4647
var resolveDeps bool
4748
var maxDepth int
49+
var maxFileSizeStr string
4850

4951
flag.BoolVar(&showHelp, "help", false, "Display help information")
5052
flag.BoolVar(&showHelp, "h", false, "Display help information (shorthand)")
@@ -80,6 +82,9 @@ func main() {
8082
flag.BoolVar(&skipRedaction, "skip-redaction", false, "Skip automatic secret redaction (WARNING: this may expose secrets)")
8183
flag.BoolVar(&skipRedaction, "S", false, "Skip automatic secret redaction (shorthand)")
8284

85+
maxFileSizeUsage := "Maximum file size to include (e.g., 50kb, 2MB). No limit by default."
86+
flag.StringVar(&maxFileSizeStr, "max-file-size", "", maxFileSizeUsage)
87+
8388
flag.Parse()
8489

8590
if showHelp {
@@ -104,6 +109,15 @@ func main() {
104109
maxDepth = math.MaxInt
105110
}
106111

112+
// Default to no limit if the flag is not set
113+
var maxFileSize int64 = math.MaxInt64
114+
if maxFileSizeStr != "" {
115+
maxFileSize, err = utils.ParseSizeString(maxFileSizeStr)
116+
if err != nil {
117+
log.Fatalf("Error parsing max file size %q: %v", maxFileSizeStr, err)
118+
}
119+
}
120+
107121
// Use current directory if no argument is provided
108122
root := "."
109123
if flag.NArg() > 0 {
@@ -134,7 +148,7 @@ func main() {
134148
}
135149

136150
if nonInteractive {
137-
runNonInteractive(root, filterMgr, outputPath, useTempFile, formatName, skipRedaction, resolveDeps, maxDepth)
151+
runNonInteractive(root, filterMgr, outputPath, useTempFile, formatName, skipRedaction, resolveDeps, maxDepth, maxFileSize)
138152
} else {
139153
config := model.Config{
140154
RootPath: root,
@@ -145,6 +159,7 @@ func main() {
145159
SkipRedaction: skipRedaction,
146160
ResolveDeps: resolveDeps,
147161
MaxDepth: maxDepth,
162+
MaxFileSize: maxFileSize,
148163
}
149164

150165
m := model.NewModel(config)
@@ -157,13 +172,13 @@ func main() {
157172
}
158173

159174
// runNonInteractive processes files and generates output without user interaction
160-
func runNonInteractive(rootPath string, filterMgr *filesystem.FilterManager, outputPath string, useTempFile bool, formatName string, skipRedaction bool, resolveDeps bool, maxDepth int) {
175+
func runNonInteractive(rootPath string, filterMgr *filesystem.FilterManager, outputPath string, useTempFile bool, formatName string, skipRedaction bool, resolveDeps bool, maxDepth int, maxFileSize int64) {
161176
gitIgnoreMgr, err := filesystem.NewGitIgnoreManager(rootPath)
162177
if err != nil {
163178
log.Fatalf("Error reading .gitignore: %v\n", err)
164179
}
165180

166-
files, err := filesystem.WalkDirectory(rootPath, gitIgnoreMgr, filterMgr, true, false)
181+
files, err := filesystem.WalkDirectory(rootPath, gitIgnoreMgr, filterMgr, true, false, maxFileSize)
167182
if err != nil {
168183
log.Fatalf("Error walking directory: %v\n", err)
169184
}
@@ -224,7 +239,8 @@ func runNonInteractive(rootPath string, filterMgr *filesystem.FilterManager, out
224239
depInfo, statErr := os.Stat(depFullPath)
225240
if statErr != nil || depInfo.IsDir() ||
226241
(gitIgnoreMgr.IsIgnored(depFullPath)) ||
227-
(utils.IsHiddenPath(depPath)) {
242+
(utils.IsHiddenPath(depPath)) ||
243+
(depInfo.Size() > maxFileSize) {
228244
continue
229245
}
230246

internal/filesystem/walker.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ type FileItem struct {
1414
Path string
1515
IsDir bool
1616
Level int
17+
Size int64
1718
}
1819

19-
// WalkDirectory traverses the root directory taking into account gitignore and hidden files.
20-
func WalkDirectory(root string, gitIgnore *GitIgnoreManager, filter *FilterManager, useGitIgnore, showHidden bool) ([]FileItem, error) {
20+
// WalkDirectory traverses the root directory taking into account gitignore, hidden files, and max file size.
21+
func WalkDirectory(root string, gitIgnore *GitIgnoreManager, filter *FilterManager, useGitIgnore, showHidden bool, maxFileSize int64) ([]FileItem, error) {
2122
var files []FileItem
2223

2324
if _, err := os.Stat(root); err != nil {
@@ -33,13 +34,15 @@ func WalkDirectory(root string, gitIgnore *GitIgnoreManager, filter *FilterManag
3334
return nil
3435
}
3536

37+
// Skip hidden directories/files
3638
if !showHidden && strings.HasPrefix(info.Name(), ".") {
3739
if info.IsDir() {
3840
return filepath.SkipDir
3941
}
4042
return nil
4143
}
4244

45+
// Skip gitignored paths
4346
if useGitIgnore && gitIgnore != nil && gitIgnore.IsIgnored(path) {
4447
if info.IsDir() {
4548
return filepath.SkipDir
@@ -63,22 +66,32 @@ func WalkDirectory(root string, gitIgnore *GitIgnoreManager, filter *FilterManag
6366
Path: relPath,
6467
IsDir: true,
6568
Level: strings.Count(relPath, "/"),
69+
Size: info.Size(),
6670
})
6771
return nil
6872
}
6973

74+
// Skip files larger than maxFileSize
75+
if info.Size() > maxFileSize {
76+
return nil
77+
}
78+
79+
// Skip files not matching glob patterns
7080
if !filter.ShouldInclude(relPath) {
7181
return nil
7282
}
7383

84+
// Skip non-text files
7485
if ok, err := utils.IsTextFile(path); err != nil || !ok {
7586
return nil
7687
}
7788

89+
// Add the file if it passed all checks
7890
files = append(files, FileItem{
7991
Path: relPath,
8092
IsDir: false,
8193
Level: strings.Count(relPath, "/"),
94+
Size: info.Size(),
8295
})
8396
return nil
8497
})

0 commit comments

Comments
 (0)