context-squeezer-cli 1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Erhan Namal
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 ADDED
@@ -0,0 +1,72 @@
1
+ # 🚀 Context-Squeezer
2
+
3
+ ![CI](https://github.com/ErhanNamal/context-squeezer/actions/workflows/ci.yml/badge.svg)
4
+ ![npm](https://img.shields.io/badge/npm-friendly-brightgreen)
5
+
6
+ Lightweight CLI that packs a repository into a single, LLM-optimized context file. Ideal for feeding projects to ChatGPT, Claude, or local LLMs without wasting tokens on irrelevant files.
7
+
8
+ ---
9
+
10
+ ## 🔥 Features
11
+
12
+ - **Smart Code Squeezing:** Produces a structured, human- and LLM-friendly summary of your codebase.
13
+ - **Automatic Ignore Detection:** Skips common heavy folders and files (`node_modules`, `.git`, `dist`, lockfiles, etc.).
14
+ - **Live Stats & Token Estimation:** Shows total files, lines, characters and an estimated LLM token count.
15
+ - **Custom Output:** Change the output filename or destination with a CLI flag.
16
+
17
+ ---
18
+
19
+ ## 🛠️ Installation
20
+
21
+ Clone and install dependencies:
22
+
23
+ ```bash
24
+ git clone https://github.com/ErhanNamal/context-squeezer.git
25
+ cd context-squeezer
26
+ npm install
27
+ ```
28
+
29
+ ## 🚀 Usage
30
+
31
+ Squeeze the current project and write the default `ai_context.txt`:
32
+
33
+ ```bash
34
+ npm start
35
+ ```
36
+
37
+ Specify a custom output path:
38
+
39
+ ```bash
40
+ npm start -- --output my_project_summary.txt
41
+ ```
42
+
43
+ ## 🖥️ Terminal Dashboard (example)
44
+
45
+ When run, the CLI prints a compact analytics dashboard similar to the example below:
46
+
47
+ ```
48
+ =============================================
49
+ 🚀 CONTEXT-SQUEEZER - AI Context Prepared
50
+ =============================================
51
+
52
+ 🔍 Analyzing directory: D:\Koufc\Yazılım Çalışmaları\Karışık\context-squeezer
53
+
54
+ 📊 PROJECT ANALYTICS REPORT
55
+ ---------------------------------------------
56
+ 📂 Total Files Compressed : 5
57
+ 📝 Total Lines of Code : 155
58
+ 🔤 Total Character Count : 4940
59
+ 🪙 Estimated LLM Token Load: ~1235 tokens
60
+ ---------------------------------------------
61
+
62
+ ✅ Success! Your LLM context file is ready:
63
+ 👉 D:\Koufc\Yazılım Çalışmaları\Karışık\context-squeezer\ai_context.txt
64
+ ```
65
+
66
+ ## 🤝 Contributing
67
+
68
+ Contributions are welcome — open issues or pull requests for improvements. Add examples, edge-case handling, or CI workflows to help others use the tool.
69
+
70
+ ## 📝 License
71
+
72
+ Distributed under the MIT License. See LICENSE for details.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/index.js';
@@ -0,0 +1,75 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ const DEFAULT_IGNORES = [
4
+ 'node_modules',
5
+ '.git',
6
+ 'package-lock.json',
7
+ 'yarn.lock',
8
+ 'pnpm-lock.yaml',
9
+ 'dist',
10
+ 'build',
11
+ '.DS_Store'
12
+ ];
13
+ // Kuralları çalışma zamanında hafızada tutacak önbellek mekanizması
14
+ let cachedCustomRules = null;
15
+ /**
16
+ * Projenin kök dizinindeki .aiignore dosyasını sadece bir kez okuyarak belleğe alır.
17
+ * @param projectRoot Projenin ana çalışma dizini
18
+ */
19
+ function loadCustomIgnoreRules(projectRoot) {
20
+ if (cachedCustomRules !== null) {
21
+ return cachedCustomRules;
22
+ }
23
+ const aiIgnorePath = path.join(projectRoot, '.aiignore');
24
+ if (!fs.existsSync(aiIgnorePath)) {
25
+ cachedCustomRules = [];
26
+ return cachedCustomRules;
27
+ }
28
+ try {
29
+ const content = fs.readFileSync(aiIgnorePath, 'utf-8');
30
+ cachedCustomRules = content
31
+ .split('\n')
32
+ .map(line => line.trim())
33
+ .filter(line => line.length > 0 && !line.startsWith('#'));
34
+ }
35
+ catch (err) {
36
+ cachedCustomRules = [];
37
+ }
38
+ return cachedCustomRules;
39
+ }
40
+ /**
41
+ * Belirtilen dosya yolunun engellenip engellenmeyeceğini kontrol eder.
42
+ * @param filePath Denetlenecek dosyanın tam yolu
43
+ * @param projectRoot Projenin ana çalışma dizini
44
+ */
45
+ export function shouldIgnore(filePath, projectRoot) {
46
+ // Windows'taki \ işaretlerini / işaretine çevirerek yolları normalize ediyoruz
47
+ const relativePath = path.relative(projectRoot, filePath).replace(/\\/g, '/');
48
+ const parts = relativePath.split('/');
49
+ // 1. Çıktı ve test dosyalarının kendi kendini taramasını engeller
50
+ const file = path.basename(filePath);
51
+ if (file.endsWith('.txt') && (file === 'ai_context.txt' || file === 'kodlarim.txt' || file === 'test_output.txt')) {
52
+ return true;
53
+ }
54
+ // 2. Varsayılan global engelleme listesi kontrolü
55
+ if (parts.some(part => DEFAULT_IGNORES.includes(part))) {
56
+ return true;
57
+ }
58
+ // 3. Kullanıcı tanımlı .aiignore kurallarının kontrolü
59
+ const customRules = loadCustomIgnoreRules(projectRoot);
60
+ for (const rule of customRules) {
61
+ // .aiignore içindeki olası Windows yollarını da normalize et
62
+ const normalizedRule = rule.replace(/\\/g, '/');
63
+ if (normalizedRule.startsWith('*.')) {
64
+ const ext = normalizedRule.slice(1); // Örn: .json uzantısını yakalar
65
+ if (filePath.endsWith(ext))
66
+ return true;
67
+ }
68
+ else if (relativePath === normalizedRule ||
69
+ parts.includes(normalizedRule) ||
70
+ relativePath.startsWith(normalizedRule + '/')) {
71
+ return true;
72
+ }
73
+ }
74
+ return false;
75
+ }
package/dist/index.js ADDED
@@ -0,0 +1,54 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { packProject } from './packager.js';
4
+ function printHelp() {
5
+ console.log(`
6
+ Context-Squeezer - usage
7
+
8
+ Options:
9
+ -h, --help Show this help message
10
+ -o, --output <file> Write output to <file> (default: ai_context.txt)
11
+
12
+ Examples:
13
+ npm start -- --output my_context.txt
14
+ `);
15
+ }
16
+ function main() {
17
+ const args = process.argv.slice(2);
18
+ if (args.includes('-h') || args.includes('--help')) {
19
+ printHelp();
20
+ return;
21
+ }
22
+ let outputFileName = 'ai_context.txt';
23
+ const oIndex = args.findIndex(a => a === '-o' || a === '--output');
24
+ if (oIndex !== -1 && args[oIndex + 1]) {
25
+ outputFileName = args[oIndex + 1];
26
+ }
27
+ const targetDir = process.cwd();
28
+ console.log('\n=============================================');
29
+ console.log('🚀 CONTEXT-SQUEEZER - AI Context Prepared');
30
+ console.log('=============================================\n');
31
+ console.log(`🔍 Analyzing directory: ${targetDir}`);
32
+ try {
33
+ const { combinedContent, stats } = packProject(targetDir);
34
+ const outputPath = path.isAbsolute(outputFileName)
35
+ ? outputFileName
36
+ : path.join(targetDir, outputFileName);
37
+ fs.writeFileSync(outputPath, combinedContent, 'utf-8');
38
+ console.log('\n📊 PROJECT ANALYTICS REPORT');
39
+ console.log('---------------------------------------------');
40
+ console.log(`📂 Total Files Compressed : ${stats.totalFiles}`);
41
+ console.log(`📝 Total Lines of Code : ${stats.totalLines}`);
42
+ console.log(`🔤 Total Character Count : ${stats.totalChars}`);
43
+ console.log(`🪙 Estimated LLM Token Load: ~${stats.estimatedTokens} tokens`);
44
+ console.log('---------------------------------------------');
45
+ console.log(`\n✅ Success! Your LLM context file is ready:`);
46
+ console.log(`👉 ${outputPath}\n`);
47
+ }
48
+ catch (err) {
49
+ console.error('❌ An error occurred while building the context file:');
50
+ console.error(err instanceof Error ? err.message : String(err));
51
+ process.exit(1);
52
+ }
53
+ }
54
+ main();
@@ -0,0 +1,108 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { shouldIgnore } from './ignorer.js';
4
+ // Güvenli işleme için maksimum dosya boyutu sınırı (Örn: 500 KB)
5
+ const MAX_FILE_SIZE_BYTES = 500 * 1024;
6
+ /**
7
+ * Bir dosyanın ikili (binary) olup olmadığını ilk byte bloklarını tarayarak kontrol eder.
8
+ * @param filePath Denetlenecek dosyanın tam yolu
9
+ */
10
+ function isBinaryFile(filePath) {
11
+ const buffer = Buffer.alloc(512);
12
+ let fd = null;
13
+ try {
14
+ fd = fs.openSync(filePath, 'r');
15
+ const bytesRead = fs.readSync(fd, buffer, 0, 512, 0);
16
+ for (let i = 0; i < bytesRead; i++) {
17
+ // Null karakteri (\0) kontrolü ikili dosyaları tespit etmek için en güvenli yöntemdir
18
+ if (buffer[i] === 0) {
19
+ return true;
20
+ }
21
+ }
22
+ return false;
23
+ }
24
+ catch (err) {
25
+ return true; // Okuma hatası durumunda güvenli tarafta kalmak için binary kabul edilir
26
+ }
27
+ finally {
28
+ if (fd !== null) {
29
+ fs.closeSync(fd);
30
+ }
31
+ }
32
+ }
33
+ /**
34
+ * Kod içindeki hassas şifre, token ve API anahtarlarını sansürler
35
+ */
36
+ function maskSensitiveData(content) {
37
+ const sensitivePatterns = [
38
+ /(secret[-_]?key|api[-_]?key|password|passwd|auth[-_]?token|client[-_]?secret)\s*[:=]\s*['"`][^'"`]{4,200}['"`]/gi,
39
+ /AIzaSy[A-Za-z0-9-_]{33}/g,
40
+ /sk-[a-zA-Z0-9]{48}/g
41
+ ];
42
+ let maskedContent = content;
43
+ for (const pattern of sensitivePatterns) {
44
+ maskedContent = maskedContent.replace(pattern, (match) => {
45
+ if (match.includes('=') || match.includes(':')) {
46
+ const separator = match.includes('=') ? '=' : ':';
47
+ const parts = match.split(separator);
48
+ return `${parts[0]}${separator} "[REDACTED BY CONTEXT-SQUEEZER]"`;
49
+ }
50
+ return "[REDACTED BY CONTEXT-SQUEEZER]";
51
+ });
52
+ }
53
+ return maskedContent;
54
+ }
55
+ export function packProject(dirPath) {
56
+ let combinedContent = '';
57
+ const stats = {
58
+ totalFiles: 0,
59
+ totalLines: 0,
60
+ totalChars: 0,
61
+ estimatedTokens: 0
62
+ };
63
+ function scan(currentDir) {
64
+ const files = fs.readdirSync(currentDir);
65
+ for (const file of files) {
66
+ const fullPath = path.join(currentDir, file);
67
+ const stat = fs.statSync(fullPath);
68
+ if (shouldIgnore(fullPath, dirPath)) {
69
+ continue;
70
+ }
71
+ if (stat.isDirectory()) {
72
+ scan(fullPath);
73
+ }
74
+ else if (stat.isFile()) {
75
+ const relativePath = path.relative(dirPath, fullPath);
76
+ // Çıktı ve test dosyalarının kendi kendini taramasını engeller
77
+ if (file.endsWith('.txt') && (file === 'ai_context.txt' || file === 'kodlarim.txt' || file === 'test_output.txt')) {
78
+ continue;
79
+ }
80
+ // PERFORMANS & GÜVENLİK: Belirlenen boyuttan büyük dosyaları atlar
81
+ if (stat.size > MAX_FILE_SIZE_BYTES) {
82
+ console.warn(`⚠️ Skipped large file (over 500KB): ${relativePath}`);
83
+ continue;
84
+ }
85
+ // GÜVENLİK: İkili (Resim, PDF, Derlenmiş Dosya vb.) içerikleri filtreler
86
+ if (isBinaryFile(fullPath)) {
87
+ continue;
88
+ }
89
+ try {
90
+ const content = fs.readFileSync(fullPath, 'utf-8');
91
+ const safeContent = maskSensitiveData(content);
92
+ stats.totalFiles += 1;
93
+ stats.totalLines += safeContent.split('\n').length;
94
+ stats.totalChars += safeContent.length;
95
+ combinedContent += `\n--- START OF FILE: ${relativePath} ---\n`;
96
+ combinedContent += safeContent;
97
+ combinedContent += `\n--- END OF FILE: ${relativePath} ---\n`;
98
+ }
99
+ catch (e) {
100
+ // Okunamayan dosyaları es geç
101
+ }
102
+ }
103
+ }
104
+ }
105
+ scan(dirPath);
106
+ stats.estimatedTokens = Math.ceil(stats.totalChars / 4);
107
+ return { combinedContent, stats };
108
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "context-squeezer-cli",
3
+ "version": "1.0.0",
4
+ "description": "Lightweight CLI that packs a repository into a single, LLM-optimized context file.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "context-squeezer": "bin/context-squeezer.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "bin",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "start": "tsc && node ./dist/index.js",
19
+ "test": "node ./scripts/run-tests.js"
20
+ },
21
+ "keywords": [
22
+ "cli",
23
+ "llm",
24
+ "context",
25
+ "tokenizer",
26
+ "ai",
27
+ "devtools"
28
+ ],
29
+ "author": "Erhan Namal",
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/ErhanNamal/context-squeezer.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/ErhanNamal/context-squeezer/issues"
37
+ },
38
+ "homepage": "https://github.com/ErhanNamal/context-squeezer#readme",
39
+ "devDependencies": {
40
+ "@types/node": "^20.11.0",
41
+ "typescript": "^5.3.3"
42
+ }
43
+ }