combicode 1.0.2 → 1.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.
package/README.md CHANGED
@@ -4,15 +4,16 @@
4
4
  [![PyPI Version](https://img.shields.io/pypi/v/combicode.svg)](https://pypi.org/project/combicode/)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
- <img align="center" src="https://github.com/aaurelions/combicode/raw/main/screenshot.png" width="600"/>
7
+ <img align="center" src="https://github.com/aaurelions/combicode/raw/main/screenshot.png" width="800"/>
8
8
 
9
9
  **Combicode** is a zero-dependency CLI tool that intelligently combines your project's source code into a single, LLM-friendly text file.
10
10
 
11
- Paste the contents of `combicode.txt` into ChatGPT, Claude, or any other LLM to give it the full context of your repository instantly.
11
+ The generated file starts with a system prompt and a file tree overview, priming the LLM to understand your project's complete context instantly. Paste the contents of `combicode.txt` into ChatGPT, Claude, or any other LLM to get started.
12
12
 
13
13
  ## Why use Combicode?
14
14
 
15
- - **Maximum Context:** Give your LLM a complete picture of your project structure and code.
15
+ - **Maximum Context:** Gives your LLM a complete picture of your project structure and code.
16
+ - **Intelligent Priming:** Starts the output with a system prompt and a file tree, directing the LLM to analyze the entire codebase before responding.
16
17
  - **Intelligent Ignoring:** Automatically skips `node_modules`, `.venv`, `dist`, `.git`, binary files, and other common junk.
17
18
  - **`.gitignore` Aware:** Respects your project's existing `.gitignore` rules out of the box.
18
19
  - **Zero-Install Usage:** Run it directly with `npx` or `pipx` without polluting your environment.
@@ -34,7 +35,7 @@ npx combicode
34
35
  pipx run combicode
35
36
  ```
36
37
 
37
- This will create a `combicode.txt` file in your project directory.
38
+ This will create a `combicode.txt` file in your project directory, complete with the context-setting header.
38
39
 
39
40
  ## Usage and Options
40
41
 
@@ -88,6 +89,7 @@ npx combicode -e "**/*_test.py,docs/**"
88
89
  | `--include-ext` | `-i` | Comma-separated list of extensions to exclusively include. | (include all) |
89
90
  | `--exclude` | `-e` | Comma-separated list of additional glob patterns to exclude. | (none) |
90
91
  | `--no-gitignore` | | Do not use patterns from the project's `.gitignore` file. | `false` |
92
+ | `--no-header` | | Omit the introductory prompt and file tree from the output. | `false` |
91
93
  | `--version` | `-v` | Show the version number. | |
92
94
  | `--help` | `-h` | Show the help message. | |
93
95
 
@@ -0,0 +1,53 @@
1
+ [
2
+ "**/node_modules/**",
3
+ "**/.git/**",
4
+ "**/.vscode/**",
5
+ "**/.idea/**",
6
+ "**/*.log",
7
+ "**/.env",
8
+ "**/*.lock",
9
+ "**/.venv/**",
10
+ "**/venv/**",
11
+ "**/env/**",
12
+ "**/__pycache__/**",
13
+ "**/*.pyc",
14
+ "**/*.egg-info/**",
15
+ "**/build/**",
16
+ "**/dist/**",
17
+ "**/.pytest_cache/**",
18
+ "**/.npm/**",
19
+ "**/pnpm-lock.yaml",
20
+ "**/package-lock.json",
21
+ "**/.next/**",
22
+ "**/.DS_Store",
23
+ "**/Thumbs.db",
24
+ "**/*.png",
25
+ "**/*.jpg",
26
+ "**/*.jpeg",
27
+ "**/*.gif",
28
+ "**/*.ico",
29
+ "**/*.svg",
30
+ "**/*.webp",
31
+ "**/*.mp3",
32
+ "**/*.wav",
33
+ "**/*.flac",
34
+ "**/*.mp4",
35
+ "**/*.mov",
36
+ "**/*.avi",
37
+ "**/*.zip",
38
+ "**/*.tar.gz",
39
+ "**/*.rar",
40
+ "**/*.pdf",
41
+ "**/*.doc",
42
+ "**/*.docx",
43
+ "**/*.xls",
44
+ "**/*.xlsx",
45
+ "**/*.dll",
46
+ "**/*.exe",
47
+ "**/*.so",
48
+ "**/*.a",
49
+ "**/*.lib",
50
+ "**/*.o",
51
+ "**/*.bin",
52
+ "**/*.iso"
53
+ ]
package/index.js CHANGED
@@ -8,60 +8,29 @@ const glob = require("fast-glob");
8
8
 
9
9
  const { version } = require("./package.json");
10
10
 
11
- const DEFAULT_IGNORE_PATTERNS = [
12
- "**/node_modules/**",
13
- "**/.git/**",
14
- "**/.vscode/**",
15
- "**/.idea/**",
16
- "**/*.log",
17
- "**/.env",
18
- "**/*.lock",
19
- "**/.venv/**",
20
- "**/venv/**",
21
- "**/env/**",
22
- "**/__pycache__/**",
23
- "**/*.pyc",
24
- "**/*.egg-info/**",
25
- "**/build/**",
26
- "**/dist/**",
27
- "**/.pytest_cache/**",
28
- "**/.npm/**",
29
- "**/pnpm-lock.yaml",
30
- "**/package-lock.json",
31
- "**/.next/**",
32
- "**/.DS_Store",
33
- "**/Thumbs.db",
34
- // Common binary file extensions
35
- "**/*.png",
36
- "**/*.jpg",
37
- "**/*.jpeg",
38
- "**/*.gif",
39
- "**/*.ico",
40
- "**/*.svg",
41
- "**/*.webp",
42
- "**/*.mp3",
43
- "**/*.wav",
44
- "**/*.flac",
45
- "**/*.mp4",
46
- "**/*.mov",
47
- "**/*.avi",
48
- "**/*.zip",
49
- "**/*.tar.gz",
50
- "**/*.rar",
51
- "**/*.pdf",
52
- "**/*.doc",
53
- "**/*.docx",
54
- "**/*.xls",
55
- "**/*.xlsx",
56
- "**/*.dll",
57
- "**/*.exe",
58
- "**/*.so",
59
- "**/*.a",
60
- "**/*.lib",
61
- "**/*.o",
62
- "**/*.bin",
63
- "**/*.iso",
64
- ];
11
+ const SYSTEM_PROMPT = `You are an expert software architect. The user is providing you with the complete source code for a project, contained in a single file. Your task is to meticulously analyze the provided codebase to gain a comprehensive understanding of its structure, functionality, dependencies, and overall architecture.
12
+
13
+ A file tree is provided below to give you a high-level overview. The subsequent sections contain the full content of each file, clearly marked with "// FILE: <path>".
14
+
15
+ Your instructions are:
16
+ 1. **Analyze Thoroughly:** Read through every file to understand its purpose and how it interacts with other files.
17
+ 2. **Identify Key Components:** Pay close attention to configuration files (like package.json, pyproject.toml), entry points (like index.js, main.py), and core logic.
18
+ `;
19
+
20
+ function loadDefaultIgnorePatterns() {
21
+ const configPath = path.resolve(__dirname, "config", "ignore.json");
22
+ try {
23
+ const rawConfig = fs.readFileSync(configPath, "utf8");
24
+ return JSON.parse(rawConfig);
25
+ } catch (err) {
26
+ console.error(
27
+ `❌ Critical: Could not read or parse bundled ignore config at ${configPath}`
28
+ );
29
+ process.exit(1);
30
+ }
31
+ }
32
+
33
+ const DEFAULT_IGNORE_PATTERNS = loadDefaultIgnorePatterns();
65
34
 
66
35
  function isLikelyBinary(file) {
67
36
  const buffer = Buffer.alloc(512);
@@ -69,18 +38,52 @@ function isLikelyBinary(file) {
69
38
  try {
70
39
  fd = fs.openSync(file, "r");
71
40
  const bytesRead = fs.readSync(fd, buffer, 0, 512, 0);
72
- // Check for null bytes, a strong indicator of a binary file
73
41
  return buffer.slice(0, bytesRead).includes(0);
74
42
  } catch (e) {
75
- // If we can't read it, treat it as something to skip
76
43
  return true;
77
44
  } finally {
78
45
  if (fd) fs.closeSync(fd);
79
46
  }
80
47
  }
81
48
 
49
+ function generateFileTree(files, root) {
50
+ let tree = `${path.basename(root)}/\n`;
51
+ const structure = {};
52
+
53
+ files.forEach((file) => {
54
+ const parts = file.split(path.sep);
55
+ let currentLevel = structure;
56
+ parts.forEach((part) => {
57
+ if (!currentLevel[part]) {
58
+ currentLevel[part] = {};
59
+ }
60
+ currentLevel = currentLevel[part];
61
+ });
62
+ });
63
+
64
+ const buildTree = (level, prefix) => {
65
+ const entries = Object.keys(level);
66
+ entries.forEach((entry, index) => {
67
+ const isLast = index === entries.length - 1;
68
+ tree += `${prefix}${isLast ? "└── " : "├── "}${entry}\n`;
69
+ if (Object.keys(level[entry]).length > 0) {
70
+ buildTree(level[entry], `${prefix}${isLast ? " " : "│ "}`);
71
+ }
72
+ });
73
+ };
74
+
75
+ buildTree(structure, "");
76
+ return tree;
77
+ }
78
+
82
79
  async function main() {
83
- const argv = yargs(hideBin(process.argv))
80
+ const rawArgv = hideBin(process.argv);
81
+ if (rawArgv.includes("--version") || rawArgv.includes("-v")) {
82
+ console.log(`Combicode (JavaScript), version ${version}`);
83
+ process.exit(0);
84
+ }
85
+
86
+ const argv = yargs(rawArgv)
84
87
  .scriptName("combicode")
85
88
  .usage("$0 [options]")
86
89
  .option("o", {
@@ -110,7 +113,12 @@ async function main() {
110
113
  type: "boolean",
111
114
  default: false,
112
115
  })
113
- .version(version) // Read version from package.json
116
+ .option("no-header", {
117
+ describe: "Omit the introductory prompt and file tree from the output",
118
+ type: "boolean",
119
+ default: false,
120
+ })
121
+ .version(version)
114
122
  .alias("v", "version")
115
123
  .help()
116
124
  .alias("h", "help").argv;
@@ -155,7 +163,8 @@ async function main() {
155
163
 
156
164
  const includedFiles = allFiles
157
165
  .filter((file) => {
158
- if (fs.statSync(file).isDirectory()) return false;
166
+ const stats = fs.statSync(file, { throwIfNoEntry: false });
167
+ if (!stats || stats.isDirectory()) return false;
159
168
  if (isLikelyBinary(file)) return false;
160
169
  if (allowedExtensions && !allowedExtensions.has(path.extname(file)))
161
170
  return false;
@@ -168,22 +177,40 @@ async function main() {
168
177
  process.exit(1);
169
178
  }
170
179
 
180
+ const relativeFiles = includedFiles.map((file) =>
181
+ path.relative(projectRoot, file)
182
+ );
183
+
171
184
  if (argv.dryRun) {
172
- console.log("\n📋 Files to be included (Dry Run):");
173
- includedFiles.forEach((file) =>
174
- console.log(` - ${path.relative(projectRoot, file)}`)
175
- );
185
+ console.log("\n📋 Files to be included (Dry Run):\n");
186
+ const tree = generateFileTree(relativeFiles, projectRoot);
187
+ console.log(tree);
176
188
  console.log(`\nTotal: ${includedFiles.length} files.`);
177
189
  return;
178
190
  }
179
191
 
180
192
  const outputStream = fs.createWriteStream(argv.output);
193
+
194
+ if (!argv.noHeader) {
195
+ outputStream.write(SYSTEM_PROMPT + "\n");
196
+ outputStream.write("## Project File Tree\n\n");
197
+ outputStream.write("```\n");
198
+ const tree = generateFileTree(relativeFiles, projectRoot);
199
+ outputStream.write(tree);
200
+ outputStream.write("```\n\n");
201
+ outputStream.write("---\n\n");
202
+ }
203
+
181
204
  for (const file of includedFiles) {
182
205
  const relativePath = path.relative(projectRoot, file).replace(/\\/g, "/");
183
206
  outputStream.write(`// FILE: ${relativePath}` + "\n");
184
207
  outputStream.write("```\n");
185
- const content = fs.readFileSync(file, "utf8");
186
- outputStream.write(content);
208
+ try {
209
+ const content = fs.readFileSync(file, "utf8");
210
+ outputStream.write(content);
211
+ } catch (e) {
212
+ outputStream.write(`... (error reading file: ${e.message}) ...`);
213
+ }
187
214
  outputStream.write("\n```\n\n");
188
215
  }
189
216
  outputStream.end();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "combicode",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "A CLI tool to combine a project's codebase into a single file for LLM context.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -30,7 +30,6 @@
30
30
  "license": "MIT",
31
31
  "dependencies": {
32
32
  "fast-glob": "^3.3.1",
33
- "ignore": "^5.2.4",
34
33
  "yargs": "^17.7.2"
35
34
  }
36
35
  }