├── .codetreeignore
├── .editorconfig
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── bin
└── codetree.cjs
├── biome.json
├── package-lock.json
├── package.json
├── src
├── cli
│ ├── actions
│ │ ├── defaultAction.ts
│ │ ├── initAction.ts
│ │ ├── remoteAction.ts
│ │ └── versionAction.ts
│ ├── cliPrint.ts
│ ├── cliRun.ts
│ └── cliSpinner.ts
├── config
│ ├── configLoad.ts
│ ├── configSchema.ts
│ ├── defaultIgnore.ts
│ └── globalDirectory.ts
├── core
│ ├── file
│ │ ├── fileCollect.ts
│ │ ├── fileManipulate.ts
│ │ ├── filePathSort.ts
│ │ ├── fileProcess.ts
│ │ ├── fileSearch.ts
│ │ ├── fileTreeGenerate.ts
│ │ ├── fileTypes.ts
│ │ ├── packageJsonParse.ts
│ │ └── permissionCheck.ts
│ ├── output
│ │ ├── outputGenerate.ts
│ │ ├── outputGeneratorTypes.ts
│ │ └── outputStyles
│ │ │ ├── markdownStyle.ts
│ │ │ ├── plainStyle.ts
│ │ │ └── xmlStyle.ts
│ ├── packager.ts
│ └── tokenCount
│ │ └── tokenCount.ts
├── index.ts
└── shared
│ ├── errorHandle.ts
│ ├── logger.ts
│ ├── processConcurrency.ts
│ └── types.ts
├── tsconfig.build.json
└── tsconfig.json
/.codetreeignore:
--------------------------------------------------------------------------------
1 | # Add patterns to ignore here, one per line
2 | # Example:
3 | # *.log
4 | # tmp/
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.*]
4 | indent_style = space
5 | indent_size = 2
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 | end_of_line = lf
10 | max_line_length = null
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependency directories
2 | node_modules/
3 |
4 | # Build output
5 | lib/
6 |
7 | # Logs
8 | *.log
9 |
10 | # OS generated files
11 | .DS_Store
12 |
13 | # Editor directories and files
14 | .vscode/
15 | .idea/
16 |
17 | # Temporary files
18 | *.tmp
19 | *.temp
20 |
21 | # Codetree output
22 | Codetree.txt
23 | Codetree.xml
24 | Codetree.md
25 |
26 | # ESLint cache
27 | .eslintcache
28 |
29 | # yarn
30 | .yarn/
31 |
32 | # biome
33 | .biome/
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Source files
2 | src/
3 |
4 | # Configuration files
5 | tsconfig.json
6 | tsconfig.build.json
7 | .eslintrc.js
8 | eslint.config.mjs
9 | prettier.config.mjs
10 | vite.config.mts
11 | biome.json
12 |
13 | # Git files
14 | .gitignore
15 | .git
16 |
17 | # CI files
18 | .github/
19 |
20 | # yarn files
21 | .yarn
22 |
23 | # ESLint files
24 | .eslintcache
25 |
26 | # Config files
27 | .editorconfig
28 | .node-version
29 | .tool-versions
30 | codetree.config.js
31 |
32 | # Editor files
33 | .vscode/
34 | .idea/
35 |
36 | # Logs
37 | *.log
38 |
39 | # CodeTree output
40 | codetree.txt
41 |
42 | # Development scripts
43 | scripts/
44 |
45 | # Documentation files (except README and LICENSE)
46 | docs/
47 | CONTRIBUTING.md
48 | CHANGELOG.md
49 |
50 | # Temporary files
51 | *.tmp
52 | *.temp
53 |
54 | # OS generated files
55 | .DS_Store
56 | Thumbs.db
57 |
58 | # biome
59 | .biome/
60 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2024 Mostafa Alahyari
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🌳 CodeTree
2 | > A powerful CLI tool that packs your entire repository into a single, AI-friendly file for seamless integration with Large Language Models (LLMs) like Claude, ChatGPT, and Gemini.
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | ## 📚 Table of Contents
12 |
13 | - [Features](#-features)
14 | - [Installation](#-installation)
15 | - [Quick Start](#-quick-start)
16 | - [Usage](#-usage)
17 | - [Configuration](#-configuration)
18 | - [Output Formats](#-output-formats)
19 | - [AI Integration Guide](#-ai-integration-guide)
20 | - [Updating](#-updating)
21 | - [Contributing](#-contributing)
22 | - [License](#-license)
23 |
24 | ## ✨ Features
25 |
26 | - **🤖 AI-Optimized Output**: Creates LLM-friendly file formats with clear structure and context
27 | - **📊 Token Analytics**: Tracks token usage per file and repository-wide for LLM context management
28 | - **🎯 Smart File Selection**: Intelligent file filtering with extensive ignore patterns support
29 | - **🔄 Remote Repository Support**: Direct processing of GitHub repositories without manual cloning
30 | - **⚙️ Highly Configurable**: Flexible configuration through CLI options or config files
31 | - **📝 Multiple Output Formats**: Supports Plain Text, XML, and Markdown output styles
32 | - **🧹 Code Processing**: Optional comment removal and line number addition
33 | - **📋 Clipboard Integration**: Direct copying to system clipboard for quick AI tool usage
34 |
35 | ## 🚀 Installation
36 |
37 | ### Global Installation (Recommended)
38 | ```bash
39 | npm install -g @mimalef70/codetree
40 | ```
41 |
42 | ### Per-Project Installation
43 | ```bash
44 | npm install --save-dev @mimalef70/codetree
45 | ```
46 |
47 | ### No Installation Required
48 | ```bash
49 | npx @mimalef70/codetree
50 | ```
51 |
52 | ## 🔄 Updating
53 |
54 | To update to the latest version:
55 | ```bash
56 | npm update -g @mimalef70/codetree
57 | ```
58 |
59 | To check your current version:
60 | ```bash
61 | codetree --version
62 | ```
63 |
64 | To see if you have the latest version:
65 | ```bash
66 | npm view @mimalef70/codetree version
67 | ```
68 |
69 | ## 🎯 Quick Start
70 |
71 | 1. Navigate to your project directory
72 | 2. Run CodeTree:
73 | ```bash
74 | codetree
75 | ```
76 | 3. Find your packed code in `codetree.txt`
77 |
78 | ## 💻 Usage
79 |
80 | ### Basic Commands
81 | ```bash
82 | # Pack current directory
83 | codetree
84 |
85 | # Pack specific directory
86 | codetree path/to/directory
87 |
88 | # Pack with specific output format
89 | codetree --style markdown
90 |
91 | # Pack with specific include patterns
92 | codetree --include "src/**/*.ts,**/*.md"
93 |
94 | # Pack remote repository
95 | codetree --remote username/repository
96 |
97 | # Initialize configuration
98 | codetree --init
99 | ```
100 |
101 | ### Advanced Options
102 | ```bash
103 | # Remove comments and empty lines
104 | codetree --removeComments --removeEmptyLines
105 |
106 | # Show line numbers and copy to clipboard
107 | codetree --output-show-line-numbers --copy
108 |
109 | # Custom output file
110 | codetree --output custom-output.md
111 |
112 | # Process with custom config file
113 | codetree -c ./custom-config.json
114 |
115 | # Show verbose output
116 | codetree --verbose
117 | ```
118 |
119 | ### Command Line Options
120 | ```bash
121 | Options:
122 | -v, --version show version information
123 | -o, --output specify the output file name
124 | --include list of include patterns (comma-separated)
125 | -i, --ignore additional ignore patterns (comma-separated)
126 | -c, --config path to a custom config file
127 | --copy copy generated output to system clipboard
128 | --top-files-len specify the number of top files to display
129 | --output-show-line-numbers add line numbers to each line in the output
130 | --style specify the output style (plain, xml, markdown)
131 | --verbose enable verbose logging for detailed output
132 | --init initialize a new codetree.config.json file
133 | --global use global configuration (only with --init)
134 | --remote process a remote Git repository
135 | -h, --help display help for command
136 | ```
137 |
138 | ## ⚙️ Configuration
139 |
140 | ### Config File Structure
141 | ```json
142 | {
143 | "output": {
144 | "filePath": "codetree.txt",
145 | "style": "plain",
146 | "showLineNumbers": false,
147 | "removeComments": false,
148 | "removeEmptyLines": false,
149 | "topFilesLength": 5,
150 | "copyToClipboard": false,
151 | "headerText": "",
152 | "instructionFilePath": ""
153 | },
154 | "include": ["**/*"],
155 | "ignore": {
156 | "useGitignore": true,
157 | "useDefaultPatterns": true,
158 | "customPatterns": []
159 | }
160 | }
161 | ```
162 |
163 | ### Global vs Local Config
164 | - Local: `./codetree.config.json`
165 | - Global:
166 | - Windows: `%LOCALAPPDATA%\CodeTree\codetree.config.json`
167 | - macOS: `~/.config/codetree/codetree.config.json`
168 | - Linux: `~/.config/codetree/codetree.config.json`
169 |
170 | To create a config file:
171 | ```bash
172 | codetree --init
173 | # For global config
174 | codetree --init --global
175 | ```
176 |
177 | ## 📄 Output Formats
178 |
179 | ### Plain Text (Default)
180 | ```text
181 | ================================================================
182 | Repository Structure
183 | ================================================================
184 | src/
185 | index.ts
186 | utils/
187 | helper.ts
188 |
189 | ===============
190 | File: src/index.ts
191 | ===============
192 | // File content here
193 | ```
194 |
195 | ### XML
196 | ```xml
197 |
198 | src/
199 | index.ts
200 | utils/
201 | helper.ts
202 |
203 |
204 |
205 | // File content here
206 |
207 | ```
208 |
209 | ### Markdown
210 | ```markdown
211 | # Repository Structure
212 | ```
213 | src/
214 | index.ts
215 | utils/
216 | helper.ts
217 | ```
218 |
219 | ## File: src/index.ts
220 | ```typescript
221 | // File content here
222 | ```
223 | ```
224 |
225 | ## 🤖 AI Integration Guide
226 |
227 | ### Best Practices
228 | 1. Choose appropriate output format based on your LLM:
229 | - Claude: Use XML format (`--style xml`)
230 | - GPT-4/3.5: Any format works well, but Markdown (`--style markdown`) is recommended
231 | - Gemini: Plain text or Markdown format works best
232 |
233 | 2. Consider token limits:
234 | - Monitor the token count summary
235 | - Use `--top-files-len` to identify large files
236 | - Remove comments with `--removeComments` if needed
237 | - Remove empty lines with `--removeEmptyLines` for more compact output
238 |
239 | ### Example Prompts
240 | ```plaintext
241 | I have exported my codebase using CodeTree. Please:
242 | 1. Analyze the overall architecture
243 | 2. Identify potential improvements
244 | 3. Review code quality and suggest optimizations
245 | 4. Check for security concerns
246 | ```
247 |
248 | ### Making Large Codebases LLM-Friendly
249 | 1. Use include patterns to focus on specific parts:
250 | ```bash
251 | codetree --include "src/**/*.ts,src/**/*.tsx"
252 | ```
253 |
254 | 2. Exclude unnecessary files:
255 | ```bash
256 | codetree --ignore "**/*.test.ts,**/*.spec.ts"
257 | ```
258 |
259 | 3. Remove unnecessary content:
260 | ```bash
261 | codetree --removeComments --removeEmptyLines
262 | ```
263 |
264 | ## 🐛 Troubleshooting
265 |
266 | ### Common Issues
267 |
268 | 1. **Permission Errors**
269 | ```bash
270 | sudo npm install -g @mimalef70/codetree
271 | ```
272 | Or fix npm permissions following [npm's guide](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally)
273 |
274 | 2. **File Path Issues**
275 | - Use quotes around paths with spaces
276 | - Use forward slashes (/) even on Windows
277 |
278 | 3. **Out of Memory**
279 | - Process specific directories: `codetree ./src`
280 | - Use include patterns: `codetree --include "src/**/*.js"`
281 |
282 | ### Debug Mode
283 | ```bash
284 | codetree --verbose
285 | ```
286 |
287 | ## 🤝 Contributing
288 |
289 | Contributions are welcome! See our [Contributing Guide](CONTRIBUTING.md) for details on:
290 | - Development Setup
291 | - Pull Request Process
292 | - Coding Standards
293 |
294 | ### Development Setup
295 | ```bash
296 | # Clone the repository
297 | git clone https://github.com/mimalef70/codetree.git
298 | cd codetree
299 |
300 | # Install dependencies
301 | npm install
302 |
303 | # Build
304 | npm run build
305 |
306 | # Link for local development
307 | npm link
308 | ```
309 |
310 | ## 📝 License
311 |
312 | [MIT](LICENSE)
313 | ---
314 |
315 |
316 | Made with ❤️ by Mostafa Alahyari
317 |
318 |
319 |
320 | If you find CodeTree helpful, please consider giving it a ⭐️ on GitHub
321 |
--------------------------------------------------------------------------------
/bin/codetree.cjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const nodeVersion = process.versions.node;
4 | const [major] = nodeVersion.split('.').map(Number);
5 |
6 | const EXIT_CODES = {
7 | SUCCESS: 0,
8 | ERROR: 1,
9 | };
10 |
11 | if (major < 16) {
12 | console.error(`CodeTree requires Node.js version 16 or higher. Current version: ${nodeVersion}\n`);
13 | process.exit(EXIT_CODES.ERROR);
14 | }
15 |
16 | function setupErrorHandlers() {
17 | process.on('uncaughtException', (error) => {
18 | console.error('Uncaught Exception:', error);
19 | process.exit(EXIT_CODES.ERROR);
20 | });
21 |
22 | process.on('unhandledRejection', (reason) => {
23 | console.error('Unhandled Promise Rejection:', reason);
24 | process.exit(EXIT_CODES.ERROR);
25 | });
26 |
27 | function shutdown() {
28 | process.exit(EXIT_CODES.SUCCESS);
29 | }
30 |
31 | process.on('SIGINT', () => {
32 | console.log('\nReceived SIGINT. Shutting down...');
33 | shutdown();
34 | });
35 | process.on('SIGTERM', shutdown);
36 | }
37 |
38 | (async () => {
39 | try {
40 | setupErrorHandlers();
41 |
42 | const { run } = await import('../lib/cli/cliRun.js');
43 | await run();
44 | } catch (error) {
45 | if (error instanceof Error) {
46 | console.error('Fatal Error:', {
47 | name: error.name,
48 | message: error.message,
49 | stack: error.stack,
50 | });
51 | } else {
52 | console.error('Fatal Error:', error);
53 | }
54 |
55 | process.exit(EXIT_CODES.ERROR);
56 | }
57 | })();
58 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
3 | "files": {
4 | "include": [
5 | "./bin/**",
6 | "./src/**",
7 | "./tests/**",
8 | "package.json",
9 | "biome.json",
10 | ".secretlintrc.json",
11 | "tsconfig.json",
12 | "tsconfig.build.json",
13 | "vite.config.ts",
14 | "codetree.config.json"
15 | ]
16 | },
17 | "organizeImports": {
18 | "enabled": true
19 | },
20 | "linter": {
21 | "enabled": true,
22 | "rules": {
23 | "recommended": true
24 | }
25 | },
26 | "formatter": {
27 | "enabled": true,
28 | "formatWithErrors": false,
29 | "indentStyle": "space",
30 | "indentWidth": 2,
31 | "lineWidth": 120
32 | },
33 | "javascript": {
34 | "formatter": {
35 | "quoteStyle": "single",
36 | "trailingCommas": "all",
37 | "semicolons": "always"
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "codetree",
3 | "version": "1.0.1",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "codetree",
9 | "version": "1.0.1",
10 | "license": "MIT",
11 | "dependencies": {
12 | "@clack/prompts": "^0.8.1",
13 | "cli-spinners": "^2.9.2",
14 | "clipboardy": "^4.0.0",
15 | "commander": "^12.1.0",
16 | "globby": "^14.0.2",
17 | "handlebars": "^4.7.8",
18 | "iconv-lite": "^0.6.3",
19 | "istextorbinary": "^9.5.0",
20 | "jschardet": "^3.1.4",
21 | "log-update": "^6.1.0",
22 | "p-map": "^7.0.2",
23 | "picocolors": "^1.1.1",
24 | "strip-comments": "^2.0.1",
25 | "tiktoken": "^1.0.17",
26 | "zod": "^3.23.8"
27 | },
28 | "bin": {
29 | "codetree": "bin/codetree.cjs"
30 | },
31 | "devDependencies": {
32 | "@biomejs/biome": "^1.9.4",
33 | "@types/node": "^22.9.1",
34 | "@types/strip-comments": "^2.0.4",
35 | "rimraf": "^6.0.1",
36 | "typescript": "^5.6.3"
37 | },
38 | "engines": {
39 | "node": ">=16.0.0",
40 | "yarn": ">=1.22.22"
41 | }
42 | },
43 | "node_modules/@biomejs/biome": {
44 | "version": "1.9.4",
45 | "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz",
46 | "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==",
47 | "dev": true,
48 | "hasInstallScript": true,
49 | "license": "MIT OR Apache-2.0",
50 | "bin": {
51 | "biome": "bin/biome"
52 | },
53 | "engines": {
54 | "node": ">=14.21.3"
55 | },
56 | "funding": {
57 | "type": "opencollective",
58 | "url": "https://opencollective.com/biome"
59 | },
60 | "optionalDependencies": {
61 | "@biomejs/cli-darwin-arm64": "1.9.4",
62 | "@biomejs/cli-darwin-x64": "1.9.4",
63 | "@biomejs/cli-linux-arm64": "1.9.4",
64 | "@biomejs/cli-linux-arm64-musl": "1.9.4",
65 | "@biomejs/cli-linux-x64": "1.9.4",
66 | "@biomejs/cli-linux-x64-musl": "1.9.4",
67 | "@biomejs/cli-win32-arm64": "1.9.4",
68 | "@biomejs/cli-win32-x64": "1.9.4"
69 | }
70 | },
71 | "node_modules/@biomejs/cli-darwin-arm64": {
72 | "version": "1.9.4",
73 | "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz",
74 | "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==",
75 | "cpu": [
76 | "arm64"
77 | ],
78 | "dev": true,
79 | "license": "MIT OR Apache-2.0",
80 | "optional": true,
81 | "os": [
82 | "darwin"
83 | ],
84 | "engines": {
85 | "node": ">=14.21.3"
86 | }
87 | },
88 | "node_modules/@biomejs/cli-darwin-x64": {
89 | "version": "1.9.4",
90 | "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz",
91 | "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==",
92 | "cpu": [
93 | "x64"
94 | ],
95 | "dev": true,
96 | "license": "MIT OR Apache-2.0",
97 | "optional": true,
98 | "os": [
99 | "darwin"
100 | ],
101 | "engines": {
102 | "node": ">=14.21.3"
103 | }
104 | },
105 | "node_modules/@biomejs/cli-linux-arm64": {
106 | "version": "1.9.4",
107 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz",
108 | "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==",
109 | "cpu": [
110 | "arm64"
111 | ],
112 | "dev": true,
113 | "license": "MIT OR Apache-2.0",
114 | "optional": true,
115 | "os": [
116 | "linux"
117 | ],
118 | "engines": {
119 | "node": ">=14.21.3"
120 | }
121 | },
122 | "node_modules/@biomejs/cli-linux-arm64-musl": {
123 | "version": "1.9.4",
124 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz",
125 | "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==",
126 | "cpu": [
127 | "arm64"
128 | ],
129 | "dev": true,
130 | "license": "MIT OR Apache-2.0",
131 | "optional": true,
132 | "os": [
133 | "linux"
134 | ],
135 | "engines": {
136 | "node": ">=14.21.3"
137 | }
138 | },
139 | "node_modules/@biomejs/cli-linux-x64": {
140 | "version": "1.9.4",
141 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz",
142 | "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==",
143 | "cpu": [
144 | "x64"
145 | ],
146 | "dev": true,
147 | "license": "MIT OR Apache-2.0",
148 | "optional": true,
149 | "os": [
150 | "linux"
151 | ],
152 | "engines": {
153 | "node": ">=14.21.3"
154 | }
155 | },
156 | "node_modules/@biomejs/cli-linux-x64-musl": {
157 | "version": "1.9.4",
158 | "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz",
159 | "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==",
160 | "cpu": [
161 | "x64"
162 | ],
163 | "dev": true,
164 | "license": "MIT OR Apache-2.0",
165 | "optional": true,
166 | "os": [
167 | "linux"
168 | ],
169 | "engines": {
170 | "node": ">=14.21.3"
171 | }
172 | },
173 | "node_modules/@biomejs/cli-win32-arm64": {
174 | "version": "1.9.4",
175 | "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz",
176 | "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==",
177 | "cpu": [
178 | "arm64"
179 | ],
180 | "dev": true,
181 | "license": "MIT OR Apache-2.0",
182 | "optional": true,
183 | "os": [
184 | "win32"
185 | ],
186 | "engines": {
187 | "node": ">=14.21.3"
188 | }
189 | },
190 | "node_modules/@biomejs/cli-win32-x64": {
191 | "version": "1.9.4",
192 | "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz",
193 | "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==",
194 | "cpu": [
195 | "x64"
196 | ],
197 | "dev": true,
198 | "license": "MIT OR Apache-2.0",
199 | "optional": true,
200 | "os": [
201 | "win32"
202 | ],
203 | "engines": {
204 | "node": ">=14.21.3"
205 | }
206 | },
207 | "node_modules/@clack/core": {
208 | "version": "0.3.4",
209 | "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.4.tgz",
210 | "integrity": "sha512-H4hxZDXgHtWTwV3RAVenqcC4VbJZNegbBjlPvzOzCouXtS2y3sDvlO3IsbrPNWuLWPPlYVYPghQdSF64683Ldw==",
211 | "license": "MIT",
212 | "dependencies": {
213 | "picocolors": "^1.0.0",
214 | "sisteransi": "^1.0.5"
215 | }
216 | },
217 | "node_modules/@clack/prompts": {
218 | "version": "0.8.1",
219 | "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.8.1.tgz",
220 | "integrity": "sha512-I263nEUNbX4lPTX93trl1fkIvGrGlz6nUYkqOddF0ZmjqcxUgUlXmpUIUqfapirRKJrFddvwF+qdZgg8cSqF7g==",
221 | "bundleDependencies": [
222 | "is-unicode-supported"
223 | ],
224 | "license": "MIT",
225 | "dependencies": {
226 | "@clack/core": "0.3.4",
227 | "is-unicode-supported": "*",
228 | "picocolors": "^1.0.0",
229 | "sisteransi": "^1.0.5"
230 | }
231 | },
232 | "node_modules/@clack/prompts/node_modules/is-unicode-supported": {
233 | "version": "1.3.0",
234 | "inBundle": true,
235 | "license": "MIT",
236 | "engines": {
237 | "node": ">=12"
238 | },
239 | "funding": {
240 | "url": "https://github.com/sponsors/sindresorhus"
241 | }
242 | },
243 | "node_modules/@isaacs/cliui": {
244 | "version": "8.0.2",
245 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
246 | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
247 | "dev": true,
248 | "dependencies": {
249 | "string-width": "^5.1.2",
250 | "string-width-cjs": "npm:string-width@^4.2.0",
251 | "strip-ansi": "^7.0.1",
252 | "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
253 | "wrap-ansi": "^8.1.0",
254 | "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
255 | },
256 | "engines": {
257 | "node": ">=12"
258 | }
259 | },
260 | "node_modules/@nodelib/fs.scandir": {
261 | "version": "2.1.5",
262 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
263 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
264 | "dependencies": {
265 | "@nodelib/fs.stat": "2.0.5",
266 | "run-parallel": "^1.1.9"
267 | },
268 | "engines": {
269 | "node": ">= 8"
270 | }
271 | },
272 | "node_modules/@nodelib/fs.stat": {
273 | "version": "2.0.5",
274 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
275 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
276 | "engines": {
277 | "node": ">= 8"
278 | }
279 | },
280 | "node_modules/@nodelib/fs.walk": {
281 | "version": "1.2.8",
282 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
283 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
284 | "dependencies": {
285 | "@nodelib/fs.scandir": "2.1.5",
286 | "fastq": "^1.6.0"
287 | },
288 | "engines": {
289 | "node": ">= 8"
290 | }
291 | },
292 | "node_modules/@pkgjs/parseargs": {
293 | "version": "0.11.0",
294 | "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
295 | "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
296 | "dev": true,
297 | "optional": true,
298 | "engines": {
299 | "node": ">=14"
300 | }
301 | },
302 | "node_modules/@sindresorhus/merge-streams": {
303 | "version": "2.3.0",
304 | "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz",
305 | "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==",
306 | "engines": {
307 | "node": ">=18"
308 | },
309 | "funding": {
310 | "url": "https://github.com/sponsors/sindresorhus"
311 | }
312 | },
313 | "node_modules/@types/node": {
314 | "version": "22.9.1",
315 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.1.tgz",
316 | "integrity": "sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==",
317 | "dev": true,
318 | "license": "MIT",
319 | "dependencies": {
320 | "undici-types": "~6.19.8"
321 | }
322 | },
323 | "node_modules/@types/strip-comments": {
324 | "version": "2.0.4",
325 | "resolved": "https://registry.npmjs.org/@types/strip-comments/-/strip-comments-2.0.4.tgz",
326 | "integrity": "sha512-YwcQqIGy90zEHrReYrMTpZfq003Um77WayeE8UwJTHvaM9g9XR9N7GMVSnjRhhDzQYVX375JnB5P6q5kAg221g==",
327 | "dev": true
328 | },
329 | "node_modules/ansi-escapes": {
330 | "version": "7.0.0",
331 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz",
332 | "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==",
333 | "dependencies": {
334 | "environment": "^1.0.0"
335 | },
336 | "engines": {
337 | "node": ">=18"
338 | },
339 | "funding": {
340 | "url": "https://github.com/sponsors/sindresorhus"
341 | }
342 | },
343 | "node_modules/ansi-regex": {
344 | "version": "6.0.1",
345 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
346 | "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
347 | "engines": {
348 | "node": ">=12"
349 | },
350 | "funding": {
351 | "url": "https://github.com/chalk/ansi-regex?sponsor=1"
352 | }
353 | },
354 | "node_modules/ansi-styles": {
355 | "version": "6.2.1",
356 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
357 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
358 | "engines": {
359 | "node": ">=12"
360 | },
361 | "funding": {
362 | "url": "https://github.com/chalk/ansi-styles?sponsor=1"
363 | }
364 | },
365 | "node_modules/balanced-match": {
366 | "version": "1.0.2",
367 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
368 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
369 | "dev": true
370 | },
371 | "node_modules/binaryextensions": {
372 | "version": "6.11.0",
373 | "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz",
374 | "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==",
375 | "dependencies": {
376 | "editions": "^6.21.0"
377 | },
378 | "engines": {
379 | "node": ">=4"
380 | },
381 | "funding": {
382 | "url": "https://bevry.me/fund"
383 | }
384 | },
385 | "node_modules/brace-expansion": {
386 | "version": "2.0.1",
387 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
388 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
389 | "dev": true,
390 | "dependencies": {
391 | "balanced-match": "^1.0.0"
392 | }
393 | },
394 | "node_modules/braces": {
395 | "version": "3.0.3",
396 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
397 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
398 | "dependencies": {
399 | "fill-range": "^7.1.1"
400 | },
401 | "engines": {
402 | "node": ">=8"
403 | }
404 | },
405 | "node_modules/cli-cursor": {
406 | "version": "5.0.0",
407 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
408 | "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
409 | "dependencies": {
410 | "restore-cursor": "^5.0.0"
411 | },
412 | "engines": {
413 | "node": ">=18"
414 | },
415 | "funding": {
416 | "url": "https://github.com/sponsors/sindresorhus"
417 | }
418 | },
419 | "node_modules/cli-spinners": {
420 | "version": "2.9.2",
421 | "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
422 | "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
423 | "engines": {
424 | "node": ">=6"
425 | },
426 | "funding": {
427 | "url": "https://github.com/sponsors/sindresorhus"
428 | }
429 | },
430 | "node_modules/clipboardy": {
431 | "version": "4.0.0",
432 | "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-4.0.0.tgz",
433 | "integrity": "sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==",
434 | "license": "MIT",
435 | "dependencies": {
436 | "execa": "^8.0.1",
437 | "is-wsl": "^3.1.0",
438 | "is64bit": "^2.0.0"
439 | },
440 | "engines": {
441 | "node": ">=18"
442 | },
443 | "funding": {
444 | "url": "https://github.com/sponsors/sindresorhus"
445 | }
446 | },
447 | "node_modules/color-convert": {
448 | "version": "2.0.1",
449 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
450 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
451 | "dev": true,
452 | "dependencies": {
453 | "color-name": "~1.1.4"
454 | },
455 | "engines": {
456 | "node": ">=7.0.0"
457 | }
458 | },
459 | "node_modules/color-name": {
460 | "version": "1.1.4",
461 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
462 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
463 | "dev": true
464 | },
465 | "node_modules/commander": {
466 | "version": "12.1.0",
467 | "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
468 | "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
469 | "license": "MIT",
470 | "engines": {
471 | "node": ">=18"
472 | }
473 | },
474 | "node_modules/cross-spawn": {
475 | "version": "7.0.6",
476 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
477 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
478 | "dependencies": {
479 | "path-key": "^3.1.0",
480 | "shebang-command": "^2.0.0",
481 | "which": "^2.0.1"
482 | },
483 | "engines": {
484 | "node": ">= 8"
485 | }
486 | },
487 | "node_modules/eastasianwidth": {
488 | "version": "0.2.0",
489 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
490 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
491 | "dev": true
492 | },
493 | "node_modules/editions": {
494 | "version": "6.21.0",
495 | "resolved": "https://registry.npmjs.org/editions/-/editions-6.21.0.tgz",
496 | "integrity": "sha512-ofkXJtn7z0urokN62DI3SBo/5xAtF0rR7tn+S/bSYV79Ka8pTajIIl+fFQ1q88DQEImymmo97M4azY3WX/nUdg==",
497 | "dependencies": {
498 | "version-range": "^4.13.0"
499 | },
500 | "engines": {
501 | "node": ">=4"
502 | },
503 | "funding": {
504 | "url": "https://bevry.me/fund"
505 | }
506 | },
507 | "node_modules/emoji-regex": {
508 | "version": "9.2.2",
509 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
510 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
511 | "dev": true
512 | },
513 | "node_modules/environment": {
514 | "version": "1.1.0",
515 | "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
516 | "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
517 | "engines": {
518 | "node": ">=18"
519 | },
520 | "funding": {
521 | "url": "https://github.com/sponsors/sindresorhus"
522 | }
523 | },
524 | "node_modules/execa": {
525 | "version": "8.0.1",
526 | "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
527 | "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
528 | "license": "MIT",
529 | "dependencies": {
530 | "cross-spawn": "^7.0.3",
531 | "get-stream": "^8.0.1",
532 | "human-signals": "^5.0.0",
533 | "is-stream": "^3.0.0",
534 | "merge-stream": "^2.0.0",
535 | "npm-run-path": "^5.1.0",
536 | "onetime": "^6.0.0",
537 | "signal-exit": "^4.1.0",
538 | "strip-final-newline": "^3.0.0"
539 | },
540 | "engines": {
541 | "node": ">=16.17"
542 | },
543 | "funding": {
544 | "url": "https://github.com/sindresorhus/execa?sponsor=1"
545 | }
546 | },
547 | "node_modules/fast-glob": {
548 | "version": "3.3.2",
549 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
550 | "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
551 | "dependencies": {
552 | "@nodelib/fs.stat": "^2.0.2",
553 | "@nodelib/fs.walk": "^1.2.3",
554 | "glob-parent": "^5.1.2",
555 | "merge2": "^1.3.0",
556 | "micromatch": "^4.0.4"
557 | },
558 | "engines": {
559 | "node": ">=8.6.0"
560 | }
561 | },
562 | "node_modules/fast-glob/node_modules/glob-parent": {
563 | "version": "5.1.2",
564 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
565 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
566 | "dependencies": {
567 | "is-glob": "^4.0.1"
568 | },
569 | "engines": {
570 | "node": ">= 6"
571 | }
572 | },
573 | "node_modules/fastq": {
574 | "version": "1.17.1",
575 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
576 | "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
577 | "dependencies": {
578 | "reusify": "^1.0.4"
579 | }
580 | },
581 | "node_modules/fill-range": {
582 | "version": "7.1.1",
583 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
584 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
585 | "dependencies": {
586 | "to-regex-range": "^5.0.1"
587 | },
588 | "engines": {
589 | "node": ">=8"
590 | }
591 | },
592 | "node_modules/foreground-child": {
593 | "version": "3.2.1",
594 | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
595 | "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==",
596 | "dev": true,
597 | "dependencies": {
598 | "cross-spawn": "^7.0.0",
599 | "signal-exit": "^4.0.1"
600 | },
601 | "engines": {
602 | "node": ">=14"
603 | },
604 | "funding": {
605 | "url": "https://github.com/sponsors/isaacs"
606 | }
607 | },
608 | "node_modules/get-east-asian-width": {
609 | "version": "1.2.0",
610 | "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz",
611 | "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==",
612 | "engines": {
613 | "node": ">=18"
614 | },
615 | "funding": {
616 | "url": "https://github.com/sponsors/sindresorhus"
617 | }
618 | },
619 | "node_modules/get-stream": {
620 | "version": "8.0.1",
621 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
622 | "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
623 | "license": "MIT",
624 | "engines": {
625 | "node": ">=16"
626 | },
627 | "funding": {
628 | "url": "https://github.com/sponsors/sindresorhus"
629 | }
630 | },
631 | "node_modules/globby": {
632 | "version": "14.0.2",
633 | "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz",
634 | "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==",
635 | "dependencies": {
636 | "@sindresorhus/merge-streams": "^2.1.0",
637 | "fast-glob": "^3.3.2",
638 | "ignore": "^5.2.4",
639 | "path-type": "^5.0.0",
640 | "slash": "^5.1.0",
641 | "unicorn-magic": "^0.1.0"
642 | },
643 | "engines": {
644 | "node": ">=18"
645 | },
646 | "funding": {
647 | "url": "https://github.com/sponsors/sindresorhus"
648 | }
649 | },
650 | "node_modules/handlebars": {
651 | "version": "4.7.8",
652 | "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
653 | "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
654 | "license": "MIT",
655 | "dependencies": {
656 | "minimist": "^1.2.5",
657 | "neo-async": "^2.6.2",
658 | "source-map": "^0.6.1",
659 | "wordwrap": "^1.0.0"
660 | },
661 | "bin": {
662 | "handlebars": "bin/handlebars"
663 | },
664 | "engines": {
665 | "node": ">=0.4.7"
666 | },
667 | "optionalDependencies": {
668 | "uglify-js": "^3.1.4"
669 | }
670 | },
671 | "node_modules/human-signals": {
672 | "version": "5.0.0",
673 | "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
674 | "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
675 | "license": "Apache-2.0",
676 | "engines": {
677 | "node": ">=16.17.0"
678 | }
679 | },
680 | "node_modules/iconv-lite": {
681 | "version": "0.6.3",
682 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
683 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
684 | "dependencies": {
685 | "safer-buffer": ">= 2.1.2 < 3.0.0"
686 | },
687 | "engines": {
688 | "node": ">=0.10.0"
689 | }
690 | },
691 | "node_modules/ignore": {
692 | "version": "5.3.1",
693 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
694 | "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
695 | "engines": {
696 | "node": ">= 4"
697 | }
698 | },
699 | "node_modules/is-docker": {
700 | "version": "3.0.0",
701 | "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
702 | "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
703 | "license": "MIT",
704 | "bin": {
705 | "is-docker": "cli.js"
706 | },
707 | "engines": {
708 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
709 | },
710 | "funding": {
711 | "url": "https://github.com/sponsors/sindresorhus"
712 | }
713 | },
714 | "node_modules/is-extglob": {
715 | "version": "2.1.1",
716 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
717 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
718 | "engines": {
719 | "node": ">=0.10.0"
720 | }
721 | },
722 | "node_modules/is-fullwidth-code-point": {
723 | "version": "3.0.0",
724 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
725 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
726 | "dev": true,
727 | "engines": {
728 | "node": ">=8"
729 | }
730 | },
731 | "node_modules/is-glob": {
732 | "version": "4.0.3",
733 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
734 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
735 | "dependencies": {
736 | "is-extglob": "^2.1.1"
737 | },
738 | "engines": {
739 | "node": ">=0.10.0"
740 | }
741 | },
742 | "node_modules/is-inside-container": {
743 | "version": "1.0.0",
744 | "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
745 | "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
746 | "license": "MIT",
747 | "dependencies": {
748 | "is-docker": "^3.0.0"
749 | },
750 | "bin": {
751 | "is-inside-container": "cli.js"
752 | },
753 | "engines": {
754 | "node": ">=14.16"
755 | },
756 | "funding": {
757 | "url": "https://github.com/sponsors/sindresorhus"
758 | }
759 | },
760 | "node_modules/is-number": {
761 | "version": "7.0.0",
762 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
763 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
764 | "engines": {
765 | "node": ">=0.12.0"
766 | }
767 | },
768 | "node_modules/is-stream": {
769 | "version": "3.0.0",
770 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
771 | "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
772 | "license": "MIT",
773 | "engines": {
774 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
775 | },
776 | "funding": {
777 | "url": "https://github.com/sponsors/sindresorhus"
778 | }
779 | },
780 | "node_modules/is-wsl": {
781 | "version": "3.1.0",
782 | "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
783 | "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==",
784 | "license": "MIT",
785 | "dependencies": {
786 | "is-inside-container": "^1.0.0"
787 | },
788 | "engines": {
789 | "node": ">=16"
790 | },
791 | "funding": {
792 | "url": "https://github.com/sponsors/sindresorhus"
793 | }
794 | },
795 | "node_modules/is64bit": {
796 | "version": "2.0.0",
797 | "resolved": "https://registry.npmjs.org/is64bit/-/is64bit-2.0.0.tgz",
798 | "integrity": "sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==",
799 | "license": "MIT",
800 | "dependencies": {
801 | "system-architecture": "^0.1.0"
802 | },
803 | "engines": {
804 | "node": ">=18"
805 | },
806 | "funding": {
807 | "url": "https://github.com/sponsors/sindresorhus"
808 | }
809 | },
810 | "node_modules/isexe": {
811 | "version": "2.0.0",
812 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
813 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
814 | },
815 | "node_modules/istextorbinary": {
816 | "version": "9.5.0",
817 | "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz",
818 | "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==",
819 | "dependencies": {
820 | "binaryextensions": "^6.11.0",
821 | "editions": "^6.21.0",
822 | "textextensions": "^6.11.0"
823 | },
824 | "engines": {
825 | "node": ">=4"
826 | },
827 | "funding": {
828 | "url": "https://bevry.me/fund"
829 | }
830 | },
831 | "node_modules/jschardet": {
832 | "version": "3.1.4",
833 | "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-3.1.4.tgz",
834 | "integrity": "sha512-/kmVISmrwVwtyYU40iQUOp3SUPk2dhNCMsZBQX0R1/jZ8maaXJ/oZIzUOiyOqcgtLnETFKYChbJ5iDC/eWmFHg==",
835 | "license": "LGPL-2.1+",
836 | "engines": {
837 | "node": ">=0.1.90"
838 | }
839 | },
840 | "node_modules/log-update": {
841 | "version": "6.1.0",
842 | "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
843 | "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
844 | "dependencies": {
845 | "ansi-escapes": "^7.0.0",
846 | "cli-cursor": "^5.0.0",
847 | "slice-ansi": "^7.1.0",
848 | "strip-ansi": "^7.1.0",
849 | "wrap-ansi": "^9.0.0"
850 | },
851 | "engines": {
852 | "node": ">=18"
853 | },
854 | "funding": {
855 | "url": "https://github.com/sponsors/sindresorhus"
856 | }
857 | },
858 | "node_modules/log-update/node_modules/emoji-regex": {
859 | "version": "10.3.0",
860 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
861 | "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw=="
862 | },
863 | "node_modules/log-update/node_modules/string-width": {
864 | "version": "7.2.0",
865 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
866 | "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
867 | "dependencies": {
868 | "emoji-regex": "^10.3.0",
869 | "get-east-asian-width": "^1.0.0",
870 | "strip-ansi": "^7.1.0"
871 | },
872 | "engines": {
873 | "node": ">=18"
874 | },
875 | "funding": {
876 | "url": "https://github.com/sponsors/sindresorhus"
877 | }
878 | },
879 | "node_modules/log-update/node_modules/wrap-ansi": {
880 | "version": "9.0.0",
881 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
882 | "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
883 | "dependencies": {
884 | "ansi-styles": "^6.2.1",
885 | "string-width": "^7.0.0",
886 | "strip-ansi": "^7.1.0"
887 | },
888 | "engines": {
889 | "node": ">=18"
890 | },
891 | "funding": {
892 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
893 | }
894 | },
895 | "node_modules/merge-stream": {
896 | "version": "2.0.0",
897 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
898 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
899 | "license": "MIT"
900 | },
901 | "node_modules/merge2": {
902 | "version": "1.4.1",
903 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
904 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
905 | "engines": {
906 | "node": ">= 8"
907 | }
908 | },
909 | "node_modules/micromatch": {
910 | "version": "4.0.8",
911 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
912 | "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
913 | "license": "MIT",
914 | "dependencies": {
915 | "braces": "^3.0.3",
916 | "picomatch": "^2.3.1"
917 | },
918 | "engines": {
919 | "node": ">=8.6"
920 | }
921 | },
922 | "node_modules/mimic-fn": {
923 | "version": "4.0.0",
924 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
925 | "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
926 | "license": "MIT",
927 | "engines": {
928 | "node": ">=12"
929 | },
930 | "funding": {
931 | "url": "https://github.com/sponsors/sindresorhus"
932 | }
933 | },
934 | "node_modules/mimic-function": {
935 | "version": "5.0.1",
936 | "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
937 | "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
938 | "engines": {
939 | "node": ">=18"
940 | },
941 | "funding": {
942 | "url": "https://github.com/sponsors/sindresorhus"
943 | }
944 | },
945 | "node_modules/minimist": {
946 | "version": "1.2.8",
947 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
948 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
949 | "license": "MIT",
950 | "funding": {
951 | "url": "https://github.com/sponsors/ljharb"
952 | }
953 | },
954 | "node_modules/minipass": {
955 | "version": "7.1.2",
956 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
957 | "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
958 | "dev": true,
959 | "engines": {
960 | "node": ">=16 || 14 >=14.17"
961 | }
962 | },
963 | "node_modules/neo-async": {
964 | "version": "2.6.2",
965 | "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
966 | "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
967 | "license": "MIT"
968 | },
969 | "node_modules/npm-run-path": {
970 | "version": "5.3.0",
971 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
972 | "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
973 | "license": "MIT",
974 | "dependencies": {
975 | "path-key": "^4.0.0"
976 | },
977 | "engines": {
978 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
979 | },
980 | "funding": {
981 | "url": "https://github.com/sponsors/sindresorhus"
982 | }
983 | },
984 | "node_modules/npm-run-path/node_modules/path-key": {
985 | "version": "4.0.0",
986 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
987 | "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
988 | "license": "MIT",
989 | "engines": {
990 | "node": ">=12"
991 | },
992 | "funding": {
993 | "url": "https://github.com/sponsors/sindresorhus"
994 | }
995 | },
996 | "node_modules/onetime": {
997 | "version": "6.0.0",
998 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
999 | "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
1000 | "license": "MIT",
1001 | "dependencies": {
1002 | "mimic-fn": "^4.0.0"
1003 | },
1004 | "engines": {
1005 | "node": ">=12"
1006 | },
1007 | "funding": {
1008 | "url": "https://github.com/sponsors/sindresorhus"
1009 | }
1010 | },
1011 | "node_modules/p-map": {
1012 | "version": "7.0.2",
1013 | "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.2.tgz",
1014 | "integrity": "sha512-z4cYYMMdKHzw4O5UkWJImbZynVIo0lSGTXc7bzB1e/rrDqkgGUNysK/o4bTr+0+xKvvLoTyGqYC4Fgljy9qe1Q==",
1015 | "license": "MIT",
1016 | "engines": {
1017 | "node": ">=18"
1018 | },
1019 | "funding": {
1020 | "url": "https://github.com/sponsors/sindresorhus"
1021 | }
1022 | },
1023 | "node_modules/package-json-from-dist": {
1024 | "version": "1.0.0",
1025 | "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
1026 | "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
1027 | "dev": true
1028 | },
1029 | "node_modules/path-key": {
1030 | "version": "3.1.1",
1031 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
1032 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
1033 | "engines": {
1034 | "node": ">=8"
1035 | }
1036 | },
1037 | "node_modules/path-type": {
1038 | "version": "5.0.0",
1039 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz",
1040 | "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==",
1041 | "engines": {
1042 | "node": ">=12"
1043 | },
1044 | "funding": {
1045 | "url": "https://github.com/sponsors/sindresorhus"
1046 | }
1047 | },
1048 | "node_modules/picocolors": {
1049 | "version": "1.1.1",
1050 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1051 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1052 | "license": "ISC"
1053 | },
1054 | "node_modules/picomatch": {
1055 | "version": "2.3.1",
1056 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
1057 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
1058 | "engines": {
1059 | "node": ">=8.6"
1060 | },
1061 | "funding": {
1062 | "url": "https://github.com/sponsors/jonschlinkert"
1063 | }
1064 | },
1065 | "node_modules/queue-microtask": {
1066 | "version": "1.2.3",
1067 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
1068 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
1069 | "funding": [
1070 | {
1071 | "type": "github",
1072 | "url": "https://github.com/sponsors/feross"
1073 | },
1074 | {
1075 | "type": "patreon",
1076 | "url": "https://www.patreon.com/feross"
1077 | },
1078 | {
1079 | "type": "consulting",
1080 | "url": "https://feross.org/support"
1081 | }
1082 | ]
1083 | },
1084 | "node_modules/restore-cursor": {
1085 | "version": "5.1.0",
1086 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
1087 | "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
1088 | "dependencies": {
1089 | "onetime": "^7.0.0",
1090 | "signal-exit": "^4.1.0"
1091 | },
1092 | "engines": {
1093 | "node": ">=18"
1094 | },
1095 | "funding": {
1096 | "url": "https://github.com/sponsors/sindresorhus"
1097 | }
1098 | },
1099 | "node_modules/restore-cursor/node_modules/onetime": {
1100 | "version": "7.0.0",
1101 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
1102 | "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
1103 | "dependencies": {
1104 | "mimic-function": "^5.0.0"
1105 | },
1106 | "engines": {
1107 | "node": ">=18"
1108 | },
1109 | "funding": {
1110 | "url": "https://github.com/sponsors/sindresorhus"
1111 | }
1112 | },
1113 | "node_modules/reusify": {
1114 | "version": "1.0.4",
1115 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
1116 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
1117 | "engines": {
1118 | "iojs": ">=1.0.0",
1119 | "node": ">=0.10.0"
1120 | }
1121 | },
1122 | "node_modules/rimraf": {
1123 | "version": "6.0.1",
1124 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz",
1125 | "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==",
1126 | "dev": true,
1127 | "dependencies": {
1128 | "glob": "^11.0.0",
1129 | "package-json-from-dist": "^1.0.0"
1130 | },
1131 | "bin": {
1132 | "rimraf": "dist/esm/bin.mjs"
1133 | },
1134 | "engines": {
1135 | "node": "20 || >=22"
1136 | },
1137 | "funding": {
1138 | "url": "https://github.com/sponsors/isaacs"
1139 | }
1140 | },
1141 | "node_modules/rimraf/node_modules/glob": {
1142 | "version": "11.0.0",
1143 | "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz",
1144 | "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==",
1145 | "dev": true,
1146 | "dependencies": {
1147 | "foreground-child": "^3.1.0",
1148 | "jackspeak": "^4.0.1",
1149 | "minimatch": "^10.0.0",
1150 | "minipass": "^7.1.2",
1151 | "package-json-from-dist": "^1.0.0",
1152 | "path-scurry": "^2.0.0"
1153 | },
1154 | "bin": {
1155 | "glob": "dist/esm/bin.mjs"
1156 | },
1157 | "engines": {
1158 | "node": "20 || >=22"
1159 | },
1160 | "funding": {
1161 | "url": "https://github.com/sponsors/isaacs"
1162 | }
1163 | },
1164 | "node_modules/rimraf/node_modules/jackspeak": {
1165 | "version": "4.0.1",
1166 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz",
1167 | "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==",
1168 | "dev": true,
1169 | "dependencies": {
1170 | "@isaacs/cliui": "^8.0.2"
1171 | },
1172 | "engines": {
1173 | "node": "20 || >=22"
1174 | },
1175 | "funding": {
1176 | "url": "https://github.com/sponsors/isaacs"
1177 | },
1178 | "optionalDependencies": {
1179 | "@pkgjs/parseargs": "^0.11.0"
1180 | }
1181 | },
1182 | "node_modules/rimraf/node_modules/lru-cache": {
1183 | "version": "11.0.0",
1184 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz",
1185 | "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==",
1186 | "dev": true,
1187 | "engines": {
1188 | "node": "20 || >=22"
1189 | }
1190 | },
1191 | "node_modules/rimraf/node_modules/minimatch": {
1192 | "version": "10.0.1",
1193 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
1194 | "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
1195 | "dev": true,
1196 | "dependencies": {
1197 | "brace-expansion": "^2.0.1"
1198 | },
1199 | "engines": {
1200 | "node": "20 || >=22"
1201 | },
1202 | "funding": {
1203 | "url": "https://github.com/sponsors/isaacs"
1204 | }
1205 | },
1206 | "node_modules/rimraf/node_modules/path-scurry": {
1207 | "version": "2.0.0",
1208 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
1209 | "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
1210 | "dev": true,
1211 | "dependencies": {
1212 | "lru-cache": "^11.0.0",
1213 | "minipass": "^7.1.2"
1214 | },
1215 | "engines": {
1216 | "node": "20 || >=22"
1217 | },
1218 | "funding": {
1219 | "url": "https://github.com/sponsors/isaacs"
1220 | }
1221 | },
1222 | "node_modules/run-parallel": {
1223 | "version": "1.2.0",
1224 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
1225 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
1226 | "funding": [
1227 | {
1228 | "type": "github",
1229 | "url": "https://github.com/sponsors/feross"
1230 | },
1231 | {
1232 | "type": "patreon",
1233 | "url": "https://www.patreon.com/feross"
1234 | },
1235 | {
1236 | "type": "consulting",
1237 | "url": "https://feross.org/support"
1238 | }
1239 | ],
1240 | "dependencies": {
1241 | "queue-microtask": "^1.2.2"
1242 | }
1243 | },
1244 | "node_modules/safer-buffer": {
1245 | "version": "2.1.2",
1246 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1247 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
1248 | },
1249 | "node_modules/shebang-command": {
1250 | "version": "2.0.0",
1251 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
1252 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
1253 | "dependencies": {
1254 | "shebang-regex": "^3.0.0"
1255 | },
1256 | "engines": {
1257 | "node": ">=8"
1258 | }
1259 | },
1260 | "node_modules/shebang-regex": {
1261 | "version": "3.0.0",
1262 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
1263 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
1264 | "engines": {
1265 | "node": ">=8"
1266 | }
1267 | },
1268 | "node_modules/signal-exit": {
1269 | "version": "4.1.0",
1270 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
1271 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
1272 | "engines": {
1273 | "node": ">=14"
1274 | },
1275 | "funding": {
1276 | "url": "https://github.com/sponsors/isaacs"
1277 | }
1278 | },
1279 | "node_modules/sisteransi": {
1280 | "version": "1.0.5",
1281 | "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
1282 | "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
1283 | "license": "MIT"
1284 | },
1285 | "node_modules/slash": {
1286 | "version": "5.1.0",
1287 | "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
1288 | "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
1289 | "engines": {
1290 | "node": ">=14.16"
1291 | },
1292 | "funding": {
1293 | "url": "https://github.com/sponsors/sindresorhus"
1294 | }
1295 | },
1296 | "node_modules/slice-ansi": {
1297 | "version": "7.1.0",
1298 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz",
1299 | "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==",
1300 | "dependencies": {
1301 | "ansi-styles": "^6.2.1",
1302 | "is-fullwidth-code-point": "^5.0.0"
1303 | },
1304 | "engines": {
1305 | "node": ">=18"
1306 | },
1307 | "funding": {
1308 | "url": "https://github.com/chalk/slice-ansi?sponsor=1"
1309 | }
1310 | },
1311 | "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": {
1312 | "version": "5.0.0",
1313 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz",
1314 | "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==",
1315 | "dependencies": {
1316 | "get-east-asian-width": "^1.0.0"
1317 | },
1318 | "engines": {
1319 | "node": ">=18"
1320 | },
1321 | "funding": {
1322 | "url": "https://github.com/sponsors/sindresorhus"
1323 | }
1324 | },
1325 | "node_modules/source-map": {
1326 | "version": "0.6.1",
1327 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
1328 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
1329 | "license": "BSD-3-Clause",
1330 | "engines": {
1331 | "node": ">=0.10.0"
1332 | }
1333 | },
1334 | "node_modules/string-width": {
1335 | "version": "5.1.2",
1336 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
1337 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
1338 | "dev": true,
1339 | "dependencies": {
1340 | "eastasianwidth": "^0.2.0",
1341 | "emoji-regex": "^9.2.2",
1342 | "strip-ansi": "^7.0.1"
1343 | },
1344 | "engines": {
1345 | "node": ">=12"
1346 | },
1347 | "funding": {
1348 | "url": "https://github.com/sponsors/sindresorhus"
1349 | }
1350 | },
1351 | "node_modules/string-width-cjs": {
1352 | "name": "string-width",
1353 | "version": "4.2.3",
1354 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
1355 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
1356 | "dev": true,
1357 | "dependencies": {
1358 | "emoji-regex": "^8.0.0",
1359 | "is-fullwidth-code-point": "^3.0.0",
1360 | "strip-ansi": "^6.0.1"
1361 | },
1362 | "engines": {
1363 | "node": ">=8"
1364 | }
1365 | },
1366 | "node_modules/string-width-cjs/node_modules/ansi-regex": {
1367 | "version": "5.0.1",
1368 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
1369 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
1370 | "dev": true,
1371 | "engines": {
1372 | "node": ">=8"
1373 | }
1374 | },
1375 | "node_modules/string-width-cjs/node_modules/emoji-regex": {
1376 | "version": "8.0.0",
1377 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
1378 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
1379 | "dev": true
1380 | },
1381 | "node_modules/string-width-cjs/node_modules/strip-ansi": {
1382 | "version": "6.0.1",
1383 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
1384 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
1385 | "dev": true,
1386 | "dependencies": {
1387 | "ansi-regex": "^5.0.1"
1388 | },
1389 | "engines": {
1390 | "node": ">=8"
1391 | }
1392 | },
1393 | "node_modules/strip-ansi": {
1394 | "version": "7.1.0",
1395 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
1396 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
1397 | "dependencies": {
1398 | "ansi-regex": "^6.0.1"
1399 | },
1400 | "engines": {
1401 | "node": ">=12"
1402 | },
1403 | "funding": {
1404 | "url": "https://github.com/chalk/strip-ansi?sponsor=1"
1405 | }
1406 | },
1407 | "node_modules/strip-ansi-cjs": {
1408 | "name": "strip-ansi",
1409 | "version": "6.0.1",
1410 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
1411 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
1412 | "dev": true,
1413 | "dependencies": {
1414 | "ansi-regex": "^5.0.1"
1415 | },
1416 | "engines": {
1417 | "node": ">=8"
1418 | }
1419 | },
1420 | "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
1421 | "version": "5.0.1",
1422 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
1423 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
1424 | "dev": true,
1425 | "engines": {
1426 | "node": ">=8"
1427 | }
1428 | },
1429 | "node_modules/strip-comments": {
1430 | "version": "2.0.1",
1431 | "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz",
1432 | "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==",
1433 | "engines": {
1434 | "node": ">=10"
1435 | }
1436 | },
1437 | "node_modules/strip-final-newline": {
1438 | "version": "3.0.0",
1439 | "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
1440 | "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
1441 | "license": "MIT",
1442 | "engines": {
1443 | "node": ">=12"
1444 | },
1445 | "funding": {
1446 | "url": "https://github.com/sponsors/sindresorhus"
1447 | }
1448 | },
1449 | "node_modules/system-architecture": {
1450 | "version": "0.1.0",
1451 | "resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-0.1.0.tgz",
1452 | "integrity": "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==",
1453 | "license": "MIT",
1454 | "engines": {
1455 | "node": ">=18"
1456 | },
1457 | "funding": {
1458 | "url": "https://github.com/sponsors/sindresorhus"
1459 | }
1460 | },
1461 | "node_modules/textextensions": {
1462 | "version": "6.11.0",
1463 | "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz",
1464 | "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==",
1465 | "dependencies": {
1466 | "editions": "^6.21.0"
1467 | },
1468 | "engines": {
1469 | "node": ">=4"
1470 | },
1471 | "funding": {
1472 | "url": "https://bevry.me/fund"
1473 | }
1474 | },
1475 | "node_modules/tiktoken": {
1476 | "version": "1.0.17",
1477 | "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.17.tgz",
1478 | "integrity": "sha512-UuFHqpy/DxOfNiC3otsqbx3oS6jr5uKdQhB/CvDEroZQbVHt+qAK+4JbIooabUWKU9g6PpsFylNu9Wcg4MxSGA==",
1479 | "license": "MIT"
1480 | },
1481 | "node_modules/to-regex-range": {
1482 | "version": "5.0.1",
1483 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1484 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1485 | "dependencies": {
1486 | "is-number": "^7.0.0"
1487 | },
1488 | "engines": {
1489 | "node": ">=8.0"
1490 | }
1491 | },
1492 | "node_modules/typescript": {
1493 | "version": "5.6.3",
1494 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
1495 | "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
1496 | "dev": true,
1497 | "license": "Apache-2.0",
1498 | "bin": {
1499 | "tsc": "bin/tsc",
1500 | "tsserver": "bin/tsserver"
1501 | },
1502 | "engines": {
1503 | "node": ">=14.17"
1504 | }
1505 | },
1506 | "node_modules/uglify-js": {
1507 | "version": "3.19.3",
1508 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
1509 | "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
1510 | "license": "BSD-2-Clause",
1511 | "optional": true,
1512 | "bin": {
1513 | "uglifyjs": "bin/uglifyjs"
1514 | },
1515 | "engines": {
1516 | "node": ">=0.8.0"
1517 | }
1518 | },
1519 | "node_modules/undici-types": {
1520 | "version": "6.19.8",
1521 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
1522 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
1523 | "dev": true,
1524 | "license": "MIT"
1525 | },
1526 | "node_modules/unicorn-magic": {
1527 | "version": "0.1.0",
1528 | "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
1529 | "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==",
1530 | "engines": {
1531 | "node": ">=18"
1532 | },
1533 | "funding": {
1534 | "url": "https://github.com/sponsors/sindresorhus"
1535 | }
1536 | },
1537 | "node_modules/version-range": {
1538 | "version": "4.14.0",
1539 | "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.14.0.tgz",
1540 | "integrity": "sha512-gjb0ARm9qlcBAonU4zPwkl9ecKkas+tC2CGwFfptTCWWIVTWY1YUbT2zZKsOAF1jR/tNxxyLwwG0cb42XlYcTg==",
1541 | "engines": {
1542 | "node": ">=4"
1543 | },
1544 | "funding": {
1545 | "url": "https://bevry.me/fund"
1546 | }
1547 | },
1548 | "node_modules/which": {
1549 | "version": "2.0.2",
1550 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
1551 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
1552 | "dependencies": {
1553 | "isexe": "^2.0.0"
1554 | },
1555 | "bin": {
1556 | "node-which": "bin/node-which"
1557 | },
1558 | "engines": {
1559 | "node": ">= 8"
1560 | }
1561 | },
1562 | "node_modules/wordwrap": {
1563 | "version": "1.0.0",
1564 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
1565 | "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
1566 | "license": "MIT"
1567 | },
1568 | "node_modules/wrap-ansi": {
1569 | "version": "8.1.0",
1570 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
1571 | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
1572 | "dev": true,
1573 | "dependencies": {
1574 | "ansi-styles": "^6.1.0",
1575 | "string-width": "^5.0.1",
1576 | "strip-ansi": "^7.0.1"
1577 | },
1578 | "engines": {
1579 | "node": ">=12"
1580 | },
1581 | "funding": {
1582 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
1583 | }
1584 | },
1585 | "node_modules/wrap-ansi-cjs": {
1586 | "name": "wrap-ansi",
1587 | "version": "7.0.0",
1588 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
1589 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
1590 | "dev": true,
1591 | "dependencies": {
1592 | "ansi-styles": "^4.0.0",
1593 | "string-width": "^4.1.0",
1594 | "strip-ansi": "^6.0.0"
1595 | },
1596 | "engines": {
1597 | "node": ">=10"
1598 | },
1599 | "funding": {
1600 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
1601 | }
1602 | },
1603 | "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
1604 | "version": "5.0.1",
1605 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
1606 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
1607 | "dev": true,
1608 | "engines": {
1609 | "node": ">=8"
1610 | }
1611 | },
1612 | "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
1613 | "version": "4.3.0",
1614 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
1615 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
1616 | "dev": true,
1617 | "dependencies": {
1618 | "color-convert": "^2.0.1"
1619 | },
1620 | "engines": {
1621 | "node": ">=8"
1622 | },
1623 | "funding": {
1624 | "url": "https://github.com/chalk/ansi-styles?sponsor=1"
1625 | }
1626 | },
1627 | "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
1628 | "version": "8.0.0",
1629 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
1630 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
1631 | "dev": true
1632 | },
1633 | "node_modules/wrap-ansi-cjs/node_modules/string-width": {
1634 | "version": "4.2.3",
1635 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
1636 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
1637 | "dev": true,
1638 | "dependencies": {
1639 | "emoji-regex": "^8.0.0",
1640 | "is-fullwidth-code-point": "^3.0.0",
1641 | "strip-ansi": "^6.0.1"
1642 | },
1643 | "engines": {
1644 | "node": ">=8"
1645 | }
1646 | },
1647 | "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
1648 | "version": "6.0.1",
1649 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
1650 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
1651 | "dev": true,
1652 | "dependencies": {
1653 | "ansi-regex": "^5.0.1"
1654 | },
1655 | "engines": {
1656 | "node": ">=8"
1657 | }
1658 | },
1659 | "node_modules/zod": {
1660 | "version": "3.23.8",
1661 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
1662 | "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
1663 | "license": "MIT",
1664 | "funding": {
1665 | "url": "https://github.com/sponsors/colinhacks"
1666 | }
1667 | }
1668 | }
1669 | }
1670 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@mimalef70/codetree",
3 | "version": "1.0.1",
4 | "description": "A tool to pack repository contents to single file for AI consumption",
5 | "main": "./lib/index.js",
6 | "types": "./lib/index.d.ts",
7 | "exports": {
8 | ".": {
9 | "types": "./lib/index.d.ts"
10 | }
11 | },
12 | "bin": "./bin/codetree.cjs",
13 | "scripts": {
14 | "clean": "rimraf lib",
15 | "build": "npm run clean && tsc -p tsconfig.build.json --sourceMap --declaration",
16 | "lint": "npm run lint-biome && npm run lint-ts",
17 | "lint-biome": "biome check --write",
18 | "lint-ts": "tsc --noEmit",
19 | "cli-run": "npm run build && node --trace-warnings bin/codetree.cjs",
20 | "npm-publish": "npm run lint && npm run build && npm publish",
21 | "npm-release-patch": "npm version patch && npm run npm-publish",
22 | "npm-release-minor": "npm version minor && npm run npm-publish",
23 | "npm-release-prerelease": "npm version prerelease && npm run npm-publish"
24 | },
25 | "keywords": [
26 | "repository",
27 | "generative-ai",
28 | "ai",
29 | "llm",
30 | "source-code",
31 | "code-analysis",
32 | "codebase-packer",
33 | "development-tool",
34 | "ai-assistant",
35 | "code-review"
36 | ],
37 | "repository": {
38 | "type": "git",
39 | "url": "git://github.com/mimalef70/CodeTree.git"
40 | },
41 | "bugs": {
42 | "url": "https://github.com/mimalef70/CodeTree/issues"
43 | },
44 | "author": "Mostafa Alahyari ",
45 | "homepage": "https://github.com/mimalef70/CodeTree",
46 | "license": "MIT",
47 | "publishConfig": {
48 | "access": "public"
49 | },
50 | "type": "module",
51 | "dependencies": {
52 | "@clack/prompts": "^0.8.1",
53 | "cli-spinners": "^2.9.2",
54 | "clipboardy": "^4.0.0",
55 | "commander": "^12.1.0",
56 | "globby": "^14.0.2",
57 | "handlebars": "^4.7.8",
58 | "iconv-lite": "^0.6.3",
59 | "istextorbinary": "^9.5.0",
60 | "jschardet": "^3.1.4",
61 | "log-update": "^6.1.0",
62 | "p-map": "^7.0.2",
63 | "picocolors": "^1.1.1",
64 | "strip-comments": "^2.0.1",
65 | "tiktoken": "^1.0.17",
66 | "zod": "^3.23.8"
67 | },
68 | "devDependencies": {
69 | "@biomejs/biome": "^1.9.4",
70 | "@types/node": "^22.9.1",
71 | "@types/strip-comments": "^2.0.4",
72 | "rimraf": "^6.0.1",
73 | "typescript": "^5.6.3"
74 | },
75 | "engines": {
76 | "node": ">=16.0.0",
77 | "yarn": ">=1.22.22"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/cli/actions/defaultAction.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path';
2 | import { loadFileConfig, mergeConfigs } from '../../config/configLoad.js';
3 | import {
4 | type CodeTreeConfigCli,
5 | type CodeTreeConfigFile,
6 | type CodeTreeConfigMerged,
7 | type CodeTreeOutputStyle,
8 | codetreeConfigCliSchema,
9 | } from '../../config/configSchema.js';
10 | import { type PackResult, pack } from '../../core/packager.js';
11 | import { rethrowValidationErrorIfZodError } from '../../shared/errorHandle.js';
12 | import { logger } from '../../shared/logger.js';
13 | import { printCompletion, printSummary, printTopFiles } from '../cliPrint.js';
14 | import type { CliOptions } from '../cliRun.js';
15 | import Spinner from '../cliSpinner.js';
16 |
17 | export interface DefaultActionRunnerResult {
18 | packResult: PackResult;
19 | config: CodeTreeConfigMerged;
20 | }
21 |
22 | export const runDefaultAction = async (
23 | directory: string,
24 | cwd: string,
25 | options: CliOptions,
26 | ): Promise => {
27 | logger.trace('Loaded CLI options:', options);
28 |
29 | // Load the config file
30 | const fileConfig: CodeTreeConfigFile = await loadFileConfig(cwd, options.config ?? null);
31 | logger.trace('Loaded file config:', fileConfig);
32 |
33 | // Parse the CLI options into a config
34 | const cliConfig: CodeTreeConfigCli = buildCliConfig(options);
35 | logger.trace('CLI config:', cliConfig);
36 |
37 | // Merge default, file, and CLI configs
38 | const config: CodeTreeConfigMerged = mergeConfigs(cwd, fileConfig, cliConfig);
39 |
40 | logger.trace('Merged config:', config);
41 |
42 | const targetPath = path.resolve(directory);
43 |
44 | const spinner = new Spinner('Packing files...');
45 | spinner.start();
46 |
47 | let packResult: PackResult;
48 |
49 | try {
50 | packResult = await pack(targetPath, config, (message) => {
51 | spinner.update(message);
52 | });
53 | } catch (error) {
54 | spinner.fail('Error during packing');
55 | throw error;
56 | }
57 |
58 | spinner.succeed('Packing completed successfully!');
59 | logger.log('');
60 |
61 | if (config.output.topFilesLength > 0) {
62 | printTopFiles(packResult.fileCharCounts, packResult.fileTokenCounts, config.output.topFilesLength);
63 | logger.log('');
64 | }
65 |
66 | logger.log('');
67 |
68 | printSummary(
69 | packResult.totalFiles,
70 | packResult.totalCharacters,
71 | packResult.totalTokens,
72 | config.output.filePath,
73 | config,
74 | );
75 | logger.log('');
76 |
77 | printCompletion();
78 |
79 | return {
80 | packResult,
81 | config,
82 | };
83 | };
84 |
85 | const buildCliConfig = (options: CliOptions): CodeTreeConfigCli => {
86 | const cliConfig: CodeTreeConfigCli = {};
87 |
88 | if (options.output) {
89 | cliConfig.output = { filePath: options.output };
90 | }
91 | if (options.include) {
92 | cliConfig.include = options.include.split(',');
93 | }
94 | if (options.ignore) {
95 | cliConfig.ignore = { customPatterns: options.ignore.split(',') };
96 | }
97 | if (options.topFilesLen !== undefined) {
98 | cliConfig.output = { ...cliConfig.output, topFilesLength: options.topFilesLen };
99 | }
100 | if (options.outputShowLineNumbers !== undefined) {
101 | cliConfig.output = { ...cliConfig.output, showLineNumbers: options.outputShowLineNumbers };
102 | }
103 | if (options.copy) {
104 | cliConfig.output = { ...cliConfig.output, copyToClipboard: options.copy };
105 | }
106 | if (options.style) {
107 | cliConfig.output = { ...cliConfig.output, style: options.style.toLowerCase() as CodeTreeOutputStyle };
108 | }
109 |
110 | try {
111 | return codetreeConfigCliSchema.parse(cliConfig);
112 | } catch (error) {
113 | rethrowValidationErrorIfZodError(error, 'Invalid cli arguments');
114 | throw error;
115 | }
116 | };
117 |
--------------------------------------------------------------------------------
/src/cli/actions/initAction.ts:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs/promises';
2 | import path from 'node:path';
3 | import * as prompts from '@clack/prompts';
4 | import pc from 'picocolors';
5 | import {
6 | type CodeTreeConfigFile,
7 | type CodeTreeOutputStyle,
8 | defaultConfig,
9 | defaultFilePathMap,
10 | } from '../../config/configSchema.js';
11 | import { getGlobalDirectory } from '../../config/globalDirectory.js';
12 | import { logger } from '../../shared/logger.js';
13 |
14 | const onCancelOperation = () => {
15 | prompts.cancel('Initialization cancelled.');
16 | process.exit(0);
17 | };
18 |
19 | export const runInitAction = async (rootDir: string, isGlobal: boolean): Promise => {
20 | prompts.intro(pc.bold(`Welcome to CodeTree ${isGlobal ? 'Global ' : ''}Configuration!`));
21 |
22 | try {
23 | // Step 1: Ask if user wants to create a config file
24 | const isCreatedConfig = await createConfigFile(rootDir, isGlobal);
25 |
26 | // Step 2: Ask if user wants to create a .codetreeignore file
27 | const isCreatedIgnoreFile = await createIgnoreFile(rootDir, isGlobal);
28 |
29 | if (!isCreatedConfig && !isCreatedIgnoreFile) {
30 | prompts.outro(
31 | pc.yellow('No files were created. You can run this command again when you need to create configuration files.'),
32 | );
33 | } else {
34 | prompts.outro(pc.green('Initialization complete! You can now use CodeTree with your specified settings.'));
35 | }
36 | } catch (error) {
37 | logger.error('An error occurred during initialization:', error);
38 | }
39 | };
40 |
41 | export const createConfigFile = async (rootDir: string, isGlobal: boolean): Promise => {
42 | const configPath = path.resolve(isGlobal ? getGlobalDirectory() : rootDir, 'codetree.config.json');
43 |
44 | const isCreateConfig = await prompts.confirm({
45 | message: `Do you want to create a ${isGlobal ? 'global ' : ''}${pc.green('codetree.config.json')} file?`,
46 | });
47 | if (!isCreateConfig) {
48 | prompts.log.info(`Skipping ${pc.green('codetree.config.json')} file creation.`);
49 | return false;
50 | }
51 | if (prompts.isCancel(isCreateConfig)) {
52 | onCancelOperation();
53 | return false;
54 | }
55 |
56 | let isConfigFileExists = false;
57 | try {
58 | await fs.access(configPath);
59 | isConfigFileExists = true;
60 | } catch {
61 | // File doesn't exist, so we can proceed
62 | }
63 |
64 | if (isConfigFileExists) {
65 | const isOverwrite = await prompts.confirm({
66 | message: `A ${isGlobal ? 'global ' : ''}${pc.green('codetree.config.json')} file already exists. Do you want to overwrite it?`,
67 | });
68 | if (!isOverwrite) {
69 | prompts.log.info(`Skipping ${pc.green('codetree.config.json')} file creation.`);
70 | return false;
71 | }
72 | if (prompts.isCancel(isOverwrite)) {
73 | onCancelOperation();
74 | return false;
75 | }
76 | }
77 |
78 | const options = await prompts.group(
79 | {
80 | outputStyle: () => {
81 | return prompts.select({
82 | message: 'Output style:',
83 | options: [
84 | { value: 'plain', label: 'Plain', hint: 'Simple text format' },
85 | { value: 'xml', label: 'XML', hint: 'Structured XML format' },
86 | { value: 'markdown', label: 'Markdown', hint: 'Markdown format' },
87 | ],
88 | initialValue: defaultConfig.output.style,
89 | });
90 | },
91 | outputFilePath: ({ results }) => {
92 | const defaultFilePath = defaultFilePathMap[results.outputStyle as CodeTreeOutputStyle];
93 | return prompts.text({
94 | message: 'Output file path:',
95 | initialValue: defaultFilePath,
96 | validate: (value) => (value.length === 0 ? 'Output file path is required' : undefined),
97 | });
98 | },
99 | },
100 | {
101 | onCancel: onCancelOperation,
102 | },
103 | );
104 |
105 | const config: CodeTreeConfigFile = {
106 | ...defaultConfig,
107 | output: {
108 | ...defaultConfig.output,
109 | filePath: options.outputFilePath as string,
110 | style: options.outputStyle as CodeTreeOutputStyle,
111 | },
112 | };
113 |
114 | await fs.mkdir(path.dirname(configPath), { recursive: true });
115 | await fs.writeFile(configPath, JSON.stringify(config, null, 2));
116 |
117 | const relativeConfigPath = path.relative(rootDir, configPath);
118 |
119 | prompts.log.success(
120 | pc.green(`${isGlobal ? 'Global config' : 'Config'} file created!\n`) + pc.dim(`Path: ${relativeConfigPath}`),
121 | );
122 |
123 | return true;
124 | };
125 |
126 | export const createIgnoreFile = async (rootDir: string, isGlobal: boolean): Promise => {
127 | if (isGlobal) {
128 | prompts.log.info(`Skipping ${pc.green('.codetreeignore')} file creation for global configuration.`);
129 | return false;
130 | }
131 |
132 | const ignorePath = path.resolve(rootDir, '.codetreeignore');
133 | const createIgnore = await prompts.confirm({
134 | message: `Do you want to create a ${pc.green('.codetreeignore')} file?`,
135 | });
136 | if (!createIgnore) {
137 | prompts.log.info(`Skipping ${pc.green('.codetreeignore')} file creation.`);
138 | return false;
139 | }
140 | if (prompts.isCancel(createIgnore)) {
141 | onCancelOperation();
142 | return false;
143 | }
144 |
145 | let isIgnoreFileExists = false;
146 | try {
147 | await fs.access(ignorePath);
148 | isIgnoreFileExists = true;
149 | } catch {
150 | // File doesn't exist, so we can proceed
151 | }
152 |
153 | if (isIgnoreFileExists) {
154 | const overwrite = await prompts.confirm({
155 | message: `A ${pc.green('.codetreeignore')} file already exists. Do you want to overwrite it?`,
156 | });
157 |
158 | if (!overwrite) {
159 | prompts.log.info(`${pc.green('.codetreeignore')} file creation skipped. Existing file will not be modified.`);
160 | return false;
161 | }
162 | }
163 |
164 | const defaultIgnoreContent = `# Add patterns to ignore here, one per line
165 | # Example:
166 | # *.log
167 | # tmp/
168 | `;
169 |
170 | await fs.writeFile(ignorePath, defaultIgnoreContent);
171 | prompts.log.success(
172 | pc.green('Created .codetreeignore file!\n') + pc.dim(`Path: ${path.relative(rootDir, ignorePath)}`),
173 | );
174 |
175 | return true;
176 | };
177 |
--------------------------------------------------------------------------------
/src/cli/actions/remoteAction.ts:
--------------------------------------------------------------------------------
1 | import { exec } from 'node:child_process';
2 | import * as fs from 'node:fs/promises';
3 | import os from 'node:os';
4 | import path from 'node:path';
5 | import { promisify } from 'node:util';
6 | import pc from 'picocolors';
7 | import { CodeTreeError } from '../../shared/errorHandle.js';
8 | import { logger } from '../../shared/logger.js';
9 | import type { CliOptions } from '../cliRun.js';
10 | import Spinner from '../cliSpinner.js';
11 | import { runDefaultAction } from './defaultAction.js';
12 |
13 | const execAsync = promisify(exec);
14 |
15 | export const runRemoteAction = async (repoUrl: string, options: CliOptions): Promise => {
16 | const gitInstalled = await checkGitInstallation();
17 | if (!gitInstalled) {
18 | throw new CodeTreeError('Git is not installed or not in the system PATH.');
19 | }
20 |
21 | const spinner = new Spinner('Cloning repository...');
22 |
23 | const tempDirPath = await createTempDirectory();
24 |
25 | try {
26 | spinner.start();
27 |
28 | // Clone the repository
29 | await cloneRepository(formatGitUrl(repoUrl), tempDirPath);
30 |
31 | spinner.succeed('Repository cloned successfully!');
32 | logger.log('');
33 |
34 | // Run the default action on the cloned repository
35 | const result = await runDefaultAction(tempDirPath, tempDirPath, options);
36 | await copyOutputToCurrentDirectory(tempDirPath, process.cwd(), result.config.output.filePath);
37 | } catch (error) {
38 | spinner.fail('Error during repository cloning. cleanup...');
39 | throw error;
40 | } finally {
41 | // Cleanup the temporary directory
42 | await cleanupTempDirectory(tempDirPath);
43 | }
44 | };
45 |
46 | export const formatGitUrl = (url: string): string => {
47 | // If the URL is in the format owner/repo, convert it to a GitHub URL
48 | if (/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+$/.test(url)) {
49 | logger.trace(`Formatting GitHub shorthand: ${url}`);
50 | return `https://github.com/${url}.git`;
51 | }
52 |
53 | // Add .git to HTTPS URLs if missing
54 | if (url.startsWith('https://') && !url.endsWith('.git')) {
55 | logger.trace(`Adding .git to HTTPS URL: ${url}`);
56 | return `${url}.git`;
57 | }
58 |
59 | return url;
60 | };
61 |
62 | export const createTempDirectory = async (): Promise => {
63 | const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'codetree-'));
64 | logger.trace(`Created temporary directory. (path: ${pc.dim(tempDir)})`);
65 | return tempDir;
66 | };
67 |
68 | export const cloneRepository = async (url: string, directory: string): Promise => {
69 | logger.log(`Clone repository: ${url} to temporary directory. ${pc.dim(`path: ${directory}`)}`);
70 | logger.log('');
71 |
72 | try {
73 | await execAsync(`git clone --depth 1 ${url} ${directory}`);
74 | } catch (error) {
75 | throw new CodeTreeError(`Failed to clone repository: ${(error as Error).message}`);
76 | }
77 | };
78 |
79 | export const cleanupTempDirectory = async (directory: string): Promise => {
80 | logger.trace(`Cleaning up temporary directory: ${directory}`);
81 | await fs.rm(directory, { recursive: true, force: true });
82 | };
83 |
84 | export const checkGitInstallation = async (): Promise => {
85 | try {
86 | const result = await execAsync('git --version');
87 | return !result.stderr;
88 | } catch (error) {
89 | logger.debug('Git is not installed:', (error as Error).message);
90 | return false;
91 | }
92 | };
93 |
94 | export const copyOutputToCurrentDirectory = async (
95 | sourceDir: string,
96 | targetDir: string,
97 | outputFileName: string,
98 | ): Promise => {
99 | const sourcePath = path.join(sourceDir, outputFileName);
100 | const targetPath = path.join(targetDir, outputFileName);
101 |
102 | try {
103 | logger.trace(`Copying output file from: ${sourcePath} to: ${targetPath}`);
104 | await fs.copyFile(sourcePath, targetPath);
105 | } catch (error) {
106 | throw new CodeTreeError(`Failed to copy output file: ${(error as Error).message}`);
107 | }
108 | };
109 |
--------------------------------------------------------------------------------
/src/cli/actions/versionAction.ts:
--------------------------------------------------------------------------------
1 | import { getVersion } from '../../core/file/packageJsonParse.js';
2 | import { logger } from '../../shared/logger.js';
3 |
4 | export const runVersionAction = async (): Promise => {
5 | const version = await getVersion();
6 | logger.log(version);
7 | };
8 |
--------------------------------------------------------------------------------
/src/cli/cliPrint.ts:
--------------------------------------------------------------------------------
1 | import pc from 'picocolors';
2 | import type { CodeTreeConfigMerged } from '../config/configSchema.js';
3 | import { logger } from '../shared/logger.js';
4 |
5 | export const printSummary = (
6 | totalFiles: number,
7 | totalCharacters: number,
8 | totalTokens: number,
9 | outputPath: string,
10 | config: CodeTreeConfigMerged,
11 | ) => {
12 |
13 | logger.log(pc.white('📊 Pack Summary:'));
14 | logger.log(pc.dim('────────────────'));
15 | logger.log(`${pc.white(' Total Files:')} ${pc.white(totalFiles.toString())}`);
16 | logger.log(`${pc.white(' Total Chars:')} ${pc.white(totalCharacters.toString())}`);
17 | logger.log(`${pc.white(' Total Tokens:')} ${pc.white(totalTokens.toString())}`);
18 | logger.log(`${pc.white(' Output:')} ${pc.white(outputPath)}`);
19 | };
20 |
21 | export const printTopFiles = (
22 | fileCharCounts: Record,
23 | fileTokenCounts: Record,
24 | topFilesLength: number,
25 | ) => {
26 | logger.log(pc.white(`📈 Top ${topFilesLength} Files by Character Count and Token Count:`));
27 | logger.log(pc.dim('──────────────────────────────────────────────────────'));
28 |
29 | const topFiles = Object.entries(fileCharCounts)
30 | .sort((a, b) => b[1] - a[1])
31 | .slice(0, topFilesLength);
32 |
33 | topFiles.forEach(([filePath, charCount], index) => {
34 | const tokenCount = fileTokenCounts[filePath];
35 | const indexString = `${index + 1}.`.padEnd(3, ' ');
36 | logger.log(
37 | `${pc.white(`${indexString}`)} ${pc.white(filePath)} ${pc.dim(`(${charCount} chars, ${tokenCount} tokens)`)}`,
38 | );
39 | });
40 | };
41 |
42 | export const printCompletion = () => {
43 | logger.log(pc.green('🎉 All Done!'));
44 | logger.log(pc.white('Your repository has been successfully packed.'));
45 | };
46 |
--------------------------------------------------------------------------------
/src/cli/cliRun.ts:
--------------------------------------------------------------------------------
1 | import process from "node:process";
2 | import { type OptionValues, program } from "commander";
3 | import pc from "picocolors";
4 | import type { CodeTreeOutputStyle } from "../config/configSchema.js";
5 | import { getVersion } from "../core/file/packageJsonParse.js";
6 | import { handleError } from "../shared/errorHandle.js";
7 | import { logger } from "../shared/logger.js";
8 | import { runDefaultAction } from "./actions/defaultAction.js";
9 | import { runInitAction } from "./actions/initAction.js";
10 | import { runRemoteAction } from "./actions/remoteAction.js";
11 | import { runVersionAction } from "./actions/versionAction.js";
12 |
13 | export interface CliOptions extends OptionValues {
14 | version?: boolean;
15 | output?: string;
16 | include?: string;
17 | ignore?: string;
18 | config?: string;
19 | copy?: boolean;
20 | verbose?: boolean;
21 | topFilesLen?: number;
22 | outputShowLineNumbers?: boolean;
23 | style?: CodeTreeOutputStyle;
24 | init?: boolean;
25 | global?: boolean;
26 | remote?: string;
27 | }
28 |
29 | export const run = async () => {
30 | try {
31 | program
32 | .description(
33 | "CodeTree - Pack your repository into a single AI-friendly file"
34 | )
35 | .arguments("[directory]")
36 | .option("-v, --version", "show version information")
37 | .option("-o, --output ", "specify the output file name")
38 | .option(
39 | "--include ",
40 | "list of include patterns (comma-separated)"
41 | )
42 | .option(
43 | "-i, --ignore ",
44 | "additional ignore patterns (comma-separated)"
45 | )
46 | .option("-c, --config ", "path to a custom config file")
47 | .option("--copy", "copy generated output to system clipboard")
48 | .option(
49 | "--top-files-len ",
50 | "specify the number of top files to display",
51 | Number.parseInt
52 | )
53 | .option(
54 | "--output-show-line-numbers",
55 | "add line numbers to each line in the output"
56 | )
57 | .option(
58 | "--style ",
59 | "specify the output style (plain, xml, markdown)"
60 | )
61 | .option("--verbose", "enable verbose logging for detailed output")
62 | .option("--init", "initialize a new codetree.config.json file")
63 | .option(
64 | "--global",
65 | "use global configuration (only applicable with --init)"
66 | )
67 | .option("--remote ", "process a remote Git repository")
68 | .action((directory = ".", options: CliOptions = {}) =>
69 | executeAction(directory, process.cwd(), options)
70 | );
71 |
72 | await program.parseAsync(process.argv);
73 | } catch (error) {
74 | handleError(error);
75 | }
76 | };
77 |
78 | export const executeAction = async (
79 | directory: string,
80 | cwd: string,
81 | options: CliOptions
82 | ) => {
83 | logger.setVerbose(options.verbose || false);
84 |
85 | if (options.version) {
86 | await runVersionAction();
87 | return;
88 | }
89 |
90 | const version = await getVersion();
91 | logger.log(`\n🌳 ${pc.bold("CodeTree")} ${pc.dim(`v${version}`)}\n`);
92 |
93 | if (options.init) {
94 | await runInitAction(cwd, options.global || false);
95 | return;
96 | }
97 |
98 | if (options.remote) {
99 | await runRemoteAction(options.remote, options);
100 | return;
101 | }
102 |
103 | await runDefaultAction(directory, cwd, options);
104 | };
105 |
--------------------------------------------------------------------------------
/src/cli/cliSpinner.ts:
--------------------------------------------------------------------------------
1 | import cliSpinners from 'cli-spinners';
2 | import logUpdate from 'log-update';
3 | import pc from 'picocolors';
4 |
5 | class Spinner {
6 | private spinner = cliSpinners.dots;
7 | private message: string;
8 | private currentFrame = 0;
9 | private interval: ReturnType | null = null;
10 |
11 | constructor(message: string) {
12 | this.message = message;
13 | }
14 |
15 | start(): void {
16 | const frames = this.spinner.frames;
17 | const framesLength = frames.length;
18 | this.interval = setInterval(() => {
19 | this.currentFrame++;
20 | const frame = frames[this.currentFrame % framesLength];
21 | logUpdate(`${pc.cyan(frame)} ${this.message}`);
22 | }, this.spinner.interval);
23 | }
24 |
25 | update(message: string): void {
26 | this.message = message;
27 | }
28 |
29 | stop(finalMessage: string): void {
30 | if (this.interval) {
31 | clearInterval(this.interval);
32 | this.interval = null;
33 | }
34 | logUpdate(finalMessage);
35 | logUpdate.done();
36 | }
37 |
38 | succeed(message: string): void {
39 | this.stop(`${pc.green('✔')} ${message}`);
40 | }
41 |
42 | fail(message: string): void {
43 | this.stop(`${pc.red('✖')} ${message}`);
44 | }
45 | }
46 |
47 | export default Spinner;
48 |
--------------------------------------------------------------------------------
/src/config/configLoad.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'node:fs/promises';
2 | import path from 'node:path';
3 | import { z } from 'zod';
4 | import { CodeTreeError, rethrowValidationErrorIfZodError } from '../shared/errorHandle.js';
5 | import { logger } from '../shared/logger.js';
6 | import {
7 | type CodeTreeConfigCli,
8 | type CodeTreeConfigFile,
9 | type CodeTreeConfigMerged,
10 | defaultConfig,
11 | defaultFilePathMap,
12 | codetreeConfigFileSchema,
13 | codetreeConfigMergedSchema,
14 | } from './configSchema.js';
15 | import { getGlobalDirectory } from './globalDirectory.js';
16 |
17 | const defaultConfigPath = 'codetree.config.json';
18 |
19 | const getGlobalConfigPath = () => {
20 | return path.join(getGlobalDirectory(), 'codetree.config.json');
21 | };
22 |
23 | export const loadFileConfig = async (rootDir: string, argConfigPath: string | null): Promise => {
24 | let useDefaultConfig = false;
25 | let configPath = argConfigPath;
26 | if (!configPath) {
27 | useDefaultConfig = true;
28 | configPath = defaultConfigPath;
29 | }
30 |
31 | const fullPath = path.resolve(rootDir, configPath);
32 |
33 | logger.trace('Loading local config from:', fullPath);
34 |
35 | // Check local file existence
36 | const isLocalFileExists = await fs
37 | .stat(fullPath)
38 | .then((stats) => stats.isFile())
39 | .catch(() => false);
40 |
41 | if (isLocalFileExists) {
42 | return await loadAndValidateConfig(fullPath);
43 | }
44 |
45 | if (useDefaultConfig) {
46 | // Try to load global config
47 | const globalConfigPath = getGlobalConfigPath();
48 | logger.trace('Loading global config from:', globalConfigPath);
49 |
50 | const isGlobalFileExists = await fs
51 | .stat(globalConfigPath)
52 | .then((stats) => stats.isFile())
53 | .catch(() => false);
54 |
55 | if (isGlobalFileExists) {
56 | return await loadAndValidateConfig(globalConfigPath);
57 | }
58 |
59 | logger.note(
60 | `No custom config found at ${configPath} or global config at ${globalConfigPath}.\nYou can add a config file for additional settings. Please check https://github.com/mimalef70/CodeTree for more information.`,
61 | );
62 | return {};
63 | }
64 | throw new CodeTreeError(`Config file not found at ${configPath}`);
65 | };
66 |
67 | const loadAndValidateConfig = async (filePath: string): Promise => {
68 | try {
69 | const fileContent = await fs.readFile(filePath, 'utf-8');
70 | const config = JSON.parse(fileContent);
71 | return codetreeConfigFileSchema.parse(config);
72 | } catch (error) {
73 | rethrowValidationErrorIfZodError(error, 'Invalid config schema');
74 | if (error instanceof SyntaxError) {
75 | throw new CodeTreeError(`Invalid JSON in config file ${filePath}: ${error.message}`);
76 | }
77 | if (error instanceof Error) {
78 | throw new CodeTreeError(`Error loading config from ${filePath}: ${error.message}`);
79 | }
80 | throw new CodeTreeError(`Error loading config from ${filePath}`);
81 | }
82 | };
83 |
84 | export const mergeConfigs = (
85 | cwd: string,
86 | fileConfig: CodeTreeConfigFile,
87 | cliConfig: CodeTreeConfigCli,
88 | ): CodeTreeConfigMerged => {
89 | logger.trace('Default config:', defaultConfig);
90 |
91 | const baseConfig = defaultConfig;
92 |
93 | // If the output file path is not provided in the config file or CLI, use the default file path for the style
94 | if (cliConfig.output?.filePath == null && fileConfig.output?.filePath == null) {
95 | const style = cliConfig.output?.style || fileConfig.output?.style || baseConfig.output.style;
96 | baseConfig.output.filePath = defaultFilePathMap[style];
97 |
98 | logger.trace('Default output file path is set to:', baseConfig.output.filePath);
99 | }
100 |
101 | const mergedConfig = {
102 | cwd,
103 | output: {
104 | ...baseConfig.output,
105 | ...fileConfig.output,
106 | ...cliConfig.output,
107 | },
108 | include: [...(baseConfig.include || []), ...(fileConfig.include || []), ...(cliConfig.include || [])],
109 | ignore: {
110 | ...baseConfig.ignore,
111 | ...fileConfig.ignore,
112 | ...cliConfig.ignore,
113 | customPatterns: [
114 | ...(baseConfig.ignore.customPatterns || []),
115 | ...(fileConfig.ignore?.customPatterns || []),
116 | ...(cliConfig.ignore?.customPatterns || []),
117 | ],
118 | }
119 | };
120 |
121 | try {
122 | return codetreeConfigMergedSchema.parse(mergedConfig);
123 | } catch (error) {
124 | rethrowValidationErrorIfZodError(error, 'Invalid merged config');
125 | throw error;
126 | }
127 | };
128 |
--------------------------------------------------------------------------------
/src/config/configSchema.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 |
3 | // Output style enum
4 | export const codetreeOutputStyleSchema = z.enum(['plain', 'xml', 'markdown']);
5 | export type CodeTreeOutputStyle = z.infer;
6 |
7 | // Default values map
8 | export const defaultFilePathMap: Record = {
9 | plain: 'codetree.txt',
10 | markdown: 'codetree.md',
11 | xml: 'codetree.xml',
12 | } as const;
13 |
14 | // Base config schema
15 | export const codetreeConfigBaseSchema = z.object({
16 | output: z
17 | .object({
18 | filePath: z.string().optional(),
19 | style: codetreeOutputStyleSchema.optional(),
20 | headerText: z.string().optional(),
21 | instructionFilePath: z.string().optional(),
22 | removeComments: z.boolean().optional(),
23 | removeEmptyLines: z.boolean().optional(),
24 | topFilesLength: z.number().optional(),
25 | showLineNumbers: z.boolean().optional(),
26 | copyToClipboard: z.boolean().optional(),
27 | })
28 | .optional(),
29 | include: z.array(z.string()).optional(),
30 | ignore: z
31 | .object({
32 | useGitignore: z.boolean().optional(),
33 | useDefaultPatterns: z.boolean().optional(),
34 | customPatterns: z.array(z.string()).optional(),
35 | })
36 | .optional(),
37 | });
38 |
39 | // Default config schema with default values
40 | export const codetreeConfigDefaultSchema = z.object({
41 | output: z
42 | .object({
43 | filePath: z.string().default(defaultFilePathMap.plain),
44 | style: codetreeOutputStyleSchema.default('plain'),
45 | headerText: z.string().optional(),
46 | instructionFilePath: z.string().optional(),
47 | removeComments: z.boolean().default(false),
48 | removeEmptyLines: z.boolean().default(false),
49 | topFilesLength: z.number().int().min(0).default(5),
50 | showLineNumbers: z.boolean().default(false),
51 | copyToClipboard: z.boolean().default(false),
52 | })
53 | .default({}),
54 | include: z.array(z.string()).default([]),
55 | ignore: z
56 | .object({
57 | useGitignore: z.boolean().default(true),
58 | useDefaultPatterns: z.boolean().default(true),
59 | customPatterns: z.array(z.string()).default([]),
60 | })
61 | .default({}),
62 | });
63 |
64 | export const codetreeConfigFileSchema = codetreeConfigBaseSchema;
65 |
66 | export const codetreeConfigCliSchema = codetreeConfigBaseSchema;
67 |
68 | export const codetreeConfigMergedSchema = codetreeConfigDefaultSchema
69 | .and(codetreeConfigFileSchema)
70 | .and(codetreeConfigCliSchema)
71 | .and(
72 | z.object({
73 | cwd: z.string(),
74 | }),
75 | );
76 |
77 | export type CodeTreeConfigDefault = z.infer;
78 | export type CodeTreeConfigFile = z.infer;
79 | export type CodeTreeConfigCli = z.infer;
80 | export type CodeTreeConfigMerged = z.infer;
81 |
82 | export const defaultConfig = codetreeConfigDefaultSchema.parse({});
83 |
--------------------------------------------------------------------------------
/src/config/defaultIgnore.ts:
--------------------------------------------------------------------------------
1 | export const defaultIgnoreList = [
2 | // Version control
3 | '.git/**',
4 | '.hg/**',
5 | '.hgignore',
6 | '.svn/**',
7 |
8 | // Dependency directories
9 | 'node_modules/**',
10 | '**/node_modules/**',
11 | 'bower_components/**',
12 | '**/bower_components/**',
13 | 'jspm_packages/**',
14 | '**/jspm_packages/**',
15 | 'vendor/**',
16 | '.bundle/**',
17 | '.gradle/**',
18 | 'target/**',
19 |
20 | // Logs
21 | 'logs/**',
22 | '**/*.log',
23 | '**/npm-debug.log*',
24 | '**/yarn-debug.log*',
25 | '**/yarn-error.log*',
26 |
27 | // Runtime data
28 | 'pids/**',
29 | '*.pid',
30 | '*.seed',
31 | '*.pid.lock',
32 |
33 | // Directory for instrumented libs generated by jscoverage/JSCover
34 | 'lib-cov/**',
35 |
36 | // Coverage directory used by tools like istanbul
37 | 'coverage/**',
38 |
39 | // nyc test coverage
40 | '.nyc_output/**',
41 |
42 | // Grunt intermediate storage
43 | '.grunt/**',
44 |
45 | // node-waf configuration
46 | '.lock-wscript',
47 |
48 | // Compiled binary addons
49 | 'build/Release/**',
50 |
51 | // TypeScript v1 declaration files
52 | 'typings/**',
53 |
54 | // Optional npm cache directory
55 | '**/.npm/**',
56 |
57 | // Optional eslint cache
58 | '.eslintcache',
59 |
60 | // Optional REPL history
61 | '.node_repl_history',
62 |
63 | // Output of 'npm pack'
64 | '*.tgz',
65 |
66 | // Yarn files
67 | '**/.yarn/**',
68 |
69 | // Yarn Integrity file
70 | '**/.yarn-integrity',
71 |
72 | // dotenv environment variables file
73 | '.env',
74 |
75 | // next.js build output
76 | '.next/**',
77 |
78 | // nuxt.js build output
79 | '.nuxt/**',
80 |
81 | // vuepress build output
82 | '.vuepress/dist/**',
83 |
84 | // Serverless directories
85 | '.serverless/**',
86 |
87 | // FuseBox cache
88 | '.fusebox/**',
89 |
90 | // DynamoDB Local files
91 | '.dynamodb/**',
92 |
93 | // TypeScript output
94 | 'dist/**',
95 |
96 | // OS generated files
97 | '**/.DS_Store',
98 | '**/Thumbs.db',
99 |
100 | // Editor directories and files
101 | '.idea/**',
102 | '.vscode/**',
103 | '**/*.swp',
104 | '**/*.swo',
105 | '**/*.swn',
106 | '**/*.bak',
107 |
108 | // Package manager locks
109 | '**/package-lock.json',
110 | '**/yarn.lock',
111 | '**/pnpm-lock.yaml',
112 |
113 | // Build outputs
114 | 'build/**',
115 | 'out/**',
116 |
117 | // Temporary files
118 | 'tmp/**',
119 | 'temp/**',
120 |
121 | // codetree output
122 | 'codetree.*',
123 |
124 | // Essential Python-related entries
125 | '**/__pycache__/**',
126 | '**/*.py[cod]',
127 | '**/venv/**',
128 | '**/.venv/**',
129 | '**/.pytest_cache/**',
130 | '**/.mypy_cache/**',
131 | '**/.ipynb_checkpoints/**',
132 | '**/Pipfile.lock',
133 | '**/poetry.lock',
134 | ];
135 |
--------------------------------------------------------------------------------
/src/config/globalDirectory.ts:
--------------------------------------------------------------------------------
1 | import os from 'node:os';
2 | import path from 'node:path';
3 |
4 | export const getGlobalDirectory = () => {
5 | if (process.platform === 'win32') {
6 | const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
7 | return path.join(localAppData, 'CodeTree');
8 | }
9 |
10 | if (process.env.XDG_CONFIG_HOME) {
11 | return path.join(process.env.XDG_CONFIG_HOME, 'codetree');
12 | }
13 |
14 | return path.join(os.homedir(), '.config', 'codetree');
15 | };
16 |
--------------------------------------------------------------------------------
/src/core/file/fileCollect.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'node:fs/promises';
2 | import path from 'node:path';
3 | import iconv from 'iconv-lite';
4 | import { isBinary } from 'istextorbinary';
5 | import jschardet from 'jschardet';
6 | import pMap from 'p-map';
7 | import { logger } from '../../shared/logger.js';
8 | import { getProcessConcurrency } from '../../shared/processConcurrency.js';
9 | import type { RawFile } from './fileTypes.js';
10 |
11 | export const collectFiles = async (filePaths: string[], rootDir: string): Promise => {
12 | const rawFiles = await pMap(
13 | filePaths,
14 | async (filePath) => {
15 | const fullPath = path.resolve(rootDir, filePath);
16 | const content = await readRawFile(fullPath);
17 | if (content) {
18 | return { path: filePath, content };
19 | }
20 | return null;
21 | },
22 | {
23 | concurrency: getProcessConcurrency(),
24 | },
25 | );
26 |
27 | return rawFiles.filter((file): file is RawFile => file != null);
28 | };
29 |
30 | const readRawFile = async (filePath: string): Promise => {
31 | if (isBinary(filePath)) {
32 | logger.debug(`Skipping binary file: ${filePath}`);
33 | return null;
34 | }
35 |
36 | logger.trace(`Processing file: ${filePath}`);
37 |
38 | try {
39 | const buffer = await fs.readFile(filePath);
40 |
41 | if (isBinary(null, buffer)) {
42 | logger.debug(`Skipping binary file (content check): ${filePath}`);
43 | return null;
44 | }
45 |
46 | const encoding = jschardet.detect(buffer).encoding || 'utf-8';
47 | const content = iconv.decode(buffer, encoding);
48 |
49 | return content;
50 | } catch (error) {
51 | logger.warn(`Failed to read file: ${filePath}`, error);
52 | return null;
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/src/core/file/fileManipulate.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path';
2 | import strip from 'strip-comments';
3 |
4 | interface FileManipulator {
5 | removeComments(content: string): string;
6 | removeEmptyLines(content: string): string;
7 | }
8 |
9 | const rtrimLines = (content: string): string =>
10 | content
11 | .split('\n')
12 | .map((line) => line.trimEnd())
13 | .join('\n');
14 |
15 | class BaseManipulator implements FileManipulator {
16 | removeComments(content: string): string {
17 | return content;
18 | }
19 |
20 | removeEmptyLines(content: string): string {
21 | return content
22 | .split('\n')
23 | .filter((line) => line.trim() !== '')
24 | .join('\n');
25 | }
26 | }
27 |
28 | class StripCommentsManipulator extends BaseManipulator {
29 | private language: string;
30 |
31 | constructor(language: string) {
32 | super();
33 | this.language = language;
34 | }
35 |
36 | removeComments(content: string): string {
37 | const result = strip(content, {
38 | language: this.language,
39 | preserveNewlines: true,
40 | });
41 | return rtrimLines(result);
42 | }
43 | }
44 |
45 | class PythonManipulator extends BaseManipulator {
46 | removeDocStrings(content: string): string {
47 | if (!content) return '';
48 | const lines = content.split('\n');
49 |
50 | let result = '';
51 |
52 | let buffer = '';
53 | let quoteType: '' | "'" | '"' = '';
54 | let tripleQuotes = 0;
55 |
56 | const doubleQuoteRegex = /^\s*(? {
95 | return pairs.some(([start, end]) => hashIndex > start && hashIndex < end);
96 | };
97 |
98 | let result = '';
99 | const pairs: [number, number][] = [];
100 | let prevQuote = 0;
101 | while (prevQuote < content.length) {
102 | const openingQuote = content.slice(prevQuote + 1).search(/(? manipulator.removeComments(acc), content);
165 | }
166 | }
167 |
168 | const manipulators: Record = {
169 | '.c': new StripCommentsManipulator('c'),
170 | '.cs': new StripCommentsManipulator('csharp'),
171 | '.css': new StripCommentsManipulator('css'),
172 | '.dart': new StripCommentsManipulator('c'),
173 | '.go': new StripCommentsManipulator('c'),
174 | '.html': new StripCommentsManipulator('html'),
175 | '.java': new StripCommentsManipulator('java'),
176 | '.js': new StripCommentsManipulator('javascript'),
177 | '.jsx': new StripCommentsManipulator('javascript'),
178 | '.kt': new StripCommentsManipulator('c'),
179 | '.less': new StripCommentsManipulator('less'),
180 | '.php': new StripCommentsManipulator('php'),
181 | '.rb': new StripCommentsManipulator('ruby'),
182 | '.rs': new StripCommentsManipulator('c'),
183 | '.sass': new StripCommentsManipulator('sass'),
184 | '.scss': new StripCommentsManipulator('sass'),
185 | '.sh': new StripCommentsManipulator('perl'),
186 | '.sql': new StripCommentsManipulator('sql'),
187 | '.swift': new StripCommentsManipulator('swift'),
188 | '.ts': new StripCommentsManipulator('javascript'),
189 | '.tsx': new StripCommentsManipulator('javascript'),
190 | '.xml': new StripCommentsManipulator('xml'),
191 | '.yaml': new StripCommentsManipulator('perl'),
192 | '.yml': new StripCommentsManipulator('perl'),
193 |
194 | '.py': new PythonManipulator(),
195 |
196 | '.vue': new CompositeManipulator(
197 | new StripCommentsManipulator('html'),
198 | new StripCommentsManipulator('css'),
199 | new StripCommentsManipulator('javascript'),
200 | ),
201 | '.svelte': new CompositeManipulator(
202 | new StripCommentsManipulator('html'),
203 | new StripCommentsManipulator('css'),
204 | new StripCommentsManipulator('javascript'),
205 | ),
206 | };
207 |
208 | export const getFileManipulator = (filePath: string): FileManipulator | null => {
209 | const ext = path.extname(filePath);
210 | return manipulators[ext] || null;
211 | };
212 |
--------------------------------------------------------------------------------
/src/core/file/filePathSort.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path';
2 |
3 | export const sortPaths = (filePaths: string[]): string[] =>
4 | filePaths.sort((a, b) => {
5 | const partsA = a.split(path.sep);
6 | const partsB = b.split(path.sep);
7 |
8 | for (let i = 0; i < Math.min(partsA.length, partsB.length); i++) {
9 | if (partsA[i] !== partsB[i]) {
10 | const isLastA = i === partsA.length - 1;
11 | const isLastB = i === partsB.length - 1;
12 |
13 | if (!isLastA && isLastB) return -1; // Directory
14 | if (isLastA && !isLastB) return 1; // File
15 |
16 | return partsA[i].localeCompare(partsB[i]); // Alphabetical order
17 | }
18 | }
19 |
20 | // Sort by length if all parts are equal
21 | return partsA.length - partsB.length;
22 | });
23 |
--------------------------------------------------------------------------------
/src/core/file/fileProcess.ts:
--------------------------------------------------------------------------------
1 | import pMap from 'p-map';
2 | import type { CodeTreeConfigMerged } from '../../config/configSchema.js';
3 | import { getProcessConcurrency } from '../../shared/processConcurrency.js';
4 | import { getFileManipulator } from './fileManipulate.js';
5 | import type { ProcessedFile, RawFile } from './fileTypes.js';
6 |
7 | export const processFiles = async (rawFiles: RawFile[], config: CodeTreeConfigMerged): Promise => {
8 | return pMap(
9 | rawFiles,
10 | async (rawFile) => ({
11 | path: rawFile.path,
12 | content: await processContent(rawFile.content, rawFile.path, config),
13 | }),
14 | {
15 | concurrency: getProcessConcurrency(),
16 | },
17 | );
18 | };
19 |
20 | export const processContent = async (
21 | content: string,
22 | filePath: string,
23 | config: CodeTreeConfigMerged,
24 | ): Promise => {
25 | let processedContent = content;
26 | const manipulator = getFileManipulator(filePath);
27 |
28 | if (config.output.removeComments && manipulator) {
29 | processedContent = manipulator.removeComments(processedContent);
30 | }
31 |
32 | if (config.output.removeEmptyLines && manipulator) {
33 | processedContent = manipulator.removeEmptyLines(processedContent);
34 | }
35 |
36 | processedContent = processedContent.trim();
37 |
38 | if (config.output.showLineNumbers) {
39 | const lines = processedContent.split('\n');
40 | const padding = lines.length.toString().length;
41 | const numberedLines = lines.map((line, index) => `${(index + 1).toString().padStart(padding)}: ${line}`);
42 | processedContent = numberedLines.join('\n');
43 | }
44 |
45 | return processedContent;
46 | };
47 |
--------------------------------------------------------------------------------
/src/core/file/fileSearch.ts:
--------------------------------------------------------------------------------
1 | import { globby } from 'globby';
2 | import type { CodeTreeConfigMerged } from '../../config/configSchema.js';
3 | import { defaultIgnoreList } from '../../config/defaultIgnore.js';
4 | import { logger } from '../../shared/logger.js';
5 | import { sortPaths } from './filePathSort.js';
6 | import { PermissionError, checkDirectoryPermissions } from './permissionCheck.js';
7 |
8 | export const searchFiles = async (rootDir: string, config: CodeTreeConfigMerged): Promise => {
9 | // First check directory permissions
10 | const permissionCheck = await checkDirectoryPermissions(rootDir);
11 |
12 | if (!permissionCheck.hasPermission) {
13 | if (permissionCheck.error instanceof PermissionError) {
14 | throw permissionCheck.error;
15 | }
16 | throw new Error(`Cannot access directory ${rootDir}: ${permissionCheck.error?.message}`);
17 | }
18 |
19 | const includePatterns = config.include.length > 0 ? config.include : ['**/*'];
20 |
21 | try {
22 | const [ignorePatterns, ignoreFilePatterns] = await Promise.all([
23 | getIgnorePatterns(rootDir, config),
24 | getIgnoreFilePatterns(config),
25 | ]);
26 |
27 | logger.trace('Include patterns:', includePatterns);
28 | logger.trace('Ignore patterns:', ignorePatterns);
29 | logger.trace('Ignore file patterns:', ignoreFilePatterns);
30 |
31 | const filePaths = await globby(includePatterns, {
32 | cwd: rootDir,
33 | ignore: [...ignorePatterns],
34 | ignoreFiles: [...ignoreFilePatterns],
35 | onlyFiles: true,
36 | absolute: false,
37 | dot: true,
38 | followSymbolicLinks: false,
39 | }).catch((error) => {
40 | // Handle EPERM errors specifically
41 | if (error.code === 'EPERM' || error.code === 'EACCES') {
42 | throw new PermissionError(
43 | 'Permission denied while scanning directory. Please check folder access permissions for your terminal app.',
44 | rootDir,
45 | );
46 | }
47 | throw error;
48 | });
49 |
50 | logger.trace(`Filtered ${filePaths.length} files`);
51 | const sortedPaths = sortPaths(filePaths);
52 |
53 | return sortedPaths;
54 | } catch (error: unknown) {
55 | // Re-throw PermissionError as is
56 | if (error instanceof PermissionError) {
57 | throw error;
58 | }
59 |
60 | if (error instanceof Error) {
61 | logger.error('Error filtering files:', error.message);
62 | throw new Error(`Failed to filter files in directory ${rootDir}. Reason: ${error.message}`);
63 | }
64 |
65 | logger.error('An unexpected error occurred:', error);
66 | throw new Error('An unexpected error occurred while filtering files.');
67 | }
68 | };
69 |
70 | export const parseIgnoreContent = (content: string): string[] => {
71 | if (!content) return [];
72 |
73 | return content.split('\n').reduce((acc, line) => {
74 | const trimmedLine = line.trim();
75 | if (trimmedLine && !trimmedLine.startsWith('#')) {
76 | acc.push(trimmedLine);
77 | }
78 | return acc;
79 | }, []);
80 | };
81 |
82 | export const getIgnoreFilePatterns = async (config: CodeTreeConfigMerged): Promise => {
83 | const ignoreFilePatterns: string[] = [];
84 |
85 | if (config.ignore.useGitignore) {
86 | ignoreFilePatterns.push('**/.gitignore');
87 | }
88 |
89 | ignoreFilePatterns.push('**/.codetreeignore');
90 |
91 | return ignoreFilePatterns;
92 | };
93 |
94 | export const getIgnorePatterns = async (rootDir: string, config: CodeTreeConfigMerged): Promise => {
95 | const ignorePatterns = new Set();
96 |
97 | // Add default ignore patterns
98 | if (config.ignore.useDefaultPatterns) {
99 | logger.trace('Adding default ignore patterns');
100 | for (const pattern of defaultIgnoreList) {
101 | ignorePatterns.add(pattern);
102 | }
103 | }
104 |
105 | // Add codetree output file
106 | if (config.output.filePath) {
107 | logger.trace('Adding output file to ignore patterns:', config.output.filePath);
108 | ignorePatterns.add(config.output.filePath);
109 | }
110 |
111 | // Add custom ignore patterns
112 | if (config.ignore.customPatterns) {
113 | logger.trace('Adding custom ignore patterns:', config.ignore.customPatterns);
114 | for (const pattern of config.ignore.customPatterns) {
115 | ignorePatterns.add(pattern);
116 | }
117 | }
118 |
119 | return Array.from(ignorePatterns);
120 | };
121 |
--------------------------------------------------------------------------------
/src/core/file/fileTreeGenerate.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path';
2 |
3 | interface TreeNode {
4 | name: string;
5 | children: TreeNode[];
6 | isDirectory: boolean;
7 | }
8 |
9 | const createTreeNode = (name: string, isDirectory: boolean): TreeNode => ({ name, children: [], isDirectory });
10 |
11 | export const generateFileTree = (files: string[]): TreeNode => {
12 | const root: TreeNode = createTreeNode('root', true);
13 |
14 | for (const file of files) {
15 | const parts = file.split(path.sep);
16 | let currentNode = root;
17 |
18 | for (let i = 0; i < parts.length; i++) {
19 | const part = parts[i];
20 | const isLastPart = i === parts.length - 1;
21 | let child = currentNode.children.find((c) => c.name === part);
22 |
23 | if (!child) {
24 | child = createTreeNode(part, !isLastPart);
25 | currentNode.children.push(child);
26 | }
27 |
28 | currentNode = child;
29 | }
30 | }
31 |
32 | return root;
33 | };
34 |
35 | const sortTreeNodes = (node: TreeNode) => {
36 | node.children.sort((a, b) => {
37 | if (a.isDirectory === b.isDirectory) {
38 | return a.name.localeCompare(b.name);
39 | }
40 | return a.isDirectory ? -1 : 1;
41 | });
42 |
43 | for (const child of node.children) {
44 | sortTreeNodes(child);
45 | }
46 | };
47 |
48 | export const treeToString = (node: TreeNode, prefix = ''): string => {
49 | sortTreeNodes(node);
50 | let result = '';
51 |
52 | for (const child of node.children) {
53 | result += `${prefix}${child.name}${child.isDirectory ? '/' : ''}\n`;
54 | if (child.isDirectory) {
55 | result += treeToString(child, `${prefix} `);
56 | }
57 | }
58 |
59 | return result;
60 | };
61 |
62 | export const generateTreeString = (files: string[]): string => {
63 | const tree = generateFileTree(files);
64 | return treeToString(tree).trim();
65 | };
66 |
--------------------------------------------------------------------------------
/src/core/file/fileTypes.ts:
--------------------------------------------------------------------------------
1 | export interface RawFile {
2 | path: string;
3 | content: string;
4 | }
5 |
6 | export interface ProcessedFile {
7 | path: string;
8 | content: string;
9 | }
10 |
--------------------------------------------------------------------------------
/src/core/file/packageJsonParse.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'node:fs/promises';
2 | import path from 'node:path';
3 | import * as url from 'node:url';
4 | import { logger } from '../../shared/logger.js';
5 |
6 | export const getVersion = async (): Promise => {
7 | try {
8 | const packageJson = await parsePackageJson();
9 |
10 | if (!packageJson.version) {
11 | logger.warn('No version found in package.json');
12 | return 'unknown';
13 | }
14 |
15 | return packageJson.version;
16 | } catch (error) {
17 | logger.error('Error reading package.json:', error);
18 | return 'unknown';
19 | }
20 | };
21 |
22 | const parsePackageJson = async (): Promise<{
23 | name: string;
24 | version: string;
25 | }> => {
26 | const dirName = url.fileURLToPath(new URL('.', import.meta.url));
27 | const packageJsonPath = path.join(dirName, '..', '..', '..', 'package.json');
28 | const packageJsonFile = await fs.readFile(packageJsonPath, 'utf-8');
29 | const packageJson = JSON.parse(packageJsonFile);
30 | return packageJson;
31 | };
32 |
--------------------------------------------------------------------------------
/src/core/file/permissionCheck.ts:
--------------------------------------------------------------------------------
1 | import { constants } from 'node:fs';
2 | import * as fs from 'node:fs/promises';
3 | import { platform } from 'node:os';
4 | import { logger } from '../../shared/logger.js';
5 |
6 | export interface PermissionCheckResult {
7 | hasPermission: boolean;
8 | error?: Error;
9 | details?: {
10 | read?: boolean;
11 | write?: boolean;
12 | execute?: boolean;
13 | };
14 | }
15 |
16 | export class PermissionError extends Error {
17 | constructor(
18 | message: string,
19 | public readonly path: string,
20 | public readonly code?: string,
21 | ) {
22 | super(message);
23 | this.name = 'PermissionError';
24 | }
25 | }
26 |
27 | export const checkDirectoryPermissions = async (dirPath: string): Promise => {
28 | try {
29 | // First try to read directory contents
30 | await fs.readdir(dirPath);
31 |
32 | // Check specific permissions
33 | const details = {
34 | read: false,
35 | write: false,
36 | execute: false,
37 | };
38 |
39 | try {
40 | await fs.access(dirPath, constants.R_OK);
41 | details.read = true;
42 | } catch {}
43 |
44 | try {
45 | await fs.access(dirPath, constants.W_OK);
46 | details.write = true;
47 | } catch {}
48 |
49 | try {
50 | await fs.access(dirPath, constants.X_OK);
51 | details.execute = true;
52 | } catch {}
53 |
54 | const hasAllPermissions = details.read && details.write && details.execute;
55 |
56 | if (!hasAllPermissions) {
57 | return {
58 | hasPermission: false,
59 | details,
60 | };
61 | }
62 |
63 | return {
64 | hasPermission: true,
65 | details,
66 | };
67 | } catch (error) {
68 | if (error instanceof Error && 'code' in error) {
69 | switch (error.code) {
70 | case 'EPERM':
71 | case 'EACCES':
72 | case 'EISDIR':
73 | return {
74 | hasPermission: false,
75 | error: new PermissionError(getMacOSPermissionMessage(dirPath, error.code), dirPath, error.code),
76 | };
77 | default:
78 | logger.debug('Directory permission check error:', error);
79 | return {
80 | hasPermission: false,
81 | error: error as Error,
82 | };
83 | }
84 | }
85 | return {
86 | hasPermission: false,
87 | error: error instanceof Error ? error : new Error(String(error)),
88 | };
89 | }
90 | };
91 |
92 | const getMacOSPermissionMessage = (dirPath: string, errorCode?: string): string => {
93 | if (platform() === 'darwin') {
94 | return `Permission denied: Cannot access '${dirPath}', error code: ${errorCode}.
95 |
96 | This error often occurs when macOS security restrictions prevent access to the directory.
97 | To fix this:
98 |
99 | 1. Open System Settings
100 | 2. Navigate to Privacy & Security > Files and Folders
101 | 3. Find your terminal app (Terminal.app, iTerm2, VS Code, etc.)
102 | 4. Grant necessary folder access permissions
103 |
104 | If your terminal app is not listed:
105 | - Try running codetree command again
106 | - When prompted by macOS, click "Allow"
107 | - Restart your terminal app if needed
108 | `;
109 | }
110 |
111 | return `Permission denied: Cannot access '${dirPath}'`;
112 | };
113 |
--------------------------------------------------------------------------------
/src/core/output/outputGenerate.ts:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs/promises';
2 | import path from 'node:path';
3 | import Handlebars from 'handlebars';
4 | import type { CodeTreeConfigMerged } from '../../config/configSchema.js';
5 | import { CodeTreeError } from '../../shared/errorHandle.js';
6 | import { generateTreeString } from '../file/fileTreeGenerate.js';
7 | import type { ProcessedFile } from '../file/fileTypes.js';
8 | import type { OutputGeneratorContext } from './outputGeneratorTypes.js';
9 | import { getMarkdownTemplate } from './outputStyles/markdownStyle.js';
10 | import { getPlainTemplate } from './outputStyles/plainStyle.js';
11 | import { getXmlTemplate } from './outputStyles/xmlStyle.js';
12 |
13 | const createRenderContext = (outputGeneratorContext: OutputGeneratorContext) => {
14 | return {
15 | headerText: outputGeneratorContext.config.output.headerText,
16 | instruction: outputGeneratorContext.instruction,
17 | treeString: outputGeneratorContext.treeString,
18 | processedFiles: outputGeneratorContext.processedFiles,
19 | };
20 | };
21 |
22 | export const generateOutput = async (
23 | rootDir: string,
24 | config: CodeTreeConfigMerged,
25 | processedFiles: ProcessedFile[],
26 | allFilePaths: string[],
27 | ): Promise => {
28 | const outputGeneratorContext = await buildOutputGeneratorContext(rootDir, config, allFilePaths, processedFiles);
29 | const renderContext = createRenderContext(outputGeneratorContext);
30 |
31 | let template: string;
32 | switch (config.output.style) {
33 | case 'xml':
34 | template = getXmlTemplate();
35 | break;
36 | case 'markdown':
37 | template = getMarkdownTemplate();
38 | break;
39 | default:
40 | template = getPlainTemplate();
41 | }
42 |
43 | const compiledTemplate = Handlebars.compile(template);
44 | return `${compiledTemplate(renderContext).trim()}\n`;
45 | };
46 |
47 | export const buildOutputGeneratorContext = async (
48 | rootDir: string,
49 | config: CodeTreeConfigMerged,
50 | allFilePaths: string[],
51 | processedFiles: ProcessedFile[],
52 | ): Promise => {
53 | let repositoryInstruction = '';
54 |
55 | if (config.output.instructionFilePath) {
56 | const instructionPath = path.resolve(rootDir, config.output.instructionFilePath);
57 | try {
58 | repositoryInstruction = await fs.readFile(instructionPath, 'utf-8');
59 | } catch {
60 | throw new CodeTreeError(`Instruction file not found at ${instructionPath}`);
61 | }
62 | }
63 |
64 | return {
65 | treeString: generateTreeString(allFilePaths),
66 | processedFiles,
67 | config,
68 | instruction: repositoryInstruction,
69 | };
70 | };
71 |
--------------------------------------------------------------------------------
/src/core/output/outputGeneratorTypes.ts:
--------------------------------------------------------------------------------
1 | import type { CodeTreeConfigMerged } from '../../config/configSchema.js';
2 | import type { ProcessedFile } from '../file/fileTypes.js';
3 |
4 | export interface OutputGeneratorContext {
5 | treeString: string;
6 | processedFiles: ProcessedFile[];
7 | config: CodeTreeConfigMerged;
8 | instruction: string;
9 | }
10 |
--------------------------------------------------------------------------------
/src/core/output/outputStyles/markdownStyle.ts:
--------------------------------------------------------------------------------
1 | import Handlebars from 'handlebars';
2 |
3 | export const getMarkdownTemplate = () => {
4 | return /* md */ `
5 | ## Additional Info
6 | {{#if headerText}}
7 | {{{headerText}}}
8 | {{/if}}
9 | # Files Structure
10 | \`\`\`
11 | {{{treeString}}}
12 | \`\`\`
13 |
14 | # Repository Files
15 |
16 | {{#each processedFiles}}
17 | ## File: {{{this.path}}}
18 | \`\`\`{{{getFileExtension this.path}}}
19 | {{{this.content}}}
20 | \`\`\`
21 |
22 | {{/each}}
23 |
24 | {{#if instruction}}
25 | # Instruction
26 | {{{instruction}}}
27 | {{/if}}
28 | `;
29 | };
30 |
31 | Handlebars.registerHelper('getFileExtension', (filePath) => {
32 | const extension = filePath.split('.').pop()?.toLowerCase();
33 | switch (extension) {
34 | case 'js':
35 | case 'jsx':
36 | return 'javascript';
37 | case 'ts':
38 | case 'tsx':
39 | return 'typescript';
40 | case 'vue':
41 | return 'vue';
42 | case 'py':
43 | return 'python';
44 | case 'rb':
45 | return 'ruby';
46 | case 'java':
47 | return 'java';
48 | case 'c':
49 | case 'cpp':
50 | return 'cpp';
51 | case 'cs':
52 | return 'csharp';
53 | case 'go':
54 | return 'go';
55 | case 'rs':
56 | return 'rust';
57 | case 'php':
58 | return 'php';
59 | case 'swift':
60 | return 'swift';
61 | case 'kt':
62 | return 'kotlin';
63 | case 'scala':
64 | return 'scala';
65 | case 'html':
66 | return 'html';
67 | case 'css':
68 | return 'css';
69 | case 'scss':
70 | case 'sass':
71 | return 'scss';
72 | case 'json':
73 | return 'json';
74 | case 'json5':
75 | return 'json5';
76 | case 'xml':
77 | return 'xml';
78 | case 'yaml':
79 | case 'yml':
80 | return 'yaml';
81 | case 'md':
82 | return 'markdown';
83 | case 'sh':
84 | case 'bash':
85 | return 'bash';
86 | case 'sql':
87 | return 'sql';
88 | case 'dockerfile':
89 | return 'dockerfile';
90 | case 'dart':
91 | return 'dart';
92 | case 'fs':
93 | case 'fsx':
94 | return 'fsharp';
95 | case 'r':
96 | return 'r';
97 | case 'pl':
98 | case 'pm':
99 | return 'perl';
100 | case 'lua':
101 | return 'lua';
102 | case 'groovy':
103 | return 'groovy';
104 | case 'hs':
105 | return 'haskell';
106 | case 'ex':
107 | case 'exs':
108 | return 'elixir';
109 | case 'erl':
110 | return 'erlang';
111 | case 'clj':
112 | case 'cljs':
113 | return 'clojure';
114 | case 'ps1':
115 | return 'powershell';
116 | case 'vb':
117 | return 'vb';
118 | case 'coffee':
119 | return 'coffeescript';
120 | case 'tf':
121 | case 'tfvars':
122 | return 'hcl';
123 | case 'proto':
124 | return 'protobuf';
125 | case 'pug':
126 | return 'pug';
127 | case 'graphql':
128 | case 'gql':
129 | return 'graphql';
130 | case 'toml':
131 | return 'toml';
132 | default:
133 | return '';
134 | }
135 | });
136 |
--------------------------------------------------------------------------------
/src/core/output/outputStyles/plainStyle.ts:
--------------------------------------------------------------------------------
1 | const PLAIN_SEPARATOR = "=".repeat(16);
2 | const PLAIN_LONG_SEPARATOR = "=".repeat(64);
3 |
4 | export const getPlainTemplate = () => {
5 | return `
6 | {{#if headerText}}
7 | {{{headerText}}}
8 | {{/if}}
9 |
10 | ${PLAIN_LONG_SEPARATOR}
11 | Files Structure
12 | ${PLAIN_LONG_SEPARATOR}
13 | {{{treeString}}}
14 |
15 | ${PLAIN_LONG_SEPARATOR}
16 | Repository Files
17 | ${PLAIN_LONG_SEPARATOR}
18 |
19 | {{#each processedFiles}}
20 | ${PLAIN_SEPARATOR}
21 | File: {{{this.path}}}
22 | ${PLAIN_SEPARATOR}
23 | {{{this.content}}}
24 |
25 | {{/each}}
26 |
27 | {{#if instruction}}
28 | ${PLAIN_LONG_SEPARATOR}
29 | Instruction
30 | ${PLAIN_LONG_SEPARATOR}
31 | {{{instruction}}}
32 | {{/if}}
33 |
34 | `;
35 | };
36 |
--------------------------------------------------------------------------------
/src/core/output/outputStyles/xmlStyle.ts:
--------------------------------------------------------------------------------
1 | export const getXmlTemplate = () => {
2 | return /* xml */ `
3 | {{#if headerText}}
4 |
5 | {{{headerText}}}
6 |
7 | {{/if}}
8 |
9 |
10 | {{{treeString}}}
11 |
12 |
13 |
14 | This section contains the contents of the repository's files.
15 |
16 | {{#each processedFiles}}
17 |
18 | {{{this.content}}}
19 |
20 |
21 | {{/each}}
22 |
23 |
24 | {{#if instruction}}
25 |
26 | {{{instruction}}}
27 |
28 | {{/if}}
29 | `;
30 | };
31 |
--------------------------------------------------------------------------------
/src/core/packager.ts:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs/promises';
2 | import path from 'node:path';
3 | import { setTimeout } from 'node:timers/promises';
4 | import clipboard from 'clipboardy';
5 | import pMap from 'p-map';
6 | import pc from 'picocolors';
7 | import type { CodeTreeConfigMerged } from '../config/configSchema.js';
8 | import { logger } from '../shared/logger.js';
9 | import { getProcessConcurrency } from '../shared/processConcurrency.js';
10 | import type { CodeTreeProgressCallback } from '../shared/types.js';
11 | import { collectFiles as defaultCollectFiles } from './file/fileCollect.js';
12 | import { processFiles as defaultProcessFiles } from './file/fileProcess.js';
13 | import { searchFiles as defaultSearchFiles } from './file/fileSearch.js';
14 | import { generateOutput as defaultGenerateOutput } from './output/outputGenerate.js';
15 | import { TokenCounter } from './tokenCount/tokenCount.js';
16 |
17 | export interface PackDependencies {
18 | searchFiles: typeof defaultSearchFiles;
19 | collectFiles: typeof defaultCollectFiles;
20 | processFiles: typeof defaultProcessFiles;
21 | generateOutput: typeof defaultGenerateOutput;
22 | }
23 |
24 | export interface PackResult {
25 | totalFiles: number;
26 | totalCharacters: number;
27 | totalTokens: number;
28 | fileCharCounts: Record;
29 | fileTokenCounts: Record;
30 | }
31 |
32 | export const pack = async (
33 | rootDir: string,
34 | config: CodeTreeConfigMerged,
35 | progressCallback: CodeTreeProgressCallback = () => {},
36 | deps: PackDependencies = {
37 | searchFiles: defaultSearchFiles,
38 | collectFiles: defaultCollectFiles,
39 | processFiles: defaultProcessFiles,
40 | generateOutput: defaultGenerateOutput,
41 | },
42 | ): Promise => {
43 | // Get all file paths considering the config
44 | progressCallback('Searching for files...');
45 | const filePaths = await deps.searchFiles(rootDir, config);
46 |
47 | // Collect raw files
48 | progressCallback('Collecting files...');
49 | const rawFiles = await deps.collectFiles(filePaths, rootDir);
50 |
51 | let safeRawFiles = rawFiles;
52 |
53 | const safeFilePaths = safeRawFiles.map((file) => file.path);
54 | logger.trace('Safe files count:', safeRawFiles.length);
55 |
56 | // Process files (remove comments, etc.)
57 | progressCallback('Processing files...');
58 | const processedFiles = await deps.processFiles(safeRawFiles, config);
59 |
60 | // Generate output
61 | progressCallback('Generating output...');
62 | const output = await deps.generateOutput(rootDir, config, processedFiles, safeFilePaths);
63 |
64 | // Write output to file. path is relative to the cwd
65 | progressCallback('Writing output file...');
66 | const outputPath = path.resolve(config.cwd, config.output.filePath);
67 | logger.trace(`Writing output to: ${outputPath}`);
68 | await fs.writeFile(outputPath, output);
69 |
70 | if (config.output.copyToClipboard) {
71 | // Additionally copy to clipboard if flag is raised
72 | progressCallback('Copying to clipboard...');
73 | logger.trace('Copying output to clipboard');
74 | await clipboard.write(output);
75 | }
76 |
77 | // Setup token counter
78 | const tokenCounter = new TokenCounter();
79 |
80 | // Metrics
81 | progressCallback('Calculating metrics...');
82 | const fileMetrics = await pMap(
83 | processedFiles,
84 | async (file, index) => {
85 | const charCount = file.content.length;
86 | const tokenCount = tokenCounter.countTokens(file.content, file.path);
87 |
88 | progressCallback(`Calculating metrics... (${index + 1}/${processedFiles.length}) ${pc.dim(file.path)}`);
89 |
90 | // Sleep for a short time to prevent blocking the event loop
91 | await setTimeout(1);
92 |
93 | return { path: file.path, charCount, tokenCount };
94 | },
95 | {
96 | concurrency: getProcessConcurrency(),
97 | },
98 | );
99 |
100 | tokenCounter.free();
101 |
102 | const totalFiles = processedFiles.length;
103 | const totalCharacters = fileMetrics.reduce((sum, fileMetric) => sum + fileMetric.charCount, 0);
104 | const totalTokens = fileMetrics.reduce((sum, fileMetric) => sum + fileMetric.tokenCount, 0);
105 |
106 | const fileCharCounts: Record = {};
107 | const fileTokenCounts: Record = {};
108 | for (const file of fileMetrics) {
109 | fileCharCounts[file.path] = file.charCount;
110 | fileTokenCounts[file.path] = file.tokenCount;
111 | }
112 |
113 | return {
114 | totalFiles,
115 | totalCharacters,
116 | totalTokens,
117 | fileCharCounts,
118 | fileTokenCounts,
119 | };
120 | };
121 |
--------------------------------------------------------------------------------
/src/core/tokenCount/tokenCount.ts:
--------------------------------------------------------------------------------
1 | import { type Tiktoken, get_encoding } from 'tiktoken';
2 | import { logger } from '../../shared/logger.js';
3 |
4 | export class TokenCounter {
5 | private encoding: Tiktoken;
6 |
7 | constructor() {
8 | // Setup encoding
9 | this.encoding = get_encoding('cl100k_base');
10 | }
11 |
12 | public countTokens(content: string, filePath?: string): number {
13 | try {
14 | return this.encoding.encode(content).length;
15 | } catch (error) {
16 | let message = '';
17 | if (error instanceof Error) {
18 | message = error.message;
19 | } else {
20 | message = String(error);
21 | }
22 |
23 | if (filePath) {
24 | logger.warn(`Failed to count tokens. path: ${filePath}, error: ${message}`);
25 | } else {
26 | logger.warn(`Failed to count tokens. error: ${message}`);
27 | }
28 |
29 | return 0;
30 | }
31 | }
32 |
33 | public free(): void {
34 | this.encoding.free();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { pack } from './core/packager.js';
2 | export type { CodeTreeConfigFile as CodeTreeConfig } from './config/configSchema.js';
3 | export { run as cli } from './cli/cliRun.js';
4 |
--------------------------------------------------------------------------------
/src/shared/errorHandle.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 | import { logger } from './logger.js';
3 |
4 | export class CodeTreeError extends Error {
5 | constructor(message: string) {
6 | super(message);
7 | this.name = 'CodeTreeError';
8 | }
9 | }
10 |
11 | export class CodeTreeConfigValidationError extends CodeTreeError {
12 | constructor(message: string) {
13 | super(message);
14 | this.name = 'CodeTreeConfigValidationError';
15 | }
16 | }
17 |
18 | export const handleError = (error: unknown): void => {
19 | if (error instanceof CodeTreeError) {
20 | logger.error(`Error: ${error.message}`);
21 | } else if (error instanceof Error) {
22 | logger.error(`Unexpected error: ${error.message}`);
23 | logger.debug('Stack trace:', error.stack);
24 | } else {
25 | logger.error('An unknown error occurred');
26 | }
27 |
28 | logger.info('For more help, please visit: https://github.com/mimalef70/CodeTree/issues');
29 | };
30 |
31 | export const rethrowValidationErrorIfZodError = (error: unknown, message: string): void => {
32 | if (error instanceof z.ZodError) {
33 | const zodErrorText = error.errors.map((err) => `[${err.path.join('.')}] ${err.message}`).join('\n ');
34 | throw new CodeTreeConfigValidationError(
35 | `${message}\n\n ${zodErrorText}\n\n Please check the config file and try again.`,
36 | );
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/src/shared/logger.ts:
--------------------------------------------------------------------------------
1 | import util from 'node:util';
2 | import pc from 'picocolors';
3 |
4 | class Logger {
5 | private isVerbose = false;
6 |
7 | setVerbose(value: boolean) {
8 | this.isVerbose = value;
9 | }
10 |
11 | error(...args: unknown[]) {
12 | console.error(pc.red(this.formatArgs(args)));
13 | }
14 |
15 | warn(...args: unknown[]) {
16 | console.log(pc.yellow(this.formatArgs(args)));
17 | }
18 |
19 | success(...args: unknown[]) {
20 | console.log(pc.green(this.formatArgs(args)));
21 | }
22 |
23 | info(...args: unknown[]) {
24 | console.log(pc.cyan(this.formatArgs(args)));
25 | }
26 |
27 | note(...args: unknown[]) {
28 | console.log(pc.dim(this.formatArgs(args)));
29 | }
30 |
31 | debug(...args: unknown[]) {
32 | if (this.isVerbose) {
33 | console.log(pc.blue(this.formatArgs(args)));
34 | }
35 | }
36 |
37 | trace(...args: unknown[]) {
38 | if (this.isVerbose) {
39 | console.log(pc.gray(this.formatArgs(args)));
40 | }
41 | }
42 |
43 | log(...args: unknown[]) {
44 | console.log(...args);
45 | }
46 |
47 | private formatArgs(args: unknown[]): string {
48 | return args
49 | .map((arg) => (typeof arg === 'object' ? util.inspect(arg, { depth: null, colors: true }) : arg))
50 | .join(' ');
51 | }
52 | }
53 |
54 | export const logger = new Logger();
55 |
--------------------------------------------------------------------------------
/src/shared/processConcurrency.ts:
--------------------------------------------------------------------------------
1 | import os from 'node:os';
2 |
3 | export const getProcessConcurrency = () => {
4 | const cpuCount = typeof os.availableParallelism === 'function' ? os.availableParallelism() : os.cpus().length;
5 |
6 | // Use all available CPUs except one
7 | return Math.max(1, cpuCount - 1);
8 | };
9 |
--------------------------------------------------------------------------------
/src/shared/types.ts:
--------------------------------------------------------------------------------
1 | export type CodeTreeProgressCallback = (message: string) => void;
2 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "rootDir": "./src"
5 | },
6 | "include": ["./src/**/*"]
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "target": "es2016",
7 | "outDir": "./lib",
8 | "rootDir": ".",
9 | "strict": true,
10 | "esModuleInterop": true,
11 | "noImplicitAny": true,
12 | "skipLibCheck": true,
13 | "lib": ["es2022"],
14 | "declaration": true,
15 | "declarationMap": true,
16 | "sourceMap": true,
17 | "forceConsistentCasingInFileNames": true,
18 | "resolveJsonModule": true,
19 | "types": ["node", "picocolors"]
20 | },
21 | "include": ["src/**/*"],
22 | "exclude": []
23 | }
24 |
--------------------------------------------------------------------------------