@xelth/eck-snapshot 1.5.1 → 2.2.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.
Files changed (3) hide show
  1. package/README.md +171 -22
  2. package/index.js +261 -16
  3. package/package.json +5 -12
package/README.md CHANGED
@@ -1,15 +1,80 @@
1
+ # eck-snapshot
1
2
 
2
- # eckSnapshot
3
+ [![npm version](https://badge.fury.io/js/%40xelth%2Feck-snapshot.svg)](https://www.npmjs.com/package/@xelth/eck-snapshot)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/xelth-com/eckSnapshot/blob/main/LICENSE)
3
5
 
4
- A CLI tool to create a single-file text snapshot of a Git repository. It generates a single `.txt` file containing the directory structure and the content of all text-based files, which is ideal for providing context to Large Language Models (LLMs) like GPT or Claude.
6
+ A CLI tool to create and restore single-file text snapshots of a Git repository. It generates a single `.txt` file containing the directory structure and the content of all text-based files, which is ideal for providing context to Large Language Models (LLMs).
5
7
 
6
- ## Features
8
+ ## Why eck-snapshot?
7
9
 
8
- - **Git Integration**: Automatically includes all files tracked by Git.
9
- - **Intelligent Ignoring**: Respects `.gitignore` rules and has its own configurable ignore lists for files, extensions, and directories.
10
- - **Directory Tree**: Generates a clean, readable tree of the repository structure at the top of the snapshot.
11
- - **Configurable**: Customize behavior using an `.ecksnapshot.config.js` file.
12
- - **Progress and Stats**: Provides a progress bar and a detailed summary of what was included and skipped.
10
+ When working with Large Language Models (LLMs), providing the full context of your project is crucial for getting accurate results. Manually copying and pasting dozens of files is tedious and inefficient.
11
+
12
+ eck-snapshot automates this by generating a single, comprehensive text file of your entire repository. This is particularly effective with models that support large context windows (like Google's Gemini), as it often allows the entire project snapshot to be analyzed at once—a task that can be challenging with smaller context windows.
13
+
14
+ ## Key Features
15
+
16
+ * **Git Integration**: Automatically includes all files tracked by Git.
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.
25
+
26
+ ## Demo
27
+
28
+ Here's an example of `eck-snapshot` in action:
29
+
30
+ ```
31
+ 🚀 Starting snapshot for repository: /path/to/your/project
32
+ ✅ .gitignore patterns loaded
33
+ 📊 Found 152 total files in the repository
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
+ ```
47
+
48
+ The beginning of the generated file will look like this:
49
+
50
+ ```text
51
+ Directory Structure:
52
+
53
+ ├── .github/
54
+ │ └── workflows/
55
+ │ └── publish.yml
56
+ ├── src/
57
+ │ ├── utils/
58
+ │ │ └── formatters.js
59
+ │ └── index.js
60
+ ├── .gitignore
61
+ ├── package.json
62
+ └── README.md
63
+
64
+
65
+ --- File: /src/index.js ---
66
+
67
+ #!/usr/bin/env node
68
+ import { Command } from 'commander';
69
+ // ... rest of the file content
70
+
71
+ --- File: /package.json ---
72
+
73
+ {
74
+ "name": "eck-snapshot",
75
+ "version": "2.1.0",
76
+ // ... rest of the file content
77
+ ```
13
78
 
14
79
  ## Installation
15
80
 
@@ -17,31 +82,55 @@ To install the tool globally, run the following command:
17
82
 
18
83
  ```bash
19
84
  npm install -g @xelth/eck-snapshot
20
- ````
21
-
85
+ ```
22
86
 
23
87
  ## Usage
24
88
 
25
- Once installed, you can run the tool from any directory in your terminal. While the project is named `eckSnapshot`, the command-line tool is invoked as **`eck-snapshot`** to follow standard CLI conventions.
89
+ Once installed, you can run the tool from any directory in your terminal.
26
90
 
27
- **Generate a snapshot of the current directory:**
91
+ ### Creating a Snapshot
28
92
 
29
93
  ```bash
94
+ # Create a snapshot of the current directory
30
95
  eck-snapshot
96
+
97
+ # Specify a path to another repository
98
+ eck-snapshot /path/to/your/other/project
99
+
100
+ # Save the snapshot to a different directory and exclude the tree view
101
+ eck-snapshot --output ./backups --no-tree
102
+
103
+ # Create a compressed JSON snapshot
104
+ eck-snapshot --format json --compress
105
+
106
+ # Include hidden files and set custom size limits
107
+ eck-snapshot --include-hidden --max-file-size 5MB --max-total-size 50MB
31
108
  ```
32
109
 
33
- **Specify a path to another repository:**
110
+ ### Restoring from a Snapshot
34
111
 
35
112
  ```bash
36
- eck-snapshot /path/to/your/other/project
37
- ```
113
+ # Basic restore to current directory
114
+ eck-snapshot restore ./snapshots/project_snapshot_...txt
115
+
116
+ # Restore to a specific directory without confirmation
117
+ eck-snapshot restore snapshot.txt ./restored-project --force
118
+
119
+ # Preview what would be restored (dry run)
120
+ eck-snapshot restore snapshot.txt --dry-run
121
+
122
+ # Restore only specific files using patterns
123
+ eck-snapshot restore snapshot.txt --include "*.js" "*.json"
124
+
125
+ # Restore everything except certain files
126
+ eck-snapshot restore snapshot.txt --exclude "*.log" "node_modules/*"
38
127
 
39
- **Common Options:**
128
+ # Restore with custom concurrency and verbose output
129
+ eck-snapshot restore snapshot.txt --concurrency 20 --verbose
40
130
 
41
- - `-o, --output <dir>`: Specify a different output directory for the snapshot file.
42
- - `-v, --verbose`: Show detailed processing information, including skipped files.
43
- - `--no-tree`: Exclude the directory tree from the snapshot.
44
- - `--config <path>`: Use a configuration file from a specific path.
131
+ # Restore compressed snapshots
132
+ eck-snapshot restore project_snapshot.txt.gz ./restored
133
+ ```
45
134
 
46
135
  ## Configuration
47
136
 
@@ -67,11 +156,71 @@ export default {
67
156
  '.git/',
68
157
  'dist/',
69
158
  ],
70
- // Maximum size for individual files
159
+ // Size and performance settings
71
160
  maxFileSize: '10MB',
161
+ maxTotalSize: '100MB',
162
+ maxDepth: 10,
163
+ concurrency: 10
72
164
  };
73
165
  ```
74
166
 
167
+ ## Advanced Features
168
+
169
+ ### Restore Command Options
170
+
171
+ The restore command offers powerful filtering and control options:
172
+
173
+ - **`--dry-run`**: Preview what files would be restored without actually writing them
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
179
+
180
+ ### Supported Formats
181
+
182
+ - **Plain Text** (`.txt`): Human-readable format, ideal for LLM context
183
+ - **JSON** (`.json`): Structured format with metadata and statistics
184
+ - **Compressed** (`.gz`): Any format can be gzipped for smaller file sizes
185
+
186
+ ### Security Features
187
+
188
+ - **Path Validation**: Prevents directory traversal attacks during restore operations
189
+ - **File Sanitization**: Validates file paths and names for security
190
+ - **Confirmation Prompts**: Requires user confirmation before overwriting files (unless `--force` is used)
191
+
192
+ ## Command Reference
193
+
194
+ ### Snapshot Command
195
+ ```bash
196
+ eck-snapshot [options] [repoPath]
197
+ ```
198
+
199
+ **Options:**
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
210
+
211
+ ### Restore Command
212
+ ```bash
213
+ eck-snapshot restore [options] <snapshot_file> [target_directory]
214
+ ```
215
+
216
+ **Options:**
217
+ - `-f, --force`: Force overwrite without confirmation
218
+ - `-v, --verbose`: Show detailed processing information
219
+ - `--dry-run`: Preview without actually writing files
220
+ - `--include <patterns>`: Include only matching files
221
+ - `--exclude <patterns>`: Exclude matching files
222
+ - `--concurrency <number>`: Number of concurrent operations
223
+
75
224
  ## License
76
225
 
77
- This project is licensed under the MIT License.
226
+ This project is licensed under the MIT License.
package/index.js CHANGED
@@ -11,9 +11,12 @@ import { SingleBar, Presets } from 'cli-progress';
11
11
  import pLimit from 'p-limit';
12
12
  import zlib from 'zlib';
13
13
  import { promisify } from 'util';
14
+ // NEW: Import inquirer for user prompts
15
+ import inquirer from 'inquirer';
14
16
 
15
17
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
18
  const gzip = promisify(zlib.gzip);
19
+ const gunzip = promisify(zlib.gunzip);
17
20
 
18
21
  const DEFAULT_CONFIG = {
19
22
  filesToIgnore: ['package-lock.json', '*.log', 'yarn.lock'],
@@ -25,6 +28,9 @@ const DEFAULT_CONFIG = {
25
28
  concurrency: 10
26
29
  };
27
30
 
31
+ // ... (остальные существующие функции: parseSize, formatSize, matchesPattern, loadConfig, и т.д. остаются без изменений)
32
+ // --- НАЧАЛО СУЩЕСТВУЮЩИХ ФУНКЦИЙ ---
33
+
28
34
  function parseSize(sizeStr) {
29
35
  const units = { B: 1, KB: 1024, MB: 1024 ** 2, GB: 1024 ** 3 };
30
36
  const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB)?$/i);
@@ -44,19 +50,9 @@ function formatSize(bytes) {
44
50
  return `${size.toFixed(1)} ${units[unitIndex]}`;
45
51
  }
46
52
 
47
- /**
48
- * Correctly matches a file path against glob-like patterns.
49
- * @param {string} filePath - The path of the file to check.
50
- * @param {string[]} patterns - An array of patterns to match against.
51
- * @returns {boolean} - True if the file path matches any of the patterns.
52
- */
53
53
  function matchesPattern(filePath, patterns) {
54
54
  const fileName = path.basename(filePath);
55
55
  return patterns.some(pattern => {
56
- // This is a robust way to convert simple globs to regex
57
- // 1. Escape all special regex characters.
58
- // 2. Convert the glob star '*' into the regex '.*'.
59
- // 3. Anchor the pattern to match the whole string.
60
56
  const regexPattern = '^' + pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*') + '$';
61
57
  try {
62
58
  const regex = new RegExp(regexPattern);
@@ -253,8 +249,13 @@ async function processFile(filePath, config, gitignore, stats) {
253
249
  return { skipped: true, reason: error.message };
254
250
  }
255
251
  }
252
+ // --- КОНЕЦ СУЩЕСТВУЮЩИХ ФУНКЦИЙ ---
253
+
254
+
255
+ // --- ОСНОВНЫЕ ФУНКЦИИ ДЛЯ КОМАНД ---
256
256
 
257
257
  async function createRepoSnapshot(repoPath, options) {
258
+ // ... (эта функция остается без изменений)
258
259
  const absoluteRepoPath = path.resolve(repoPath);
259
260
  const absoluteOutputPath = path.resolve(options.output);
260
261
  const originalCwd = process.cwd();
@@ -508,24 +509,268 @@ async function createRepoSnapshot(repoPath, options) {
508
509
  }
509
510
  }
510
511
 
511
- // CLI setup
512
+ async function restoreSnapshot(snapshotFile, targetDir, options) {
513
+ const absoluteSnapshotPath = path.resolve(snapshotFile);
514
+ const absoluteTargetDir = path.resolve(targetDir);
515
+
516
+ console.log(`🔄 Starting restore from snapshot: ${absoluteSnapshotPath}`);
517
+ console.log(`📁 Target directory: ${absoluteTargetDir}`);
518
+
519
+ try {
520
+ let rawContent;
521
+ if (snapshotFile.endsWith('.gz')) {
522
+ const compressedBuffer = await fs.readFile(absoluteSnapshotPath);
523
+ rawContent = (await gunzip(compressedBuffer)).toString('utf-8');
524
+ console.log('✅ Decompressed gzipped snapshot');
525
+ } else {
526
+ rawContent = await fs.readFile(absoluteSnapshotPath, 'utf-8');
527
+ }
528
+
529
+ let filesToRestore;
530
+ try {
531
+ const jsonData = JSON.parse(rawContent);
532
+ if (jsonData.content) {
533
+ console.log('📄 Detected JSON format, extracting content');
534
+ filesToRestore = parseSnapshotContent(jsonData.content);
535
+ } else {
536
+ throw new Error('JSON format detected, but no "content" key found');
537
+ }
538
+ } catch (e) {
539
+ console.log('📄 Treating snapshot as plain text format');
540
+ filesToRestore = parseSnapshotContent(rawContent);
541
+ }
542
+
543
+ if (filesToRestore.length === 0) {
544
+ console.warn('⚠️ No files found to restore in the snapshot');
545
+ return;
546
+ }
547
+
548
+ // Apply filters if specified
549
+ if (options.include || options.exclude) {
550
+ filesToRestore = filterFilesToRestore(filesToRestore, options);
551
+ if (filesToRestore.length === 0) {
552
+ console.warn('⚠️ No files remaining after applying filters');
553
+ return;
554
+ }
555
+ }
556
+
557
+ // Validate file paths for security
558
+ const invalidFiles = validateFilePaths(filesToRestore, absoluteTargetDir);
559
+ if (invalidFiles.length > 0) {
560
+ console.error('❌ Invalid file paths detected (potential directory traversal):');
561
+ invalidFiles.forEach(file => console.error(` ${file}`));
562
+ process.exit(1);
563
+ }
564
+
565
+ console.log(`📊 Found ${filesToRestore.length} files to restore`);
566
+
567
+ if (options.dryRun) {
568
+ console.log('\n🔍 Dry run mode - files that would be restored:');
569
+ filesToRestore.forEach(file => {
570
+ const fullPath = path.join(absoluteTargetDir, file.path);
571
+ console.log(` ${fullPath}`);
572
+ });
573
+ return;
574
+ }
575
+
576
+ if (!options.force) {
577
+ const { confirm } = await inquirer.prompt([{
578
+ type: 'confirm',
579
+ name: 'confirm',
580
+ message: `You are about to write ${filesToRestore.length} files to ${absoluteTargetDir}. Existing files will be overwritten. Continue?`,
581
+ default: false
582
+ }]);
583
+
584
+ if (!confirm) {
585
+ console.log('🚫 Restore operation cancelled by user');
586
+ return;
587
+ }
588
+ }
589
+
590
+ await fs.mkdir(absoluteTargetDir, { recursive: true });
591
+
592
+ const stats = {
593
+ totalFiles: filesToRestore.length,
594
+ restoredFiles: 0,
595
+ failedFiles: 0,
596
+ errors: []
597
+ };
598
+
599
+ const progressBar = options.verbose ? null : new SingleBar({
600
+ format: 'Restoring |{bar}| {percentage}% | {value}/{total} files',
601
+ barCompleteChar: '\u2588',
602
+ barIncompleteChar: '\u2591',
603
+ hideCursor: true
604
+ }, Presets.shades_classic);
605
+
606
+ if (progressBar) progressBar.start(filesToRestore.length, 0);
607
+
608
+ const limit = pLimit(options.concurrency || 10);
609
+ const filePromises = filesToRestore.map((file, index) =>
610
+ limit(async () => {
611
+ try {
612
+ const fullPath = path.join(absoluteTargetDir, file.path);
613
+ const dir = path.dirname(fullPath);
614
+
615
+ await fs.mkdir(dir, { recursive: true });
616
+ await fs.writeFile(fullPath, file.content, 'utf-8');
617
+
618
+ stats.restoredFiles++;
619
+
620
+ if (progressBar) {
621
+ progressBar.update(index + 1);
622
+ } else if (options.verbose) {
623
+ console.log(`✅ Restored: ${file.path}`);
624
+ }
625
+
626
+ return { success: true, file: file.path };
627
+ } catch (error) {
628
+ stats.failedFiles++;
629
+ stats.errors.push({ file: file.path, error: error.message });
630
+
631
+ if (options.verbose) {
632
+ console.log(`❌ Failed to restore: ${file.path} - ${error.message}`);
633
+ }
634
+
635
+ return { success: false, file: file.path, error: error.message };
636
+ }
637
+ })
638
+ );
639
+
640
+ await Promise.allSettled(filePromises);
641
+ if (progressBar) progressBar.stop();
642
+
643
+ console.log('\n📊 Restore Summary');
644
+ console.log('='.repeat(50));
645
+ console.log(`🎉 Restore completed!`);
646
+ console.log(`✅ Successfully restored: ${stats.restoredFiles} files`);
647
+ if (stats.failedFiles > 0) {
648
+ console.log(`❌ Failed to restore: ${stats.failedFiles} files`);
649
+ if (stats.errors.length > 0) {
650
+ console.log('\n⚠️ Errors encountered:');
651
+ stats.errors.slice(0, 5).forEach(({ file, error }) => {
652
+ console.log(` ${file}: ${error}`);
653
+ });
654
+ if (stats.errors.length > 5) {
655
+ console.log(` ... and ${stats.errors.length - 5} more errors`);
656
+ }
657
+ }
658
+ }
659
+ console.log(`📁 Target directory: ${absoluteTargetDir}`);
660
+ console.log('='.repeat(50));
661
+
662
+ } catch (error) {
663
+ console.error('\n❌ An error occurred during restore:');
664
+ console.error(error.message);
665
+ if (options.verbose) {
666
+ console.error(error.stack);
667
+ }
668
+ process.exit(1);
669
+ }
670
+ }
671
+
672
+ function parseSnapshotContent(content) {
673
+ const files = [];
674
+ const fileRegex = /--- File: \/(.+) ---/g;
675
+ const sections = content.split(fileRegex);
676
+
677
+ for (let i = 1; i < sections.length; i += 2) {
678
+ const filePath = sections[i].trim();
679
+ let fileContent = sections[i + 1] || '';
680
+
681
+ if (fileContent.startsWith('\n\n')) {
682
+ fileContent = fileContent.substring(2);
683
+ }
684
+ if (fileContent.endsWith('\n\n')) {
685
+ fileContent = fileContent.substring(0, fileContent.length - 2);
686
+ }
687
+
688
+ files.push({ path: filePath, content: fileContent });
689
+ }
690
+
691
+ return files;
692
+ }
693
+
694
+ function filterFilesToRestore(files, options) {
695
+ let filtered = files;
696
+
697
+ if (options.include) {
698
+ const includePatterns = Array.isArray(options.include) ? options.include : [options.include];
699
+ filtered = filtered.filter(file =>
700
+ includePatterns.some(pattern => {
701
+ const regex = new RegExp(pattern.replace(/\*/g, '.*'));
702
+ return regex.test(file.path);
703
+ })
704
+ );
705
+ }
706
+
707
+ if (options.exclude) {
708
+ const excludePatterns = Array.isArray(options.exclude) ? options.exclude : [options.exclude];
709
+ filtered = filtered.filter(file =>
710
+ !excludePatterns.some(pattern => {
711
+ const regex = new RegExp(pattern.replace(/\*/g, '.*'));
712
+ return regex.test(file.path);
713
+ })
714
+ );
715
+ }
716
+
717
+ return filtered;
718
+ }
719
+
720
+ function validateFilePaths(files, targetDir) {
721
+ const invalidFiles = [];
722
+
723
+ for (const file of files) {
724
+ const normalizedPath = path.normalize(file.path);
725
+
726
+ if (normalizedPath.includes('..') ||
727
+ normalizedPath.startsWith('/') ||
728
+ normalizedPath.includes('\0') ||
729
+ /[<>:"|?*]/.test(normalizedPath)) {
730
+ invalidFiles.push(file.path);
731
+ }
732
+ }
733
+
734
+ return invalidFiles;
735
+ }
736
+
737
+
738
+ // --- CLI SETUP ---
512
739
  const program = new Command();
513
740
 
514
741
  program
515
742
  .name('eck-snapshot')
516
- .description('A CLI tool to create a single text snapshot of a local Git repository.')
517
- .version('2.0.0')
743
+ .description('A CLI tool to create and restore single-file text snapshots of a Git repository.')
744
+ .version('2.1.0');
745
+
746
+ // Snapshot command (existing)
747
+ program
748
+ .command('snapshot', { isDefault: true })
749
+ .description('Create a snapshot of a Git repository (default command).')
518
750
  .argument('[repoPath]', 'Path to the git repository to snapshot.', process.cwd())
519
- .option('-o, --output <dir>', 'Output directory for the snapshot file.', path.join(__dirname, 'snapshots'))
751
+ .option('-o, --output <dir>', 'Output directory for the snapshot file.', path.join(process.cwd(), 'snapshots'))
520
752
  .option('--no-tree', 'Do not include the directory tree in the snapshot.')
521
753
  .option('-v, --verbose', 'Show detailed processing information, including skipped files.')
522
754
  .option('--max-file-size <size>', 'Maximum file size to include (e.g., 10MB)', '10MB')
523
755
  .option('--max-total-size <size>', 'Maximum total snapshot size (e.g., 100MB)', '100MB')
524
- .option('--max-depth <number>', 'Maximum directory depth for tree generation', parseInt, 10)
756
+ .option('--max-depth <number>', 'Maximum directory depth for tree generation', (val) => parseInt(val), 10)
525
757
  .option('--config <path>', 'Path to configuration file')
526
758
  .option('--compress', 'Compress output file with gzip')
527
759
  .option('--include-hidden', 'Include hidden files (starting with .)')
528
760
  .option('--format <type>', 'Output format: txt, json', 'txt')
529
- .action(createRepoSnapshot);
761
+ .action((repoPath, options) => createRepoSnapshot(repoPath, options));
762
+
763
+ program
764
+ .command('restore')
765
+ .description('Restore files and directories from a snapshot file')
766
+ .argument('<snapshot_file>', 'Path to the snapshot file (.txt, .json, or .gz)')
767
+ .argument('[target_directory]', 'Directory to restore the files into', process.cwd())
768
+ .option('-f, --force', 'Force overwrite of existing files without confirmation')
769
+ .option('-v, --verbose', 'Show detailed processing information')
770
+ .option('--dry-run', 'Show what would be restored without actually writing files')
771
+ .option('--include <patterns...>', 'Include only files matching these patterns (supports wildcards)')
772
+ .option('--exclude <patterns...>', 'Exclude files matching these patterns (supports wildcards)')
773
+ .option('--concurrency <number>', 'Number of concurrent file operations', (val) => parseInt(val), 10)
774
+ .action((snapshotFile, targetDir, options) => restoreSnapshot(snapshotFile, targetDir, options));
530
775
 
531
776
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xelth/eck-snapshot",
3
- "version": "1.5.1",
4
- "description": "A CLI tool to create a single-file text snapshot of a Git repository, ideal for providing context to LLMs.",
3
+ "version": "2.2.0",
4
+ "description": "A CLI tool to create and restore single-file text snapshots of a Git repository.",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "bin": {
@@ -16,14 +16,6 @@
16
16
  "scripts": {
17
17
  "test": "echo \"Error: no test specified\" && exit 1"
18
18
  },
19
- "keywords": [
20
- "snapshot",
21
- "repository",
22
- "cli",
23
- "git",
24
- "llm",
25
- "context"
26
- ],
27
19
  "author": "xelth-com",
28
20
  "license": "MIT",
29
21
  "repository": {
@@ -36,6 +28,7 @@
36
28
  "is-binary-path": "^2.1.0",
37
29
  "ignore": "^5.3.1",
38
30
  "cli-progress": "^3.12.0",
39
- "p-limit": "^5.0.0"
31
+ "p-limit": "^5.0.0",
32
+ "inquirer": "^9.2.20"
40
33
  }
41
- }
34
+ }