@xelth/eck-snapshot 2.2.0 → 3.0.0
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.
- package/LICENSE +21 -0
- package/README.md +205 -139
- package/index.js +251 -56
- package/package.json +2 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dmytro Surovtsev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -3,140 +3,124 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@xelth/eck-snapshot)
|
|
4
4
|
[](https://github.com/xelth-com/eckSnapshot/blob/main/LICENSE)
|
|
5
5
|
|
|
6
|
-
A CLI tool to create and restore single-file text snapshots of
|
|
6
|
+
A powerful CLI tool to create and restore single-file text snapshots of Git repositories and directories. Generate comprehensive snapshots containing directory structure and file contents, optimized for providing context to Large Language Models (LLMs) like Claude, Gemini, and ChatGPT.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## ✨ What's New in v3.0.0
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
🎯 **Universal Directory Support**: Works with any directory, not just Git repositories
|
|
11
|
+
🤖 **Enhanced AI Instructions**: Improved headers with detailed guidance for AI assistants
|
|
12
|
+
⚡ **Auto-Detection**: Automatically switches to directory mode when Git isn't available
|
|
13
|
+
🧹 **Clean Mode**: Option to create snapshots without AI instructions
|
|
11
14
|
|
|
12
|
-
eck-snapshot
|
|
15
|
+
## Why eck-snapshot?
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
When working with Large Language Models (LLMs), providing complete project context is crucial for accurate results. Manually copying and pasting dozens of files is tedious and error-prone.
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
* **Intelligent Ignoring**: Respects `.gitignore` rules and has its own configurable ignore lists for files, extensions, and directories.
|
|
18
|
-
* **Advanced Restore**: Powerful `restore` command with filtering, dry-run mode, and parallel processing.
|
|
19
|
-
* **Directory Tree**: Generates a clean, readable tree of the repository structure at the top of the snapshot.
|
|
20
|
-
* **Multiple Formats**: Supports both plain text and JSON output formats.
|
|
21
|
-
* **Configurable**: Customize behavior using an `.ecksnapshot.config.js` file.
|
|
22
|
-
* **Progress and Stats**: Provides a progress bar and a detailed summary of what was included and skipped.
|
|
23
|
-
* **Compression**: Supports gzipped (`.gz`) snapshots for smaller file sizes.
|
|
24
|
-
* **Security**: Built-in path validation to prevent directory traversal attacks during restore.
|
|
19
|
+
eck-snapshot automates this by generating a single, comprehensive text file of your entire project. This is particularly effective with models that support large context windows (like Gemini 2.0 Pro with 1M tokens), allowing the entire project to be analyzed at once.
|
|
25
20
|
|
|
26
|
-
##
|
|
21
|
+
## 🚀 Key Features
|
|
27
22
|
|
|
28
|
-
|
|
23
|
+
### 📁 **Universal Compatibility**
|
|
24
|
+
- **Git Repositories**: Leverages `git ls-files` and respects `.gitignore`
|
|
25
|
+
- **Any Directory**: Recursively scans any folder structure
|
|
26
|
+
- **Auto-Detection**: Automatically switches modes based on Git availability
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
🌳 Generating directory tree...
|
|
35
|
-
📝 Processing files...
|
|
36
|
-
Progress |██████████████████████████████| 100% | 152/152 files
|
|
37
|
-
|
|
38
|
-
📊 Snapshot Summary
|
|
39
|
-
==================================================
|
|
40
|
-
🎉 Snapshot created successfully!
|
|
41
|
-
📄 File saved to: /path/to/your/project/snapshots/project_snapshot_...txt
|
|
42
|
-
📈 Included text files: 130 of 152
|
|
43
|
-
⏭️ Skipped files: 22
|
|
44
|
-
...
|
|
45
|
-
==================================================
|
|
46
|
-
```
|
|
28
|
+
### 🤖 **AI-Optimized**
|
|
29
|
+
- **Structured Headers**: Detailed instructions for AI assistants
|
|
30
|
+
- **Clean Mode**: Option to skip AI headers for general use
|
|
31
|
+
- **LLM-Ready Format**: Optimized for Claude, Gemini, ChatGPT, and other models
|
|
47
32
|
|
|
48
|
-
|
|
33
|
+
### ⚡ **Advanced Features**
|
|
34
|
+
- **Multiple Formats**: Plain text (Markdown) and JSON output
|
|
35
|
+
- **Compression**: Built-in gzip support for smaller files
|
|
36
|
+
- **Smart Filtering**: Configurable ignore patterns and size limits
|
|
37
|
+
- **Restore Capability**: Recreate entire project structures from snapshots
|
|
38
|
+
- **Progress Tracking**: Real-time progress bars and detailed statistics
|
|
49
39
|
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
### 🔒 **Security & Performance**
|
|
41
|
+
- **Path Validation**: Prevents directory traversal attacks
|
|
42
|
+
- **Parallel Processing**: Concurrent file handling for speed
|
|
43
|
+
- **Memory Efficient**: Handles large projects without memory issues
|
|
52
44
|
|
|
53
|
-
|
|
54
|
-
│ └── workflows/
|
|
55
|
-
│ └── publish.yml
|
|
56
|
-
├── src/
|
|
57
|
-
│ ├── utils/
|
|
58
|
-
│ │ └── formatters.js
|
|
59
|
-
│ └── index.js
|
|
60
|
-
├── .gitignore
|
|
61
|
-
├── package.json
|
|
62
|
-
└── README.md
|
|
45
|
+
## 📦 Installation
|
|
63
46
|
|
|
47
|
+
```bash
|
|
48
|
+
npm install -g @xelth/eck-snapshot
|
|
49
|
+
```
|
|
64
50
|
|
|
65
|
-
|
|
51
|
+
## 🎯 Quick Start
|
|
66
52
|
|
|
67
|
-
|
|
68
|
-
import { Command } from 'commander';
|
|
69
|
-
// ... rest of the file content
|
|
53
|
+
### Create Snapshots
|
|
70
54
|
|
|
71
|
-
|
|
55
|
+
```bash
|
|
56
|
+
# Git repository (default mode)
|
|
57
|
+
eck-snapshot
|
|
72
58
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"version": "2.1.0",
|
|
76
|
-
// ... rest of the file content
|
|
77
|
-
```
|
|
59
|
+
# Any directory (auto-detects non-git folders)
|
|
60
|
+
eck-snapshot /path/to/any/folder
|
|
78
61
|
|
|
79
|
-
|
|
62
|
+
# Force directory mode (ignores git)
|
|
63
|
+
eck-snapshot --dir .
|
|
80
64
|
|
|
81
|
-
|
|
65
|
+
# Clean snapshot without AI instructions
|
|
66
|
+
eck-snapshot --no-ai-header
|
|
82
67
|
|
|
83
|
-
|
|
84
|
-
|
|
68
|
+
# Compressed JSON format
|
|
69
|
+
eck-snapshot --format json --compress
|
|
85
70
|
```
|
|
86
71
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Once installed, you can run the tool from any directory in your terminal.
|
|
90
|
-
|
|
91
|
-
### Creating a Snapshot
|
|
72
|
+
### Restore from Snapshots
|
|
92
73
|
|
|
93
74
|
```bash
|
|
94
|
-
#
|
|
95
|
-
eck-snapshot
|
|
75
|
+
# Basic restore
|
|
76
|
+
eck-snapshot restore snapshot.md
|
|
96
77
|
|
|
97
|
-
#
|
|
98
|
-
eck-snapshot
|
|
78
|
+
# Restore to specific directory
|
|
79
|
+
eck-snapshot restore snapshot.md ./restored-project
|
|
99
80
|
|
|
100
|
-
#
|
|
101
|
-
eck-snapshot
|
|
81
|
+
# Preview without writing files
|
|
82
|
+
eck-snapshot restore snapshot.md --dry-run
|
|
102
83
|
|
|
103
|
-
#
|
|
104
|
-
eck-snapshot --
|
|
105
|
-
|
|
106
|
-
# Include hidden files and set custom size limits
|
|
107
|
-
eck-snapshot --include-hidden --max-file-size 5MB --max-total-size 50MB
|
|
84
|
+
# Restore only specific files
|
|
85
|
+
eck-snapshot restore snapshot.md --include "*.js" "*.json"
|
|
108
86
|
```
|
|
109
87
|
|
|
110
|
-
|
|
88
|
+
## 📋 Usage Examples
|
|
89
|
+
|
|
90
|
+
### For AI Development
|
|
111
91
|
|
|
112
92
|
```bash
|
|
113
|
-
#
|
|
114
|
-
eck-snapshot
|
|
93
|
+
# Create AI-optimized snapshot for Gemini/Claude
|
|
94
|
+
eck-snapshot --format md --compress
|
|
95
|
+
# Result: project_snapshot_2025-01-19_12-00-00.md.gz
|
|
115
96
|
|
|
116
|
-
#
|
|
117
|
-
eck-snapshot
|
|
97
|
+
# Clean snapshot for general documentation
|
|
98
|
+
eck-snapshot --no-ai-header --output ./docs
|
|
99
|
+
```
|
|
118
100
|
|
|
119
|
-
|
|
120
|
-
eck-snapshot restore snapshot.txt --dry-run
|
|
101
|
+
### Project Backup & Migration
|
|
121
102
|
|
|
122
|
-
|
|
123
|
-
|
|
103
|
+
```bash
|
|
104
|
+
# Full project backup
|
|
105
|
+
eck-snapshot --include-hidden --format json --compress
|
|
124
106
|
|
|
125
|
-
#
|
|
126
|
-
eck-snapshot restore
|
|
107
|
+
# Selective restore
|
|
108
|
+
eck-snapshot restore backup.json.gz --exclude "node_modules/*" --include "src/*"
|
|
109
|
+
```
|
|
127
110
|
|
|
128
|
-
|
|
129
|
-
eck-snapshot restore snapshot.txt --concurrency 20 --verbose
|
|
111
|
+
### Cross-Platform Development
|
|
130
112
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
113
|
+
```bash
|
|
114
|
+
# Create snapshot on Windows
|
|
115
|
+
eck-snapshot --output ./transfer
|
|
134
116
|
|
|
135
|
-
|
|
117
|
+
# Restore on Linux/Mac
|
|
118
|
+
eck-snapshot restore transfer/project_snapshot.md ./project
|
|
119
|
+
```
|
|
136
120
|
|
|
137
|
-
|
|
121
|
+
## ⚙️ Configuration
|
|
138
122
|
|
|
139
|
-
|
|
123
|
+
Create `.ecksnapshot.config.js` in your project root:
|
|
140
124
|
|
|
141
125
|
```javascript
|
|
142
126
|
export default {
|
|
@@ -144,19 +128,29 @@ export default {
|
|
|
144
128
|
filesToIgnore: [
|
|
145
129
|
'package-lock.json',
|
|
146
130
|
'*.log',
|
|
131
|
+
'*.tmp'
|
|
147
132
|
],
|
|
133
|
+
|
|
148
134
|
// File extensions to ignore
|
|
149
135
|
extensionsToIgnore: [
|
|
150
136
|
'.sqlite3',
|
|
151
137
|
'.env',
|
|
138
|
+
'.DS_Store',
|
|
139
|
+
'.ico',
|
|
140
|
+
'.png',
|
|
141
|
+
'.jpg'
|
|
152
142
|
],
|
|
153
|
-
|
|
143
|
+
|
|
144
|
+
// Directories to ignore
|
|
154
145
|
dirsToIgnore: [
|
|
155
146
|
'node_modules/',
|
|
156
147
|
'.git/',
|
|
157
148
|
'dist/',
|
|
149
|
+
'build/',
|
|
150
|
+
'coverage/'
|
|
158
151
|
],
|
|
159
|
-
|
|
152
|
+
|
|
153
|
+
// Size and performance limits
|
|
160
154
|
maxFileSize: '10MB',
|
|
161
155
|
maxTotalSize: '100MB',
|
|
162
156
|
maxDepth: 10,
|
|
@@ -164,63 +158,135 @@ export default {
|
|
|
164
158
|
};
|
|
165
159
|
```
|
|
166
160
|
|
|
167
|
-
##
|
|
161
|
+
## 📖 Command Reference
|
|
162
|
+
|
|
163
|
+
### Snapshot Command
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
eck-snapshot [options] [path]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Core Options:**
|
|
170
|
+
- `-o, --output <dir>` - Output directory (default: ./snapshots)
|
|
171
|
+
- `-d, --dir` - Directory mode: scan any folder recursively
|
|
172
|
+
- `--no-ai-header` - Skip AI instruction header (clean mode)
|
|
173
|
+
- `-v, --verbose` - Show detailed processing information
|
|
168
174
|
|
|
169
|
-
|
|
175
|
+
**Format & Compression:**
|
|
176
|
+
- `--format <type>` - Output format: md (default) or json
|
|
177
|
+
- `--compress` - Create gzipped output (.gz extension)
|
|
178
|
+
- `--no-tree` - Exclude directory tree from output
|
|
170
179
|
|
|
171
|
-
|
|
180
|
+
**Filtering:**
|
|
181
|
+
- `--include-hidden` - Include hidden files (starting with .)
|
|
182
|
+
- `--max-file-size <size>` - Maximum individual file size (e.g., 5MB)
|
|
183
|
+
- `--max-total-size <size>` - Maximum total snapshot size (e.g., 50MB)
|
|
184
|
+
- `--config <path>` - Path to custom configuration file
|
|
172
185
|
|
|
173
|
-
|
|
174
|
-
- **`--include <patterns>`**: Only restore files matching the specified patterns (supports wildcards)
|
|
175
|
-
- **`--exclude <patterns>`**: Skip files matching the specified patterns (supports wildcards)
|
|
176
|
-
- **`--concurrency <number>`**: Control how many files are processed simultaneously (default: 10)
|
|
177
|
-
- **`--force`**: Skip confirmation prompts and overwrite existing files
|
|
178
|
-
- **`--verbose`**: Show detailed information about each file being processed
|
|
186
|
+
### Restore Command
|
|
179
187
|
|
|
180
|
-
|
|
188
|
+
```bash
|
|
189
|
+
eck-snapshot restore [options] <snapshot_file> [target_directory]
|
|
190
|
+
```
|
|
181
191
|
|
|
182
|
-
|
|
183
|
-
-
|
|
184
|
-
-
|
|
192
|
+
**Control Options:**
|
|
193
|
+
- `-f, --force` - Skip confirmation prompts
|
|
194
|
+
- `--dry-run` - Preview without writing files
|
|
195
|
+
- `-v, --verbose` - Show detailed processing information
|
|
185
196
|
|
|
186
|
-
|
|
197
|
+
**Filtering:**
|
|
198
|
+
- `--include <patterns>` - Include only matching files (wildcards supported)
|
|
199
|
+
- `--exclude <patterns>` - Exclude matching files (wildcards supported)
|
|
200
|
+
- `--concurrency <number>` - Number of concurrent operations (default: 10)
|
|
187
201
|
|
|
188
|
-
|
|
189
|
-
- **File Sanitization**: Validates file paths and names for security
|
|
190
|
-
- **Confirmation Prompts**: Requires user confirmation before overwriting files (unless `--force` is used)
|
|
202
|
+
## 🎭 Working with AI Models
|
|
191
203
|
|
|
192
|
-
|
|
204
|
+
### For Gemini 2.0 Pro (1M context)
|
|
205
|
+
```bash
|
|
206
|
+
# Create comprehensive snapshot with AI instructions
|
|
207
|
+
eck-snapshot --format md --compress
|
|
208
|
+
```
|
|
209
|
+
The generated file includes detailed instructions for Gemini to analyze your project and provide structured commands for Claude Code.
|
|
193
210
|
|
|
194
|
-
###
|
|
211
|
+
### For Claude Code
|
|
212
|
+
```bash
|
|
213
|
+
# Clean, focused snapshot
|
|
214
|
+
eck-snapshot --no-ai-header --max-total-size 200MB
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### For ChatGPT/Other Models
|
|
195
218
|
```bash
|
|
196
|
-
|
|
219
|
+
# Standard snapshot with moderate size limits
|
|
220
|
+
eck-snapshot --max-total-size 50MB --no-ai-header
|
|
197
221
|
```
|
|
198
222
|
|
|
199
|
-
|
|
200
|
-
- `-o, --output <dir>`: Output directory for snapshots
|
|
201
|
-
- `--no-tree`: Exclude directory tree from output
|
|
202
|
-
- `-v, --verbose`: Show detailed processing information
|
|
203
|
-
- `--max-file-size <size>`: Maximum individual file size (e.g., 10MB)
|
|
204
|
-
- `--max-total-size <size>`: Maximum total snapshot size (e.g., 100MB)
|
|
205
|
-
- `--max-depth <number>`: Maximum directory depth for tree generation
|
|
206
|
-
- `--config <path>`: Path to custom configuration file
|
|
207
|
-
- `--compress`: Create gzipped output
|
|
208
|
-
- `--include-hidden`: Include hidden files (starting with .)
|
|
209
|
-
- `--format <type>`: Output format: txt or json
|
|
223
|
+
## 🔧 Advanced Use Cases
|
|
210
224
|
|
|
211
|
-
###
|
|
225
|
+
### Monorepo Support
|
|
212
226
|
```bash
|
|
213
|
-
|
|
227
|
+
# Snapshot specific package in monorepo
|
|
228
|
+
eck-snapshot ./packages/core --dir --output ./snapshots/core
|
|
229
|
+
|
|
230
|
+
# Multiple packages
|
|
231
|
+
eck-snapshot ./packages/api --dir && eck-snapshot ./packages/web --dir
|
|
214
232
|
```
|
|
215
233
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
-
|
|
234
|
+
### CI/CD Integration
|
|
235
|
+
```bash
|
|
236
|
+
# Create release snapshot
|
|
237
|
+
eck-snapshot --format json --compress --output ./artifacts
|
|
238
|
+
|
|
239
|
+
# Documentation generation
|
|
240
|
+
eck-snapshot --no-ai-header --format md --output ./docs/snapshots
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Migration & Archival
|
|
244
|
+
```bash
|
|
245
|
+
# Complete project archive
|
|
246
|
+
eck-snapshot --include-hidden --format json --compress --max-total-size 1GB
|
|
247
|
+
|
|
248
|
+
# Selective migration
|
|
249
|
+
eck-snapshot restore archive.json.gz --include "src/*" "docs/*" --exclude "*.test.*"
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## 📊 Output Formats
|
|
253
|
+
|
|
254
|
+
### Markdown Format (Default)
|
|
255
|
+
- Human-readable structure
|
|
256
|
+
- AI instruction headers (optional)
|
|
257
|
+
- Directory tree visualization
|
|
258
|
+
- File content with clear delimiters
|
|
259
|
+
|
|
260
|
+
### JSON Format
|
|
261
|
+
- Structured metadata
|
|
262
|
+
- Programmatic processing friendly
|
|
263
|
+
- Includes statistics and file information
|
|
264
|
+
- Perfect for automation workflows
|
|
265
|
+
|
|
266
|
+
## 🛡️ Security Features
|
|
267
|
+
|
|
268
|
+
- **Path Validation**: Prevents directory traversal during restore
|
|
269
|
+
- **File Sanitization**: Validates all file paths and names
|
|
270
|
+
- **Confirmation Prompts**: Requires approval before overwriting files
|
|
271
|
+
- **Size Limits**: Protects against extremely large operations
|
|
272
|
+
|
|
273
|
+
## 🚀 Performance
|
|
274
|
+
|
|
275
|
+
- **Parallel Processing**: Concurrent file operations for speed
|
|
276
|
+
- **Progress Tracking**: Real-time progress bars for long operations
|
|
277
|
+
- **Memory Efficient**: Streams large files to avoid memory issues
|
|
278
|
+
- **Smart Caching**: Optimized for repeated operations
|
|
279
|
+
|
|
280
|
+
## 📝 License
|
|
281
|
+
|
|
282
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
283
|
+
|
|
284
|
+
## 🤝 Contributing
|
|
285
|
+
|
|
286
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
223
287
|
|
|
224
|
-
##
|
|
288
|
+
## 📞 Support
|
|
225
289
|
|
|
226
|
-
|
|
290
|
+
- **Issues**: [GitHub Issues](https://github.com/xelth-com/eckSnapshot/issues)
|
|
291
|
+
- **Documentation**: This README and `--help` commands
|
|
292
|
+
- **Examples**: See the examples directory in the repository
|
package/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
|
|
3
5
|
import { Command } from 'commander';
|
|
4
6
|
import { execa } from 'execa';
|
|
5
7
|
import fs from 'fs/promises';
|
|
@@ -28,8 +30,157 @@ const DEFAULT_CONFIG = {
|
|
|
28
30
|
concurrency: 10
|
|
29
31
|
};
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generates the detailed, universal Markdown header for the snapshot file.
|
|
37
|
+
* This header contains the full operational instructions for the AI model.
|
|
38
|
+
* @param {object} stats - The statistics object from the snapshot process.
|
|
39
|
+
* @param {string} repoName - The name of the repository.
|
|
40
|
+
* @returns {string} A formatted Markdown string with comprehensive AI instructions.
|
|
41
|
+
*/
|
|
42
|
+
function generateSnapshotHeader(stats, repoName, includeAiInstructions = true) {
|
|
43
|
+
const timestamp = new Date().toISOString();
|
|
44
|
+
|
|
45
|
+
if (!includeAiInstructions) {
|
|
46
|
+
return `# Repository Snapshot
|
|
47
|
+
|
|
48
|
+
**Repository:** ${repoName}
|
|
49
|
+
**Generated:** ${timestamp}
|
|
50
|
+
**Tool:** eck-snapshot
|
|
51
|
+
**Files Included:** ${stats.includedFiles} of ${stats.totalFiles}
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return `# AI Instructions
|
|
59
|
+
|
|
60
|
+
## 1. How to Read This Snapshot
|
|
61
|
+
|
|
62
|
+
This document is a self-contained, single-file snapshot of the **${repoName}** software repository, generated by the \`eck-snapshot\` tool on **${timestamp}**. It is designed to provide a Large Language Model (LLM) with the complete context of a project.
|
|
63
|
+
|
|
64
|
+
* **Source of Truth:** Treat this snapshot as the complete and authoritative source code.
|
|
65
|
+
* **Structure:** The file contains a **Directory Structure** tree, followed by the full content of each file, demarcated by \`--- File: /path/to/file ---\` headers.
|
|
66
|
+
|
|
67
|
+
**Snapshot Stats:**
|
|
68
|
+
- **Files Included:** ${stats.includedFiles}
|
|
69
|
+
- **Total Files in Repo:** ${stats.totalFiles}
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 2. Your Core Operational Workflow
|
|
74
|
+
|
|
75
|
+
You are the Project Manager and Solution Architect AI. Your primary goal is to translate user requests into technical plans and then generate precise commands for a code-execution AI agent.
|
|
76
|
+
|
|
77
|
+
### PROJECT OVERVIEW
|
|
78
|
+
- **Project:** ${repoName}
|
|
79
|
+
- **Description:** A CLI tool to create and restore single-file text snapshots of a Git repository, optimized for providing context to Large Language Models (LLMs).
|
|
80
|
+
|
|
81
|
+
### CORE WORKFLOW: The Interactive Command Cycle
|
|
82
|
+
1. **Analyze User Request:** Understand the user's goal in their native language.
|
|
83
|
+
2. **Formulate a Plan:** Create a high-level technical plan to solve the user's request.
|
|
84
|
+
3. **Propose & Await Confirmation:** Present the plan to the user in their language and ask for approval to generate the command. **CRITICAL: Stop and wait for the user's response. Do NOT generate the command block at this stage.**
|
|
85
|
+
4. **Generate Command on Demand:** This is the execution step, triggered ONLY by a positive user response.
|
|
86
|
+
- **On Approval:** If the user confirms the plan (e.g., "yes", "proceed") or provides a minor correction, your *next response* must be **only the command block**. Do not include any conversational text.
|
|
87
|
+
- **On Direct Order:** If the user explicitly asks for the command (e.g., "make the command for Claude now") and you have all the necessary information, you may skip step 3 and directly generate the command block.
|
|
88
|
+
5. **Review & Report:** After the command is executed, analyze the results and report back to the user in their language.
|
|
89
|
+
6. **Iterate:** Continue the cycle based on user feedback.
|
|
90
|
+
|
|
91
|
+
### COMMUNICATION PROTOCOL
|
|
92
|
+
- **User Interaction:** ALWAYS communicate with the user in the language they use.
|
|
93
|
+
- **Agent Commands:** ALWAYS formulate the JSON payload and technical instructions for the execution agent in **ENGLISH** to ensure technical accuracy.
|
|
94
|
+
|
|
95
|
+
### COMMAND BLOCK FORMAT
|
|
96
|
+
To ensure error-free execution, all tasks for the agent must be presented in a special block with a "Copy" button. Use this enhanced format for maximum clarity and execution accuracy:
|
|
97
|
+
|
|
98
|
+
\`\`\`json
|
|
99
|
+
{
|
|
100
|
+
"command_for_agent": "apply_code_changes",
|
|
101
|
+
"task_id": "unique-task-id",
|
|
102
|
+
"payload": {
|
|
103
|
+
"objective": "Brief, clear task description",
|
|
104
|
+
"context": "Why this change is needed",
|
|
105
|
+
"files_to_modify": [
|
|
106
|
+
{
|
|
107
|
+
"path": "exact/file/path.js",
|
|
108
|
+
"action": "specific action (add, modify, replace, delete)",
|
|
109
|
+
"location": "line numbers, function name, or search pattern",
|
|
110
|
+
"details": "precise description of the change"
|
|
111
|
+
}
|
|
112
|
+
],
|
|
113
|
+
"new_files": [
|
|
114
|
+
{
|
|
115
|
+
"path": "path/to/new/file.js",
|
|
116
|
+
"content_type": "javascript/json/markdown/config",
|
|
117
|
+
"purpose": "why this file is needed"
|
|
118
|
+
}
|
|
119
|
+
],
|
|
120
|
+
"dependencies": {
|
|
121
|
+
"install": ["package-name@version"],
|
|
122
|
+
"remove": ["old-package-name"]
|
|
123
|
+
},
|
|
124
|
+
"validation_steps": [
|
|
125
|
+
"npm run test",
|
|
126
|
+
"node index.js --help",
|
|
127
|
+
"specific command to verify functionality"
|
|
128
|
+
],
|
|
129
|
+
"expected_outcome": "what should work after changes"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
\`\`\`
|
|
133
|
+
|
|
134
|
+
### PROJECT CONTEXT (\`eck-snapshot\`)
|
|
135
|
+
- **Type:** Node.js CLI Application, executed directly.
|
|
136
|
+
- **Module System:** ES Modules (\`"type": "module"\` in package.json).
|
|
137
|
+
- **Main File:** \`index.js\` contains all primary logic (837 lines).
|
|
138
|
+
- **Configuration:** \`.ecksnapshot.config.js\` is used for custom filtering and settings.
|
|
139
|
+
- **Key Dependencies:** \`commander\`, \`execa\`, \`inquirer\`, \`ignore\`, \`p-limit\`, \`cli-progress\`.
|
|
140
|
+
|
|
141
|
+
### ARCHITECTURE DETAILS FOR CLAUDE CODE
|
|
142
|
+
**Core Functions Location:**
|
|
143
|
+
- \`createRepoSnapshot()\` - Line 333: Main snapshot creation
|
|
144
|
+
- \`restoreSnapshot()\` - Line 579: Snapshot restoration
|
|
145
|
+
- \`processFile()\` - Line 265: Individual file processing
|
|
146
|
+
- \`generateDirectoryTree()\` - Line 224: Tree generation
|
|
147
|
+
- \`generateSnapshotHeader()\` - Line 42: AI instruction header
|
|
148
|
+
- CLI setup - Lines 800-837: Commander.js configuration
|
|
149
|
+
|
|
150
|
+
**Common Modification Patterns:**
|
|
151
|
+
- CLI options: Modify commander setup (lines 808-822, 824-835)
|
|
152
|
+
- Configuration: Update DEFAULT_CONFIG object (lines 23-31)
|
|
153
|
+
- File processing: Enhance processFile() function
|
|
154
|
+
- Output formats: Modify generateSnapshotHeader() or output logic
|
|
155
|
+
- Dependencies: Update package.json and import statements
|
|
156
|
+
|
|
157
|
+
**Testing Status:**
|
|
158
|
+
- No test framework currently configured
|
|
159
|
+
- package.json test script returns error
|
|
160
|
+
- Manual testing via \`node index.js\` commands
|
|
161
|
+
- Consider adding vitest or jest for future testing
|
|
162
|
+
|
|
163
|
+
**Development Workflow:**
|
|
164
|
+
- Direct execution: \`node index.js [command] [options]\`
|
|
165
|
+
- Package creation: \`npm pack\`
|
|
166
|
+
- Local testing: \`node index.js --help\`
|
|
167
|
+
- Configuration testing: modify \`.ecksnapshot.config.js\`
|
|
168
|
+
|
|
169
|
+
**Critical Implementation Notes:**
|
|
170
|
+
- All file paths normalized to forward slashes
|
|
171
|
+
- ES module imports only (no CommonJS)
|
|
172
|
+
- Error handling with detailed user messages
|
|
173
|
+
- Progress tracking for long operations
|
|
174
|
+
- Security: Path validation prevents directory traversal
|
|
175
|
+
- Cross-platform compatibility maintained
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
33
184
|
|
|
34
185
|
function parseSize(sizeStr) {
|
|
35
186
|
const units = { B: 1, KB: 1024, MB: 1024 ** 2, GB: 1024 ** 3 };
|
|
@@ -67,7 +218,6 @@ function matchesPattern(filePath, patterns) {
|
|
|
67
218
|
|
|
68
219
|
async function loadConfig(configPath) {
|
|
69
220
|
let config = { ...DEFAULT_CONFIG };
|
|
70
|
-
|
|
71
221
|
if (configPath) {
|
|
72
222
|
try {
|
|
73
223
|
const configModule = await import(path.resolve(configPath));
|
|
@@ -82,7 +232,6 @@ async function loadConfig(configPath) {
|
|
|
82
232
|
'.ecksnapshot.config.mjs',
|
|
83
233
|
'ecksnapshot.config.js'
|
|
84
234
|
];
|
|
85
|
-
|
|
86
235
|
for (const configFile of possibleConfigs) {
|
|
87
236
|
try {
|
|
88
237
|
await fs.access(configFile);
|
|
@@ -110,11 +259,55 @@ async function checkGitAvailability() {
|
|
|
110
259
|
async function checkGitRepository(repoPath) {
|
|
111
260
|
try {
|
|
112
261
|
await execa('git', ['rev-parse', '--git-dir'], { cwd: repoPath });
|
|
262
|
+
return true;
|
|
113
263
|
} catch (error) {
|
|
114
|
-
|
|
264
|
+
return false;
|
|
115
265
|
}
|
|
116
266
|
}
|
|
117
267
|
|
|
268
|
+
async function scanDirectoryRecursively(dirPath, config, relativeTo = dirPath) {
|
|
269
|
+
const files = [];
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
273
|
+
|
|
274
|
+
for (const entry of entries) {
|
|
275
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
276
|
+
const relativePath = path.relative(relativeTo, fullPath).replace(/\\/g, '/');
|
|
277
|
+
|
|
278
|
+
// Skip if matches ignore patterns
|
|
279
|
+
if (config.dirsToIgnore.some(dir =>
|
|
280
|
+
entry.name === dir.replace('/', '') ||
|
|
281
|
+
relativePath.startsWith(dir)
|
|
282
|
+
)) {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Skip hidden files unless explicitly included
|
|
287
|
+
if (!config.includeHidden && entry.name.startsWith('.')) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (entry.isDirectory()) {
|
|
292
|
+
const subFiles = await scanDirectoryRecursively(fullPath, config, relativeTo);
|
|
293
|
+
files.push(...subFiles);
|
|
294
|
+
} else {
|
|
295
|
+
// Skip ignored files and extensions
|
|
296
|
+
if (config.extensionsToIgnore.includes(path.extname(entry.name)) ||
|
|
297
|
+
matchesPattern(relativePath, config.filesToIgnore)) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
files.push(relativePath);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.warn(`⚠️ Warning: Could not read directory: ${dirPath} - ${error.message}`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return files;
|
|
309
|
+
}
|
|
310
|
+
|
|
118
311
|
async function loadGitignore(repoPath) {
|
|
119
312
|
try {
|
|
120
313
|
const gitignoreContent = await fs.readFile(path.join(repoPath, '.gitignore'), 'utf-8');
|
|
@@ -142,7 +335,6 @@ async function readFileWithSizeCheck(filePath, maxFileSize) {
|
|
|
142
335
|
|
|
143
336
|
async function generateDirectoryTree(dir, prefix = '', allFiles, depth = 0, maxDepth = 10, config) {
|
|
144
337
|
if (depth > maxDepth) return '';
|
|
145
|
-
|
|
146
338
|
try {
|
|
147
339
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
148
340
|
const sortedEntries = entries.sort((a, b) => {
|
|
@@ -150,16 +342,13 @@ async function generateDirectoryTree(dir, prefix = '', allFiles, depth = 0, maxD
|
|
|
150
342
|
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
151
343
|
return a.name.localeCompare(b.name);
|
|
152
344
|
});
|
|
153
|
-
|
|
154
345
|
let tree = '';
|
|
155
346
|
const validEntries = [];
|
|
156
347
|
|
|
157
348
|
for (const entry of sortedEntries) {
|
|
158
349
|
if (config.dirsToIgnore.some(d => entry.name.includes(d.replace('/', '')))) continue;
|
|
159
|
-
|
|
160
350
|
const fullPath = path.join(dir, entry.name);
|
|
161
351
|
const relativePath = path.relative(process.cwd(), fullPath).replace(/\\/g, '/');
|
|
162
|
-
|
|
163
352
|
if (entry.isDirectory() || allFiles.includes(relativePath)) {
|
|
164
353
|
validEntries.push({ entry, fullPath, relativePath });
|
|
165
354
|
}
|
|
@@ -171,7 +360,6 @@ async function generateDirectoryTree(dir, prefix = '', allFiles, depth = 0, maxD
|
|
|
171
360
|
|
|
172
361
|
const connector = isLast ? '└── ' : '├── ';
|
|
173
362
|
const nextPrefix = prefix + (isLast ? ' ' : '│ ');
|
|
174
|
-
|
|
175
363
|
if (entry.isDirectory()) {
|
|
176
364
|
tree += `${prefix}${connector}${entry.name}/\n`;
|
|
177
365
|
tree += await generateDirectoryTree(fullPath, nextPrefix, allFiles, depth + 1, maxDepth, config);
|
|
@@ -227,10 +415,10 @@ async function processFile(filePath, config, gitignore, stats) {
|
|
|
227
415
|
|
|
228
416
|
stats.includedFiles++;
|
|
229
417
|
stats.includedFileTypes.set(fileExt, (stats.includedFileTypes.get(fileExt) || 0) + 1);
|
|
230
|
-
|
|
231
418
|
return { content: fileContent, size: fileContent.length };
|
|
232
419
|
} catch (error) {
|
|
233
|
-
const errorReason = error.message.includes('too large') ?
|
|
420
|
+
const errorReason = error.message.includes('too large') ?
|
|
421
|
+
'file-too-large' : 'read-error';
|
|
234
422
|
|
|
235
423
|
stats.errors.push({ file: filePath, error: error.message });
|
|
236
424
|
stats.skippedFiles++;
|
|
@@ -255,12 +443,11 @@ async function processFile(filePath, config, gitignore, stats) {
|
|
|
255
443
|
// --- ОСНОВНЫЕ ФУНКЦИИ ДЛЯ КОМАНД ---
|
|
256
444
|
|
|
257
445
|
async function createRepoSnapshot(repoPath, options) {
|
|
258
|
-
// ... (эта функция остается без изменений)
|
|
259
446
|
const absoluteRepoPath = path.resolve(repoPath);
|
|
260
447
|
const absoluteOutputPath = path.resolve(options.output);
|
|
261
448
|
const originalCwd = process.cwd();
|
|
262
449
|
|
|
263
|
-
console.log(`🚀 Starting snapshot for repository: ${absoluteRepoPath}`);
|
|
450
|
+
console.log(`🚀 Starting snapshot for ${options.dir ? 'directory' : 'repository'}: ${absoluteRepoPath}`);
|
|
264
451
|
console.log(`📁 Snapshots will be saved to: ${absoluteOutputPath}`);
|
|
265
452
|
|
|
266
453
|
try {
|
|
@@ -269,19 +456,37 @@ async function createRepoSnapshot(repoPath, options) {
|
|
|
269
456
|
config.maxTotalSize = options.maxTotalSize || config.maxTotalSize;
|
|
270
457
|
config.maxDepth = options.maxDepth || config.maxDepth;
|
|
271
458
|
config.includeHidden = options.includeHidden || false;
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
459
|
+
|
|
460
|
+
let allFiles = [];
|
|
461
|
+
let gitignore = null;
|
|
462
|
+
let isGitRepo = false;
|
|
463
|
+
|
|
464
|
+
// Check if it's a git repository (unless --dir is explicitly used)
|
|
465
|
+
if (!options.dir) {
|
|
466
|
+
await checkGitAvailability();
|
|
467
|
+
isGitRepo = await checkGitRepository(absoluteRepoPath);
|
|
468
|
+
|
|
469
|
+
if (!isGitRepo) {
|
|
470
|
+
console.log('ℹ️ Not a git repository, switching to directory mode');
|
|
471
|
+
options.dir = true;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
275
474
|
|
|
276
475
|
process.chdir(absoluteRepoPath);
|
|
277
476
|
console.log('✅ Successfully changed working directory');
|
|
278
477
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
478
|
+
if (options.dir) {
|
|
479
|
+
console.log('📋 Scanning directory recursively...');
|
|
480
|
+
allFiles = await scanDirectoryRecursively(absoluteRepoPath, config);
|
|
481
|
+
gitignore = ignore(); // Empty gitignore for directory mode
|
|
482
|
+
console.log(`📊 Found ${allFiles.length} total files in the directory`);
|
|
483
|
+
} else {
|
|
484
|
+
gitignore = await loadGitignore(absoluteRepoPath);
|
|
485
|
+
console.log('📋 Fetching file list from Git...');
|
|
486
|
+
const { stdout } = await execa('git', ['ls-files']);
|
|
487
|
+
allFiles = stdout.split('\n').filter(Boolean);
|
|
488
|
+
console.log(`📊 Found ${allFiles.length} total files in the repository`);
|
|
489
|
+
}
|
|
285
490
|
|
|
286
491
|
const stats = {
|
|
287
492
|
totalFiles: allFiles.length,
|
|
@@ -295,7 +500,6 @@ async function createRepoSnapshot(repoPath, options) {
|
|
|
295
500
|
skipReasons: new Map(),
|
|
296
501
|
skippedFilesDetails: new Map()
|
|
297
502
|
};
|
|
298
|
-
|
|
299
503
|
let snapshotContent = '';
|
|
300
504
|
|
|
301
505
|
if (options.tree) {
|
|
@@ -308,13 +512,13 @@ async function createRepoSnapshot(repoPath, options) {
|
|
|
308
512
|
|
|
309
513
|
console.log('📝 Processing files...');
|
|
310
514
|
const limit = pLimit(config.concurrency);
|
|
311
|
-
const progressBar = options.verbose ?
|
|
515
|
+
const progressBar = options.verbose ?
|
|
516
|
+
null : new SingleBar({
|
|
312
517
|
format: 'Progress |{bar}| {percentage}% | {value}/{total} files | ETA: {eta}s',
|
|
313
518
|
barCompleteChar: '\u2588',
|
|
314
519
|
barIncompleteChar: '\u2591',
|
|
315
520
|
hideCursor: true
|
|
316
521
|
}, Presets.shades_classic);
|
|
317
|
-
|
|
318
522
|
if (progressBar) progressBar.start(allFiles.length, 0);
|
|
319
523
|
|
|
320
524
|
const filePromises = allFiles.map((filePath, index) =>
|
|
@@ -325,6 +529,7 @@ async function createRepoSnapshot(repoPath, options) {
|
|
|
325
529
|
progressBar.update(index + 1);
|
|
326
530
|
} else if (options.verbose) {
|
|
327
531
|
if (result.skipped) {
|
|
532
|
+
|
|
328
533
|
console.log(`⏭️ Skipping: ${filePath} (${result.reason})`);
|
|
329
534
|
} else {
|
|
330
535
|
console.log(`✅ Processed: ${filePath}`);
|
|
@@ -334,14 +539,12 @@ async function createRepoSnapshot(repoPath, options) {
|
|
|
334
539
|
return result;
|
|
335
540
|
})
|
|
336
541
|
);
|
|
337
|
-
|
|
338
542
|
const results = await Promise.allSettled(filePromises);
|
|
339
543
|
if (progressBar) progressBar.stop();
|
|
340
544
|
|
|
341
545
|
const contentArray = [];
|
|
342
546
|
let totalSize = 0;
|
|
343
547
|
const maxTotalSize = parseSize(config.maxTotalSize);
|
|
344
|
-
|
|
345
548
|
for (const result of results) {
|
|
346
549
|
if (result.status === 'rejected') {
|
|
347
550
|
console.warn(`⚠️ Promise rejected: ${result.reason}`);
|
|
@@ -357,21 +560,22 @@ async function createRepoSnapshot(repoPath, options) {
|
|
|
357
560
|
}
|
|
358
561
|
}
|
|
359
562
|
|
|
360
|
-
|
|
563
|
+
// Add the header to the beginning of the file content
|
|
564
|
+
const repoName = path.basename(absoluteRepoPath);
|
|
565
|
+
const header = generateSnapshotHeader(stats, repoName, options.aiHeader !== false);
|
|
566
|
+
snapshotContent = header + snapshotContent + contentArray.join('');
|
|
567
|
+
|
|
361
568
|
const totalChars = snapshotContent.length;
|
|
362
569
|
const estimatedTokens = Math.round(totalChars / 4);
|
|
363
570
|
|
|
364
571
|
const timestamp = new Date().toISOString().slice(0, 19).replace('T', '_').replace(/:/g, '-');
|
|
365
|
-
const
|
|
366
|
-
const extension = options.format === 'json' ? 'json' : 'txt';
|
|
572
|
+
const extension = options.format === 'json' ? 'json' : 'md'; // Changed default to md
|
|
367
573
|
let outputFilename = `${repoName}_snapshot_${timestamp}.${extension}`;
|
|
368
|
-
|
|
369
574
|
if (options.compress) {
|
|
370
575
|
outputFilename += '.gz';
|
|
371
576
|
}
|
|
372
577
|
|
|
373
578
|
const fullOutputFilePath = path.join(absoluteOutputPath, outputFilename);
|
|
374
|
-
|
|
375
579
|
let finalContent = snapshotContent;
|
|
376
580
|
if (options.format === 'json') {
|
|
377
581
|
const jsonData = {
|
|
@@ -383,6 +587,7 @@ async function createRepoSnapshot(repoPath, options) {
|
|
|
383
587
|
skippedFileTypes: Object.fromEntries(stats.skippedFileTypes),
|
|
384
588
|
skipReasons: Object.fromEntries(stats.skipReasons),
|
|
385
589
|
skippedFilesDetails: Object.fromEntries(
|
|
590
|
+
|
|
386
591
|
Array.from(stats.skippedFilesDetails.entries()).map(([reason, files]) => [
|
|
387
592
|
reason,
|
|
388
593
|
files.map(({file, ext}) => ({file, ext}))
|
|
@@ -395,7 +600,6 @@ async function createRepoSnapshot(repoPath, options) {
|
|
|
395
600
|
}
|
|
396
601
|
|
|
397
602
|
await fs.mkdir(absoluteOutputPath, { recursive: true });
|
|
398
|
-
|
|
399
603
|
if (options.compress) {
|
|
400
604
|
const compressed = await gzip(finalContent);
|
|
401
605
|
await fs.writeFile(fullOutputFilePath, compressed);
|
|
@@ -421,8 +625,7 @@ async function createRepoSnapshot(repoPath, options) {
|
|
|
421
625
|
console.log('\n📋 Included File Types Distribution:');
|
|
422
626
|
const sortedIncludedTypes = Array.from(stats.includedFileTypes.entries())
|
|
423
627
|
.sort(([,a], [,b]) => b - a)
|
|
424
|
-
.slice(0, 10);
|
|
425
|
-
|
|
628
|
+
.slice(0, 10);
|
|
426
629
|
for (const [ext, count] of sortedIncludedTypes) {
|
|
427
630
|
console.log(` ${ext}: ${count} files`);
|
|
428
631
|
}
|
|
@@ -432,8 +635,7 @@ async function createRepoSnapshot(repoPath, options) {
|
|
|
432
635
|
console.log('\n⏭️ Skipped File Types Distribution:');
|
|
433
636
|
const sortedSkippedTypes = Array.from(stats.skippedFileTypes.entries())
|
|
434
637
|
.sort(([,a], [,b]) => b - a)
|
|
435
|
-
.slice(0, 10);
|
|
436
|
-
|
|
638
|
+
.slice(0, 10);
|
|
437
639
|
for (const [ext, count] of sortedSkippedTypes) {
|
|
438
640
|
console.log(` ${ext}: ${count} files`);
|
|
439
641
|
}
|
|
@@ -443,7 +645,6 @@ async function createRepoSnapshot(repoPath, options) {
|
|
|
443
645
|
console.log('\n📊 Skip Reasons:');
|
|
444
646
|
const sortedReasons = Array.from(stats.skipReasons.entries())
|
|
445
647
|
.sort(([,a], [,b]) => b - a);
|
|
446
|
-
|
|
447
648
|
const reasonLabels = {
|
|
448
649
|
'ignored-directory': 'Ignored directories',
|
|
449
650
|
'ignored-extension': 'Ignored extensions',
|
|
@@ -454,7 +655,6 @@ async function createRepoSnapshot(repoPath, options) {
|
|
|
454
655
|
'file-too-large': 'Files too large',
|
|
455
656
|
'read-error': 'Read errors'
|
|
456
657
|
};
|
|
457
|
-
|
|
458
658
|
for (const [reason, count] of sortedReasons) {
|
|
459
659
|
const label = reasonLabels[reason] || reason;
|
|
460
660
|
console.log(` ${label}: ${count} files`);
|
|
@@ -471,7 +671,7 @@ async function createRepoSnapshot(repoPath, options) {
|
|
|
471
671
|
console.log(` ... and ${files.length - 10} more files`);
|
|
472
672
|
}
|
|
473
673
|
}
|
|
474
|
-
console.log();
|
|
674
|
+
console.log();
|
|
475
675
|
}
|
|
476
676
|
}
|
|
477
677
|
|
|
@@ -486,7 +686,6 @@ async function createRepoSnapshot(repoPath, options) {
|
|
|
486
686
|
}
|
|
487
687
|
|
|
488
688
|
console.log('='.repeat(50));
|
|
489
|
-
|
|
490
689
|
} catch (error) {
|
|
491
690
|
console.error('\n❌ An error occurred:');
|
|
492
691
|
if (error.code === 'ENOENT' && error.path && error.path.includes('.git')) {
|
|
@@ -512,7 +711,6 @@ async function createRepoSnapshot(repoPath, options) {
|
|
|
512
711
|
async function restoreSnapshot(snapshotFile, targetDir, options) {
|
|
513
712
|
const absoluteSnapshotPath = path.resolve(snapshotFile);
|
|
514
713
|
const absoluteTargetDir = path.resolve(targetDir);
|
|
515
|
-
|
|
516
714
|
console.log(`🔄 Starting restore from snapshot: ${absoluteSnapshotPath}`);
|
|
517
715
|
console.log(`📁 Target directory: ${absoluteTargetDir}`);
|
|
518
716
|
|
|
@@ -563,7 +761,6 @@ async function restoreSnapshot(snapshotFile, targetDir, options) {
|
|
|
563
761
|
}
|
|
564
762
|
|
|
565
763
|
console.log(`📊 Found ${filesToRestore.length} files to restore`);
|
|
566
|
-
|
|
567
764
|
if (options.dryRun) {
|
|
568
765
|
console.log('\n🔍 Dry run mode - files that would be restored:');
|
|
569
766
|
filesToRestore.forEach(file => {
|
|
@@ -580,7 +777,6 @@ async function restoreSnapshot(snapshotFile, targetDir, options) {
|
|
|
580
777
|
message: `You are about to write ${filesToRestore.length} files to ${absoluteTargetDir}. Existing files will be overwritten. Continue?`,
|
|
581
778
|
default: false
|
|
582
779
|
}]);
|
|
583
|
-
|
|
584
780
|
if (!confirm) {
|
|
585
781
|
console.log('🚫 Restore operation cancelled by user');
|
|
586
782
|
return;
|
|
@@ -588,21 +784,18 @@ async function restoreSnapshot(snapshotFile, targetDir, options) {
|
|
|
588
784
|
}
|
|
589
785
|
|
|
590
786
|
await fs.mkdir(absoluteTargetDir, { recursive: true });
|
|
591
|
-
|
|
592
787
|
const stats = {
|
|
593
788
|
totalFiles: filesToRestore.length,
|
|
594
789
|
restoredFiles: 0,
|
|
595
790
|
failedFiles: 0,
|
|
596
791
|
errors: []
|
|
597
792
|
};
|
|
598
|
-
|
|
599
793
|
const progressBar = options.verbose ? null : new SingleBar({
|
|
600
794
|
format: 'Restoring |{bar}| {percentage}% | {value}/{total} files',
|
|
601
795
|
barCompleteChar: '\u2588',
|
|
602
796
|
barIncompleteChar: '\u2591',
|
|
603
797
|
hideCursor: true
|
|
604
798
|
}, Presets.shades_classic);
|
|
605
|
-
|
|
606
799
|
if (progressBar) progressBar.start(filesToRestore.length, 0);
|
|
607
800
|
|
|
608
801
|
const limit = pLimit(options.concurrency || 10);
|
|
@@ -616,6 +809,7 @@ async function restoreSnapshot(snapshotFile, targetDir, options) {
|
|
|
616
809
|
await fs.writeFile(fullPath, file.content, 'utf-8');
|
|
617
810
|
|
|
618
811
|
stats.restoredFiles++;
|
|
812
|
+
|
|
619
813
|
|
|
620
814
|
if (progressBar) {
|
|
621
815
|
progressBar.update(index + 1);
|
|
@@ -624,6 +818,7 @@ async function restoreSnapshot(snapshotFile, targetDir, options) {
|
|
|
624
818
|
}
|
|
625
819
|
|
|
626
820
|
return { success: true, file: file.path };
|
|
821
|
+
|
|
627
822
|
} catch (error) {
|
|
628
823
|
stats.failedFiles++;
|
|
629
824
|
stats.errors.push({ file: file.path, error: error.message });
|
|
@@ -632,6 +827,7 @@ async function restoreSnapshot(snapshotFile, targetDir, options) {
|
|
|
632
827
|
console.log(`❌ Failed to restore: ${file.path} - ${error.message}`);
|
|
633
828
|
}
|
|
634
829
|
|
|
830
|
+
|
|
635
831
|
return { success: false, file: file.path, error: error.message };
|
|
636
832
|
}
|
|
637
833
|
})
|
|
@@ -658,7 +854,6 @@ async function restoreSnapshot(snapshotFile, targetDir, options) {
|
|
|
658
854
|
}
|
|
659
855
|
console.log(`📁 Target directory: ${absoluteTargetDir}`);
|
|
660
856
|
console.log('='.repeat(50));
|
|
661
|
-
|
|
662
857
|
} catch (error) {
|
|
663
858
|
console.error('\n❌ An error occurred during restore:');
|
|
664
859
|
console.error(error.message);
|
|
@@ -673,7 +868,6 @@ function parseSnapshotContent(content) {
|
|
|
673
868
|
const files = [];
|
|
674
869
|
const fileRegex = /--- File: \/(.+) ---/g;
|
|
675
870
|
const sections = content.split(fileRegex);
|
|
676
|
-
|
|
677
871
|
for (let i = 1; i < sections.length; i += 2) {
|
|
678
872
|
const filePath = sections[i].trim();
|
|
679
873
|
let fileContent = sections[i + 1] || '';
|
|
@@ -695,7 +889,8 @@ function filterFilesToRestore(files, options) {
|
|
|
695
889
|
let filtered = files;
|
|
696
890
|
|
|
697
891
|
if (options.include) {
|
|
698
|
-
const includePatterns = Array.isArray(options.include) ?
|
|
892
|
+
const includePatterns = Array.isArray(options.include) ?
|
|
893
|
+
options.include : [options.include];
|
|
699
894
|
filtered = filtered.filter(file =>
|
|
700
895
|
includePatterns.some(pattern => {
|
|
701
896
|
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
|
@@ -719,10 +914,8 @@ function filterFilesToRestore(files, options) {
|
|
|
719
914
|
|
|
720
915
|
function validateFilePaths(files, targetDir) {
|
|
721
916
|
const invalidFiles = [];
|
|
722
|
-
|
|
723
917
|
for (const file of files) {
|
|
724
918
|
const normalizedPath = path.normalize(file.path);
|
|
725
|
-
|
|
726
919
|
if (normalizedPath.includes('..') ||
|
|
727
920
|
normalizedPath.startsWith('/') ||
|
|
728
921
|
normalizedPath.includes('\0') ||
|
|
@@ -741,14 +934,14 @@ const program = new Command();
|
|
|
741
934
|
program
|
|
742
935
|
.name('eck-snapshot')
|
|
743
936
|
.description('A CLI tool to create and restore single-file text snapshots of a Git repository.')
|
|
744
|
-
.version('
|
|
937
|
+
.version('3.0.0');
|
|
745
938
|
|
|
746
939
|
// Snapshot command (existing)
|
|
747
940
|
program
|
|
748
941
|
.command('snapshot', { isDefault: true })
|
|
749
942
|
.description('Create a snapshot of a Git repository (default command).')
|
|
750
943
|
.argument('[repoPath]', 'Path to the git repository to snapshot.', process.cwd())
|
|
751
|
-
|
|
944
|
+
.option('-o, --output <dir>', 'Output directory for the snapshot file.', path.join(__dirname, 'snapshots'))
|
|
752
945
|
.option('--no-tree', 'Do not include the directory tree in the snapshot.')
|
|
753
946
|
.option('-v, --verbose', 'Show detailed processing information, including skipped files.')
|
|
754
947
|
.option('--max-file-size <size>', 'Maximum file size to include (e.g., 10MB)', '10MB')
|
|
@@ -757,7 +950,9 @@ program
|
|
|
757
950
|
.option('--config <path>', 'Path to configuration file')
|
|
758
951
|
.option('--compress', 'Compress output file with gzip')
|
|
759
952
|
.option('--include-hidden', 'Include hidden files (starting with .)')
|
|
760
|
-
.option('--format <type>', 'Output format:
|
|
953
|
+
.option('--format <type>', 'Output format: md, json', 'md')
|
|
954
|
+
.option('--no-ai-header', 'Skip AI instruction header (create clean snapshot)')
|
|
955
|
+
.option('-d, --dir', 'Directory mode: scan directory recursively (auto-enabled if no git repo found)')
|
|
761
956
|
.action((repoPath, options) => createRepoSnapshot(repoPath, options));
|
|
762
957
|
|
|
763
958
|
program
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xelth/eck-snapshot",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A CLI tool to create and restore single-file text snapshots of
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "A powerful CLI tool to create and restore single-file text snapshots of Git repositories and directories. Optimized for AI context and LLM workflows.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|