├── .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 | npm version 6 | npm downloads 7 | license 8 | GitHub Stars 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 | --------------------------------------------------------------------------------