filemerger-cli 0.3.1__tar.gz
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.
- filemerger_cli-0.3.1/LICENSE +23 -0
- filemerger_cli-0.3.1/PKG-INFO +208 -0
- filemerger_cli-0.3.1/README.md +192 -0
- filemerger_cli-0.3.1/filemerger/__init__.py +4 -0
- filemerger_cli-0.3.1/filemerger/cli.py +91 -0
- filemerger_cli-0.3.1/filemerger/config.py +28 -0
- filemerger_cli-0.3.1/filemerger/core.py +75 -0
- filemerger_cli-0.3.1/filemerger/filters.py +37 -0
- filemerger_cli-0.3.1/filemerger/format_ai.py +44 -0
- filemerger_cli-0.3.1/filemerger/format_default.py +53 -0
- filemerger_cli-0.3.1/filemerger/format_llm.py +36 -0
- filemerger_cli-0.3.1/filemerger/formatter.py +6 -0
- filemerger_cli-0.3.1/filemerger/gitignore.py +29 -0
- filemerger_cli-0.3.1/filemerger/stats.py +8 -0
- filemerger_cli-0.3.1/filemerger/user_config.py +15 -0
- filemerger_cli-0.3.1/filemerger/utils.py +20 -0
- filemerger_cli-0.3.1/filemerger_cli.egg-info/PKG-INFO +208 -0
- filemerger_cli-0.3.1/filemerger_cli.egg-info/SOURCES.txt +22 -0
- filemerger_cli-0.3.1/filemerger_cli.egg-info/dependency_links.txt +1 -0
- filemerger_cli-0.3.1/filemerger_cli.egg-info/entry_points.txt +2 -0
- filemerger_cli-0.3.1/filemerger_cli.egg-info/requires.txt +1 -0
- filemerger_cli-0.3.1/filemerger_cli.egg-info/top_level.txt +1 -0
- filemerger_cli-0.3.1/pyproject.toml +29 -0
- filemerger_cli-0.3.1/setup.cfg +4 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
This project is licensed under the MIT License.
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2026 binaryfleet
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: filemerger-cli
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Developer CLI tool to consolidate project files into a single output
|
|
5
|
+
Author: BinaryFleet
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/binaryfleet/filemerger
|
|
8
|
+
Project-URL: Repository, https://github.com/binaryfleet/filemerger
|
|
9
|
+
Project-URL: Issues, https://github.com/binaryfleet/filemerger/issues
|
|
10
|
+
Keywords: cli,developer-tools,file-merger,llm,code-review
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pathspec>=0.11
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# FileMerger
|
|
18
|
+
|
|
19
|
+
**FileMerger** is a developer-focused CLI tool that consolidates project files into a
|
|
20
|
+
single plain-text output.
|
|
21
|
+
|
|
22
|
+
It is designed to help developers:
|
|
23
|
+
- Share complete code context with AI tools (ChatGPT, Gemini, Grok, Claude, etc.)
|
|
24
|
+
- Review large codebases
|
|
25
|
+
- Create audit or snapshot files
|
|
26
|
+
- Prepare structured input for analysis
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install filemerger-cli
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Basic Usage
|
|
39
|
+
|
|
40
|
+
Merge a directory:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
filemerger src/
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Specify output file:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
filemerger src/ --output context.txt
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Dry run (no file written):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
filemerger . --dry-run
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Output Modes
|
|
61
|
+
|
|
62
|
+
FileMerger supports multiple output modes depending on **who (or what)** will consume the output.
|
|
63
|
+
|
|
64
|
+
### 1. Default Mode (Human-Readable)
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
filemerger src/
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Use this when:**
|
|
71
|
+
|
|
72
|
+
* You want to read the output yourself
|
|
73
|
+
* You are reviewing or auditing code
|
|
74
|
+
* You want clear visual separation
|
|
75
|
+
|
|
76
|
+
**Characteristics:**
|
|
77
|
+
|
|
78
|
+
* File lists and headers
|
|
79
|
+
* Visual separators
|
|
80
|
+
* Structured, readable layout
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
### 2. LLM Mode (`--llm`)
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
filemerger src/ --llm
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Use this when:**
|
|
91
|
+
|
|
92
|
+
* The output will be pasted into an AI system
|
|
93
|
+
* You want deterministic file references
|
|
94
|
+
* You want to reduce semantic noise
|
|
95
|
+
|
|
96
|
+
**Characteristics:**
|
|
97
|
+
|
|
98
|
+
* Files are numbered (`[1]`, `[2]`, …)
|
|
99
|
+
* No decorative separators
|
|
100
|
+
* Simple, predictable structure
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
[1] path/to/file.py
|
|
106
|
+
<content>
|
|
107
|
+
|
|
108
|
+
[2] another/file.js
|
|
109
|
+
<content>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### 3. LLM Compact Mode (`--llm-compact`)
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
filemerger src/ --llm-compact
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Use this when:**
|
|
121
|
+
|
|
122
|
+
* Token limits are tight
|
|
123
|
+
* The project is very large
|
|
124
|
+
* Maximum efficiency matters
|
|
125
|
+
|
|
126
|
+
**Characteristics:**
|
|
127
|
+
|
|
128
|
+
* Same structure as `--llm`
|
|
129
|
+
* Fewer blank lines
|
|
130
|
+
* Minimal formatting overhead
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### 4. Statistics
|
|
135
|
+
|
|
136
|
+
Use `--stats` to print merge statistics:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
filemerger src/ --stats
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Reported values:
|
|
143
|
+
|
|
144
|
+
* Number of files
|
|
145
|
+
* Total lines
|
|
146
|
+
* Total bytes
|
|
147
|
+
* Skipped files (binary / non-UTF8)
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### 5. AI Marker Mode (`--ai-markers`)
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
filemerger src/ --ai-markers
|
|
155
|
+
````
|
|
156
|
+
|
|
157
|
+
**Use this when:**
|
|
158
|
+
|
|
159
|
+
* You need strong, explicit file boundaries for AI systems
|
|
160
|
+
* You want deterministic multi-file reasoning
|
|
161
|
+
* You are feeding large structured context into LLMs
|
|
162
|
+
* You need machine-parsable output
|
|
163
|
+
|
|
164
|
+
**Characteristics:**
|
|
165
|
+
|
|
166
|
+
* Explicit file boundary markers
|
|
167
|
+
* Clear begin/end delimiters
|
|
168
|
+
* Unambiguous separation between files
|
|
169
|
+
* Designed for reliable AI ingestion
|
|
170
|
+
|
|
171
|
+
Example:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
<<<FILE 1: path/to/file.py>>>
|
|
175
|
+
<content>
|
|
176
|
+
<<<END FILE>>>
|
|
177
|
+
|
|
178
|
+
<<<FILE 2: another/file.js>>>
|
|
179
|
+
<content>
|
|
180
|
+
<<<END FILE>>>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
## Configuration (Optional)
|
|
187
|
+
|
|
188
|
+
FileMerger supports an optional `.filemerger.toml` file in the project root.
|
|
189
|
+
|
|
190
|
+
Example:
|
|
191
|
+
|
|
192
|
+
```toml
|
|
193
|
+
[filters]
|
|
194
|
+
max_file_size_mb = 1
|
|
195
|
+
exclude_dirs = ["tests"]
|
|
196
|
+
|
|
197
|
+
[output]
|
|
198
|
+
separator_length = 60
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
If the file is not present, default behavior is used.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
This project is licensed under the MIT License.
|
|
208
|
+
See the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# FileMerger
|
|
2
|
+
|
|
3
|
+
**FileMerger** is a developer-focused CLI tool that consolidates project files into a
|
|
4
|
+
single plain-text output.
|
|
5
|
+
|
|
6
|
+
It is designed to help developers:
|
|
7
|
+
- Share complete code context with AI tools (ChatGPT, Gemini, Grok, Claude, etc.)
|
|
8
|
+
- Review large codebases
|
|
9
|
+
- Create audit or snapshot files
|
|
10
|
+
- Prepare structured input for analysis
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install filemerger-cli
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Basic Usage
|
|
23
|
+
|
|
24
|
+
Merge a directory:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
filemerger src/
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Specify output file:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
filemerger src/ --output context.txt
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Dry run (no file written):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
filemerger . --dry-run
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Output Modes
|
|
45
|
+
|
|
46
|
+
FileMerger supports multiple output modes depending on **who (or what)** will consume the output.
|
|
47
|
+
|
|
48
|
+
### 1. Default Mode (Human-Readable)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
filemerger src/
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Use this when:**
|
|
55
|
+
|
|
56
|
+
* You want to read the output yourself
|
|
57
|
+
* You are reviewing or auditing code
|
|
58
|
+
* You want clear visual separation
|
|
59
|
+
|
|
60
|
+
**Characteristics:**
|
|
61
|
+
|
|
62
|
+
* File lists and headers
|
|
63
|
+
* Visual separators
|
|
64
|
+
* Structured, readable layout
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### 2. LLM Mode (`--llm`)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
filemerger src/ --llm
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Use this when:**
|
|
75
|
+
|
|
76
|
+
* The output will be pasted into an AI system
|
|
77
|
+
* You want deterministic file references
|
|
78
|
+
* You want to reduce semantic noise
|
|
79
|
+
|
|
80
|
+
**Characteristics:**
|
|
81
|
+
|
|
82
|
+
* Files are numbered (`[1]`, `[2]`, …)
|
|
83
|
+
* No decorative separators
|
|
84
|
+
* Simple, predictable structure
|
|
85
|
+
|
|
86
|
+
Example:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
[1] path/to/file.py
|
|
90
|
+
<content>
|
|
91
|
+
|
|
92
|
+
[2] another/file.js
|
|
93
|
+
<content>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### 3. LLM Compact Mode (`--llm-compact`)
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
filemerger src/ --llm-compact
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Use this when:**
|
|
105
|
+
|
|
106
|
+
* Token limits are tight
|
|
107
|
+
* The project is very large
|
|
108
|
+
* Maximum efficiency matters
|
|
109
|
+
|
|
110
|
+
**Characteristics:**
|
|
111
|
+
|
|
112
|
+
* Same structure as `--llm`
|
|
113
|
+
* Fewer blank lines
|
|
114
|
+
* Minimal formatting overhead
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### 4. Statistics
|
|
119
|
+
|
|
120
|
+
Use `--stats` to print merge statistics:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
filemerger src/ --stats
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Reported values:
|
|
127
|
+
|
|
128
|
+
* Number of files
|
|
129
|
+
* Total lines
|
|
130
|
+
* Total bytes
|
|
131
|
+
* Skipped files (binary / non-UTF8)
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### 5. AI Marker Mode (`--ai-markers`)
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
filemerger src/ --ai-markers
|
|
139
|
+
````
|
|
140
|
+
|
|
141
|
+
**Use this when:**
|
|
142
|
+
|
|
143
|
+
* You need strong, explicit file boundaries for AI systems
|
|
144
|
+
* You want deterministic multi-file reasoning
|
|
145
|
+
* You are feeding large structured context into LLMs
|
|
146
|
+
* You need machine-parsable output
|
|
147
|
+
|
|
148
|
+
**Characteristics:**
|
|
149
|
+
|
|
150
|
+
* Explicit file boundary markers
|
|
151
|
+
* Clear begin/end delimiters
|
|
152
|
+
* Unambiguous separation between files
|
|
153
|
+
* Designed for reliable AI ingestion
|
|
154
|
+
|
|
155
|
+
Example:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
<<<FILE 1: path/to/file.py>>>
|
|
159
|
+
<content>
|
|
160
|
+
<<<END FILE>>>
|
|
161
|
+
|
|
162
|
+
<<<FILE 2: another/file.js>>>
|
|
163
|
+
<content>
|
|
164
|
+
<<<END FILE>>>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
## Configuration (Optional)
|
|
171
|
+
|
|
172
|
+
FileMerger supports an optional `.filemerger.toml` file in the project root.
|
|
173
|
+
|
|
174
|
+
Example:
|
|
175
|
+
|
|
176
|
+
```toml
|
|
177
|
+
[filters]
|
|
178
|
+
max_file_size_mb = 1
|
|
179
|
+
exclude_dirs = ["tests"]
|
|
180
|
+
|
|
181
|
+
[output]
|
|
182
|
+
separator_length = 60
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
If the file is not present, default behavior is used.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
This project is licensed under the MIT License.
|
|
192
|
+
See the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from .core import collect_files, merge_files
|
|
6
|
+
from .utils import normalize_output_filename
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main():
|
|
10
|
+
parser = argparse.ArgumentParser(
|
|
11
|
+
description="Consolidate project files into a single text output"
|
|
12
|
+
)
|
|
13
|
+
parser.add_argument(
|
|
14
|
+
"paths",
|
|
15
|
+
nargs="+",
|
|
16
|
+
help="Files or directories to include"
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"-o", "--output",
|
|
20
|
+
help="Output file name (always saved as .txt)"
|
|
21
|
+
)
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"--dry-run",
|
|
24
|
+
action="store_true",
|
|
25
|
+
help="Show files that would be included without writing output"
|
|
26
|
+
)
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
"--stats",
|
|
29
|
+
action="store_true",
|
|
30
|
+
help="Print merge statistics"
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--llm",
|
|
34
|
+
action="store_true",
|
|
35
|
+
help="Optimize output for LLM context ingestion"
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--llm-compact",
|
|
39
|
+
action="store_true",
|
|
40
|
+
help="More compact LLM output with fewer blank lines"
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--ai-markers",
|
|
44
|
+
action="store_true",
|
|
45
|
+
help="Use explicit AI-friendly file boundary markers"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
args = parser.parse_args()
|
|
49
|
+
|
|
50
|
+
if args.llm_compact:
|
|
51
|
+
args.llm = True
|
|
52
|
+
|
|
53
|
+
output_file = normalize_output_filename(args.output)
|
|
54
|
+
output_file = os.path.join(os.getcwd(), output_file)
|
|
55
|
+
|
|
56
|
+
files = collect_files(args.paths, output_file=output_file)
|
|
57
|
+
|
|
58
|
+
if not files:
|
|
59
|
+
print("No valid files found.")
|
|
60
|
+
sys.exit(2)
|
|
61
|
+
|
|
62
|
+
if args.dry_run:
|
|
63
|
+
print("Files to be included:")
|
|
64
|
+
for f in files:
|
|
65
|
+
print(f" - {f}")
|
|
66
|
+
|
|
67
|
+
if args.stats:
|
|
68
|
+
print("\nStats:")
|
|
69
|
+
print(f" Files: {len(files)}")
|
|
70
|
+
sys.exit(0)
|
|
71
|
+
|
|
72
|
+
stats = merge_files(
|
|
73
|
+
files,
|
|
74
|
+
output_file,
|
|
75
|
+
llm_mode=args.llm,
|
|
76
|
+
llm_compact=args.llm_compact,
|
|
77
|
+
ai_markers=args.ai_markers,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
print(f"✔ Merged {stats.files} files into {output_file}")
|
|
81
|
+
|
|
82
|
+
if args.stats:
|
|
83
|
+
print("\nStats:")
|
|
84
|
+
print(f" Files: {stats.files}")
|
|
85
|
+
print(f" Lines: {stats.lines}")
|
|
86
|
+
print(f" Bytes: {stats.bytes}")
|
|
87
|
+
print(f" Skipped files: {stats.skipped_files}")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
main()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
ALLOWED_EXTENSIONS = {
|
|
2
|
+
".py", ".js", ".json", ".html", ".css", ".txt", ".md"
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
EXCLUDED_FILES = {
|
|
6
|
+
".DS_Store",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
EXCLUDED_DIRECTORIES = {
|
|
10
|
+
"__pycache__",
|
|
11
|
+
".git",
|
|
12
|
+
".idea",
|
|
13
|
+
".vscode",
|
|
14
|
+
"node_modules",
|
|
15
|
+
"migrations",
|
|
16
|
+
"tests",
|
|
17
|
+
".pytest_cache",
|
|
18
|
+
".mypy_cache",
|
|
19
|
+
".ruff_cache",
|
|
20
|
+
"env",
|
|
21
|
+
".env",
|
|
22
|
+
".venv",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
DEFAULT_SEPARATOR_LENGTH = 90
|
|
26
|
+
MAX_FILE_SIZE_MB = 2
|
|
27
|
+
|
|
28
|
+
DEFAULT_OUTPUT_FILE = "filemerger-output.txt"
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import List, Set
|
|
3
|
+
from .filters import is_allowed_file
|
|
4
|
+
from .gitignore import load_gitignore
|
|
5
|
+
from .user_config import load_user_config
|
|
6
|
+
from .config import EXCLUDED_DIRECTORIES, MAX_FILE_SIZE_MB
|
|
7
|
+
from .format_default import DefaultFormatter
|
|
8
|
+
from .format_llm import LLMFormatter
|
|
9
|
+
from .format_ai import AIMarkerFormatter
|
|
10
|
+
from .stats import MergeStats
|
|
11
|
+
|
|
12
|
+
def collect_files(
|
|
13
|
+
paths: List[str],
|
|
14
|
+
*,
|
|
15
|
+
output_file: str | None = None,
|
|
16
|
+
) -> List[str]:
|
|
17
|
+
collected: Set[str] = set()
|
|
18
|
+
root = os.getcwd()
|
|
19
|
+
|
|
20
|
+
gitignore_spec = load_gitignore(root)
|
|
21
|
+
user_config = load_user_config()
|
|
22
|
+
|
|
23
|
+
max_mb = user_config.get("filters", {}).get("max_file_size_mb", MAX_FILE_SIZE_MB)
|
|
24
|
+
excluded_dirs = set(EXCLUDED_DIRECTORIES)
|
|
25
|
+
excluded_dirs.update(
|
|
26
|
+
user_config.get("filters", {}).get("exclude_dirs", [])
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
max_file_size_bytes = int(max_mb * 1024 * 1024)
|
|
30
|
+
|
|
31
|
+
for path in paths:
|
|
32
|
+
if os.path.isfile(path) and is_allowed_file(
|
|
33
|
+
path,
|
|
34
|
+
output_file=output_file,
|
|
35
|
+
gitignore_spec=gitignore_spec,
|
|
36
|
+
root=root,
|
|
37
|
+
max_file_size_bytes=max_file_size_bytes,
|
|
38
|
+
excluded_dirs=excluded_dirs,
|
|
39
|
+
):
|
|
40
|
+
collected.add(os.path.abspath(path))
|
|
41
|
+
|
|
42
|
+
elif os.path.isdir(path):
|
|
43
|
+
for current_root, _, files in os.walk(path):
|
|
44
|
+
for name in sorted(files):
|
|
45
|
+
full_path = os.path.join(current_root, name)
|
|
46
|
+
if is_allowed_file(
|
|
47
|
+
full_path,
|
|
48
|
+
output_file=output_file,
|
|
49
|
+
gitignore_spec=gitignore_spec,
|
|
50
|
+
root=root,
|
|
51
|
+
max_file_size_bytes=max_file_size_bytes,
|
|
52
|
+
excluded_dirs=excluded_dirs,
|
|
53
|
+
):
|
|
54
|
+
collected.add(os.path.abspath(full_path))
|
|
55
|
+
|
|
56
|
+
return sorted(collected)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def merge_files(
|
|
60
|
+
files: List[str],
|
|
61
|
+
output_file: str,
|
|
62
|
+
*,
|
|
63
|
+
llm_mode: bool = False,
|
|
64
|
+
llm_compact: bool = False,
|
|
65
|
+
ai_markers: bool = False,
|
|
66
|
+
) -> MergeStats:
|
|
67
|
+
|
|
68
|
+
if ai_markers:
|
|
69
|
+
formatter = AIMarkerFormatter()
|
|
70
|
+
elif llm_mode or llm_compact:
|
|
71
|
+
formatter = LLMFormatter(compact=llm_compact)
|
|
72
|
+
else:
|
|
73
|
+
formatter = DefaultFormatter()
|
|
74
|
+
|
|
75
|
+
return formatter.write(files, output_file)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from .gitignore import is_ignored
|
|
3
|
+
|
|
4
|
+
def is_allowed_file(
|
|
5
|
+
path: str,
|
|
6
|
+
*,
|
|
7
|
+
output_file: str | None = None,
|
|
8
|
+
gitignore_spec=None,
|
|
9
|
+
root: str | None = None,
|
|
10
|
+
max_file_size_bytes: int,
|
|
11
|
+
excluded_dirs: set[str],
|
|
12
|
+
) -> bool:
|
|
13
|
+
if not os.path.isfile(path):
|
|
14
|
+
return False
|
|
15
|
+
|
|
16
|
+
if output_file and os.path.abspath(path) == os.path.abspath(output_file):
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
if gitignore_spec and root and is_ignored(path, root=root, spec=gitignore_spec):
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
if os.path.splitext(path)[1].lower() not in {
|
|
23
|
+
".py", ".js", ".json", ".html", ".css", ".txt", ".md"
|
|
24
|
+
}:
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
parts = path.split(os.sep)
|
|
28
|
+
if any(part in excluded_dirs for part in parts):
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
if os.path.getsize(path) > max_file_size_bytes:
|
|
33
|
+
return False
|
|
34
|
+
except OSError:
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
return True
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from .stats import MergeStats
|
|
3
|
+
|
|
4
|
+
class AIMarkerFormatter:
|
|
5
|
+
"""
|
|
6
|
+
Explicit AI-friendly formatter using strong file boundary markers.
|
|
7
|
+
|
|
8
|
+
Format:
|
|
9
|
+
|
|
10
|
+
<<<FILE 1: path/to/file.py>>>
|
|
11
|
+
<content>
|
|
12
|
+
<<<END FILE>>>
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def write(self, files: List[str], output_file: str) -> MergeStats:
|
|
17
|
+
stats = MergeStats(files=len(files))
|
|
18
|
+
|
|
19
|
+
with open(output_file, "w", encoding="utf-8") as out:
|
|
20
|
+
for idx, file_path in enumerate(files, start=1):
|
|
21
|
+
header = f"<<<FILE {idx}: {file_path}>>>\n"
|
|
22
|
+
out.write(header)
|
|
23
|
+
stats.lines += 1
|
|
24
|
+
stats.bytes += len(header.encode("utf-8"))
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
28
|
+
content = f.read().rstrip() + "\n"
|
|
29
|
+
out.write(content)
|
|
30
|
+
stats.lines += content.count("\n")
|
|
31
|
+
stats.bytes += len(content.encode("utf-8"))
|
|
32
|
+
except UnicodeDecodeError:
|
|
33
|
+
skipped = "[Skipped: binary or non-UTF8 file]\n"
|
|
34
|
+
out.write(skipped)
|
|
35
|
+
stats.lines += 1
|
|
36
|
+
stats.bytes += len(skipped.encode("utf-8"))
|
|
37
|
+
stats.skipped_files += 1
|
|
38
|
+
|
|
39
|
+
footer = "<<<END FILE>>>\n\n"
|
|
40
|
+
out.write(footer)
|
|
41
|
+
stats.lines += 2
|
|
42
|
+
stats.bytes += len(footer.encode("utf-8"))
|
|
43
|
+
|
|
44
|
+
return stats
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from .stats import MergeStats
|
|
3
|
+
from .user_config import load_user_config
|
|
4
|
+
from .config import DEFAULT_SEPARATOR_LENGTH
|
|
5
|
+
|
|
6
|
+
class DefaultFormatter:
|
|
7
|
+
def write(self, files: List[str], output_file: str) -> MergeStats:
|
|
8
|
+
user_config = load_user_config()
|
|
9
|
+
sep_len = user_config.get("output", {}).get(
|
|
10
|
+
"separator_length", DEFAULT_SEPARATOR_LENGTH
|
|
11
|
+
)
|
|
12
|
+
separator = "-" * int(sep_len)
|
|
13
|
+
|
|
14
|
+
stats = MergeStats(files=len(files))
|
|
15
|
+
|
|
16
|
+
with open(output_file, "w", encoding="utf-8") as out:
|
|
17
|
+
header = "FILES INCLUDED\n" + separator + "\n"
|
|
18
|
+
out.write(header)
|
|
19
|
+
stats.lines += header.count("\n")
|
|
20
|
+
stats.bytes += len(header.encode("utf-8"))
|
|
21
|
+
|
|
22
|
+
for f in files:
|
|
23
|
+
line = f"{f}\n"
|
|
24
|
+
out.write(line)
|
|
25
|
+
stats.lines += 1
|
|
26
|
+
stats.bytes += len(line.encode("utf-8"))
|
|
27
|
+
|
|
28
|
+
out.write("\n")
|
|
29
|
+
stats.lines += 1
|
|
30
|
+
stats.bytes += 1
|
|
31
|
+
|
|
32
|
+
for file_path in files:
|
|
33
|
+
block_header = (
|
|
34
|
+
f"{separator}\nFILE: {file_path}\n{separator}\n"
|
|
35
|
+
)
|
|
36
|
+
out.write(block_header)
|
|
37
|
+
stats.lines += block_header.count("\n")
|
|
38
|
+
stats.bytes += len(block_header.encode("utf-8"))
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
42
|
+
content = f.read().rstrip() + "\n"
|
|
43
|
+
out.write(content)
|
|
44
|
+
stats.lines += content.count("\n")
|
|
45
|
+
stats.bytes += len(content.encode("utf-8"))
|
|
46
|
+
except UnicodeDecodeError:
|
|
47
|
+
skipped = "[Skipped: binary or non-UTF8 file]\n"
|
|
48
|
+
out.write(skipped)
|
|
49
|
+
stats.lines += 1
|
|
50
|
+
stats.bytes += len(skipped.encode("utf-8"))
|
|
51
|
+
stats.skipped_files += 1
|
|
52
|
+
|
|
53
|
+
return stats
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from .stats import MergeStats
|
|
3
|
+
|
|
4
|
+
class LLMFormatter:
|
|
5
|
+
def __init__(self, *, compact: bool = False):
|
|
6
|
+
self.compact = compact
|
|
7
|
+
|
|
8
|
+
def write(self, files: List[str], output_file: str) -> MergeStats:
|
|
9
|
+
stats = MergeStats(files=len(files))
|
|
10
|
+
|
|
11
|
+
with open(output_file, "w", encoding="utf-8") as out:
|
|
12
|
+
for idx, file_path in enumerate(files, start=1):
|
|
13
|
+
header = f"[{idx}] {file_path}\n"
|
|
14
|
+
out.write(header)
|
|
15
|
+
stats.lines += 1
|
|
16
|
+
stats.bytes += len(header.encode("utf-8"))
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
20
|
+
content = f.read().rstrip() + ("\n" if self.compact else "\n\n")
|
|
21
|
+
out.write(content)
|
|
22
|
+
stats.lines += content.count("\n")
|
|
23
|
+
stats.bytes += len(content.encode("utf-8"))
|
|
24
|
+
except UnicodeDecodeError:
|
|
25
|
+
skipped = "[Skipped: binary or non-UTF8 file]\n"
|
|
26
|
+
out.write(skipped)
|
|
27
|
+
stats.lines += 1
|
|
28
|
+
stats.bytes += len(skipped.encode("utf-8"))
|
|
29
|
+
stats.skipped_files += 1
|
|
30
|
+
|
|
31
|
+
if not self.compact:
|
|
32
|
+
out.write("\n")
|
|
33
|
+
stats.lines += 1
|
|
34
|
+
stats.bytes += 1
|
|
35
|
+
|
|
36
|
+
return stats
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pathspec
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
def load_gitignore(root: str) -> Optional[pathspec.PathSpec]:
|
|
6
|
+
"""
|
|
7
|
+
Load .gitignore from the given root directory.
|
|
8
|
+
Returns None if no .gitignore exists.
|
|
9
|
+
"""
|
|
10
|
+
gitignore_path = os.path.join(root, ".gitignore")
|
|
11
|
+
if not os.path.isfile(gitignore_path):
|
|
12
|
+
return None
|
|
13
|
+
|
|
14
|
+
with open(gitignore_path, "r", encoding="utf-8") as f:
|
|
15
|
+
lines = f.read().splitlines()
|
|
16
|
+
|
|
17
|
+
return pathspec.PathSpec.from_lines("gitwildmatch", lines)
|
|
18
|
+
|
|
19
|
+
def is_ignored(
|
|
20
|
+
path: str,
|
|
21
|
+
*,
|
|
22
|
+
root: str,
|
|
23
|
+
spec: Optional[pathspec.PathSpec],
|
|
24
|
+
) -> bool:
|
|
25
|
+
if not spec:
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
rel_path = os.path.relpath(path, root)
|
|
29
|
+
return spec.match_file(rel_path)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tomllib
|
|
3
|
+
from typing import Dict, Any
|
|
4
|
+
|
|
5
|
+
def load_user_config() -> Dict[str, Any]:
|
|
6
|
+
"""
|
|
7
|
+
Load .filemerger.toml from the current working directory.
|
|
8
|
+
Returns empty dict if not found.
|
|
9
|
+
"""
|
|
10
|
+
config_path = os.path.join(os.getcwd(), ".filemerger.toml")
|
|
11
|
+
if not os.path.isfile(config_path):
|
|
12
|
+
return {}
|
|
13
|
+
|
|
14
|
+
with open(config_path, "rb") as f:
|
|
15
|
+
return tomllib.load(f)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from .config import DEFAULT_OUTPUT_FILE
|
|
3
|
+
|
|
4
|
+
def normalize_output_filename(output: str | None) -> str:
|
|
5
|
+
"""
|
|
6
|
+
Normalize output filename:
|
|
7
|
+
- Default to DEFAULT_OUTPUT_FILE
|
|
8
|
+
- Force .txt extension
|
|
9
|
+
- Strip directory components (always write to CWD)
|
|
10
|
+
"""
|
|
11
|
+
if not output:
|
|
12
|
+
return DEFAULT_OUTPUT_FILE
|
|
13
|
+
|
|
14
|
+
base = os.path.basename(output)
|
|
15
|
+
name, ext = os.path.splitext(base)
|
|
16
|
+
|
|
17
|
+
if ext.lower() != ".txt":
|
|
18
|
+
return f"{name}.txt"
|
|
19
|
+
|
|
20
|
+
return base
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: filemerger-cli
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Developer CLI tool to consolidate project files into a single output
|
|
5
|
+
Author: BinaryFleet
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/binaryfleet/filemerger
|
|
8
|
+
Project-URL: Repository, https://github.com/binaryfleet/filemerger
|
|
9
|
+
Project-URL: Issues, https://github.com/binaryfleet/filemerger/issues
|
|
10
|
+
Keywords: cli,developer-tools,file-merger,llm,code-review
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pathspec>=0.11
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# FileMerger
|
|
18
|
+
|
|
19
|
+
**FileMerger** is a developer-focused CLI tool that consolidates project files into a
|
|
20
|
+
single plain-text output.
|
|
21
|
+
|
|
22
|
+
It is designed to help developers:
|
|
23
|
+
- Share complete code context with AI tools (ChatGPT, Gemini, Grok, Claude, etc.)
|
|
24
|
+
- Review large codebases
|
|
25
|
+
- Create audit or snapshot files
|
|
26
|
+
- Prepare structured input for analysis
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install filemerger-cli
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Basic Usage
|
|
39
|
+
|
|
40
|
+
Merge a directory:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
filemerger src/
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Specify output file:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
filemerger src/ --output context.txt
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Dry run (no file written):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
filemerger . --dry-run
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Output Modes
|
|
61
|
+
|
|
62
|
+
FileMerger supports multiple output modes depending on **who (or what)** will consume the output.
|
|
63
|
+
|
|
64
|
+
### 1. Default Mode (Human-Readable)
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
filemerger src/
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Use this when:**
|
|
71
|
+
|
|
72
|
+
* You want to read the output yourself
|
|
73
|
+
* You are reviewing or auditing code
|
|
74
|
+
* You want clear visual separation
|
|
75
|
+
|
|
76
|
+
**Characteristics:**
|
|
77
|
+
|
|
78
|
+
* File lists and headers
|
|
79
|
+
* Visual separators
|
|
80
|
+
* Structured, readable layout
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
### 2. LLM Mode (`--llm`)
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
filemerger src/ --llm
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Use this when:**
|
|
91
|
+
|
|
92
|
+
* The output will be pasted into an AI system
|
|
93
|
+
* You want deterministic file references
|
|
94
|
+
* You want to reduce semantic noise
|
|
95
|
+
|
|
96
|
+
**Characteristics:**
|
|
97
|
+
|
|
98
|
+
* Files are numbered (`[1]`, `[2]`, …)
|
|
99
|
+
* No decorative separators
|
|
100
|
+
* Simple, predictable structure
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
[1] path/to/file.py
|
|
106
|
+
<content>
|
|
107
|
+
|
|
108
|
+
[2] another/file.js
|
|
109
|
+
<content>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### 3. LLM Compact Mode (`--llm-compact`)
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
filemerger src/ --llm-compact
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Use this when:**
|
|
121
|
+
|
|
122
|
+
* Token limits are tight
|
|
123
|
+
* The project is very large
|
|
124
|
+
* Maximum efficiency matters
|
|
125
|
+
|
|
126
|
+
**Characteristics:**
|
|
127
|
+
|
|
128
|
+
* Same structure as `--llm`
|
|
129
|
+
* Fewer blank lines
|
|
130
|
+
* Minimal formatting overhead
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### 4. Statistics
|
|
135
|
+
|
|
136
|
+
Use `--stats` to print merge statistics:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
filemerger src/ --stats
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Reported values:
|
|
143
|
+
|
|
144
|
+
* Number of files
|
|
145
|
+
* Total lines
|
|
146
|
+
* Total bytes
|
|
147
|
+
* Skipped files (binary / non-UTF8)
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### 5. AI Marker Mode (`--ai-markers`)
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
filemerger src/ --ai-markers
|
|
155
|
+
````
|
|
156
|
+
|
|
157
|
+
**Use this when:**
|
|
158
|
+
|
|
159
|
+
* You need strong, explicit file boundaries for AI systems
|
|
160
|
+
* You want deterministic multi-file reasoning
|
|
161
|
+
* You are feeding large structured context into LLMs
|
|
162
|
+
* You need machine-parsable output
|
|
163
|
+
|
|
164
|
+
**Characteristics:**
|
|
165
|
+
|
|
166
|
+
* Explicit file boundary markers
|
|
167
|
+
* Clear begin/end delimiters
|
|
168
|
+
* Unambiguous separation between files
|
|
169
|
+
* Designed for reliable AI ingestion
|
|
170
|
+
|
|
171
|
+
Example:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
<<<FILE 1: path/to/file.py>>>
|
|
175
|
+
<content>
|
|
176
|
+
<<<END FILE>>>
|
|
177
|
+
|
|
178
|
+
<<<FILE 2: another/file.js>>>
|
|
179
|
+
<content>
|
|
180
|
+
<<<END FILE>>>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
## Configuration (Optional)
|
|
187
|
+
|
|
188
|
+
FileMerger supports an optional `.filemerger.toml` file in the project root.
|
|
189
|
+
|
|
190
|
+
Example:
|
|
191
|
+
|
|
192
|
+
```toml
|
|
193
|
+
[filters]
|
|
194
|
+
max_file_size_mb = 1
|
|
195
|
+
exclude_dirs = ["tests"]
|
|
196
|
+
|
|
197
|
+
[output]
|
|
198
|
+
separator_length = 60
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
If the file is not present, default behavior is used.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
This project is licensed under the MIT License.
|
|
208
|
+
See the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
filemerger/__init__.py
|
|
5
|
+
filemerger/cli.py
|
|
6
|
+
filemerger/config.py
|
|
7
|
+
filemerger/core.py
|
|
8
|
+
filemerger/filters.py
|
|
9
|
+
filemerger/format_ai.py
|
|
10
|
+
filemerger/format_default.py
|
|
11
|
+
filemerger/format_llm.py
|
|
12
|
+
filemerger/formatter.py
|
|
13
|
+
filemerger/gitignore.py
|
|
14
|
+
filemerger/stats.py
|
|
15
|
+
filemerger/user_config.py
|
|
16
|
+
filemerger/utils.py
|
|
17
|
+
filemerger_cli.egg-info/PKG-INFO
|
|
18
|
+
filemerger_cli.egg-info/SOURCES.txt
|
|
19
|
+
filemerger_cli.egg-info/dependency_links.txt
|
|
20
|
+
filemerger_cli.egg-info/entry_points.txt
|
|
21
|
+
filemerger_cli.egg-info/requires.txt
|
|
22
|
+
filemerger_cli.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pathspec>=0.11
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
filemerger
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "filemerger-cli"
|
|
3
|
+
version = "0.3.1"
|
|
4
|
+
description = "Developer CLI tool to consolidate project files into a single output"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.8"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "BinaryFleet" }
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
keywords = ["cli", "developer-tools", "file-merger", "llm", "code-review"]
|
|
14
|
+
|
|
15
|
+
dependencies = [
|
|
16
|
+
"pathspec>=0.11"
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://github.com/binaryfleet/filemerger"
|
|
21
|
+
Repository = "https://github.com/binaryfleet/filemerger"
|
|
22
|
+
Issues = "https://github.com/binaryfleet/filemerger/issues"
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
filemerger = "filemerger.cli:main"
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["setuptools>=61.0"]
|
|
29
|
+
build-backend = "setuptools.build_meta"
|