@xelth/eck-snapshot 1.5.1 → 2.1.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 +86 -22
  2. package/index.js +155 -16
  3. package/package.json +4 -11
package/README.md CHANGED
@@ -1,15 +1,76 @@
1
+ # eck-snapshot
1
2
 
2
- # eckSnapshot
3
+ [](https://www.google.com/search?q=https://www.npmjs.com/package/%40xelth/eck-snapshot)
4
+ [](https://www.google.com/search?q=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 LLMs like Claude or GPT-4, you often need to provide the full context of your project. Manually copying and pasting dozens of files is tedious and inefficient. This tool automates the process by creating a single, comprehensive file that you can easily feed into an LLM. And with the new `restore` feature, you can instantly recreate a project structure from a snapshot.
11
+
12
+ ## Key Features
13
+
14
+ * **Git Integration**: Automatically includes all files tracked by Git.
15
+ * **Intelligent Ignoring**: Respects `.gitignore` rules and has its own configurable ignore lists for files, extensions, and directories.
16
+ * **Restore from Snapshot**: The new `restore` command allows you to recreate files and folders from a snapshot file.
17
+ * **Directory Tree**: Generates a clean, readable tree of the repository structure at the top of the snapshot.
18
+ * **Configurable**: Customize behavior using an `.ecksnapshot.config.js` file.
19
+ * **Progress and Stats**: Provides a progress bar and a detailed summary of what was included and skipped.
20
+ * **Compression**: Supports gzipped (`.gz`) snapshots for smaller file sizes.
21
+
22
+ ## Demo
23
+
24
+ Here's an example of `eck-snapshot` in action:
25
+
26
+ ```
27
+ 🚀 Starting snapshot for repository: /path/to/your/project
28
+ ✅ .gitignore patterns loaded
29
+ 📊 Found 152 total files in the repository
30
+ 🌳 Generating directory tree...
31
+ 📝 Processing files...
32
+ Progress |██████████████████████████████| 100% | 152/152 files
33
+
34
+ 📊 Snapshot Summary
35
+ ==================================================
36
+ 🎉 Snapshot created successfully!
37
+ 📄 File saved to: /path/to/your/project/snapshots/project_snapshot_...txt
38
+ 📈 Included text files: 130 of 152
39
+ ⏭️ Skipped files: 22
40
+ ...
41
+ ==================================================
42
+ ```
43
+
44
+ The beginning of the generated file will look like this:
45
+
46
+ ```text
47
+ Directory Structure:
48
+
49
+ ├── .github/
50
+ │ └── workflows/
51
+ │ └── publish.yml
52
+ ├── src/
53
+ │ ├── utils/
54
+ │ │ └── formatters.js
55
+ │ └── index.js
56
+ ├── .gitignore
57
+ ├── package.json
58
+ └── README.md
59
+
60
+
61
+ --- File: /src/index.js ---
62
+
63
+ #!/usr/bin/env node
64
+ import { Command } from 'commander';
65
+ // ... rest of the file content
66
+
67
+ --- File: /package.json ---
68
+
69
+ {
70
+ "name": "eck-snapshot",
71
+ "version": "2.1.0",
72
+ // ... rest of the file content
73
+ ```
13
74
 
14
75
  ## Installation
15
76
 
@@ -17,31 +78,34 @@ To install the tool globally, run the following command:
17
78
 
18
79
  ```bash
19
80
  npm install -g @xelth/eck-snapshot
20
- ````
21
-
81
+ ```
22
82
 
23
83
  ## Usage
24
84
 
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.
85
+ Once installed, you can run the tool from any directory in your terminal.
26
86
 
27
- **Generate a snapshot of the current directory:**
87
+ ### Creating a Snapshot
28
88
 
29
89
  ```bash
90
+ # Create a snapshot of the current directory
30
91
  eck-snapshot
31
- ```
32
92
 
33
- **Specify a path to another repository:**
34
-
35
- ```bash
93
+ # Specify a path to another repository
36
94
  eck-snapshot /path/to/your/other/project
95
+
96
+ # Save the snapshot to a different directory and exclude the tree view
97
+ eck-snapshot --output ./backups --no-tree
37
98
  ```
38
99
 
39
- **Common Options:**
100
+ ### Restoring from a Snapshot
40
101
 
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.
102
+ ```bash
103
+ # Restore files from a snapshot into the current directory
104
+ eck-snapshot restore ./snapshots/project_snapshot_...txt
105
+
106
+ # Restore into a new directory without a confirmation prompt
107
+ eck-snapshot restore snapshot.txt ./restored-project --force
108
+ ```
45
109
 
46
110
  ## Configuration
47
111
 
@@ -74,4 +138,4 @@ export default {
74
138
 
75
139
  ## License
76
140
 
77
- This project is licensed under the MIT License.
141
+ 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,162 @@ async function createRepoSnapshot(repoPath, options) {
508
509
  }
509
510
  }
510
511
 
511
- // CLI setup
512
+ // NEW: Restore Snapshot Function
513
+ async function restoreSnapshot(snapshotFile, targetDir, options) {
514
+ const absoluteSnapshotPath = path.resolve(snapshotFile);
515
+ const absoluteTargetDir = path.resolve(targetDir);
516
+
517
+ console.log(`⚙️ Starting restore from snapshot: ${absoluteSnapshotPath}`);
518
+ console.log(`🗂️ Target directory: ${absoluteTargetDir}`);
519
+
520
+ try {
521
+ let rawContent;
522
+ // Check if the file is compressed
523
+ if (snapshotFile.endsWith('.gz')) {
524
+ const compressedBuffer = await fs.readFile(absoluteSnapshotPath);
525
+ rawContent = (await gunzip(compressedBuffer)).toString('utf-8');
526
+ console.log('✅ Decompressed gzipped snapshot.');
527
+ } else {
528
+ rawContent = await fs.readFile(absoluteSnapshotPath, 'utf-8');
529
+ }
530
+
531
+ // Check if the content is JSON
532
+ let filesToRestore;
533
+ try {
534
+ const jsonData = JSON.parse(rawContent);
535
+ if (jsonData.content) {
536
+ console.log('📄 Detected JSON format, extracting content.');
537
+ filesToRestore = parseSnapshotContent(jsonData.content);
538
+ } else {
539
+ throw new Error('JSON format detected, but no "content" key found.');
540
+ }
541
+ } catch (e) {
542
+ // Not a JSON file, or not the format we expect. Treat as plain text.
543
+ console.log('📄 Treating snapshot as plain text format.');
544
+ filesToRestore = parseSnapshotContent(rawContent);
545
+ }
546
+
547
+ if (filesToRestore.length === 0) {
548
+ console.warn('⚠️ No files found to restore in the snapshot.');
549
+ return;
550
+ }
551
+
552
+ console.log(`📊 Found ${filesToRestore.length} files in the snapshot.`);
553
+
554
+ // Confirmation prompt
555
+ if (!options.force) {
556
+ const { confirm } = await inquirer.prompt([
557
+ {
558
+ type: 'confirm',
559
+ name: 'confirm',
560
+ message: `You are about to write ${filesToRestore.length} files to ${absoluteTargetDir}. Existing files will be overwritten. Are you sure you want to continue?`,
561
+ default: false,
562
+ },
563
+ ]);
564
+
565
+ if (!confirm) {
566
+ console.log('🚫 Restore operation cancelled by user.');
567
+ return;
568
+ }
569
+ }
570
+
571
+ // Create target directory if it doesn't exist
572
+ await fs.mkdir(absoluteTargetDir, { recursive: true });
573
+
574
+ // Restore files
575
+ const progressBar = new SingleBar({
576
+ format: 'Restoring |{bar}| {percentage}% | {value}/{total} files',
577
+ barCompleteChar: '\u2588',
578
+ barIncompleteChar: '\u2591',
579
+ hideCursor: true
580
+ }, Presets.shades_classic);
581
+
582
+ progressBar.start(filesToRestore.length, 0);
583
+
584
+ for (const file of filesToRestore) {
585
+ const fullPath = path.join(absoluteTargetDir, file.path);
586
+ const dir = path.dirname(fullPath);
587
+
588
+ // Create directory for the file
589
+ await fs.mkdir(dir, { recursive: true });
590
+ // Write the file content
591
+ await fs.writeFile(fullPath, file.content);
592
+ progressBar.increment();
593
+ }
594
+
595
+ progressBar.stop();
596
+ console.log(`\n🎉 Restore complete! ${filesToRestore.length} files have been written to ${absoluteTargetDir}.`);
597
+
598
+ } catch (error) {
599
+ console.error('\n❌ An error occurred during restore:');
600
+ console.error(error.message);
601
+ if (options.verbose) {
602
+ console.error(error.stack);
603
+ }
604
+ process.exit(1);
605
+ }
606
+ }
607
+
608
+ // NEW: Snapshot Parser Function
609
+ function parseSnapshotContent(content) {
610
+ const files = [];
611
+ const fileRegex = /--- File: \/(.+) ---/g;
612
+ const sections = content.split(fileRegex);
613
+
614
+ // sections will be [ '...everything before first match...', 'path1', 'content1', 'path2', 'content2', ...]
615
+ // We start at index 1 because index 0 is anything before the first delimiter (like the directory tree)
616
+ for (let i = 1; i < sections.length; i += 2) {
617
+ const filePath = sections[i].trim();
618
+ let fileContent = sections[i + 1] || '';
619
+
620
+ // Remove the trailing newline that the split might leave
621
+ if (fileContent.startsWith('\n\n')) {
622
+ fileContent = fileContent.substring(2);
623
+ }
624
+ if (fileContent.endsWith('\n\n')) {
625
+ fileContent = fileContent.substring(0, fileContent.length - 2);
626
+ }
627
+
628
+ files.push({ path: filePath, content: fileContent });
629
+ }
630
+
631
+ return files;
632
+ }
633
+
634
+
635
+ // --- CLI SETUP ---
512
636
  const program = new Command();
513
637
 
514
638
  program
515
639
  .name('eck-snapshot')
516
- .description('A CLI tool to create a single text snapshot of a local Git repository.')
517
- .version('2.0.0')
640
+ .description('A CLI tool to create and restore single-file text snapshots of a Git repository.')
641
+ .version('2.1.0');
642
+
643
+ // Snapshot command (existing)
644
+ program
645
+ .command('snapshot', { isDefault: true })
646
+ .description('Create a snapshot of a Git repository (default command).')
518
647
  .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'))
648
+ .option('-o, --output <dir>', 'Output directory for the snapshot file.', path.join(process.cwd(), 'snapshots'))
520
649
  .option('--no-tree', 'Do not include the directory tree in the snapshot.')
521
650
  .option('-v, --verbose', 'Show detailed processing information, including skipped files.')
522
651
  .option('--max-file-size <size>', 'Maximum file size to include (e.g., 10MB)', '10MB')
523
652
  .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)
653
+ .option('--max-depth <number>', 'Maximum directory depth for tree generation', (val) => parseInt(val), 10)
525
654
  .option('--config <path>', 'Path to configuration file')
526
655
  .option('--compress', 'Compress output file with gzip')
527
656
  .option('--include-hidden', 'Include hidden files (starting with .)')
528
657
  .option('--format <type>', 'Output format: txt, json', 'txt')
529
- .action(createRepoSnapshot);
658
+ .action((repoPath, options) => createRepoSnapshot(repoPath, options));
659
+
660
+ // NEW: Restore command (Corrected)
661
+ program
662
+ .command('restore') // Убираем аргументы отсюда
663
+ .description('Restore files and directories from a snapshot file.')
664
+ .argument('<snapshot_file>', 'Path to the snapshot file (.txt, .json, or .gz).') // Аргументы определяем здесь
665
+ .argument('[target_directory]', 'Directory to restore the files into.', process.cwd())
666
+ .option('-f, --force', 'Force overwrite of existing files without confirmation.')
667
+ .option('-v, --verbose', 'Show detailed processing information.')
668
+ .action((snapshotFile, targetDir, options) => restoreSnapshot(snapshotFile, targetDir, options));
530
669
 
531
670
  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.1.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
  }