``` ├── .clinerules ├── .codecov.yml ├── .cursorrules ├── .devcontainer/ ├── devcontainer.json ├── .dockerignore ├── .editorconfig (omitted) ├── .github/ ├── CODEOWNERS ├── FUNDING.yml ├── ISSUE_TEMPLATE/ ├── 01_feature_request.yml (100 tokens) ├── 02_bug_report.yml (200 tokens) ├── config.yml ├── actions/ ├── repomix/ ├── action.yml (600 tokens) ├── dependabot.yml ├── instructions/ ├── base.instructions.md (1000 tokens) ├── github-release-note.instructions.md (1300 tokens) ├── website.instructions.md (400 tokens) ├── pull_request_template.md ├── renovate.json5 (100 tokens) ├── workflows/ ├── ci.yml (700 tokens) ├── claude.yml (200 tokens) ├── codeql.yml (300 tokens) ├── docker.yml (400 tokens) ├── homebrew.yml (100 tokens) ├── pack-repository.yml (100 tokens) ├── test-action.yml (300 tokens) ├── .gitignore (100 tokens) ├── .node-version ├── .npmignore (100 tokens) ├── .repomixignore ├── .secretlintrc.json ├── .tool-versions ├── CLAUDE.md (300 tokens) ├── CODE_OF_CONDUCT.md (700 tokens) ├── CONTRIBUTING.md (600 tokens) ├── Dockerfile (100 tokens) ├── LICENSE (omitted) ├── README.md (10k tokens) ├── SECURITY.md ├── bin/ ├── repomix.cjs (300 tokens) ├── biome.json (200 tokens) ├── browser/ ├── .gitignore (100 tokens) ├── README.md (300 tokens) ├── app/ ├── _locales/ ├── de/ ├── detailed-description.txt (300 tokens) ├── messages.json (100 tokens) ├── en/ ├── detailed-description.txt (300 tokens) ├── messages.json (100 tokens) ├── es/ ├── detailed-description.txt (300 tokens) ├── messages.json (100 tokens) ├── fr/ ├── detailed-description.txt (300 tokens) ├── messages.json (100 tokens) ├── ja/ ├── detailed-description.txt (100 tokens) ├── messages.json (100 tokens) ├── ko/ ├── detailed-description.txt (200 tokens) ├── messages.json (100 tokens) ├── pt_BR/ ├── detailed-description.txt (300 tokens) ├── messages.json (100 tokens) ├── zh_CN/ ├── detailed-description.txt (100 tokens) ├── messages.json (100 tokens) ├── zh_TW/ ├── detailed-description.txt (100 tokens) ├── messages.json (100 tokens) ├── images/ ├── icon-128.png ├── icon-16.png ├── icon-19.png ├── icon-32.png ├── icon-38.png ├── icon-48.png ├── icon-64.png ├── icon.svg (200 tokens) ├── manifest.json (300 tokens) ├── scripts/ ├── background.ts (200 tokens) ├── content.ts (800 tokens) ├── styles/ ├── content.css ├── package-lock.json (71.7k tokens) ├── package.json (200 tokens) ├── promo/ ├── Chrome-Webstore-Icon_128x128.png ├── Promo-Image-Marquee_1400x560.png ├── Promo-Image-Small_440x280.png ├── Screenshot_1280x800.png ├── scripts/ ├── generate-icons.ts (400 tokens) ├── tests/ ├── repomix-integration.test.ts (300 tokens) ├── tsconfig.json (100 tokens) ├── vitest.config.ts ├── llms-install.md (2k tokens) ├── package-lock.json (omitted) ├── package.json (700 tokens) ├── repomix-instruction.md (2.1k tokens) ├── repomix.config.json (300 tokens) ├── src/ ├── cli/ ├── actions/ ├── defaultAction.ts (1400 tokens) ├── initAction.ts (1100 tokens) ├── mcpAction.ts ├── migrationAction.ts (2000 tokens) ├── remoteAction.ts (800 tokens) ├── versionAction.ts ├── cliPrint.ts (1000 tokens) ├── cliRun.ts (1600 tokens) ├── cliSpinner.ts (300 tokens) ├── types.ts (200 tokens) ├── config/ ├── configLoad.ts (900 tokens) ├── configSchema.ts (1000 tokens) ├── defaultIgnore.ts (600 tokens) ├── globalDirectory.ts (100 tokens) ├── core/ ├── file/ ├── fileCollect.ts (400 tokens) ├── fileManipulate.ts (1500 tokens) ├── filePathSort.ts (200 tokens) ├── fileProcess.ts (400 tokens) ├── fileProcessContent.ts (500 tokens) ├── fileRead.ts (300 tokens) ├── fileSearch.ts (1800 tokens) ├── fileTreeGenerate.ts (400 tokens) ├── fileTypes.ts ├── packageJsonParse.ts (200 tokens) ├── permissionCheck.ts (600 tokens) ├── workers/ ├── fileCollectWorker.ts (100 tokens) ├── fileProcessWorker.ts (100 tokens) ├── git/ ├── gitCommand.ts (1000 tokens) ├── gitDiffHandle.ts (500 tokens) ├── gitRemoteHandle.ts (200 tokens) ├── gitRemoteParse.ts (400 tokens) ├── gitRepositoryHandle.ts (300 tokens) ├── metrics/ ├── TokenCounter.ts (200 tokens) ├── calculateAllFileMetrics.ts (400 tokens) ├── calculateMetrics.ts (500 tokens) ├── calculateOutputMetrics.ts (400 tokens) ├── workers/ ├── fileMetricsWorker.ts (300 tokens) ├── outputMetricsWorker.ts (200 tokens) ├── types.ts ├── output/ ├── outputGenerate.ts (1600 tokens) ├── outputGeneratorTypes.ts (200 tokens) ├── outputSort.ts (400 tokens) ├── outputStyleDecorate.ts (1300 tokens) ├── outputStyles/ ├── markdownStyle.ts (600 tokens) ├── plainStyle.ts (300 tokens) ├── xmlStyle.ts (200 tokens) ├── packager.ts (800 tokens) ├── packager/ ├── copyToClipboardIfEnabled.ts (300 tokens) ├── writeOutputToDisk.ts (200 tokens) ├── security/ ├── filterOutUntrustedFiles.ts (100 tokens) ├── securityCheck.ts (600 tokens) ├── validateFileSafety.ts (400 tokens) ├── workers/ ├── securityCheckWorker.ts (400 tokens) ├── treeSitter/ ├── ext2Lang.ts (100 tokens) ├── lang2Query.ts (200 tokens) ├── languageParser.ts (600 tokens) ├── loadLanguage.ts (200 tokens) ├── parseFile.ts (800 tokens) ├── parseStrategies/ ├── CssParseStrategy.ts (300 tokens) ├── DefaultParseStrategy.ts (200 tokens) ├── GoParseStrategy.ts (1300 tokens) ├── ParseStrategy.ts (300 tokens) ├── PythonParseStrategy.ts (900 tokens) ├── TypeScriptParseStrategy.ts (1000 tokens) ├── VueParseStrategy.ts (200 tokens) ├── queries/ ├── README.md (500 tokens) ├── queryC.ts (100 tokens) ├── queryCSharp.ts (200 tokens) ├── queryCpp.ts (200 tokens) ├── queryCss.ts ├── queryGo.ts (300 tokens) ├── queryJava.ts (200 tokens) ├── queryJavascript.ts (500 tokens) ├── queryPhp.ts (200 tokens) ├── queryPython.ts (200 tokens) ├── queryRuby.ts (300 tokens) ├── queryRust.ts (400 tokens) ├── querySolidity.ts (100 tokens) ├── querySwift.ts (200 tokens) ├── queryTypescript.ts (400 tokens) ├── queryVue.ts ├── index.ts (500 tokens) ├── mcp/ ├── mcpServer.ts (500 tokens) ├── prompts/ ├── packRemoteRepositoryPrompts.ts (400 tokens) ├── tools/ ├── fileSystemReadDirectoryTool.ts (500 tokens) ├── fileSystemReadFileTool.ts (600 tokens) ├── grepRepomixOutputTool.ts (1400 tokens) ├── mcpToolRuntime.ts (1200 tokens) ├── packCodebaseTool.ts (700 tokens) ├── packRemoteRepositoryTool.ts (700 tokens) ├── readRepomixOutputTool.ts (700 tokens) ├── shared/ ├── constants.ts ├── errorHandle.ts (400 tokens) ├── logger.ts (500 tokens) ├── patternUtils.ts (200 tokens) ├── processConcurrency.ts (200 tokens) ├── types.ts ├── types/ ├── git-url-parse.d.ts (omitted) ├── tests/ ├── cli/ ├── actions/ ├── defaultAction.test.ts (3k tokens) ├── diffsFlag.test.ts (200 tokens) ├── initAction.test.ts (1200 tokens) ├── mcpAction.test.ts (200 tokens) ├── migrationAction.test.ts (1600 tokens) ├── remoteAction.test.ts (600 tokens) ├── versionAction.test.ts (200 tokens) ├── cliPrint.test.ts (1200 tokens) ├── cliRun.test.ts (2.4k tokens) ├── config/ ├── configLoad.test.ts (1300 tokens) ├── configSchema.test.ts (1300 tokens) ├── globalDirectory.test.ts (600 tokens) ├── core/ ├── file/ ├── fileCollect.test.ts (1300 tokens) ├── fileManipulate.test.ts (3.4k tokens) ├── filePathSort.test.ts (500 tokens) ├── fileProcess.test.ts (1100 tokens) ├── fileProcessContent.test.ts (1100 tokens) ├── fileSearch.test.ts (3k tokens) ├── packageJsonParse.test.ts (300 tokens) ├── permissionCheck.test.ts (1600 tokens) ├── git/ ├── gitCommand.test.ts (2.3k tokens) ├── gitDiffHandle.test.ts (1300 tokens) ├── gitRemoteHandle.test.ts (700 tokens) ├── gitRemoteParse.test.ts (1100 tokens) ├── gitRepositoryHandle.test.ts (800 tokens) ├── metrics/ ├── TokenCounter.test.ts (900 tokens) ├── calculateAllFileMetrics.test.ts (300 tokens) ├── calculateMetrics.test.ts (500 tokens) ├── calculateOutputMetrics.test.ts (1000 tokens) ├── diffTokenCount.test.ts (1500 tokens) ├── output/ ├── diffsInOutput.test.ts (1400 tokens) ├── outputGenerate.test.ts (1400 tokens) ├── outputGenerateDiffs.test.ts (1500 tokens) ├── outputSort.test.ts (800 tokens) ├── outputStyleDecorate.test.ts (1000 tokens) ├── outputStyles/ ├── markdownStyle.test.ts (1900 tokens) ├── plainStyle.test.ts (300 tokens) ├── xmlStyle.test.ts (300 tokens) ├── packager.test.ts (800 tokens) ├── packager/ ├── copyToClipboardIfEnabled.test.ts (900 tokens) ├── diffsFunctionality.test.ts (1000 tokens) ├── writeOutputToDisk.test.ts (300 tokens) ├── security/ ├── filterOutUntrustedFiles.test.ts (300 tokens) ├── securityCheck.test.ts (1600 tokens) ├── validateFileSafety.test.ts (400 tokens) ├── workers/ ├── securityCheckWorker.test.ts (500 tokens) ├── treeSitter/ ├── LanguageParser.test.ts (200 tokens) ├── loadLanguage.test.ts (400 tokens) ├── parseFile.c.test.ts (1000 tokens) ├── parseFile.comments.test.ts (1300 tokens) ├── parseFile.cpp.test.ts (600 tokens) ├── parseFile.csharp.test.ts (300 tokens) ├── parseFile.css.test.ts (1100 tokens) ├── parseFile.go.test.ts (900 tokens) ├── parseFile.java.test.ts (300 tokens) ├── parseFile.javascript.test.ts (400 tokens) ├── parseFile.php.test.ts (400 tokens) ├── parseFile.python.test.ts (700 tokens) ├── parseFile.ruby.test.ts (1100 tokens) ├── parseFile.rust.test.ts (400 tokens) ├── parseFile.solidity.test.ts (500 tokens) ├── parseFile.swift.test.ts (1500 tokens) ├── parseFile.test.ts (300 tokens) ├── parseFile.typescript.test.ts (3.3k tokens) ├── parseFile.vue.test.ts (800 tokens) ├── integration-tests/ ├── fixtures/ ├── packager/ ├── inputs/ ├── simple-project/ ├── .repomixignore ├── README.md ├── build/ ├── test.js ├── package.json (100 tokens) ├── repomix.config.json (100 tokens) ├── resources/ ├── .repomixignore ├── data.txt ├── ignored-data.txt ├── src/ ├── build/ ├── test.js ├── index.js ├── utils.js ├── outputs/ ├── simple-project-output.md (600 tokens) ├── simple-project-output.txt (800 tokens) ├── simple-project-output.xml (700 tokens) ├── packager.test.ts (1700 tokens) ├── mcp/ ├── mcpServer.test.ts (1300 tokens) ├── prompts/ ├── packRemoteRepositoryPrompts.test.ts (600 tokens) ├── tools/ ├── fileSystemReadDirectoryTool.test.ts (500 tokens) ├── fileSystemReadFileTool.test.ts (500 tokens) ├── grepRepomixOutputTool.test.ts (5.5k tokens) ├── mcpToolRuntime.test.ts (1800 tokens) ├── packCodebaseTool.test.ts (1200 tokens) ├── readRepomixOutputTool.test.ts (1400 tokens) ├── shared/ ├── logger.test.ts (700 tokens) ├── patternUtils.test.ts (400 tokens) ├── processConcurrency.test.ts (500 tokens) ├── testing/ ├── testUtils.ts (300 tokens) ├── tsconfig.build.json ├── tsconfig.json (100 tokens) ├── typos.toml (100 tokens) ├── vitest.config.ts (100 tokens) ├── website/ ├── README.md (100 tokens) ├── client/ ├── .gitignore ├── .tool-versions ├── .vitepress/ ├── config.ts (300 tokens) ├── config/ ├── configDe.ts (600 tokens) ├── configEnUs.ts (400 tokens) ├── configEs.ts (500 tokens) ├── configFr.ts (600 tokens) ├── configHi.ts (500 tokens) ├── configId.ts (500 tokens) ├── configJa.ts (500 tokens) ├── configKo.ts (500 tokens) ├── configPtBr.ts (600 tokens) ├── configShard.ts (1100 tokens) ├── configVi.ts (500 tokens) ├── configZhCn.ts (500 tokens) ├── configZhTw.ts (500 tokens) ├── theme/ ├── component.d.ts (omitted) ├── custom.css (200 tokens) ├── index.ts (100 tokens) ├── style.css (900 tokens) ├── Dockerfile ├── components/ ├── Home.vue ├── Home/ ├── Hero.vue (200 tokens) ├── PackButton.vue (300 tokens) ├── TryIt.vue (1700 tokens) ├── TryItFileUpload.vue (900 tokens) ├── TryItFolderUpload.vue (1700 tokens) ├── TryItPackOptions.vue (2.3k tokens) ├── TryItResult.vue (500 tokens) ├── TryItResultContent.vue (2k tokens) ├── TryItResultErrorContent.vue (600 tokens) ├── TryItUrlInput.vue (900 tokens) ├── HomeBadges.vue (300 tokens) ├── api/ ├── client.ts (300 tokens) ├── utils/ ├── analytics.ts (800 tokens) ├── requestHandlers.ts (400 tokens) ├── resultViewer.ts (700 tokens) ├── validation.ts (100 tokens) ├── package-lock.json (53.8k tokens) ├── package.json (100 tokens) ├── scripts/ ├── generateSchema.ts (400 tokens) ├── src/ ├── de/ ├── guide/ ├── code-compress.md (500 tokens) ├── command-line-options.md (700 tokens) ├── comment-removal.md (300 tokens) ├── configuration.md (2.3k tokens) ├── custom-instructions.md (300 tokens) ├── development/ ├── index.md (800 tokens) ├── using-repomix-as-a-library.md (500 tokens) ├── github-actions.md (800 tokens) ├── index.md (500 tokens) ├── installation.md (400 tokens) ├── mcp-server.md (2.4k tokens) ├── output.md (300 tokens) ├── prompt-examples.md (700 tokens) ├── remote-repository-processing.md (300 tokens) ├── security.md (300 tokens) ├── tips/ ├── best-practices.md (700 tokens) ├── usage.md (300 tokens) ├── index.md (1100 tokens) ├── en/ ├── guide/ ├── code-compress.md (500 tokens) ├── command-line-options.md (700 tokens) ├── comment-removal.md (200 tokens) ├── configuration.md (2.2k tokens) ├── custom-instructions.md (300 tokens) ├── development/ ├── index.md (800 tokens) ├── using-repomix-as-a-library.md (500 tokens) ├── github-actions.md (700 tokens) ├── index.md (400 tokens) ├── installation.md (400 tokens) ├── mcp-server.md (2.2k tokens) ├── output.md (400 tokens) ├── prompt-examples.md (600 tokens) ├── remote-repository-processing.md (300 tokens) ├── security.md (300 tokens) ├── tips/ ├── best-practices.md (700 tokens) ├── usage.md (300 tokens) ├── index.md (1000 tokens) ├── es/ ├── guide/ ├── code-compress.md (500 tokens) ├── command-line-options.md (800 tokens) ├── comment-removal.md (300 tokens) ├── configuration.md (2.4k tokens) ├── custom-instructions.md (300 tokens) ├── development/ ├── index.md (800 tokens) ├── using-repomix-as-a-library.md (500 tokens) ├── github-actions.md (700 tokens) ├── index.md (500 tokens) ├── installation.md (400 tokens) ├── mcp-server.md (2.4k tokens) ├── output.md (400 tokens) ├── prompt-examples.md (700 tokens) ├── remote-repository-processing.md (300 tokens) ├── security.md (300 tokens) ├── tips/ ├── best-practices.md (800 tokens) ├── usage.md (300 tokens) ├── index.md (1100 tokens) ├── fr/ ├── guide/ ├── code-compress.md (600 tokens) ├── command-line-options.md (800 tokens) ├── comment-removal.md (300 tokens) ├── configuration.md (2.4k tokens) ├── custom-instructions.md (300 tokens) ├── development/ ├── index.md (800 tokens) ├── using-repomix-as-a-library.md (500 tokens) ├── github-actions.md (700 tokens) ├── index.md (500 tokens) ├── installation.md (400 tokens) ├── mcp-server.md (2.5k tokens) ├── output.md (400 tokens) ├── prompt-examples.md (700 tokens) ├── remote-repository-processing.md (300 tokens) ├── security.md (300 tokens) ├── tips/ ├── best-practices.md (800 tokens) ├── usage.md (300 tokens) ├── index.md (1100 tokens) ├── hi/ ├── guide/ ├── code-compress.md (700 tokens) ├── command-line-options.md (800 tokens) ├── comment-removal.md (800 tokens) ├── configuration.md (800 tokens) ├── custom-instructions.md (800 tokens) ├── development/ ├── index.md (400 tokens) ├── using-repomix-as-a-library.md (900 tokens) ├── github-actions.md (1000 tokens) ├── index.md (500 tokens) ├── installation.md (300 tokens) ├── mcp-server.md (600 tokens) ├── output.md (800 tokens) ├── prompt-examples.md (800 tokens) ├── remote-repository-processing.md (700 tokens) ├── security.md (500 tokens) ├── tips/ ├── best-practices.md (1000 tokens) ├── usage.md (400 tokens) ├── index.md (1100 tokens) ├── id/ ├── guide/ ├── code-compress.md (500 tokens) ├── command-line-options.md (500 tokens) ├── comment-removal.md (300 tokens) ├── configuration.md (600 tokens) ├── custom-instructions.md (300 tokens) ├── development/ ├── index.md (700 tokens) ├── using-repomix-as-a-library.md (1100 tokens) ├── github-actions.md (600 tokens) ├── index.md (500 tokens) ├── installation.md (300 tokens) ├── mcp-server.md (700 tokens) ├── output.md (400 tokens) ├── prompt-examples.md (600 tokens) ├── remote-repository-processing.md (400 tokens) ├── security.md (500 tokens) ├── tips/ ├── best-practices.md (800 tokens) ├── usage.md (500 tokens) ├── index.md (1100 tokens) ├── ja/ ├── guide/ ├── code-compress.md (300 tokens) ├── command-line-options.md (500 tokens) ├── comment-removal.md (200 tokens) ├── configuration.md (1600 tokens) ├── custom-instructions.md (200 tokens) ├── development/ ├── index.md (600 tokens) ├── using-repomix-as-a-library.md (400 tokens) ├── github-actions.md (600 tokens) ├── index.md (300 tokens) ├── installation.md (300 tokens) ├── mcp-server.md (1400 tokens) ├── output.md (300 tokens) ├── prompt-examples.md (300 tokens) ├── remote-repository-processing.md (200 tokens) ├── security.md (200 tokens) ├── tips/ ├── best-practices.md (300 tokens) ├── usage.md (200 tokens) ├── index.md (700 tokens) ├── ko/ ├── guide/ ├── code-compress.md (300 tokens) ├── command-line-options.md (500 tokens) ├── comment-removal.md (200 tokens) ├── configuration.md (1700 tokens) ├── custom-instructions.md (300 tokens) ├── development/ ├── index.md (500 tokens) ├── using-repomix-as-a-library.md (400 tokens) ├── github-actions.md (600 tokens) ├── index.md (300 tokens) ├── installation.md (300 tokens) ├── mcp-server.md (1400 tokens) ├── output.md (300 tokens) ├── prompt-examples.md (300 tokens) ├── remote-repository-processing.md (200 tokens) ├── security.md (200 tokens) ├── tips/ ├── best-practices.md (300 tokens) ├── usage.md (200 tokens) ├── index.md (700 tokens) ├── pt-br/ ├── guide/ ├── code-compress.md (500 tokens) ├── command-line-options.md (700 tokens) ├── comment-removal.md (200 tokens) ├── configuration.md (2.3k tokens) ├── custom-instructions.md (300 tokens) ├── development/ ├── index.md (800 tokens) ├── using-repomix-as-a-library.md (500 tokens) ├── github-actions.md (700 tokens) ├── index.md (500 tokens) ├── installation.md (400 tokens) ├── mcp-server.md (2.4k tokens) ├── output.md (400 tokens) ├── prompt-examples.md (700 tokens) ├── remote-repository-processing.md (300 tokens) ├── security.md (300 tokens) ├── tips/ ├── best-practices.md (600 tokens) ├── usage.md (300 tokens) ├── index.md (1100 tokens) ├── public/ ├── images/ ├── docs/ ├── browser-extension.png ├── repomix-file-usage-1.png ├── repomix-file-usage-2.png ├── og-image-large.png ├── pwa/ ├── repomix-192x192.png ├── repomix-512x512.png ├── repomix-logo.png ├── repomix-logo.svg (200 tokens) ├── repomix-title.png ├── sponsors/ ├── warp/ ├── Terminal-Image.png ├── schemas/ ├── 0.3.5/ ├── schema.json (600 tokens) ├── latest/ ├── schema.json (600 tokens) ├── vi/ ├── guide/ ├── code-compress.md (900 tokens) ├── command-line-options.md (500 tokens) ├── comment-removal.md (700 tokens) ├── configuration.md (800 tokens) ├── custom-instructions.md (700 tokens) ├── development/ ├── index.md (900 tokens) ├── using-repomix-as-a-library.md (1400 tokens) ├── github-actions.md (1000 tokens) ├── index.md (500 tokens) ├── installation.md (400 tokens) ├── mcp-server.md (800 tokens) ├── output.md (900 tokens) ├── prompt-examples.md (900 tokens) ├── remote-repository-processing.md (700 tokens) ├── security.md (700 tokens) ├── tips/ ├── best-practices.md (1100 tokens) ├── usage.md (800 tokens) ├── index.md (1000 tokens) ├── zh-cn/ ├── guide/ ├── code-compress.md (300 tokens) ├── command-line-options.md (400 tokens) ├── comment-removal.md (200 tokens) ├── configuration.md (1600 tokens) ├── custom-instructions.md (200 tokens) ├── development/ ├── index.md (400 tokens) ├── using-repomix-as-a-library.md (400 tokens) ├── github-actions.md (600 tokens) ├── index.md (200 tokens) ├── installation.md (300 tokens) ├── mcp-server.md (1200 tokens) ├── output.md (300 tokens) ├── prompt-examples.md (200 tokens) ├── remote-repository-processing.md (200 tokens) ├── security.md (200 tokens) ├── tips/ ├── best-practices.md (200 tokens) ├── usage.md (200 tokens) ├── index.md (700 tokens) ├── zh-tw/ ├── guide/ ├── code-compress.md (300 tokens) ├── command-line-options.md (400 tokens) ├── comment-removal.md (200 tokens) ├── configuration.md (1600 tokens) ├── custom-instructions.md (200 tokens) ├── development/ ├── index.md (400 tokens) ├── using-repomix-as-a-library.md (400 tokens) ├── github-actions.md (600 tokens) ├── index.md (300 tokens) ├── installation.md (300 tokens) ├── mcp-server.md (1200 tokens) ├── output.md (200 tokens) ├── prompt-examples.md (200 tokens) ├── remote-repository-processing.md (200 tokens) ├── security.md (200 tokens) ├── tips/ ├── best-practices.md (200 tokens) ├── usage.md (200 tokens) ├── index.md (700 tokens) ├── tsconfig.json (100 tokens) ├── tsconfig.node.json (100 tokens) ├── compose.yml (200 tokens) ├── server/ ├── .dockerignore (100 tokens) ├── .gcloudignore ├── .gitignore ├── Dockerfile (200 tokens) ├── cloudbuild.yaml (400 tokens) ├── package-lock.json (35.2k tokens) ├── package.json (100 tokens) ├── src/ ├── constants.ts (100 tokens) ├── index.ts (900 tokens) ├── processZipFile.ts (1700 tokens) ├── remoteRepo.ts (800 tokens) ├── schemas/ ├── request.ts (400 tokens) ├── types.ts (100 tokens) ├── utils/ ├── cache.ts (400 tokens) ├── errorHandler.ts (300 tokens) ├── fileUtils.ts (700 tokens) ├── logger.ts (900 tokens) ├── network.ts (100 tokens) ├── processConcurrency.ts (100 tokens) ├── rateLimit.ts (200 tokens) ├── sharedInstance.ts (100 tokens) ├── time.ts (200 tokens) ├── validation.ts (200 tokens) ├── tsconfig.json (100 tokens) ``` ## /.clinerules ```clinerules path="/.clinerules" # Important - Follow all AI instructions in the `.github/instructions/` directory - The base instructions are located in `.github/instructions/base.instructions.md` ``` ## /.codecov.yml ```yml path="/.codecov.yml" coverage: status: patch: default: target: 80% informational: true project: default: target: 75% threshold: 1% ``` ## /.cursorrules ```cursorrules path="/.cursorrules" # Important - Follow all AI instructions in the `.github/instructions/` directory - The base instructions are located in `.github/instructions/base.instructions.md` ``` ## /.devcontainer/devcontainer.json ```json path="/.devcontainer/devcontainer.json" { "name": "Repomix", "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bullseye", "runArgs": ["--name", "repomix-devcontainer"], "postCreateCommand": "npm install" } ``` ## /.dockerignore ```dockerignore path="/.dockerignore" node_modules npm-debug.log .git .gitignore .env .env.* *.md .vscode coverage .nyc_output dist build ``` ## /.github/CODEOWNERS ```github/CODEOWNERS path="/.github/CODEOWNERS" # Default owner for everything in the repo * @yamadashy ``` ## /.github/FUNDING.yml ```yml path="/.github/FUNDING.yml" github: yamadashy ``` ## /.github/ISSUE_TEMPLATE/01_feature_request.yml ```yml path="/.github/ISSUE_TEMPLATE/01_feature_request.yml" name: 🚀 Feature Request description: Suggest an idea or improvement for Repomix labels: - enhancement body: - type: markdown attributes: value: | Thank you for helping improve Repomix! We appreciate your feedback and will review your request as soon as possible. - type: textarea id: description attributes: label: Description description: "A clear and concise description of the feature you’d like to see." placeholder: | e.g. Add support for a `.repomixignore` file to exclude certain paths when packing. validations: required: true ``` ## /.github/ISSUE_TEMPLATE/02_bug_report.yml ```yml path="/.github/ISSUE_TEMPLATE/02_bug_report.yml" name: 🐛 Bug Report description: Report an unexpected behavior or error in Repomix labels: [triage] body: - type: markdown attributes: value: | Thank you for reporting an issue! We appreciate your help in making Repomix better. If you need real-time support, feel free to join our [Discord server](https://discord.gg/wNYzTwZFku). Please provide as much detail as possible. - type: textarea id: description attributes: label: Description description: "Please provide a concise description of what happened." placeholder: | e.g. Running `repomix --version` prints an error instead of the version. validations: required: true - type: dropdown id: usage_context attributes: label: Usage Context description: "Where did you encounter this issue?" options: - Repomix CLI - repomix.com - type: input id: repomix_version attributes: label: Repomix Version description: "Output of `repomix --version`" placeholder: "e.g. v0.3.5" - type: input id: node_version attributes: label: Node.js Version description: "Output of `node --version`" placeholder: "e.g. v18.16.0" ``` ## /.github/ISSUE_TEMPLATE/config.yml ```yml path="/.github/ISSUE_TEMPLATE/config.yml" blank_issues_enabled: true contact_links: - name: "💬 Discord Community" about: "Join our Discord server for support and discussion" url: "https://discord.gg/wNYzTwZFku" ``` ## /.github/actions/repomix/action.yml ```yml path="/.github/actions/repomix/action.yml" name: "Repomix Action" description: "Pack repository contents into a single file that is easy for LLMs to process" author: "Kazuki Yamada <koukun0120@gmail.com>" branding: icon: archive color: purple inputs: directories: description: "Space-separated list of directories to process (defaults to '.')" required: false default: "." include: description: "Comma-separated glob patterns to include" required: false default: "" ignore: description: "Comma-separated glob patterns to ignore" required: false default: "" output: description: "Relative path to write packed file" required: false default: "repomix-output.xml" compress: description: "Set to 'false' to disable smart compression" required: false default: "true" style: description: "Output style (xml, markdown, plain)" required: false default: "xml" additional-args: description: "Any extra raw arguments to pass directly to the repomix CLI" required: false default: "" repomix-version: description: "Version (or tag) of the npm package to install – defaults to latest" required: false default: "latest" runs: using: "composite" steps: - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "24" cache: "npm" - name: Install Repomix shell: bash run: | npm install --global repomix@${{ inputs.repomix-version }} - name: Run Repomix id: build shell: bash run: | set -e # Using an array for safer command execution # Safely split directories input into an array, handling spaces correctly IFS=' ' read -r -a ARGS <<< "${{ inputs.directories }}" if [ -n "${{ inputs.include }}" ]; then ARGS+=(--include "${{ inputs.include }}") fi if [ -n "${{ inputs.ignore }}" ]; then ARGS+=(--ignore "${{ inputs.ignore }}") fi if [ "${{ inputs.compress }}" = "false" ]; then ARGS+=(--no-compress) else ARGS+=(--compress) fi if [ -n "${{ inputs.style }}" ]; then ARGS+=(--style "${{ inputs.style }}") fi ARGS+=(--output "${{ inputs.output }}") # Only add additional args if not empty if [ -n "${{ inputs.additional-args }}" ]; then # Use safer parsing for additional arguments IFS=' ' read -r -a ADDITIONAL_ARGS <<< "${{ inputs.additional-args }}" ARGS+=("${ADDITIONAL_ARGS[@]}") fi echo "Running: repomix ${ARGS[*]}" repomix "${ARGS[@]}" echo "output_file=${{ inputs.output }}" >> "$GITHUB_OUTPUT" outputs: output_file: description: "Path to the file generated by Repomix" value: ${{ steps.build.outputs.output_file }} ``` ## /.github/dependabot.yml ```yml path="/.github/dependabot.yml" version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" ``` ## /.github/instructions/base.instructions.md --- applyTo: '**' --- # Repomix Project Structure and Overview This document provides a structural overview of the Repomix project, designed to aid AI code assistants (like Copilot) in understanding the codebase. Please refer to `README.md` for a complete and up-to-date project overview, and `CONTRIBUTING.md` for implementation guidelines and contribution procedures. ## Project Overview Repomix is a tool that packs the contents of a software repository into a single file, making it easier for AI systems to analyze and process the codebase. It supports various output formats (XML, Markdown, or plain text), ignores files based on configurable patterns, and performs security checks to exclude potentially sensitive information. ## Directory Structure The project is organized into the following directories: ``` repomix/ ├── src/ # Main source code │ ├── cli/ # Command-line interface logic (argument parsing, command handling, output) │ ├── config/ # Configuration loading, schema, and defaults │ ├── core/ # Core logic of Repomix │ │ ├── file/ # File handling (reading, processing, searching, tree structure generation, git commands) │ │ ├── metrics/ # Calculating code metrics (character count, token count) │ │ ├── output/ # Output generation (different styles, headers, etc.) │ │ ├── packager/ # Orchestrates file collection, processing, output, and clipboard operations. │ │ ├── security/ # Security checks to exclude sensitive files │ │ ├── mcp/ # MCP server integration (packaging codebases for AI analysis) │ │ ├── tokenCount/ # Token counting using Tiktoken │ │ └── treeSitter/ # Code parsing using Tree-sitter and language-specific queries │ └── shared/ # Shared utilities and types (error handling, logging, helper functions) ├── tests/ # Unit and integration tests (organized mirroring src/) │ ├── cli/ │ ├── config/ │ ├── core/ │ ├── integration-tests/ │ ├── shared/ │ └── testing/ └── website/ # Documentation website (VitePress) ├── client/ # Client-side code (Vue.js components, styles, configuration) │ ├── .vitepress/ # VitePress configuration and theme │ │ ├── config/ # Site configuration files (navigation, sidebar, etc.) │ │ └── theme/ # Custom theme and styles │ ├── components/ # Vue.js components for the website │ └── src/ # Markdown files for multilingual documentation └── server/ # Server-side API (for remote repository processing) └── src/ # Server source code (API endpoints, request handling) ``` # Coding Guidelines - Follow the Airbnb JavaScript Style Guide. - Split files into smaller, focused units when appropriate: - Aim to keep code files under 250 lines. If a file exceeds 250 lines, split it into multiple files based on functionality. - Add comments to clarify non-obvious logic. **Ensure all comments are written in English.** - Provide corresponding unit tests for all new features. - After implementation, verify changes by running: ```bash npm run lint # Ensure code style compliance npm run test # Verify all tests pass ``` ## Commit Messages - Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification for all commit messages - Always include a scope in your commit messages - Format: `type(scope): Description` ``` # Examples: feat(cli): Add new --no-progress flag fix(security): Handle special characters in file paths docs(website): Update installation guide style(website): Update GitHub sponsor button color refactor(core): Split packager into smaller modules test(cli): Add tests for new CLI options ``` - Types: feat, fix, docs, style, refactor, test, chore, etc. - Scope should indicate the affected part of the codebase (cli, core, website, security, etc.) - Description should be clear and concise in present tense - Description must start with a capital letter ## Pull Request Guidelines - All pull requests must follow the template: ```md <!-- Please include a summary of the changes --> ## Checklist - [ ] Run `npm run test` - [ ] Run `npm run lint` ``` - Include a clear summary of the changes at the top of the pull request description - Reference any related issues using the format `#issue-number` ## Dependencies and Testing - Inject dependencies through a deps object parameter for testability - Example: ```typescript export const functionName = async ( param1: Type1, param2: Type2, deps = { defaultFunction1, defaultFunction2, } ) => { // Use deps.defaultFunction1() instead of direct call }; ``` - Mock dependencies by passing test doubles through deps object - Use vi.mock() only when dependency injection is not feasible ## Generate Comprehensive Output - Include all content without abbreviation, unless specified otherwise - Optimize for handling large codebases while maintaining output quality ## /.github/instructions/github-release-note.instructions.md --- applyTo: '**' --- # GitHub Release Note Guidelines When writing release notes, please follow these guidelines: - When referencing issues or PRs, use the gh command to verify the content: ```bash gh issue view <issue-number> # For checking issue content gh pr view <pr-number> # For checking PR content ``` This helps ensure accuracy in release note descriptions. Here are some examples of release notes that follow the guidelines: v0.2.25 ````md This release brings significant improvements to output formatting and introduces flexible remote repository handling capabilities along with enhanced logging features. # Improvements ⚡ ## Remote Repository Enhancement (#335) - Added branch/tag parsing directly from repository URLs: ```bash repomix --remote https://github.com/yamadashy/repomix/tree/0.1.x ``` Functions identically to: ```bash repomix --remote https://github.com/yamadashy/repomix --remote-branch 0.1.x ``` Special thanks to @huy-trn for implementing this user-friendly feature! ## Enhanced Output Formatting (#328, #329, #330) - Added "End of Codebase" marker for better clarity in output - Improved output header accuracy: - Better representation of codebase scope - Clear indication when using `--include` or `--ignore` options Special thanks to @gitkenan for adding the "End of Codebase" marker and reporting the header issue! ## Path Pattern Support (#337) - Added support for special characters in paths: - Handles parentheses in include patterns (e.g., `src/(categories)/**/*`) - Improved escaping for `[]` and `{}` - Essential for Next.js route groups and similar frameworks Thank you @matheuscoelhomalta for improving path pattern support! # How to Update ```bash npm update -g repomix ``` --- As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support. ```` v0.2.24 ````md This release significantly enhances configuration flexibility with comprehensive CLI flag support and expands default ignore patterns for better project scaffolding. # What's New 🚀 ## CLI Flags Revolution (#324) - New command-line configuration now available. ``` - `--no-gitignore`: Disable .gitignore file usage - `--no-default-patterns`: Disable default patterns - `--header-text <text>`: Custom text to include in the file header - `--instruction-file-path <path>`: Path to a file containing detailed custom instructions - `--include-empty-directories`: Include empty directories in the output ``` Special recognition to @massdo for driving ecosystem growth. # Improvements ⚡ ## Enhanced Ignore Patterns (#318, #322) - Expanded default ignores for Rust projects: - `target/`, `Cargo.lock`, build artifacts - PHP, Ruby, Go, Elixir, Haskell: package manager lock files To @boralg for helping curate Rust-specific patterns! # How to Update ```bash npm update -g repomix ``` --- As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support. ```` v0.2.23 ````md This release adds significant performance improvements for large repositories, making Repomix faster and more efficient when needed. # Improvements ⚡ ## Parallel Processing Enhancement (#309) - Implemented worker threads using [Piscina](https://github.com/piscinajs/piscina) for parallel processing ### Benchmark Results - `yamadashy.repomix`: No significant change - Before: 868.73 millis - After: 671.26 millis - `facebook/react`: 29x faster - Before: 123.31 secs - After: 4.19 secs - `vercel/next.js`: 58x faster - Before: 17.85 mins - After: 17.27 secs Note: While Repomix is not primarily designed for processing large repositories, and speed is not a primary goal, faster processing can provide a better user experience when working with larger codebases. # How to Update ```bash npm update -g repomix ``` --- As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support. ```` v0.2.22 ````md This release introduces significant improvements to large file handling and expands the Repomix ecosystem with new tools and community channels. # Improvements ⚡ ## Improved Large File Handling (#302) - Added a file size limit check (50MB) to prevent memory issues - Graceful error handling for large files with clear user guidance: Special thanks to @slavashvets for their continued contributions! # Ecosystem Growth 🤝 ## New VS Code Extension (#300) A community-created VS Code extension "Repomix Runner" is now available: - Run Repomix directly from VS Code - Extension by @massdo: [View on VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner) Thank you @massdo for bringing Repomix to VS Code and expanding our tooling ecosystem! ## Official Social Media - Launched official Repomix X (Twitter) account: [@repomix_ai](https://x.com/repomix_ai) - Follow for updates, tips, and community highlights # How to Update ```bash npm update -g repomix ``` --- Join our growing community on [Discord](https://discord.gg/BF8GxZHE2C) and follow us on [X](https://x.com/repomix_ai) for updates! ```` v0.2.21 ````md This release introduces significant improvements to output formatting and documentation, featuring a new parsable style option for enhanced XML handling. # What's New 🚀 ## Enhanced Output Style Control (#287) - Added new `parsableStyle` option for better output handling: - Ensures output strictly follows the specification of the chosen format - Provides properly escaped XML output with fast-xml-parser - Dynamically adjusts markdown code block delimiters to avoid content conflicts - Available via CLI flag `--parsable-style` or in configuration file Special thanks to @atollk for their first contribution! # Documentation 📚 ## README Enhancements (#296) - Updated Homebrew installation documentation to include Linux support Special thanks to @chenrui333 for their continued contributions! ## Website Multi-Language Support (#293) - Enhanced multi-language support in [repomix.com](https://repomix.com) # How to Update To update to the latest version, run: ```bash npm update -g repomix ``` --- As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support. ```` ## /.github/instructions/website.instructions.md --- applyTo: '**' --- # Website Documentation ## Supported Languages The website documentation is available in the following languages: - English (en) - Japanese (日本語) (ja) - Chinese Simplified (简体中文) (zh-cn) - Chinese Traditional (繁體中文) (zh-tw) - Korean (한국어) (ko) - German (Deutsch) (de) - French (Français) (fr) - Spanish (Español) (es) - Portuguese Brazilian (Português do Brasil) (pt-br) - Indonesian (Bahasa Indonesia) (id) - Vietnamese (Tiếng Việt) (vi) All translations should be accurate and maintain consistent terminology across languages. When adding new features or documentation, please ensure that the English version is updated first, followed by translations in other languages. ## Navigation Configuration When modifying website navigation or adding new pages: 1. Update the configuration files in `website/client/.vitepress/config/`. Ensure all language configurations are synchronized to maintain consistency across the documentation. ## Adding New Languages When adding support for a new language, follow these steps: 1. Create a configuration file (e.g., `configXx.ts`) in `website/client/.vitepress/config/` based on existing language configurations 2. Include proper sidebar navigation, labels, and search translations 3. Update the imports and locale entries in the main VitePress configuration (`config.ts`) 4. Add search configurations to `configShard.ts` 5. Update the supported languages list in this file 6. Create directory structure for content (e.g., `website/client/src/xx/`) 7. Create content files starting with main index page and guide index 8. Progressively translate remaining documentation pages 9. Test navigation and search functionality in the new language When working on multiple languages simultaneously, approach one language at a time completely before moving to the next language to maintain quality and consistency. ## /.github/pull_request_template.md <!-- Please include a summary of the changes --> ## Checklist - [ ] Run `npm run test` - [ ] Run `npm run lint` ## /.github/renovate.json5 ```json5 path="/.github/renovate.json5" { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:recommended", "schedule:weekly", 'group:allNonMajor' ], "rangeStrategy": "bump", "dependencyDashboard": false, "labels": ["dependencies", "renovate"], "packageRules": [ { matchDepTypes: ['peerDependencies'], enabled: false, }, ], "ignoreDeps": [ "node", ] } ``` ## /.github/workflows/ci.yml ```yml path="/.github/workflows/ci.yml" name: CI on: push: branches: [ main ] pull_request: branches: [ main ] workflow_dispatch: jobs: lint-biome: name: Lint Biome runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: .tool-versions cache: npm - run: npm ci - run: npm run lint-biome && git diff --exit-code lint-ts: name: Lint TypeScript runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: .tool-versions cache: npm - run: npm ci - run: npm run lint-ts lint-secretlint: name: Lint Secretlint runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: .tool-versions cache: npm - run: npm ci - run: npm run lint-secretlint lint-action: name: Lint GitHub Actions runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: docker://rhysd/actionlint:latest with: args: "-color" check-typos: name: Check typos runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: crate-ci/typos@master test: name: Test strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] node-version: [18.0.0, 18.x, 19.x, 20.x, 21.x, 22.x, 23.x, 24.x] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm ci - run: npm run test --reporter=verbose env: CI_OS: ${{ runner.os }} test-coverage: name: Test coverage runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: .tool-versions cache: npm - run: npm ci - run: npm run test-coverage -- --reporter=verbose env: CI_OS: ${{ runner.os }} - uses: actions/upload-artifact@v4 with: name: test-coverage path: coverage/ - uses: codecov/codecov-action@v5 with: fail_ci_if_error: true directory: ./coverage env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} build-and-run: name: Build and run strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] node-version: [18.0.0, 18.x, 19.x, 20.x, 21.x, 22.x, 23.x, 24.x] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm ci - run: npm run build - name: Install only production dependencies run: npm ci --omit=dev - run: node bin/repomix.cjs - run: node bin/repomix.cjs --version - run: node bin/repomix.cjs --help - name: Upload build artifact uses: actions/upload-artifact@v4 with: name: repomix-output-${{ matrix.os }}-${{ matrix.node-version }}.txt path: repomix-output.txt ``` ## /.github/workflows/claude.yml ```yml path="/.github/workflows/claude.yml" name: Claude Code on: issue_comment: types: [created] pull_request_review_comment: types: [created] issues: types: [opened, assigned] pull_request_review: types: [submitted] jobs: claude: if: | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) runs-on: ubuntu-latest permissions: contents: read pull-requests: read issues: read id-token: write steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 1 - name: Run Claude Code id: claude uses: anthropics/claude-code-action@beta with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} ``` ## /.github/workflows/codeql.yml ```yml path="/.github/workflows/codeql.yml" name: "CodeQL" on: push: branches: [ "main" ] pull_request: branches: [ "main" ] schedule: - cron: '25 11 * * 0' jobs: analyze: name: Analyze (${{ matrix.language }}) runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} permissions: security-events: write packages: read actions: read contents: read strategy: fail-fast: false matrix: include: - language: javascript-typescript build-mode: none steps: - name: Checkout repository uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - if: matrix.build-mode == 'manual' shell: bash run: | echo 'If you are using a "manual" build mode for one or more of the' \ 'languages you are analyzing, replace this with the commands to build' \ 'your code, for example:' echo ' make bootstrap' echo ' make release' exit 1 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" ``` ## /.github/workflows/docker.yml ```yml path="/.github/workflows/docker.yml" name: Docker on: push: branches: - "main" paths-ignore: - "**.md" - LICENSE pull_request: branches: - "*" paths: - "Dockerfile" - ".dockerignore" - ".github/workflows/docker.yml" workflow_dispatch: release: types: [published, edited] permissions: contents: read packages: write jobs: build-and-publish-image: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Docker metadata id: meta uses: docker/metadata-action@v5 with: images: | ghcr.io/yamadashy/repomix tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}} type=semver,pattern={{major}}.{{minor}} type=raw,value=latest,enable=${{ github.event_name == 'release' }} - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and Publish Docker Image uses: docker/build-push-action@v6 with: context: . push: ${{ github.event_name != 'pull_request' }} cache-from: type=gha cache-to: type=gha,mode=max platforms: linux/amd64,linux/arm64,linux/arm/v7 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} ``` ## /.github/workflows/homebrew.yml ```yml path="/.github/workflows/homebrew.yml" name: Homebrew on: release: types: - created jobs: homebrew: runs-on: macos-latest steps: - name: Set up Homebrew uses: Homebrew/actions/setup-homebrew@master with: test-bot: false - name: Configure Git user uses: Homebrew/actions/git-user-config@master - name: Bump packages uses: Homebrew/actions/bump-packages@master with: token: ${{ secrets.COMMITTER_TOKEN }} formulae: repomix ``` ## /.github/workflows/pack-repository.yml ```yml path="/.github/workflows/pack-repository.yml" name: Pack repository with Repomix on: workflow_dispatch: push: branches: [ main ] pull_request: branches: [ main ] jobs: pack-repo: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Pack repository with Repomix uses: yamadashy/repomix/.github/actions/repomix@main with: output: repomix-output.xml - name: Upload Repomix output uses: actions/upload-artifact@v4 with: name: repomix-output.xml path: repomix-output.xml retention-days: 30 ``` ## /.github/workflows/test-action.yml ```yml path="/.github/workflows/test-action.yml" name: Test Repomix Action on: workflow_dispatch: push: paths: - '.github/actions/repomix/**' jobs: test-action: name: Test Node.js ${{ matrix.node-version }} runs-on: ubuntu-latest strategy: matrix: node-version: [18, 20, 22] include: - node-version: 18 test-case: "minimal" - node-version: 20 test-case: "basic" - node-version: 22 test-case: "full" steps: - uses: actions/checkout@v4 - name: Run Repomix Action (Minimal) if: matrix['test-case'] == 'minimal' uses: ./.github/actions/repomix with: output: "repomix-minimal-output.txt" - name: Run Repomix Action (Basic) if: matrix['test-case'] == 'basic' uses: ./.github/actions/repomix with: directories: "src" include: "**/*.ts" output: "repomix-basic-output.txt" compress: "true" - name: Run Repomix Action (Full) if: matrix['test-case'] == 'full' uses: ./.github/actions/repomix with: directories: "src tests" include: "**/*.ts,**/*.md" ignore: "**/*.test.ts" output: "repomix-full-output.txt" compress: "true" additional-args: "--no-file-summary" - name: Upload result uses: actions/upload-artifact@v4 with: name: repomix-output-node${{ matrix.node-version }} path: repomix-*-output.txt ``` ## /.gitignore ```gitignore path="/.gitignore" # Dependency directories node_modules/ # Build output lib/ # Logs *.log # OS generated files .DS_Store # Editor directories and files .vscode/ .idea/ # Test coverage coverage/ # Temporary files *.tmp *.temp # Repomix output repomix-output.txt repomix-output.xml repomix-output.md # ESLint cache .eslintcache # yarn .yarn/ # biome .biome/ # aider .aider* # private files .cursor/rules/ .github/todo.md ``` ## /.node-version ```node-version path="/.node-version" 23.6.0 ``` ## /.npmignore ```npmignore path="/.npmignore" # Source files src/ # Test files tests/ coverage/ # Configuration files tsconfig.json tsconfig.build.json .eslintrc.js eslint.config.mjs prettier.config.mjs vite.config.ts biome.json # Git files .gitignore .git # CI files .github/ # yarn files .yarn # ESLint files .eslintcache # Config files .editorconfig .node-version .tool-versions repomix.config.js # Editor files .vscode/ .idea/ .memo/ # Logs *.log # Repomix output repomix-output.txt # Development scripts scripts/ # Documentation files (except README and LICENSE) docs/ CONTRIBUTING.md CHANGELOG.md # Temporary files *.tmp *.temp # OS generated files .DS_Store Thumbs.db # biome .biome/ # Website website/ # Devcontainer .devcontainer/ # Browser browser/ ``` ## /.repomixignore ```repomixignore path="/.repomixignore" node_modules .yarn .eslinttcache tests/integration-tests/fixtures ``` ## /.secretlintrc.json ```json path="/.secretlintrc.json" { "rules": [ { "id": "@secretlint/secretlint-rule-preset-recommend" } ] } ``` ## /.tool-versions ```tool-versions path="/.tool-versions" nodejs 24.0.1 ``` ## /CLAUDE.md # Repomix Development Guide ## Build & Development Commands - `npm run build` - Build the project - `npm run lint` - Run all linters (Biome, TypeScript, secretlint) - `npm run lint-biome` - Run Biome linter with auto-fix - `npm run lint-ts` - Run TypeScript type checking - `npm test` - Run all tests - `npm test -- /path/to/file.test.ts` - Run a specific test file - `npm test -- -t "test name"` - Run tests matching description - `npm run test-coverage` - Run tests with coverage report - `npm run repomix` - Build and run the CLI tool ## Code Style Guidelines - **Formatting**: 2 spaces indentation, 120 char line width, single quotes, trailing commas - **Imports**: Use `node:` prefix for built-ins, `.js` extension for relative imports, `import type` for types - **TypeScript**: Strict type checking, explicit return types, prefer interfaces for object types - **Error Handling**: Custom errors extend `RepomixError`, descriptive messages, proper try/catch - **Dependencies**: Inject dependencies through deps object parameter for testability - **File Structure**: Keep files under 250 lines, organize by feature, use workers for concurrent operations - **Testing**: Use Vitest, mock dependencies, descriptive test names, arrange/act/assert pattern ## Naming Conventions - PascalCase: Classes, interfaces, types - camelCase: Variables, functions, methods, file names - Test files: `[filename].test.ts` ## Git Workflow - Write detailed commit messages focusing on the "why" rather than the "what" - Run `npm run lint && npm test` before committing changes ## /CODE_OF_CONDUCT.md # Repomix Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we, as contributors and maintainers, pledge to make participation in our project and community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contribute to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as physical or electronic addresses, without explicit permission * Other conduct that could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that do not align with this Code of Conduct or to ban temporarily or permanently any contributor for behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [koukun0120@gmail.com](mailto:koukun0120@gmail.com). All complaints will be reviewed and investigated, resulting in a response deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality concerning the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html). [homepage]: https://www.contributor-covenant.org For answers to common questions about this Code of Conduct, see [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). ## /CONTRIBUTING.md # Contribution Guide Thanks for your interest in **Repomix**! 🚀 We'd love your help to make it even better. Here's how you can get involved: - **Create an Issue**: Spot a bug? Have an idea for a new feature? Let us know by creating an issue. - **Submit a Pull Request**: Found something to fix or improve? Jump in and submit a PR! - **Spread the Word**: Share your experience with Repomix on social media, blogs, or with your tech community. - **Use Repomix**: The best feedback comes from real-world usage, so feel free to integrate Repomix into your own projects! ## Maintainers Repomix is maintained by Yamadashy ([@yamadashy](https://github.com/yamadashy)). While all contributions are welcome, please understand that not every suggestion may be accepted if they don't align with the project's goals or coding standards. --- ## Pull Requests Before submitting a Pull Request, please ensure: 1. Your code passes all tests: Run `npm run test` 2. Your code adheres to our linting standards: Run `npm run lint` 3. You have updated relevant documentation (especially README.md) if you've added or changed functionality. ## Local Development To set up Repomix for local development: ```bash git clone https://github.com/yamadashy/repomix.git cd repomix npm install ``` To run Repomix locally: ```bash npm run repomix ``` ### Docker Usage You can also run Repomix using Docker. Here's how: First, build the Docker image: ```bash docker build -t repomix . ``` Then, run the Docker container: ```bash docker run -v ./:/app -it --rm repomix ``` ### Coding Style We use [Biome](https://biomejs.dev/) for linting and formatting. Please make sure your code follows the style guide by running: ```bash npm run lint ``` ### Testing We use [Vitest](https://vitest.dev/) for testing. To run the tests: ```bash npm run test ``` For test coverage: ```bash npm run test-coverage ``` ### Documentation When adding new features or making changes, please update the relevant documentation in the README.md file. ### Website Development The Repomix website is built with [VitePress](https://vitepress.dev/). To run the website locally: ```bash # Prerequisites: Docker must be installed on your system # Start the website development server npm run website # Access the website at http://localhost:5173/ ``` The website source code is located in the `website` directory. The main components are: - `website/client`: Frontend code (Vue.js components, styles, etc.) - `website/server`: Backend API server When updating documentation, you only need to update the English version (`website/client/src/en/`). The maintainers will handle translations to other languages. ## Releasing New versions are managed by the maintainer. If you think a release is needed, open an issue to discuss it Thank you for contributing to Repomix! ## /Dockerfile ``` path="/Dockerfile" FROM node:22-slim RUN apt-get update && apt-get install -y --no-install-recommends \ git \ ca-certificates \ && rm -rf /var/lib/apt/lists/* RUN mkdir /repomix WORKDIR /repomix # Install dependencies and build repomix, then link the package to the global scope # To reduce the size of the layer, npm ci and npm link are executed in the same RUN command COPY . . RUN npm ci \ && npm run build \ && npm link \ && npm ci --omit=dev \ && npm cache clean --force WORKDIR /app # Check the operation of repomix RUN repomix --version RUN repomix --help ENTRYPOINT ["repomix"] ``` ## /README.md <div align="center" markdown="1"> <sup>Special thanks to:</sup> <br> <br> <a href="https://www.warp.dev/repomix"> <img alt="Warp sponsorship" width="400" src="website/client/src/public/images/sponsors/warp/Terminal-Image.png"> </a> ### [Warp, the agent terminal for developers](https://www.warp.dev/repomix) [Available for MacOS, Linux, & Windows](https://www.warp.dev/repomix)<br> </div> <hr /> <div align="center"> <a href="https://repomix.com"> <img src="website/client/src/public/images/repomix-title.png" alt="Repomix" width="500" height="auto" /> </a> <p align="center"> <b>Pack your codebase into AI-friendly formats</b> </p> </div> <p align="center"> <a href="https://repomix.com"><b>Use Repomix online! 👉 repomix.com</b></a><br> </p> <p align="center"> Need discussion? Join us on <a href="https://discord.gg/wNYzTwZFku">Discord</a>!<br> <i>Share your experience and tips</i><br> <i>Stay updated on new features</i><br> <i>Get help with configuration and usage</i><br> </p> <hr /> [](https://www.npmjs.com/package/repomix) [](https://www.npmjs.com/package/repomix) [](https://github.com/yamadashy/repomix/actions?query=workflow%3A"ci") [](https://codecov.io/github/yamadashy/repomix) [](https://github.com/sponsors/yamadashy) [](https://discord.gg/wNYzTwZFku) [](https://deepwiki.com/yamadashy/repomix) <!-- DeepWiki badge generated by https://deepwiki.ryoppippi.com/ --> 📦 Repomix is a powerful tool that packs your entire repository into a single, AI-friendly file. It is perfect for when you need to feed your codebase to Large Language Models (LLMs) or other AI tools like Claude, ChatGPT, DeepSeek, Perplexity, Gemini, Gemma, Llama, Grok, and more. ## 🏆 Open Source Awards Nomination We're honored! Repomix has been nominated for the **Powered by AI** category at the [JSNation Open Source Awards 2025](https://osawards.com/javascript/). This wouldn't have been possible without all of you using and supporting Repomix. Thank you! If Repomix has helped you analyze or pack codebases for AI tools, we'd be grateful for your vote in the **Powered by AI** category. You can vote here: [https://forms.gle/5QaYBM6pNoyWLfL2A](https://forms.gle/5QaYBM6pNoyWLfL2A) Thank you for your support! ## 🎉 New: Repomix Website & Discord Community! - Try Repomix in your browser at [repomix.com](https://repomix.com/) - Join our [Discord Server](https://discord.gg/wNYzTwZFku) for support and discussion **We look forward to seeing you there!** ## 🌟 Features - **AI-Optimized**: Formats your codebase in a way that's easy for AI to understand and process. - **Token Counting**: Provides token counts for each file and the entire repository, useful for LLM context limits. - **Simple to Use**: You need just one command to pack your entire repository. - **Customizable**: Easily configure what to include or exclude. - **Git-Aware**: Automatically respects your `.gitignore` files and `.git/info/exclude`. - **Security-Focused**: Incorporates [Secretlint](https://github.com/secretlint/secretlint) for robust security checks to detect and prevent inclusion of sensitive information. - **Code Compression**: The `--compress` option uses [Tree-sitter](https://github.com/tree-sitter/tree-sitter) to extract key code elements, reducing token count while preserving structure. ## 🚀 Quick Start ### Using the CLI Tool `>_` You can try Repomix instantly in your project directory without installation: ```bash npx repomix ``` Or install globally for repeated use: ```bash # Install using npm npm install -g repomix # Alternatively using yarn yarn global add repomix # Alternatively using Homebrew (macOS/Linux) brew install repomix # Then run in any project directory repomix ``` That's it! Repomix will generate a `repomix-output.xml` file in your current directory, containing your entire repository in an AI-friendly format. You can then send this file to an AI assistant with a prompt like: ``` This file contains all the files in the repository combined into one. I want to refactor the code, so please review it first. ```  When you propose specific changes, the AI might be able to generate code accordingly. With features like Claude's Artifacts, you could potentially output multiple files, allowing for the generation of multiple interdependent pieces of code.  Happy coding! 🚀 ### Using The Website 🌐 Want to try it quickly? Visit the official website at [repomix.com](https://repomix.com). Simply enter your repository name, fill in any optional details, and click the **Pack** button to see your generated output. #### Available Options The website offers several convenient features: - Customizable output format (XML, Markdown, or Plain Text) - Instant token count estimation - Much more! ### Using The Browser Extension 🧩 Get instant access to Repomix directly from any GitHub repository! Our Chrome extension adds a convenient "Repomix" button to GitHub repository pages.  #### Install - Chrome Extension: [Repomix - Chrome Web Store](https://chromewebstore.google.com/detail/repomix/fimfamikepjgchehkohedilpdigcpkoa) - Firefox Add-on: Coming soon #### Features - One-click access to Repomix for any GitHub repository - More exciting features coming soon! ### Using The VSCode Extension ⚡️ A community-maintained VSCode extension called [Repomix Runner](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner) (created by [massdo](https://github.com/massdo)) lets you run Repomix right inside your editor with just a few clicks. Run it on any folder, manage outputs seamlessly, and control everything through VSCode's intuitive interface. Want your output as a file or just the content? Need automatic cleanup? This extension has you covered. Plus, it works smoothly with your existing repomix.config.json. Try it now on the [VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner)! Source code is available on [GitHub](https://github.com/massdo/repomix-runner). ### Alternative Tools 🛠️ If you're using Python, you might want to check out `Gitingest`, which is better suited for Python ecosystem and data science workflows: https://github.com/cyclotruc/gitingest ## 📊 Usage To pack your entire repository: ```bash repomix ``` To pack a specific directory: ```bash repomix path/to/directory ``` To pack specific files or directories using [glob patterns](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax): ```bash repomix --include "src/**/*.ts,**/*.md" ``` To exclude specific files or directories: ```bash repomix --ignore "**/*.log,tmp/" ``` To pack a remote repository: ```bash repomix --remote https://github.com/yamadashy/repomix # You can also use GitHub shorthand: repomix --remote yamadashy/repomix # You can specify the branch name, tag, or commit hash: repomix --remote https://github.com/yamadashy/repomix --remote-branch main # Or use a specific commit hash: repomix --remote https://github.com/yamadashy/repomix --remote-branch 935b695 # Another convenient way is specifying the branch's URL repomix --remote https://github.com/yamadashy/repomix/tree/main # Commit's URL is also supported repomix --remote https://github.com/yamadashy/repomix/commit/836abcd7335137228ad77feb28655d85712680f1 ``` To compress the output: ```bash repomix --compress # You can also use it with remote repositories: repomix --remote yamadashy/repomix --compress ``` To initialize a new configuration file (`repomix.config.json`): ```bash repomix --init ``` Once you have generated the packed file, you can use it with Generative AI tools like ChatGPT, DeepSeek, Perplexity, Gemini, Gemma, Llama, Grok, and more. ### Docker Usage 🐳 You can also run Repomix using Docker. This is useful if you want to run Repomix in an isolated environment or prefer using containers. Basic usage (current directory): ```bash docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix ``` To pack a specific directory: ```bash docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix path/to/directory ``` Process a remote repository and output to a `output` directory: ```bash docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote https://github.com/yamadashy/repomix ``` ### Prompt Examples Once you have generated the packed file with Repomix, you can use it with AI tools like ChatGPT, DeepSeek, Perplexity, Gemini, Gemma, Llama, Grok, and more. Here are some example prompts to get you started: #### Code Review and Refactoring For a comprehensive code review and refactoring suggestions: ``` This file contains my entire codebase. Please review the overall structure and suggest any improvements or refactoring opportunities, focusing on maintainability and scalability. ``` #### Documentation Generation To generate project documentation: ``` Based on the codebase in this file, please generate a detailed README.md that includes an overview of the project, its main features, setup instructions, and usage examples. ``` #### Test Case Generation For generating test cases: ``` Analyze the code in this file and suggest a comprehensive set of unit tests for the main functions and classes. Include edge cases and potential error scenarios. ``` #### Code Quality Assessment Evaluate code quality and adherence to best practices: ``` Review the codebase for adherence to coding best practices and industry standards. Identify areas where the code could be improved in terms of readability, maintainability, and efficiency. Suggest specific changes to align the code with best practices. ``` #### Library Overview Get a high-level understanding of the library ``` This file contains the entire codebase of library. Please provide a comprehensive overview of the library, including its main purpose, key features, and overall architecture. ``` Feel free to modify these prompts based on your specific needs and the capabilities of the AI tool you're using. ### Community Discussion Check out our [community discussion](https://github.com/yamadashy/repomix/discussions/154) where users share: - Which AI tools they're using with Repomix - Effective prompts they've discovered - How Repomix has helped them - Tips and tricks for getting the most out of AI code analysis Feel free to join the discussion and share your own experiences! Your insights could help others make better use of Repomix. ### Output File Format Repomix generates a single file with clear separators between different parts of your codebase. To enhance AI comprehension, the output file begins with an AI-oriented explanation, making it easier for AI models to understand the context and structure of the packed repository. #### XML Format (default) The XML format structures the content in a hierarchical manner: ```xml This file is a merged representation of the entire codebase, combining all repository files into a single document. <file_summary> (Metadata and usage AI instructions) </file_summary> <directory_structure> src/ cli/ cliOutput.ts index.ts (...remaining directories) </directory_structure> <files> <file path="src/index.js"> // File contents here </file> (...remaining files) </files> <instruction> (Custom instructions from `output.instructionFilePath`) </instruction> ``` For those interested in the potential of XML tags in AI contexts: https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/use-xml-tags > When your prompts involve multiple components like context, instructions, and examples, XML tags can be a > game-changer. They help Claude parse your prompts more accurately, leading to higher-quality outputs. This means that the XML output from Repomix is not just a different format, but potentially a more effective way to feed your codebase into AI systems for analysis, code review, or other tasks. #### Markdown Format To generate output in Markdown format, use the `--style markdown` option: ```bash repomix --style markdown ``` The Markdown format structures the content in a hierarchical manner: ````markdown This file is a merged representation of the entire codebase, combining all repository files into a single document. # File Summary (Metadata and usage AI instructions) # Repository Structure ``` src/ cli/ cliOutput.ts index.ts ``` (...remaining directories) # Repository Files ## File: src/index.js ``` // File contents here ``` (...remaining files) # Instruction (Custom instructions from `output.instructionFilePath`) ```` This format provides a clean, readable structure that is both human-friendly and easily parseable by AI systems. #### Plain Text Format To generate output in plain text format, use the `--style plain` option: ```bash repomix --style plain ``` ```text This file is a merged representation of the entire codebase, combining all repository files into a single document. ================================================================ File Summary ================================================================ (Metadata and usage AI instructions) ================================================================ Directory Structure ================================================================ src/ cli/ cliOutput.ts index.ts config/ configLoader.ts (...remaining directories) ================================================================ Files ================================================================ ================ File: src/index.js ================ // File contents here ================ File: src/utils.js ================ // File contents here (...remaining files) ================================================================ Instruction ================================================================ (Custom instructions from `output.instructionFilePath`) ``` ### Command Line Options #### Basic Options - `-v, --version`: Show tool version #### Output Options - `-o, --output <file>`: Specify the output file name - `--stdout`: Output to stdout instead of writing to a file (cannot be used with `--output` option) - `--style <style>`: Specify the output style (`xml`, `markdown`, `plain`) - `--parsable-style`: Enable parsable output based on the chosen style schema. Note that this can increase token count. - `--compress`: Perform intelligent code extraction, focusing on essential function and class signatures to reduce token count - `--output-show-line-numbers`: Show line numbers in the output - `--copy`: Additionally copy generated output to system clipboard - `--no-file-summary`: Disable file summary section output - `--no-directory-structure`: Disable directory structure section output - `--remove-comments`: Remove comments from supported file types - `--remove-empty-lines`: Remove empty lines from the output - `--header-text <text>`: Custom text to include in the file header - `--instruction-file-path <path>`: Path to a file containing detailed custom instructions - `--include-empty-directories`: Include empty directories in the output - `--include-diffs`: Include git diffs in the output (includes both work tree and staged changes separately) - `--no-git-sort-by-changes`: Disable sorting files by git change count (enabled by default) #### Filter Options - `--include <patterns>`: List of include patterns (comma-separated) - `-i, --ignore <patterns>`: Additional ignore patterns (comma-separated) - `--no-gitignore`: Disable .gitignore file usage - `--no-default-patterns`: Disable default patterns #### Remote Repository Options - `--remote <url>`: Process a remote Git repository - `--remote-branch <name>`: Specify the remote branch name, tag, or commit hash (defaults to repository default branch) #### Configuration Options - `-c, --config <path>`: Path to a custom config file - `--init`: Create config file - `--global`: Use global config #### Security Options - `--no-security-check`: Disable security check #### Token Count Options - `--token-count-encoding <encoding>`: Specify token count encoding used by OpenAI's [tiktoken](https://github.com/openai/tiktoken) tokenizer (e.g., `o200k_base` for GPT-4o, `cl100k_base` for GPT-4/3.5). See [tiktoken model.py](https://github.com/openai/tiktoken/blob/main/tiktoken/model.py#L24) for encoding details. #### MCP - `--mcp`: Run as a [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server #### Other Options - `--top-files-len <number>`: Number of top files to display in the summary - `--verbose`: Enable verbose logging - `--quiet`: Disable all output to stdout Examples: ```bash # Basic usage repomix # Custom output repomix -o output.xml --style xml # Output to stdout repomix --stdout > custom-output.txt # Send output to stdout, then pipe into another command (for example, simonw/llm) repomix --stdout | llm "Please explain what this code does." # Custom output with compression repomix --compress # Process specific files repomix --include "src/**/*.ts" --ignore "**/*.test.ts" # Remote repository with branch repomix --remote https://github.com/user/repo/tree/main # Remote repository with commit repomix --remote https://github.com/user/repo/commit/836abcd7335137228ad77feb28655d85712680f1 # Remote repository with shorthand repomix --remote user/repo ``` ### Updating Repomix To update a globally installed Repomix: ```bash # Using npm npm update -g repomix # Using yarn yarn global upgrade repomix ``` Using `npx repomix` is generally more convenient as it always uses the latest version. ### Remote Repository Processing Repomix supports processing remote Git repositories without the need for manual cloning. This feature allows you to quickly analyze any public Git repository with a single command. To process a remote repository, use the `--remote` option followed by the repository URL: ```bash repomix --remote https://github.com/yamadashy/repomix ``` You can also use GitHub's shorthand format: ```bash repomix --remote yamadashy/repomix ``` You can specify the branch name, tag, or commit hash: ```bash # Using --remote-branch option repomix --remote https://github.com/yamadashy/repomix --remote-branch main # Using branch's URL repomix --remote https://github.com/yamadashy/repomix/tree/main ``` Or use a specific commit hash: ```bash # Using --remote-branch option repomix --remote https://github.com/yamadashy/repomix --remote-branch 935b695 # Using commit's URL repomix --remote https://github.com/yamadashy/repomix/commit/836abcd7335137228ad77feb28655d85712680f1 ``` ### Code Compression The `--compress` option utilizes [Tree-sitter](https://github.com/tree-sitter/tree-sitter) to perform intelligent code extraction, focusing on essential function and class signatures while removing implementation details. This can help reduce token count while retaining important structural information. ```bash repomix --compress ``` For example, this code: ```typescript import { ShoppingItem } from './shopping-item'; /** * Calculate the total price of shopping items */ const calculateTotal = ( items: ShoppingItem[] ) => { let total = 0; for (const item of items) { total += item.price * item.quantity; } return total; } // Shopping item interface interface Item { name: string; price: number; quantity: number; } ``` Will be compressed to: ```typescript import { ShoppingItem } from './shopping-item'; ⋮---- /** * Calculate the total price of shopping items */ const calculateTotal = ( items: ShoppingItem[] ) => { ⋮---- // Shopping item interface interface Item { name: string; price: number; quantity: number; } ``` > [!NOTE] > This is an experimental feature that we'll be actively improving based on user feedback and real-world usage ### MCP Server Integration Repomix supports the [Model Context Protocol (MCP)](https://modelcontextprotocol.io), allowing AI assistants to directly interact with your codebase. When run as an MCP server, Repomix provides tools that enable AI assistants to package local or remote repositories for analysis without requiring manual file preparation. ```bash repomix --mcp ``` #### Configuring MCP Servers To use Repomix as an MCP server with AI assistants like Claude, you need to configure the MCP settings: **For VS Code:** You can install the Repomix MCP server in VS Code using one of these methods: 1. **Using the Install Badge:** [](https://insiders.vscode.dev/redirect/mcp/install?name=repomix&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22repomix%22%2C%22--mcp%22%5D%7D) [](https://insiders.vscode.dev/redirect/mcp/install?name=repomix&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22repomix%22%2C%22--mcp%22%5D%7D&quality=insiders) 2. **Using the Command Line:** ```bash code --add-mcp '{"name":"repomix","command":"npx","args":["-y","repomix","--mcp"]}' ``` For VS Code Insiders: ```bash code-insiders --add-mcp '{"name":"repomix","command":"npx","args":["-y","repomix","--mcp"]}' ``` **For Cline (VS Code extension):** Edit the `cline_mcp_settings.json` file: ```json { "mcpServers": { "repomix": { "command": "npx", "args": [ "-y", "repomix", "--mcp" ] } } } ``` **For Cursor:** In Cursor, add a new MCP server from `Cursor Settings` > `MCP` > `+ Add new global MCP server` with a configuration similar to Cline. **For Claude Desktop:** Edit the `claude_desktop_config.json` file with similar configuration to Cline's config. **Using Docker instead of npx:** You can use Docker as an alternative to npx for running Repomix as an MCP server: ```json { "mcpServers": { "repomix-docker": { "command": "docker", "args": [ "run", "-i", "--rm", "ghcr.io/yamadashy/repomix", "--mcp" ] } } } ``` Once configured, your AI assistant can directly use Repomix's capabilities to analyze codebases without manual file preparation, making code analysis workflows more efficient. #### Available MCP Tools When running as an MCP server, Repomix provides the following tools: 1. **pack_codebase**: Package a local code directory into a consolidated XML file for AI analysis - Parameters: - `directory`: Absolute path to the directory to pack - `compress`: (Optional, default: false) Enable Tree-sitter compression to extract essential code signatures and structure while removing implementation details. Reduces token usage by ~70% while preserving semantic meaning. Generally not needed since grep_repomix_output allows incremental content retrieval. Use only when you specifically need the entire codebase content for large repositories. - `includePatterns`: (Optional) Specify files to include using fast-glob patterns. Multiple patterns can be comma-separated (e.g., "**/*.{js,ts}", "src/**,docs/**"). Only matching files will be processed. - `ignorePatterns`: (Optional) Specify additional files to exclude using fast-glob patterns. Multiple patterns can be comma-separated (e.g., "test/**,*.spec.js", "node_modules/**,dist/**"). These patterns supplement .gitignore and built-in exclusions. - `topFilesLength`: (Optional, default: 10) Number of largest files by size to display in the metrics summary for codebase analysis. 2. **pack_remote_repository**: Fetch, clone, and package a GitHub repository into a consolidated XML file for AI analysis - Parameters: - `remote`: GitHub repository URL or user/repo format (e.g., "yamadashy/repomix", "https://github.com/user/repo", or "https://github.com/user/repo/tree/branch") - `compress`: (Optional, default: false) Enable Tree-sitter compression to extract essential code signatures and structure while removing implementation details. Reduces token usage by ~70% while preserving semantic meaning. Generally not needed since grep_repomix_output allows incremental content retrieval. Use only when you specifically need the entire codebase content for large repositories. - `includePatterns`: (Optional) Specify files to include using fast-glob patterns. Multiple patterns can be comma-separated (e.g., "**/*.{js,ts}", "src/**,docs/**"). Only matching files will be processed. - `ignorePatterns`: (Optional) Specify additional files to exclude using fast-glob patterns. Multiple patterns can be comma-separated (e.g., "test/**,*.spec.js", "node_modules/**,dist/**"). These patterns supplement .gitignore and built-in exclusions. - `topFilesLength`: (Optional, default: 10) Number of largest files by size to display in the metrics summary for codebase analysis. 3. **read_repomix_output**: Read the contents of a Repomix-generated output file. Supports partial reading with line range specification for large files. - Parameters: - `outputId`: ID of the Repomix output file to read - `startLine`: (Optional) Starting line number (1-based, inclusive). If not specified, reads from beginning. - `endLine`: (Optional) Ending line number (1-based, inclusive). If not specified, reads to end. - Features: - Specifically designed for web-based environments or sandboxed applications - Retrieves the content of previously generated outputs using their ID - Provides secure access to packed codebase without requiring file system access - Supports partial reading for large files 4. **grep_repomix_output**: Search for patterns in a Repomix output file using grep-like functionality with JavaScript RegExp syntax - Parameters: - `outputId`: ID of the Repomix output file to search - `pattern`: Search pattern (JavaScript RegExp regular expression syntax) - `contextLines`: (Optional, default: 0) Number of context lines to show before and after each match. Overridden by beforeLines/afterLines if specified. - `beforeLines`: (Optional) Number of context lines to show before each match (like grep -B). Takes precedence over contextLines. - `afterLines`: (Optional) Number of context lines to show after each match (like grep -A). Takes precedence over contextLines. - `ignoreCase`: (Optional, default: false) Perform case-insensitive matching - Features: - Uses JavaScript RegExp syntax for powerful pattern matching - Supports context lines for better understanding of matches - Allows separate control of before/after context lines - Case-sensitive and case-insensitive search options 5. **file_system_read_file**: Read a file from the local file system using an absolute path. Includes built-in security validation to detect and prevent access to files containing sensitive information. - Parameters: - `path`: Absolute path to the file to read - Security features: - Implements security validation using [Secretlint](https://github.com/secretlint/secretlint) - Prevents access to files containing sensitive information (API keys, passwords, secrets) - Validates absolute paths to prevent directory traversal attacks 6. **file_system_read_directory**: List the contents of a directory using an absolute path. Returns a formatted list showing files and subdirectories with clear indicators. - Parameters: - `path`: Absolute path to the directory to list - Features: - Shows files and directories with clear indicators (`[FILE]` or `[DIR]`) - Provides safe directory traversal with proper error handling - Validates paths and ensures they are absolute - Useful for exploring project structure and understanding codebase organization ## ⚙️ Configuration Create a `repomix.config.json` file in your project root for custom configurations. ```bash repomix --init ``` Here's an explanation of the configuration options: | Option | Description | Default | |----------------------------------|------------------------------------------------------------------------------------------------------------------------------|------------------------| | `input.maxFileSize` | Maximum file size in bytes to process. Files larger than this will be skipped | `50000000` | | `output.filePath` | The name of the output file | `"repomix-output.xml"` | | `output.style` | The style of the output (`xml`, `markdown`, `plain`) | `"xml"` | | `output.parsableStyle` | Whether to escape the output based on the chosen style schema. Note that this can increase token count. | `false` | | `output.compress` | Whether to perform intelligent code extraction to reduce token count | `false` | | `output.headerText` | Custom text to include in the file header | `null` | | `output.instructionFilePath` | Path to a file containing detailed custom instructions | `null` | | `output.fileSummary` | Whether to include a summary section at the beginning of the output | `true` | | `output.directoryStructure` | Whether to include the directory structure in the output | `true` | | `output.files` | Whether to include file contents in the output | `true` | | `output.removeComments` | Whether to remove comments from supported file types | `false` | | `output.removeEmptyLines` | Whether to remove empty lines from the output | `false` | | `output.showLineNumbers` | Whether to add line numbers to each line in the output | `false` | | `output.copyToClipboard` | Whether to copy the output to system clipboard in addition to saving the file | `false` | | `output.topFilesLength` | Number of top files to display in the summary. If set to 0, no summary will be displayed | `5` | | `output.includeEmptyDirectories` | Whether to include empty directories in the repository structure | `false` | | `output.git.sortByChanges` | Whether to sort files by git change count (files with more changes appear at the bottom) | `true` | | `output.git.sortByChangesMaxCommits` | Maximum number of commits to analyze for git changes | `100` | | `output.git.includeDiffs` | Whether to include git diffs in the output (includes both work tree and staged changes separately) | `false` | | `include` | Patterns of files to include (using [glob patterns](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax)) | `[]` | | `ignore.useGitignore` | Whether to use patterns from the project's `.gitignore` file | `true` | | `ignore.useDefaultPatterns` | Whether to use default ignore patterns | `true` | | `ignore.customPatterns` | Additional patterns to ignore (using [glob patterns](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax)) | `[]` | | `security.enableSecurityCheck` | Whether to perform security checks on files | `true` | | `tokenCount.encoding` | Token count encoding used by OpenAI's [tiktoken](https://github.com/openai/tiktoken) tokenizer (e.g., `o200k_base` for GPT-4o, `cl100k_base` for GPT-4/3.5). See [tiktoken model.py](https://github.com/openai/tiktoken/blob/main/tiktoken/model.py#L24) for encoding details. | `"o200k_base"` | The configuration file supports [JSON5](https://json5.org/) syntax, which allows: - Comments (both single-line and multi-line) - Trailing commas in objects and arrays - Unquoted property names - More relaxed string syntax Example configuration: ```json5 { "input": { "maxFileSize": 50000000 }, "output": { "filePath": "repomix-output.xml", "style": "xml", "parsableStyle": false, "compress": false, "headerText": "Custom header information for the packed file.", "fileSummary": true, "directoryStructure": true, "files": true, "removeComments": false, "removeEmptyLines": false, "topFilesLength": 5, "showLineNumbers": false, "copyToClipboard": false, "includeEmptyDirectories": false, "git": { "sortByChanges": true, "sortByChangesMaxCommits": 100, "includeDiffs": false } }, "include": ["**/*"], "ignore": { "useGitignore": true, "useDefaultPatterns": true, // Patterns can also be specified in .repomixignore "customPatterns": [ "additional-folder", "**/*.log" ], }, "security": { "enableSecurityCheck": true }, "tokenCount": { "encoding": "o200k_base" } } ``` ### Global Configuration To create a global configuration file: ```bash repomix --init --global ``` The global configuration file will be created in: - Windows: `%LOCALAPPDATA%\Repomix\repomix.config.json` - macOS/Linux: `$XDG_CONFIG_HOME/repomix/repomix.config.json` or `~/.config/repomix/repomix.config.json` Note: Local configuration (if present) takes precedence over global configuration. ### Include and Ignore #### Include Patterns Repomix now supports specifying files to include using [glob patterns](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax). This allows for more flexible and powerful file selection: - Use `**/*.js` to include all JavaScript files in any directory - Use `src/**/*` to include all files within the `src` directory and its subdirectories - Combine multiple patterns like `["src/**/*.js", "**/*.md"]` to include JavaScript files in `src` and all Markdown files #### Ignore Patterns Repomix offers multiple methods to set ignore patterns for excluding specific files or directories during the packing process: - **.gitignore**: By default, patterns listed in your project's `.gitignore` files and `.git/info/exclude` are used. This behavior can be controlled with the `ignore.useGitignore` setting or the `--no-gitignore` cli option. - **Default patterns**: Repomix includes a default list of commonly excluded files and directories (e.g., node_modules, .git, binary files). This feature can be controlled with the `ignore.useDefaultPatterns` setting or the `--no-default-patterns` cli option. Please see [defaultIgnore.ts](src/config/defaultIgnore.ts) for more details. - **.repomixignore**: You can create a `.repomixignore` file in your project root to define Repomix-specific ignore patterns. This file follows the same format as `.gitignore`. - **Custom patterns**: Additional ignore patterns can be specified using the `ignore.customPatterns` option in the configuration file. You can overwrite this setting with the `-i, --ignore` command line option. Priority Order (from highest to lowest): 1. Custom patterns `ignore.customPatterns` 2. `.repomixignore` 3. `.gitignore` and `.git/info/exclude` (if `ignore.useGitignore` is true and `--no-gitignore` is not used) 4. Default patterns (if `ignore.useDefaultPatterns` is true and `--no-default-patterns` is not used) This approach allows for flexible file exclusion configuration based on your project's needs. It helps optimize the size of the generated pack file by ensuring the exclusion of security-sensitive files and large binary files, while preventing the leakage of confidential information. Note: Binary files are not included in the packed output by default, but their paths are listed in the "Repository Structure" section of the output file. This provides a complete overview of the repository structure while keeping the packed file efficient and text-based. ### Custom Instruction The `output.instructionFilePath` option allows you to specify a separate file containing detailed instructions or context about your project. This allows AI systems to understand the specific context and requirements of your project, potentially leading to more relevant and tailored analysis or suggestions. Here's an example of how you might use this feature: 1. Create a file named `repomix-instruction.md` in your project root: ```markdown # Coding Guidelines - Follow the Airbnb JavaScript Style Guide - Suggest splitting files into smaller, focused units when appropriate - Add comments for non-obvious logic. Keep all text in English - All new features should have corresponding unit tests # Generate Comprehensive Output - Include all content without abbreviation, unless specified otherwise - Optimize for handling large codebases while maintaining output quality ``` 2. In your `repomix.config.json`, add the `instructionFilePath` option: ```json5 { "output": { "instructionFilePath": "repomix-instruction.md", // other options... } } ``` When Repomix generates the output, it will include the contents of `repomix-instruction.md` in a dedicated section. Note: The instruction content is appended at the end of the output file. This placement can be particularly effective for AI systems. For those interested in understanding why this might be beneficial, Anthropic provides some insights in their documentation: https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/long-context-tips > Put long-form data at the top: Place your long documents and inputs (~20K+ tokens) near the top of your prompt, above > your query, instructions, and examples. This can significantly improve Claude's performance across all models. > Queries at the end can improve response quality by up to 30% in tests, especially with complex, multi-document inputs. ### Comment Removal When `output.removeComments` is set to `true`, Repomix will attempt to remove comments from supported file types. This feature can help reduce the size of the output file and focus on the essential code content. Supported languages include: HTML, CSS, JavaScript, TypeScript, Vue, Svelte, Python, PHP, Ruby, C, C#, Java, Go, Rust, Swift, Kotlin, Dart, Shell, and YAML. Note: The comment removal process is conservative to avoid accidentally removing code. In complex cases, some comments might be retained. ## 🔍 Security Check Repomix includes a security check feature that uses [Secretlint](https://github.com/secretlint/secretlint) to detect potentially sensitive information in your files. This feature helps you identify possible security risks before sharing your packed repository. The security check results will be displayed in the CLI output after the packing process is complete. If any suspicious files are detected, you'll see a list of these files along with a warning message. Example output: ``` 🔍 Security Check: ────────────────── 2 suspicious file(s) detected: 1. src/utils/test.txt 2. tests/utils/secretLintUtils.test.ts Please review these files for potentially sensitive information. ``` By default, Repomix's security check feature is enabled. You can disable it by setting `security.enableSecurityCheck` to `false` in your configuration file: ```json { "security": { "enableSecurityCheck": false } } ``` Or using the `--no-security-check` command line option: ```bash repomix --no-security-check ``` > [!NOTE] > Disabling security checks may expose sensitive information. Use this option with caution and only when necessary, such > as when working with test files or documentation that contains example credentials. ## 🤖 Using Repomix with GitHub Actions You can also use Repomix in your GitHub Actions workflows. This is useful for automating the process of packing your codebase for AI analysis. Basic usage: ```yaml - name: Pack repository with Repomix uses: yamadashy/repomix/.github/actions/repomix@main with: output: repomix-output.xml style: xml ``` Use `--style` to generate output in different formats: ```yaml - name: Pack repository with Repomix uses: yamadashy/repomix/.github/actions/repomix@main with: output: repomix-output.md style: markdown ``` Pack specific directories with compression: ```yaml - name: Pack repository with Repomix uses: yamadashy/repomix/.github/actions/repomix@main with: directories: src tests include: "**/*.ts,**/*.md" ignore: "**/*.test.ts" output: repomix-output.txt compress: true ``` Upload the output file as an artifact: ```yaml - name: Pack repository with Repomix uses: yamadashy/repomix/.github/actions/repomix@main with: directories: src output: repomix-output.txt compress: true - name: Upload Repomix output uses: actions/upload-artifact@v4 with: name: repomix-output path: repomix-output.txt ``` Complete workflow example: ```yaml name: Pack repository with Repomix on: workflow_dispatch: push: branches: [ main ] pull_request: branches: [ main ] jobs: pack-repo: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Pack repository with Repomix uses: yamadashy/repomix/.github/actions/repomix@main with: output: repomix-output.xml - name: Upload Repomix output uses: actions/upload-artifact@v4 with: name: repomix-output.xml path: repomix-output.xml retention-days: 30 ``` See the complete workflow example [here](https://github.com/yamadashy/repomix/blob/main/.github/workflows/pack-repository.yml). ### Action Inputs | Name | Description | Default | |------|-------------|---------| | `directories` | Space-separated list of directories to process (e.g., `src tests docs`) | `.` | | `include` | Comma-separated glob patterns to include files (e.g., `**/*.ts,**/*.md`) | `""` | | `ignore` | Comma-separated glob patterns to ignore files (e.g., `**/*.test.ts,**/node_modules/**`) | `""` | | `output` | Relative path for the packed file (extension determines format: `.txt`, `.md`, `.xml`) | `repomix-output.xml` | | `compress` | Enable smart compression to reduce output size by pruning implementation details | `true` | | `style` | Output style (`xml`, `markdown`, `plain`) | `xml` | | `additional-args` | Extra raw arguments for the repomix CLI (e.g., `--no-file-summary --no-security-check`) | `""` | | `repomix-version` | Version of the npm package to install (supports semver ranges, tags, or specific versions like `0.2.25`) | `latest` | ### Action Outputs | Name | Description | |------|-------------| | `output_file` | Path to the generated output file. Can be used in subsequent steps for artifact upload, LLM processing, or other operations. The file contains a formatted representation of your codebase based on the specified options. | ## 📚 Using Repomix as a Library In addition to using Repomix as a CLI tool, you can also use it as a library in your Node.js applications. ### Installation ```bash npm install repomix ``` ### Basic Usage ```javascript import { runCli, type CliOptions } from 'repomix'; // Process current directory with custom options async function packProject() { const options = { output: 'output.xml', style: 'xml', compress: true, quiet: true } as CliOptions; const result = await runCli(['.'], process.cwd(), options); return result.packResult; } ``` ### Process Remote Repository ```javascript import { runCli, type CliOptions } from 'repomix'; // Clone and process a GitHub repo async function processRemoteRepo(repoUrl) { const options = { remote: repoUrl, output: 'output.xml', compress: true } as CliOptions; return await runCli(['.'], process.cwd(), options); } ``` ### Using Core Components If you need more control, you can use the low-level APIs: ```javascript import { searchFiles, collectFiles, processFiles, TokenCounter } from 'repomix'; async function analyzeFiles(directory) { // Find and collect files const { filePaths } = await searchFiles(directory, { /* config */ }); const rawFiles = await collectFiles(filePaths, directory); const processedFiles = await processFiles(rawFiles, { /* config */ }); // Count tokens const tokenCounter = new TokenCounter('o200k_base'); // Return analysis results return processedFiles.map(file => ({ path: file.path, tokens: tokenCounter.countTokens(file.content) })); } ``` For more examples, check the source code at [website/server/src/remoteRepo.ts](https://github.com/yamadashy/repomix/blob/main/website/server/src/remoteRepo.ts) which demonstrates how repomix.com uses the library. ## 🤝 Contribution We welcome contributions from the community! To get started, please refer to our [Contributing Guide](CONTRIBUTING.md). ### Contributors <a href="https://github.com/yamadashy/repomix/graphs/contributors"> <img alt="contributors" src="https://contrib.rocks/image?repo=yamadashy/repomix"/> </a> ## 🔒 Privacy Policy ### Repomix CLI Tool - **Data Collection**: The Repomix CLI tool does **not** collect, transmit, or store any user data, telemetry, or repository information. - **Network Usage**: Repomix CLI operates fully offline after installation. The only cases where an internet connection is needed are: - Installation via npm/yarn. - Using the `--remote` flag to process remote repositories. - Checking for updates (manually triggered). - **Security Considerations**: Since all processing is local, Repomix CLI is safe to use with private and internal repositories. ### Repomix Website ([repomix.com](https://repomix.com/)) - **Data Collection**: The Repomix website uses **Google Analytics** to collect usage data, such as page views and user interactions. This helps us understand how the website is used and improve the user experience. ### Liability Disclaimer Repomix (both the CLI tool and the website) is provided "as is" without any warranties or guarantees. We do not take responsibility for how the generated output is used, including but not limited to its accuracy, legality, or any potential consequences arising from its use. ## 📜 License This project is licensed under the [MIT License](LICENSE). <p align="center"> <a href="#repo-content-pjax-container" target="_blank"> Back To Top </a> </p> ## /SECURITY.md # Security Policy ## Reporting a Vulnerability To securely report a vulnerability, please [open an advisory on GitHub](https://github.com/yamadashy/repomix/security/advisories/new) or report it by sending an email to `koukun0120@gmail.com`. ## /bin/repomix.cjs ```cjs path="/bin/repomix.cjs" #!/usr/bin/env node const nodeVersion = process.versions.node; const [major] = nodeVersion.split('.').map(Number); const EXIT_CODES = { SUCCESS: 0, ERROR: 1, }; if (major < 16) { console.error(`Repomix requires Node.js version 16 or higher. Current version: ${nodeVersion}\n`); process.exit(EXIT_CODES.ERROR); } function setupErrorHandlers() { process.on('uncaughtException', (error) => { console.error('Uncaught Exception:', error); process.exit(EXIT_CODES.ERROR); }); process.on('unhandledRejection', (reason) => { console.error('Unhandled Promise Rejection:', reason); process.exit(EXIT_CODES.ERROR); }); function shutdown() { process.exit(EXIT_CODES.SUCCESS); } process.on('SIGINT', () => { console.log('\nReceived SIGINT. Shutting down...'); shutdown(); }); process.on('SIGTERM', shutdown); } (async () => { try { setupErrorHandlers(); const { run } = await import('../lib/cli/cliRun.js'); await run(); } catch (error) { if (error instanceof Error) { console.error('Fatal Error:', { name: error.name, message: error.message, stack: error.stack, }); } else { console.error('Fatal Error:', error); } process.exit(EXIT_CODES.ERROR); } })(); ``` ## /biome.json ```json path="/biome.json" { "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", "files": { "include": [ "./bin/**", "./src/**", "./tests/**", "./website/**", "./browser/**", "./.devcontainer/**", "./.github/**", "package.json", "biome.json", ".secretlintrc.json", "tsconfig.json", "tsconfig.build.json", "vite.config.ts", "repomix.config.json" ], "ignore": [ "website/client/.vitepress/.temp", "website/client/.vitepress/dist", "website/client/.vitepress/cache", "website/server/dist", "browser/dist", "browser/packages" ] }, "organizeImports": { "enabled": true }, "linter": { "enabled": true, "rules": { "recommended": true } }, "formatter": { "enabled": true, "formatWithErrors": false, "indentStyle": "space", "indentWidth": 2, "lineWidth": 120 }, "javascript": { "formatter": { "quoteStyle": "single", "trailingCommas": "all", "semicolons": "always" } }, "json": { "parser": { "allowComments": true, "allowTrailingCommas": true }, "formatter": { "enabled": false } } } ``` ## /browser/.gitignore ```gitignore path="/browser/.gitignore" # Dependencies node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* # Build output dist/ packages/ # Environment variables .env .env.local .env.development.local .env.test.local .env.production.local # OS generated files .DS_Store .DS_Store? ._* .Spotlight-V100 .Trashes ehthumbs.db Thumbs.db # IDE .vscode/ .idea/ *.swp *.swo *~ # Logs *.log # Runtime data pids *.pid *.seed *.pid.lock # Coverage directory used by tools like istanbul coverage/ # Temporary folders tmp/ temp/ ``` ## /browser/README.md # Repomix Extension A browser extension that adds a Repomix button to GitHub repository pages. ## 🚀 Features - Adds a "Repomix" button to GitHub repository pages - One-click redirect to Repomix (https://repomix.com) - Seamlessly integrates with GitHub's UI design - Works on Chrome, Firefox, and Edge ## 🛠️ Usage 1. Install the browser extension 2. Navigate to any GitHub repository page 3. A "Repomix" button will appear in the page header action area 4. Click the button to open the repository in Repomix ## 💻 Development ### Prerequisites - Node.js 22 or higher ### Setup ```bash # Install dependencies npm install # Generate icons npm run generate-icons # Development mode for Chrome npm run dev chrome # Development mode for Firefox npm run dev firefox # Development mode for Edge npm run dev edge ``` ### Build ```bash # Build for all browsers npm run build-all # Build for specific browsers npm run build chrome npm run build firefox npm run build edge ``` Built files will be generated in the `dist/` folder. ### Manual Installation 1. Run `npm run build chrome` to build 2. Open `chrome://extensions/` in Chrome 3. Enable "Developer mode" 4. Click "Load unpacked extension" 5. Select the `dist/chrome` folder ## 📝 Technical Specifications - **Manifest V3** - Latest browser extension specification - **Content Scripts** - Direct button injection into GitHub pages - **Internationalization** - English and Japanese support - **Cross-browser** - Chrome, Firefox, Edge support ## 🔒 Privacy This extension: - Does not collect any data - Does not track user behavior - Only accesses github.com - Requires minimal permissions ## /browser/app/_locales/de/detailed-description.txt Diese Browsererweiterung fügt eine praktische "Repomix"-Schaltfläche zu GitHub-Repository-Seiten hinzu, mit der Sie Repositories schnell mit Repomix verpacken und analysieren können - einem leistungsstarken Tool, das Software-Repositories in AI-freundliche Einzeldateien umwandelt. 🛠️ Funktionen: - Ein-Klick-Zugriff auf Repomix für jedes GitHub-Repository - Weitere aufregende Funktionen kommen bald - freuen Sie sich auf erweiterte Funktionalität! 🚀 Verwendung: 1. Erweiterung installieren 2. Zu einem beliebigen GitHub-Repository navigieren 3. Auf die "Repomix"-Schaltfläche im Repository-Header klicken 4. Sie werden zur Repomix-Weboberfläche weitergeleitet 5. Eine verpackte Version des Repositories für AI-Analyse generieren ✨ Was ist Repomix? Repomix ist ein innovatives Tool, das Ihre gesamte Codebasis in eine einzige, umfassende Datei verpackt, die für AI-Analyse optimiert ist. Es unterstützt mehrere Ausgabeformate (XML, Markdown, Plain Text), enthält Sicherheitsprüfungen zum Ausschluss sensibler Informationen und bietet detaillierte Metriken zu Ihrem Code. 💻 Open Source: Sowohl diese Erweiterung als auch Repomix selbst sind Open-Source-Projekte. Sie können den Quellcode einsehen, beitragen oder die Erweiterung selbst erstellen. Weitere Details finden Sie unter: https://github.com/yamadashy/repomix 🌐 Mehr erfahren: - Offizielle Website: https://repomix.com - GitHub-Repository: https://github.com/yamadashy/repomix ## /browser/app/_locales/de/messages.json ```json path="/browser/app/_locales/de/messages.json" { "appDescription": { "message": "Fügt eine Schaltfläche hinzu, um schnell von GitHub-Repositories auf Repomix zuzugreifen", "description": "The description of the extension" }, "openWithRepomix": { "message": "Mit Repomix öffnen", "description": "Button tooltip text for opening repository with Repomix" } } ``` ## /browser/app/_locales/en/detailed-description.txt This browser extension adds a convenient "Repomix" button to GitHub repository pages, allowing you to quickly package and analyze repositories with Repomix - a powerful tool that transforms software repositories into AI-friendly single files. 🛠️ Features: - One-click access to Repomix for any GitHub repository - More exciting features coming soon - stay tuned for enhanced functionality! 🚀 How to Use: 1. Install the extension 2. Navigate to any GitHub repository 3. Click the "Repomix" button in the repository header 4. You'll be redirected to Repomix web interface 5. Generate a packaged version of the repository for AI analysis ✨ What is Repomix? Repomix is an innovative tool that packages your entire codebase into a single, comprehensive file optimized for AI analysis. It supports multiple output formats (XML, Markdown, Plain text), includes security checks to exclude sensitive information, and provides detailed metrics about your code. 💻 Open Source: Both this extension and Repomix itself are open source projects. You can view the source code, contribute, or build the extension yourself. For more details, please visit: https://github.com/yamadashy/repomix 🌐 Learn More: - Official Website: https://repomix.com - GitHub Repository: https://github.com/yamadashy/repomix ## /browser/app/_locales/en/messages.json ```json path="/browser/app/_locales/en/messages.json" { "appDescription": { "message": "Add a button to quickly access Repomix from GitHub repositories", "description": "The description of the extension" }, "openWithRepomix": { "message": "Open with Repomix", "description": "Button tooltip text for opening repository with Repomix" } } ``` ## /browser/app/_locales/es/detailed-description.txt Esta extensión de navegador añade un botón "Repomix" conveniente a las páginas de repositorio de GitHub, permitiéndote empaquetar y analizar rápidamente repositorios con Repomix - una herramienta poderosa que transforma repositorios de software en archivos únicos adaptados para IA. 🛠️ Características: - Acceso con un clic a Repomix para cualquier repositorio de GitHub - ¡Más características emocionantes llegarán pronto - mantente atento a la funcionalidad mejorada! 🚀 Cómo usar: 1. Instalar la extensión 2. Navegar a cualquier repositorio de GitHub 3. Hacer clic en el botón "Repomix" en el encabezado del repositorio 4. Serás redirigido a la interfaz web de Repomix 5. Generar una versión empaquetada del repositorio para análisis de IA ✨ ¿Qué es Repomix? Repomix es una herramienta innovadora que empaqueta toda tu base de código en un solo archivo integral optimizado para análisis de IA. Soporta múltiples formatos de salida (XML, Markdown, texto plano), incluye verificaciones de seguridad para excluir información sensible y proporciona métricas detalladas sobre tu código. 💻 Código Abierto: Tanto esta extensión como Repomix en sí son proyectos de código abierto. Puedes ver el código fuente, contribuir o construir la extensión tú mismo. Para más detalles, por favor visita: https://github.com/yamadashy/repomix 🌐 Aprende Más: - Sitio Web Oficial: https://repomix.com - Repositorio GitHub: https://github.com/yamadashy/repomix ## /browser/app/_locales/es/messages.json ```json path="/browser/app/_locales/es/messages.json" { "appDescription": { "message": "Añade un botón para acceder rápidamente a Repomix desde repositorios de GitHub", "description": "The description of the extension" }, "openWithRepomix": { "message": "Abrir con Repomix", "description": "Button tooltip text for opening repository with Repomix" } } ``` ## /browser/app/_locales/fr/detailed-description.txt Cette extension de navigateur ajoute un bouton "Repomix" pratique aux pages de dépôt GitHub, vous permettant d'empaqueter et d'analyser rapidement les dépôts avec Repomix - un outil puissant qui transforme les dépôts logiciels en fichiers uniques adaptés à l'IA. 🛠️ Fonctionnalités: - Accès en un clic à Repomix pour n'importe quel dépôt GitHub - Plus de fonctionnalités passionnantes arrivent bientôt - restez à l'écoute pour des fonctionnalités améliorées ! 🚀 Utilisation: 1. Installer l'extension 2. Naviguer vers n'importe quel dépôt GitHub 3. Cliquer sur le bouton "Repomix" dans l'en-tête du dépôt 4. Vous serez redirigé vers l'interface web Repomix 5. Générer une version empaquetée du dépôt pour l'analyse IA ✨ Qu'est-ce que Repomix ? Repomix est un outil innovant qui empaquette l'ensemble de votre base de code en un seul fichier complet optimisé pour l'analyse IA. Il prend en charge plusieurs formats de sortie (XML, Markdown, texte brut), inclut des vérifications de sécurité pour exclure les informations sensibles et fournit des métriques détaillées sur votre code. 💻 Open Source: Cette extension et Repomix lui-même sont des projets open source. Vous pouvez consulter le code source, contribuer ou construire l'extension vous-même. Pour plus de détails, veuillez visiter: https://github.com/yamadashy/repomix 🌐 En savoir plus: - Site officiel: https://repomix.com - Dépôt GitHub: https://github.com/yamadashy/repomix ## /browser/app/_locales/fr/messages.json ```json path="/browser/app/_locales/fr/messages.json" { "appDescription": { "message": "Ajoute un bouton pour accéder rapidement à Repomix depuis les dépôts GitHub", "description": "The description of the extension" }, "openWithRepomix": { "message": "Ouvrir avec Repomix", "description": "Button tooltip text for opening repository with Repomix" } } ``` ## /browser/app/_locales/ja/detailed-description.txt このブラウザ拡張機能は、GitHubリポジトリページに便利な「Repomix」ボタンを追加し、ソフトウェアリポジトリをAI分析に適した単一ファイルに変換する強力なツールであるRepomixで、リポジトリを素早くパッケージ化・分析できるようにします。 🛠️ 機能: - あらゆるGitHubリポジトリへのワンクリックアクセス - さらなるエキサイティングな機能を近日リリース予定 - 機能強化にご期待ください! 🚀 使用方法: 1. 拡張機能をインストール 2. 任意のGitHubリポジトリに移動 3. リポジトリヘッダーの「Repomix」ボタンをクリック 4. Repomixウェブインターフェースにリダイレクトされます 5. AI分析用のリポジトリのパッケージ版を生成 ✨ Repomixとは? Repomixは、コードベース全体をAI分析用に最適化された単一の包括的ファイルにパッケージ化する革新的なツールです。複数の出力フォーマット(XML、Markdown、プレーンテキスト)をサポートし、機密情報を除外するセキュリティチェックを含み、コードに関する詳細なメトリクスを提供します。 💻 オープンソース: この拡張機能とRepomix自体の両方がオープンソースプロジェクトです。ソースコードを確認したり、貢献したり、自分で拡張機能をビルドしたりできます。 詳細については、以下をご覧ください: https://github.com/yamadashy/repomix 🌐 詳細情報: - 公式ウェブサイト: https://repomix.com - GitHubリポジトリ: https://github.com/yamadashy/repomix ## /browser/app/_locales/ja/messages.json ```json path="/browser/app/_locales/ja/messages.json" { "appDescription": { "message": "GitHubリポジトリからRepomixへ簡単にアクセスするためのボタンを追加します", "description": "The description of the extension" }, "openWithRepomix": { "message": "Repomixで開く", "description": "Button tooltip text for opening repository with Repomix" } } ``` ## /browser/app/_locales/ko/detailed-description.txt 이 브라우저 확장 프로그램은 GitHub 리포지토리 페이지에 편리한 "Repomix" 버튼을 추가하여, 소프트웨어 리포지토리를 AI 분석에 적합한 단일 파일로 변환하는 강력한 도구인 Repomix로 리포지토리를 빠르게 패키지화하고 분석할 수 있게 해줍니다. 🛠️ 기능: - 모든 GitHub 리포지토리에 원클릭으로 Repomix 액세스 - 더 흥미로운 기능들이 곧 출시 예정 - 기능 향상을 기대해 주세요! 🚀 사용 방법: 1. 확장 프로그램 설치 2. 임의의 GitHub 리포지토리로 이동 3. 리포지토리 헤더의 "Repomix" 버튼 클릭 4. Repomix 웹 인터페이스로 리디렉션됩니다 5. AI 분석용 리포지토리의 패키지 버전 생성 ✨ Repomix란? Repomix는 전체 코드베이스를 AI 분석에 최적화된 포괄적인 단일 파일로 패키지화하는 혁신적인 도구입니다. 여러 출력 형식(XML, Markdown, 일반 텍스트)을 지원하고, 민감한 정보를 제외하는 보안 검사를 포함하며, 코드에 대한 자세한 메트릭을 제공합니다. 💻 오픈 소스: 이 확장 프로그램과 Repomix 자체 모두 오픈 소스 프로젝트입니다. 소스 코드를 확인하거나, 기여하거나, 직접 확장 프로그램을 빌드할 수 있습니다. 자세한 내용은 다음을 참조하세요: https://github.com/yamadashy/repomix 🌐 자세히 알아보기: - 공식 웹사이트: https://repomix.com - GitHub 리포지토리: https://github.com/yamadashy/repomix ## /browser/app/_locales/ko/messages.json ```json path="/browser/app/_locales/ko/messages.json" { "appDescription": { "message": "GitHub 리포지토리에서 Repomix에 빠르게 액세스할 수 있는 버튼을 추가합니다", "description": "The description of the extension" }, "openWithRepomix": { "message": "Repomix로 열기", "description": "Button tooltip text for opening repository with Repomix" } } ``` ## /browser/app/_locales/pt_BR/detailed-description.txt Esta extensão de navegador adiciona um botão "Repomix" conveniente às páginas de repositório do GitHub, permitindo que você empacote e analise rapidamente repositórios com Repomix - uma ferramenta poderosa que transforma repositórios de software em arquivos únicos adequados para IA. 🛠️ Recursos: - Acesso com um clique ao Repomix para qualquer repositório do GitHub - Mais recursos emocionantes chegando em breve - fique atento para funcionalidades aprimoradas! 🚀 Como usar: 1. Instalar a extensão 2. Navegar para qualquer repositório do GitHub 3. Clicar no botão "Repomix" no cabeçalho do repositório 4. Você será redirecionado para a interface web do Repomix 5. Gerar uma versão empacotada do repositório para análise de IA ✨ O que é Repomix? Repomix é uma ferramenta inovadora que empacota toda a sua base de código em um único arquivo abrangente otimizado para análise de IA. Suporta múltiplos formatos de saída (XML, Markdown, texto simples), inclui verificações de segurança para excluir informações sensíveis e fornece métricas detalhadas sobre seu código. 💻 Código Aberto: Tanto esta extensão quanto o próprio Repomix são projetos de código aberto. Você pode visualizar o código fonte, contribuir ou construir a extensão você mesmo. Para mais detalhes, por favor visite: https://github.com/yamadashy/repomix 🌐 Saiba Mais: - Site Oficial: https://repomix.com - Repositório GitHub: https://github.com/yamadashy/repomix ## /browser/app/_locales/pt_BR/messages.json ```json path="/browser/app/_locales/pt_BR/messages.json" { "appDescription": { "message": "Adiciona um botão para acessar rapidamente o Repomix a partir de repositórios do GitHub", "description": "The description of the extension" }, "openWithRepomix": { "message": "Abrir com Repomix", "description": "Button tooltip text for opening repository with Repomix" } } ``` ## /browser/app/_locales/zh_CN/detailed-description.txt 这个浏览器扩展程序为GitHub仓库页面添加了一个便捷的"Repomix"按钮,让您可以快速使用Repomix打包和分析仓库。Repomix是一个强大的工具,可以将软件仓库转换为AI分析友好的单个文件。 🛠️ 功能: - 一键访问任何GitHub仓库的Repomix - 更多精彩功能即将推出 - 敬请期待功能增强! 🚀 使用方法: 1. 安装扩展程序 2. 导航到任何GitHub仓库 3. 点击仓库标题中的"Repomix"按钮 4. 您将被重定向到Repomix网页界面 5. 为AI分析生成仓库的打包版本 ✨ 什么是Repomix? Repomix是一个创新工具,可以将您的整个代码库打包成一个针对AI分析优化的综合文件。它支持多种输出格式(XML、Markdown、纯文本),包含安全检查以排除敏感信息,并提供有关代码的详细指标。 💻 开源: 这个扩展程序和Repomix本身都是开源项目。您可以查看源代码、贡献代码或自己构建扩展程序。 更多详情,请访问: https://github.com/yamadashy/repomix 🌐 了解更多: - 官方网站: https://repomix.com - GitHub仓库: https://github.com/yamadashy/repomix ## /browser/app/_locales/zh_CN/messages.json ```json path="/browser/app/_locales/zh_CN/messages.json" { "appDescription": { "message": "为GitHub仓库添加一个按钮,快速访问Repomix", "description": "The description of the extension" }, "openWithRepomix": { "message": "使用Repomix打开", "description": "Button tooltip text for opening repository with Repomix" } } ``` ## /browser/app/_locales/zh_TW/detailed-description.txt 這個瀏覽器擴充功能為GitHub儲存庫頁面添加了一個便捷的「Repomix」按鈕,讓您可以快速使用Repomix打包和分析儲存庫。Repomix是一個強大的工具,可以將軟體儲存庫轉換為AI分析友好的單一檔案。 🛠️ 功能: - 一鍵存取任何GitHub儲存庫的Repomix - 更多精彩功能即將推出 - 敬請期待功能增強! 🚀 使用方法: 1. 安裝擴充功能 2. 導航到任何GitHub儲存庫 3. 點擊儲存庫標題中的「Repomix」按鈕 4. 您將被重新導向到Repomix網頁介面 5. 為AI分析生成儲存庫的打包版本 ✨ 什麼是Repomix? Repomix是一個創新工具,可以將您的整個程式碼庫打包成一個針對AI分析最佳化的綜合檔案。它支援多種輸出格式(XML、Markdown、純文字),包含安全檢查以排除敏感資訊,並提供有關程式碼的詳細指標。 💻 開源: 這個擴充功能和Repomix本身都是開源專案。您可以查看原始碼、貢獻程式碼或自己建置擴充功能。 更多詳情,請造訪: https://github.com/yamadashy/repomix 🌐 了解更多: - 官方網站: https://repomix.com - GitHub儲存庫: https://github.com/yamadashy/repomix ## /browser/app/_locales/zh_TW/messages.json ```json path="/browser/app/_locales/zh_TW/messages.json" { "appDescription": { "message": "為GitHub儲存庫添加一個按鈕,快速存取Repomix", "description": "The description of the extension" }, "openWithRepomix": { "message": "使用Repomix開啟", "description": "Button tooltip text for opening repository with Repomix" } } ``` ## /browser/app/images/icon-128.png Binary file available at https://raw.githubusercontent.com/yamadashy/repomix/refs/heads/main/browser/app/images/icon-128.png ## /browser/app/images/icon-16.png Binary file available at https://raw.githubusercontent.com/yamadashy/repomix/refs/heads/main/browser/app/images/icon-16.png ## /browser/app/images/icon-19.png Binary file available at https://raw.githubusercontent.com/yamadashy/repomix/refs/heads/main/browser/app/images/icon-19.png ## /browser/app/images/icon-32.png Binary file available at https://raw.githubusercontent.com/yamadashy/repomix/refs/heads/main/browser/app/images/icon-32.png ## /browser/app/images/icon-38.png Binary file available at https://raw.githubusercontent.com/yamadashy/repomix/refs/heads/main/browser/app/images/icon-38.png ## /browser/app/images/icon-48.png Binary file available at https://raw.githubusercontent.com/yamadashy/repomix/refs/heads/main/browser/app/images/icon-48.png ## /browser/app/images/icon-64.png Binary file available at https://raw.githubusercontent.com/yamadashy/repomix/refs/heads/main/browser/app/images/icon-64.png ## /browser/app/images/icon.svg ```svg path="/browser/app/images/icon.svg" <?xml version="1.0" encoding="utf-8"?> <svg xmlns="http://www.w3.org/2000/svg" viewBox="96.259 93.171 300 300" xmlns:bx="https://boxy-svg.com"> <g id="group-1" transform="matrix(1.160932, 0, 0, 1.160932, 97.635941, 94.725143)" style=""> <path style="fill-rule: nonzero; fill-opacity: 1; stroke-width: 2; fill: rgb(234, 127, 58);" d="M 128.03 -1.486 L 21.879 65.349 L 21.848 190.25 L 127.979 256.927 L 234.2 190.27 L 234.197 65.463 L 128.03 -1.486 Z M 208.832 70.323 L 127.984 121.129 L 47.173 70.323 L 128.144 19.57 L 208.832 70.323 Z M 39.669 86.367 L 119.188 136.415 L 119.255 230.529 L 39.637 180.386 L 39.669 86.367 Z M 136.896 230.506 L 136.887 136.575 L 216.469 86.192 L 216.417 180.46 L 136.896 230.506 Z M 136.622 230.849"/> </g> </svg> ``` ## /browser/app/manifest.json ```json path="/browser/app/manifest.json" { "name": "Repomix", "short_name": "repomix", "version": "1.0.0", "manifest_version": 3, "description": "__MSG_appDescription__", "default_locale": "en", "icons": { "16": "images/icon-16.png", "19": "images/icon-19.png", "32": "images/icon-32.png", "38": "images/icon-38.png", "48": "images/icon-48.png", "64": "images/icon-64.png", "128": "images/icon-128.png" }, "minimum_chrome_version": "88.0", "content_scripts": [ { "matches": [ "https://github.com/*" ], "css": [ "styles/content.css" ], "js": [ "scripts/content.js" ], "run_at": "document_start", "all_frames": true } ], "background": { "service_worker": "scripts/background.js" }, "permissions": [ "scripting" ], "web_accessible_resources": [ { "resources": [ "images/icon-16.png", "images/icon-19.png", "images/icon-32.png", "images/icon-38.png", "images/icon-48.png", "images/icon-64.png", "images/icon-128.png" ], "matches": ["https://github.com/*"] } ], "host_permissions": [ "https://github.com/*" ], "__firefox__background": { "scripts": [ "scripts/background.js" ] }, "__firefox__browser_specific_settings": { "gecko": { "id": "{3AB97897-F299-4DBC-8084-A92813FE2685}", "strict_min_version": "102.0" } } } ``` ## /browser/app/scripts/background.ts ```ts path="/browser/app/scripts/background.ts" const injectContentToTab = async (tab: chrome.tabs.Tab): Promise<void> => { // Skip if URL is undefined if (!tab.url) { return; } // Skip if tab is discarded if (tab.discarded) { return; } // Skip if tab ID is undefined if (tab.id === undefined) { return; } // Skip if not a GitHub URL if (!tab.url.startsWith('https://github.com/')) { return; } try { const manifest = chrome.runtime.getManifest(); // Inject CSS if (manifest.content_scripts?.[0]?.css) { await chrome.scripting.insertCSS({ target: { tabId: tab.id }, files: manifest.content_scripts[0].css, }); } // Inject JavaScript if (manifest.content_scripts?.[0]?.js) { await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: manifest.content_scripts[0].js, }); } } catch (error) { console.error('Error injecting content script:', error); } }; // Update extension content for tabs chrome.tabs.query({}, async (tabs: chrome.tabs.Tab[]) => { for (const tab of tabs) { try { await injectContentToTab(tab); } catch (e) { console.error(e); } } }); ``` ## /browser/app/scripts/content.ts ```ts path="/browser/app/scripts/content.ts" interface RepositoryInfo { owner: string; repo: string; url: string; } interface RepomixButtonOptions { text: string; href: string; iconSrc?: string; } // Constants const BUTTON_CLASS = 'repomix-button'; const ICON_SIZE = 16; const REPOMIX_BASE_URL = 'https://repomix.com'; const BUTTON_TEXT = 'Repomix'; const DEFAULT_ICON_PATH = 'images/icon-64.png'; // Button functions function isRepomixButtonAlreadyExists(): boolean { return document.querySelector(`.${BUTTON_CLASS}`) !== null; } function createRepomixButton(options: RepomixButtonOptions): HTMLElement { const container = document.createElement('li'); const button = document.createElement('a'); button.className = `${BUTTON_CLASS} btn-sm btn BtnGroup-item`; button.href = options.href; button.target = '_blank'; button.rel = 'noopener noreferrer'; button.title = chrome.i18n.getMessage('openWithRepomix'); // Create octicon container const octicon = document.createElement('span'); octicon.className = 'octicon'; octicon.setAttribute('aria-hidden', 'true'); // Use chrome.runtime.getURL for the icon const iconSrc = options.iconSrc || chrome.runtime.getURL(DEFAULT_ICON_PATH); octicon.innerHTML = `<img src="${iconSrc}" width="${ICON_SIZE}" height="${ICON_SIZE}" alt="Repomix">`; button.appendChild(octicon); // Add button text const textSpan = document.createElement('span'); textSpan.textContent = options.text; button.appendChild(textSpan); container.appendChild(button); return container; } // GitHub functions function extractRepositoryInfo(): RepositoryInfo | null { const pathMatch = window.location.pathname.match(/^\/([^/]+)\/([^/]+)/); if (!pathMatch) { return null; } const [, owner, repo] = pathMatch; return { owner, repo, url: `https://github.com/${owner}/${repo}`, }; } function findNavigationContainer(): Element | null { return document.querySelector('ul.pagehead-actions'); } function isRepositoryPage(): boolean { // Check if we're on a repository page (not user profile, organization, etc.) const pathParts = window.location.pathname.split('/').filter(Boolean); return pathParts.length >= 2 && !pathParts[0].startsWith('@'); } // Main integration functions function addRepomixButton(): void { try { // Check if button already exists if (isRepomixButtonAlreadyExists()) { return; } // Check if we're on a repository page if (!isRepositoryPage()) { return; } // Get repository information const repoInfo = extractRepositoryInfo(); if (!repoInfo) { return; } // Find navigation container const navContainer = findNavigationContainer(); if (!navContainer) { return; } // Create Repomix URL const repomixUrl = `${REPOMIX_BASE_URL}/?repo=${encodeURIComponent(repoInfo.url)}`; // Create button const buttonContainer = createRepomixButton({ text: BUTTON_TEXT, href: repomixUrl, }); // Insert button at the beginning (left side) navContainer.prepend(buttonContainer); console.log(`Repomix button added for ${repoInfo.owner}/${repoInfo.repo}`); } catch (error) { console.error('Error adding Repomix button:', error); } } function observePageChanges(): void { // Observe changes to handle GitHub's dynamic navigation const observer = new MutationObserver(() => { addRepomixButton(); }); observer.observe(document.body, { childList: true, subtree: true, }); // Also listen for popstate events (back/forward navigation) window.addEventListener('popstate', () => { setTimeout(() => addRepomixButton(), 100); }); } function initRepomixIntegration(): void { try { addRepomixButton(); observePageChanges(); } catch (error) { console.error('Error initializing Repomix integration:', error); } } // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => initRepomixIntegration()); } else { initRepomixIntegration(); } ``` ## /browser/app/styles/content.css ```css path="/browser/app/styles/content.css" .repomix-button.btn-sm.btn.BtnGroup-item { display: inline-flex; align-items: center; gap: 0px; } .repomix-button .octicon { display: inline-flex; align-items: center; } .repomix-button .octicon img { vertical-align: text-bottom; } ``` ## /browser/package.json ```json path="/browser/package.json" { "private": true, "name": "repomix", "description": "A browser extension that adds a Repomix button to GitHub repositories", "scripts": { "dev": "webextension-toolbox dev", "build": "webextension-toolbox build", "build-all": "npm run build chrome && npm run build firefox && npm run build edge", "generate-icons": "tsx scripts/generate-icons.ts", "lint": "npm run lint-tsc", "lint-tsc": "tsc --noEmit", "test": "vitest", "archive": "git archive HEAD -o storage/source.zip" }, "keywords": [ "chrome", "extension", "firefox", "addon", "repomix", "github", "documentation" ], "author": "yamadashy", "license": "MIT", "devDependencies": { "@biomejs/biome": "^1.9.4", "@secretlint/secretlint-rule-preset-recommend": "^9.3.1", "@types/chrome": "^0.0.323", "@types/node": "^22.10.2", "@types/webextension-polyfill": "^0.10.7", "@webextension-toolbox/webextension-toolbox": "^7.1.1", "secretlint": "^9.3.1", "sharp": "^0.34.1", "tsx": "^4.19.2", "typescript": "^5.8.3" }, "browserslist": [ "last 2 versions, not dead, > 0.2%" ], "engines": { "node": ">=24.0.1" } } ``` ## /browser/promo/Chrome-Webstore-Icon_128x128.png Binary file available at https://raw.githubusercontent.com/yamadashy/repomix/refs/heads/main/browser/promo/Chrome-Webstore-Icon_128x128.png ## /browser/promo/Promo-Image-Marquee_1400x560.png Binary file available at https://raw.githubusercontent.com/yamadashy/repomix/refs/heads/main/browser/promo/Promo-Image-Marquee_1400x560.png ## /browser/promo/Promo-Image-Small_440x280.png Binary file available at https://raw.githubusercontent.com/yamadashy/repomix/refs/heads/main/browser/promo/Promo-Image-Small_440x280.png ## /browser/promo/Screenshot_1280x800.png Binary file available at https://raw.githubusercontent.com/yamadashy/repomix/refs/heads/main/browser/promo/Screenshot_1280x800.png ## /browser/scripts/generate-icons.ts ```ts path="/browser/scripts/generate-icons.ts" import fs from 'node:fs'; import path from 'node:path'; import sharp from 'sharp'; interface IconSize { width: number; height: number; } const ICON_SIZES: readonly number[] = [16, 19, 32, 38, 48, 64, 128] as const; const INPUT_SVG_PATH = path.join(__dirname, '../app/images/icon.svg'); const OUTPUT_DIR = path.join(__dirname, '../app/images'); /** * Ensures the output directory exists */ function ensureOutputDirectory(): void { if (!fs.existsSync(OUTPUT_DIR)) { fs.mkdirSync(OUTPUT_DIR, { recursive: true }); console.log(`Created output directory: ${OUTPUT_DIR}`); } } /** * Generates a PNG icon of the specified size */ async function generateIcon(size: number): Promise<void> { try { const outputPath = path.join(OUTPUT_DIR, `icon-${size}.png`); await sharp(INPUT_SVG_PATH).resize(size, size).png().toFile(outputPath); console.log(`✅ Generated ${size}x${size} icon: ${outputPath}`); } catch (error) { console.error(`❌ Error generating ${size}x${size} icon:`, error); throw error; } } /** * Validates that the input SVG file exists */ function validateInputFile(): void { if (!fs.existsSync(INPUT_SVG_PATH)) { throw new Error(`Input SVG file not found: ${INPUT_SVG_PATH}`); } console.log(`📁 Input SVG: ${INPUT_SVG_PATH}`); } /** * Main function to generate all icon sizes */ async function generateAllIcons(): Promise<void> { console.log('🚀 Starting icon generation...'); try { validateInputFile(); ensureOutputDirectory(); // Generate all icons in parallel const iconPromises = ICON_SIZES.map((size) => generateIcon(size)); await Promise.all(iconPromises); console.log(`🎉 Successfully generated ${ICON_SIZES.length} icons!`); console.log(`📂 Output directory: ${OUTPUT_DIR}`); } catch (error) { console.error('💥 Failed to generate icons:', error); process.exit(1); } } // Execute if this file is run directly if (require.main === module) { void generateAllIcons(); } export { generateAllIcons, generateIcon, ICON_SIZES }; ``` ## /browser/tests/repomix-integration.test.ts ```ts path="/browser/tests/repomix-integration.test.ts" import { beforeEach, describe, expect, it } from 'vitest'; // Mock DOM environment Object.defineProperty(window, 'location', { value: { pathname: '/yamadashy/repomix', href: 'https://github.com/yamadashy/repomix', }, writable: true, }); describe('RepomixIntegration', () => { beforeEach(() => { // Reset DOM document.body.innerHTML = ''; // Mock GitHub page structure const navActions = document.createElement('ul'); navActions.className = 'pagehead-actions'; document.body.appendChild(navActions); }); it('should extract repository information correctly', () => { // This is a placeholder test since we're testing static methods // In a real scenario, we'd need to import and test the actual classes const pathMatch = window.location.pathname.match(/^\/([^/]+)\/([^/]+)/); expect(pathMatch).toBeTruthy(); if (pathMatch) { const [, owner, repo] = pathMatch; expect(owner).toBe('yamadashy'); expect(repo).toBe('repomix'); } }); it('should construct correct Repomix URL', () => { const repoUrl = 'https://github.com/yamadashy/repomix'; const expectedUrl = `https://repomix.com/?repo=${encodeURIComponent(repoUrl)}`; expect(expectedUrl).toBe('https://repomix.com/?repo=https%3A%2F%2Fgithub.com%2Fyamadashy%2Frepomix'); }); it('should find navigation container', () => { const navContainer = document.querySelector('ul.pagehead-actions'); expect(navContainer).toBeTruthy(); }); }); ``` ## /browser/tsconfig.json ```json path="/browser/tsconfig.json" { "compilerOptions": { "strict": true, "target": "esnext", "module": "commonjs", "moduleResolution": "node", "esModuleInterop": true, "lib": [ "dom", "esnext" ], "allowJs": false, "noImplicitAny": true, "removeComments": true, "skipLibCheck": true, "sourceMap": true } } ``` ## /browser/vitest.config.ts ```ts path="/browser/vitest.config.ts" import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { environment: 'jsdom', globals: true, setupFiles: [], watch: false, }, resolve: { extensions: ['.ts', '.js'], }, }); ``` ## /llms-install.md # Repomix MCP Server Installation Guide This guide is specifically designed for AI agents like Cline to install and configure the Repomix MCP server for use with LLM applications like Claude Desktop, Cursor, Roo Code, and Cline. ## Overview Repomix MCP server is a powerful tool that packages local or remote codebases into AI-friendly formats. It allows AI assistants to analyze code efficiently without manual file preparation, optimizing token usage and providing consistent output. ## Prerequisites Before installation, you need: 1. Node.js 18.0.0 or higher 2. npm (Node Package Manager) ## Installation and Configuration ### Configure MCP Settings Add the Repomix MCP server configuration to your MCP settings file based on your LLM client: #### Configuration File Locations - Cline (VS Code Extension): `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` - Roo Code (VS Code Extension): `~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json` - Claude Desktop: `~/Library/Application Support/Claude/claude_desktop_config.json` - Cursor: `[project root]/.cursor/mcp.json` Add this configuration to your chosen client's settings file: ```json { "mcpServers": { "repomix": { "command": "npx", "args": [ "-y", "repomix", "--mcp" ], "disabled": false, "autoApprove": [] } } } ``` This configuration uses `npx` to run Repomix directly without requiring a global installation. ## Available MCP Tools Once configured, you'll have access to these Repomix tools: ### 1. pack_codebase This tool packages a local code directory into a consolidated XML file for AI analysis. It analyzes the codebase structure, extracts relevant code content, and generates a comprehensive report including metrics, file tree, and formatted code content. **Parameters:** - `directory`: (Required) Absolute path to the directory to pack - `compress`: (Optional, default: false) Enable Tree-sitter compression to extract essential code signatures and structure while removing implementation details. Reduces token usage by ~70% while preserving semantic meaning. Generally not needed since grep_repomix_output allows incremental content retrieval. Use only when you specifically need the entire codebase content for large repositories. - `includePatterns`: (Optional) Specify files to include using fast-glob patterns. Multiple patterns can be comma-separated (e.g., "**/*.{js,ts}", "src/**,docs/**"). Only matching files will be processed. - `ignorePatterns`: (Optional) Specify additional files to exclude using fast-glob patterns. Multiple patterns can be comma-separated (e.g., "test/**,*.spec.js", "node_modules/**,dist/**"). These patterns supplement .gitignore and built-in exclusions. - `topFilesLength`: (Optional, default: 10) Number of largest files by size to display in the metrics summary for codebase analysis. **Example:** ```json { "directory": "/path/to/your/project", "compress": false, "includePatterns": "src/**/*.ts,**/*.md", "ignorePatterns": "**/*.log,tmp/", "topFilesLength": 10 } ``` ### 2. pack_remote_repository This tool fetches, clones, and packages a GitHub repository into a consolidated XML file for AI analysis. It automatically clones the remote repository, analyzes its structure, and generates a comprehensive report. **Parameters:** - `remote`: (Required) GitHub repository URL or user/repo format (e.g., "yamadashy/repomix", "https://github.com/user/repo", or "https://github.com/user/repo/tree/branch") - `compress`: (Optional, default: false) Enable Tree-sitter compression to extract essential code signatures and structure while removing implementation details. Reduces token usage by ~70% while preserving semantic meaning. Generally not needed since grep_repomix_output allows incremental content retrieval. Use only when you specifically need the entire codebase content for large repositories. - `includePatterns`: (Optional) Specify files to include using fast-glob patterns. Multiple patterns can be comma-separated (e.g., "**/*.{js,ts}", "src/**,docs/**"). Only matching files will be processed. - `ignorePatterns`: (Optional) Specify additional files to exclude using fast-glob patterns. Multiple patterns can be comma-separated (e.g., "test/**,*.spec.js", "node_modules/**,dist/**"). These patterns supplement .gitignore and built-in exclusions. - `topFilesLength`: (Optional, default: 10) Number of largest files by size to display in the metrics summary for codebase analysis. **Example:** ```json { "remote": "yamadashy/repomix", "compress": false, "includePatterns": "src/**/*.ts,**/*.md", "ignorePatterns": "**/*.log,tmp/", "topFilesLength": 10 } ``` ### 3. read_repomix_output This tool reads the contents of a Repomix-generated output file. Supports partial reading with line range specification for large files. This tool is designed for environments where direct file system access is limited. **Parameters:** - `outputId`: (Required) ID of the Repomix output file to read - `startLine`: (Optional) Starting line number (1-based, inclusive). If not specified, reads from beginning. - `endLine`: (Optional) Ending line number (1-based, inclusive). If not specified, reads to end. **Features:** - Specifically designed for web-based environments or sandboxed applications - Retrieves the content of previously generated outputs using their ID - Provides secure access to packed codebase without requiring file system access - Supports partial reading for large files **Example:** ```json { "outputId": "8f7d3b1e2a9c6054", "startLine": 100, "endLine": 200 } ``` ### 4. grep_repomix_output This tool searches for patterns in a Repomix output file using grep-like functionality with JavaScript RegExp syntax. Returns matching lines with optional context lines around matches. **Parameters:** - `outputId`: (Required) ID of the Repomix output file to search - `pattern`: (Required) Search pattern (JavaScript RegExp regular expression syntax) - `contextLines`: (Optional, default: 0) Number of context lines to show before and after each match. Overridden by beforeLines/afterLines if specified. - `beforeLines`: (Optional) Number of context lines to show before each match (like grep -B). Takes precedence over contextLines. - `afterLines`: (Optional) Number of context lines to show after each match (like grep -A). Takes precedence over contextLines. - `ignoreCase`: (Optional, default: false) Perform case-insensitive matching **Features:** - Uses JavaScript RegExp syntax for powerful pattern matching - Supports context lines for better understanding of matches - Allows separate control of before/after context lines - Case-sensitive and case-insensitive search options **Example:** ```json { "outputId": "8f7d3b1e2a9c6054", "pattern": "function\\s+\\w+\\(", "contextLines": 3, "ignoreCase": false } ``` ### 5. file_system_read_file This tool reads a file from the local file system using an absolute path. Includes built-in security validation to detect and prevent access to files containing sensitive information. **Parameters:** - `path`: (Required) Absolute path to the file to read **Security features:** - Implements security validation using [Secretlint](https://github.com/secretlint/secretlint) - Prevents access to files containing sensitive information (API keys, passwords, secrets) - Validates absolute paths to prevent directory traversal attacks **Example:** ```json { "path": "/absolute/path/to/file.txt" } ``` ### 6. file_system_read_directory This tool lists the contents of a directory using an absolute path. Returns a formatted list showing files and subdirectories with clear indicators. **Parameters:** - `path`: (Required) Absolute path to the directory to list **Features:** - Shows files and directories with clear indicators (`[FILE]` or `[DIR]`) - Provides safe directory traversal with proper error handling - Validates paths and ensures they are absolute - Useful for exploring project structure and understanding codebase organization **Example:** ```json { "path": "/absolute/path/to/directory" } ``` ## Verify Installation To verify the installation is working: 1. Restart your LLM application (Cline, Claude Desktop, etc.) 2. Test the connection by running a simple command like: ``` Please package the local directory /path/to/project for AI analysis using Repomix. ``` or ``` Please fetch and package the GitHub repository yamadashy/repomix for AI analysis. ``` ## Usage Examples Here are some examples of how to use Repomix MCP server with AI assistants: ### Local Codebase Analysis ``` Can you analyze the code in my project at /path/to/project? Please use Repomix to package it first. ``` ### Remote Repository Analysis ``` I'd like you to review the code in the GitHub repository username/repo. Please use Repomix to package it first. ``` ### Specific File Types Analysis ``` Please package my project at /path/to/project, but only include TypeScript files and markdown documentation. ``` ## Troubleshooting ### Common Issues and Solutions 1. **MCP server connection issues** - Verify the syntax in your MCP settings file is correct - Ensure you have an active internet connection (needed for npx to fetch the package) - Check if any other MCP servers are causing conflicts 2. **Packaging failures** - Verify the specified directory or repository exists - Check if you have sufficient disk space - For remote repositories, ensure you have internet connectivity - Try with simpler parameters first, then add complexity 3. **JSON parsing errors in configuration** - Make sure your MCP settings file is properly formatted - Verify all paths use forward slashes, even on Windows - Check for any missing commas or brackets in the configuration ## Additional Information For more detailed information, visit the [Repomix official documentation](https://repomix.com). You can also join the [Discord community](https://discord.gg/wNYzTwZFku) for support and questions. ## /package.json ```json path="/package.json" { "name": "repomix", "version": "0.3.7", "description": "A tool to pack repository contents to single file for AI consumption", "main": "./lib/index.js", "types": "./lib/index.d.ts", "exports": { ".": { "import": { "types": "./lib/index.d.ts", "default": "./lib/index.js" }, "require": { "types": "./lib/index.d.ts", "default": "./lib/index.js" }, "default": "./lib/index.js" } }, "bin": "./bin/repomix.cjs", "scripts": { "clean": "rimraf lib", "build": "npm run clean && tsc -p tsconfig.build.json --sourceMap --declaration", "lint": "npm run lint-biome && npm run lint-ts && npm run lint-secretlint", "lint-biome": "biome check --write", "lint-ts": "tsc --noEmit", "lint-secretlint": "secretlint \"**/*\" --secretlintignore .gitignore", "test": "vitest", "test-coverage": "vitest run --coverage", "repomix": "npm run build && node --trace-warnings bin/repomix.cjs", "repomix-src": "npm run repomix -- --include 'src,tests'", "repomix-website": "npm run repomix -- --include 'website'", "website": "docker compose -f website/compose.yml up --build", "website-generate-schema": "tsx website/client/scripts/generateSchema.ts", "npm-publish": "npm run npm-publish-check-branch && npm run lint && npm run test-coverage && npm run build && npm publish", "npm-publish-check-branch": "git branch --show-current | grep -q '^main{{contextString}}#39; || (echo 'Release is only allowed from the main branch' && exit 1)", "npm-release-patch": "npm version patch && npm run npm-publish", "npm-release-minor": "npm version minor && npm run npm-publish", "npm-release-prerelease": "npm version prerelease && npm run npm-publish" }, "keywords": [ "repository", "generative-ai", "ai", "llm", "source-code", "code-analysis", "codebase-packer", "development-tool", "ai-assistant", "code-review" ], "repository": { "type": "git", "url": "git://github.com/yamadashy/repomix.git" }, "bugs": { "url": "https://github.com/yamadashy/repomix/issues" }, "author": "Kazuki Yamada <koukun0120@gmail.com>", "homepage": "https://github.com/yamadashy/repomix", "license": "MIT", "publishConfig": { "access": "public" }, "type": "module", "dependencies": { "@clack/prompts": "^0.10.1", "@modelcontextprotocol/sdk": "^1.11.0", "@secretlint/core": "^9.3.1", "@secretlint/secretlint-rule-preset-recommend": "^9.3.1", "cli-spinners": "^2.9.2", "clipboardy": "^4.0.0", "commander": "^14.0.0", "fast-xml-parser": "^5.2.0", "git-url-parse": "^16.1.0", "globby": "^14.1.0", "handlebars": "^4.7.8", "iconv-lite": "^0.6.3", "istextorbinary": "^9.5.0", "jschardet": "^3.1.4", "json5": "^2.2.3", "log-update": "^6.1.0", "minimatch": "^10.0.1", "picocolors": "^1.1.1", "piscina": "^4.9.2", "strip-comments": "^2.0.1", "strip-json-comments": "^5.0.1", "tiktoken": "^1.0.20", "tree-sitter-wasms": "^0.1.12", "web-tree-sitter": "^0.24.7", "zod": "^3.24.3" }, "devDependencies": { "@biomejs/biome": "^1.9.4", "@types/node": "^22.14.1", "@types/strip-comments": "^2.0.4", "@vitest/coverage-v8": "^3.1.1", "rimraf": "^6.0.1", "secretlint": "^9.3.1", "tsx": "^4.19.4", "typescript": "^5.8.3", "vite": "^5.4.18", "vitest": "^3.1.1" }, "engines": { "node": ">=18.0.0", "yarn": ">=1.22.22" } } ``` ## /repomix-instruction.md # Repomix Project Structure and Overview This document provides a structural overview of the Repomix project, designed to aid AI code assistants (like Copilot) in understanding the codebase. Please refer to `README.md` for a complete and up-to-date project overview, and `CONTRIBUTING.md` for implementation guidelines and contribution procedures. ## Project Overview Repomix is a tool that packs the contents of a software repository into a single file, making it easier for AI systems to analyze and process the codebase. It supports various output formats (plain text, XML, Markdown), ignores files based on configurable patterns, and performs security checks to exclude potentially sensitive information. ## Directory Structure The project is organized into the following directories: ``` repomix/ ├── src/ # Main source code │ ├── cli/ # Command-line interface logic (argument parsing, command handling, output) │ ├── config/ # Configuration loading, schema, and defaults │ ├── core/ # Core logic of Repomix │ │ ├── file/ # File handling (reading, processing, searching, tree structure generation, git commands) │ │ ├── metrics/ # Calculating code metrics (character count, token count) │ │ ├── output/ # Output generation (different styles, headers, etc.) │ │ ├── packager/ # Orchestrates file collection, processing, output, and clipboard operations. │ │ ├── security/ # Security checks to exclude sensitive files │ │ ├── tokenCount/ # Token counting using Tiktoken │ │ └── tree-sitter/ # Code parsing using Tree-sitter and language-specific queries │ └── shared/ # Shared utilities and types (error handling, logging, helper functions) ├── tests/ # Unit and integration tests (organized mirroring src/) │ ├── cli/ │ ├── config/ │ ├── core/ │ ├── integration-tests/ │ ├── shared/ │ └── testing/ └── website/ # Documentation website (VitePress) ├── client/ # Client-side code (Vue.js components, styles, configuration) │ ├── .vitepress/ # VitePress configuration and theme │ │ ├── config/ # Site configuration files (navigation, sidebar, etc.) │ │ └── theme/ # Custom theme and styles │ ├── components/ # Vue.js components for the website │ └── src/ # Markdown files for the documentation in various languages (en, ja, etc.) └── server/ # Server-side API (for remote repository processing) └── src/ # Server source code (API endpoints, request handling) ``` ---------------------------------------------------------------- # Coding Guidelines - Follow the Airbnb JavaScript Style Guide. - Split files into smaller, focused units when appropriate: - Aim to keep code files under 250 lines. If a file exceeds 250 lines, split it into multiple files based on functionality. - Add comments to clarify non-obvious logic. **Ensure all comments are written in English.** - Provide corresponding unit tests for all new features. - After implementation, verify changes by running: ```bash npm run lint # Ensure code style compliance npm run test # Verify all tests pass ``` ## Dependencies and Testing - Inject dependencies through a deps object parameter for testability - Example: ```typescript export const functionName = async ( param1: Type1, param2: Type2, deps = { defaultFunction1, defaultFunction2, } ) => { // Use deps.defaultFunction1() instead of direct call }; ``` - Mock dependencies by passing test doubles through deps object - Use vi.mock() only when dependency injection is not feasible ## Generate Comprehensive Output - Include all content without abbreviation, unless specified otherwise - Optimize for handling large codebases while maintaining output quality ---------------------------------------------------------------- # GitHub Release Note Guidelines When writing release notes, please follow these guidelines: - When referencing issues or PRs, use the gh command to verify the content: ```bash gh issue view <issue-number> # For checking issue content gh pr view <pr-number> # For checking PR content ``` This helps ensure accuracy in release note descriptions. Here are some examples of release notes that follow the guidelines: v0.2.25 ````md This release brings significant improvements to output formatting and introduces flexible remote repository handling capabilities along with enhanced logging features. # Improvements ⚡ ## Remote Repository Enhancement (#335) - Added branch/tag parsing directly from repository URLs: ```bash repomix --remote https://github.com/yamadashy/repomix/tree/0.1.x ``` Functions identically to: ```bash repomix --remote https://github.com/yamadashy/repomix --remote-branch 0.1.x ``` Special thanks to @huy-trn for implementing this user-friendly feature! ## Enhanced Output Formatting (#328, #329, #330) - Added "End of Codebase" marker for better clarity in output - Improved output header accuracy: - Better representation of codebase scope - Clear indication when using `--include` or `--ignore` options Special thanks to @gitkenan for adding the "End of Codebase" marker and reporting the header issue! ## Path Pattern Support (#337) - Added support for special characters in paths: - Handles parentheses in include patterns (e.g., `src/(categories)/**/*`) - Improved escaping for `[]` and `{}` - Essential for Next.js route groups and similar frameworks Thank you @matheuscoelhomalta for improving path pattern support! # How to Update ```bash npm update -g repomix ``` --- As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support. ```` v0.2.24 ````md This release significantly enhances configuration flexibility with comprehensive CLI flag support and expands default ignore patterns for better project scaffolding. # What's New 🚀 ## CLI Flags Revolution (#324) - New command-line configuration now available. ``` - `--no-gitignore`: Disable .gitignore file usage - `--no-default-patterns`: Disable default patterns - `--header-text <text>`: Custom text to include in the file header - `--instruction-file-path <path>`: Path to a file containing detailed custom instructions - `--include-empty-directories`: Include empty directories in the output ``` Special recognition to @massdo for driving ecosystem growth. # Improvements ⚡ ## Enhanced Ignore Patterns (#318, #322) - Expanded default ignores for Rust projects: - `target/`, `Cargo.lock`, build artifacts - PHP, Ruby, Go, Elixir, Haskell: package manager lock files To @boralg for helping curate Rust-specific patterns! # How to Update ```bash npm update -g repomix ``` --- As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support. ```` v0.2.23 ````md This release adds significant performance improvements for large repositories, making Repomix faster and more efficient when needed. # Improvements ⚡ ## Parallel Processing Enhancement (#309) - Implemented worker threads using [Piscina](https://github.com/piscinajs/piscina) for parallel processing ### Benchmark Results - `yamadashy.repomix`: No significant change - Before: 868.73 millis - After: 671.26 millis - `facebook/react`: 29x faster - Before: 123.31 secs - After: 4.19 secs - `vercel/next.js`: 58x faster - Before: 17.85 mins - After: 17.27 secs Note: While Repomix is not primarily designed for processing large repositories, and speed is not a primary goal, faster processing can provide a better user experience when working with larger codebases. # How to Update ```bash npm update -g repomix ``` --- As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support. ```` v0.2.22 ````md This release introduces significant improvements to large file handling and expands the Repomix ecosystem with new tools and community channels. # Improvements ⚡ ## Improved Large File Handling (#302) - Added a file size limit check (50MB) to prevent memory issues - Graceful error handling for large files with clear user guidance: Special thanks to @slavashvets for their continued contributions! # Ecosystem Growth 🤝 ## New VS Code Extension (#300) A community-created VS Code extension "Repomix Runner" is now available: - Run Repomix directly from VS Code - Extension by @massdo: [View on VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner) Thank you @massdo for bringing Repomix to VS Code and expanding our tooling ecosystem! ## Official Social Media - Launched official Repomix X (Twitter) account: [@repomix_ai](https://x.com/repomix_ai) - Follow for updates, tips, and community highlights # How to Update ```bash npm update -g repomix ``` --- Join our growing community on [Discord](https://discord.gg/BF8GxZHE2C) and follow us on [X](https://x.com/repomix_ai) for updates! ```` v0.2.21 ````md This release introduces significant improvements to output formatting and documentation, featuring a new parsable style option for enhanced XML handling. # What's New 🚀 ## Enhanced Output Style Control (#287) - Added new `parsableStyle` option for better output handling: - Ensures output strictly follows the specification of the chosen format - Provides properly escaped XML output with fast-xml-parser - Dynamically adjusts markdown code block delimiters to avoid content conflicts - Available via CLI flag `--parsable-style` or in configuration file Special thanks to @atollk for their first contribution! # Documentation 📚 ## README Enhancements (#296) - Updated Homebrew installation documentation to include Linux support Special thanks to @chenrui333 for their continued contributions! ## Website Multi-Language Support (#293) - Enhanced multi-language support in [repomix.com](https://repomix.com) # How to Update To update to the latest version, run: ```bash npm update -g repomix ``` --- As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support. ## /repomix.config.json ```json path="/repomix.config.json" { "$schema": "https://repomix.com/schemas/latest/schema.json", "input": { "maxFileSize": 50000000 }, "output": { "filePath": "repomix-output.xml", "style": "xml", "parsableStyle": false, "compress": false, "headerText": "This repository contains the source code for the Repomix tool.\nRepomix is designed to pack repository contents into a single file,\nmaking it easier for AI systems to analyze and process the codebase.\n\nKey Features:\n- Configurable ignore patterns\n- Custom header text support\n- Efficient file processing and packing\n\nPlease refer to the README.md file for more detailed information on usage and configuration.\n", "instructionFilePath": "repomix-instruction.md", "fileSummary": true, "directoryStructure": true, "files": true, "removeComments": false, "removeEmptyLines": false, "topFilesLength": 5, "showLineNumbers": false, "includeEmptyDirectories": true, "git": { "sortByChanges": true, "sortByChangesMaxCommits": 100, "includeDiffs": false } }, "include": [], "ignore": { "useGitignore": true, "useDefaultPatterns": true, // ignore is specified in .repomixignore "customPatterns": [] }, "security": { "enableSecurityCheck": true }, "tokenCount": { "encoding": "o200k_base" } } ``` ## /src/cli/actions/defaultAction.ts ```ts path="/src/cli/actions/defaultAction.ts" import path from 'node:path'; import { loadFileConfig, mergeConfigs } from '../../config/configLoad.js'; import { type RepomixConfigCli, type RepomixConfigFile, type RepomixConfigMerged, type RepomixOutputStyle, repomixConfigCliSchema, } from '../../config/configSchema.js'; import { type PackResult, pack } from '../../core/packager.js'; import { rethrowValidationErrorIfZodError } from '../../shared/errorHandle.js'; import { logger } from '../../shared/logger.js'; import { splitPatterns } from '../../shared/patternUtils.js'; import { printCompletion, printSecurityCheck, printSummary, printTopFiles } from '../cliPrint.js'; import { Spinner } from '../cliSpinner.js'; import type { CliOptions } from '../types.js'; import { runMigrationAction } from './migrationAction.js'; export interface DefaultActionRunnerResult { packResult: PackResult; config: RepomixConfigMerged; } export const runDefaultAction = async ( directories: string[], cwd: string, cliOptions: CliOptions, ): Promise<DefaultActionRunnerResult> => { logger.trace('Loaded CLI options:', cliOptions); // Run migration before loading config await runMigrationAction(cwd); // Load the config file const fileConfig: RepomixConfigFile = await loadFileConfig(cwd, cliOptions.config ?? null); logger.trace('Loaded file config:', fileConfig); // Parse the CLI options into a config const cliConfig: RepomixConfigCli = buildCliConfig(cliOptions); logger.trace('CLI config:', cliConfig); // Merge default, file, and CLI configs const config: RepomixConfigMerged = mergeConfigs(cwd, fileConfig, cliConfig); logger.trace('Merged config:', config); const targetPaths = directories.map((directory) => path.resolve(cwd, directory)); const spinner = new Spinner('Packing files...', cliOptions); spinner.start(); let packResult: PackResult; try { packResult = await pack(targetPaths, config, (message) => { spinner.update(message); }); } catch (error) { spinner.fail('Error during packing'); throw error; } spinner.succeed('Packing completed successfully!'); logger.log(''); if (config.output.topFilesLength > 0) { printTopFiles(packResult.fileCharCounts, packResult.fileTokenCounts, config.output.topFilesLength); logger.log(''); } printSecurityCheck(cwd, packResult.suspiciousFilesResults, packResult.suspiciousGitDiffResults, config); logger.log(''); printSummary(packResult, config); logger.log(''); printCompletion(); return { packResult, config, }; }; /** * Builds CLI configuration from command-line options. * * Note: Due to Commander.js behavior with --no-* flags: * - When --no-* flags are used (e.g., --no-file-summary), the options explicitly become false * - When no flag is specified, Commander defaults to true (e.g., options.fileSummary === true) * - For --no-* flags, we only apply the setting when it's explicitly false to respect config file values * - This allows the config file to maintain control unless explicitly overridden by CLI */ export const buildCliConfig = (options: CliOptions): RepomixConfigCli => { const cliConfig: RepomixConfigCli = {}; if (options.output) { cliConfig.output = { filePath: options.output }; } if (options.include) { cliConfig.include = splitPatterns(options.include); } if (options.ignore) { cliConfig.ignore = { customPatterns: splitPatterns(options.ignore) }; } // Only apply gitignore setting if explicitly set to false if (options.gitignore === false) { cliConfig.ignore = { ...cliConfig.ignore, useGitignore: options.gitignore }; } // Only apply defaultPatterns setting if explicitly set to false if (options.defaultPatterns === false) { cliConfig.ignore = { ...cliConfig.ignore, useDefaultPatterns: options.defaultPatterns, }; } if (options.topFilesLen !== undefined) { cliConfig.output = { ...cliConfig.output, topFilesLength: options.topFilesLen, }; } if (options.outputShowLineNumbers !== undefined) { cliConfig.output = { ...cliConfig.output, showLineNumbers: options.outputShowLineNumbers, }; } if (options.copy) { cliConfig.output = { ...cliConfig.output, copyToClipboard: options.copy }; } if (options.style) { cliConfig.output = { ...cliConfig.output, style: options.style.toLowerCase() as RepomixOutputStyle, }; } if (options.parsableStyle !== undefined) { cliConfig.output = { ...cliConfig.output, parsableStyle: options.parsableStyle, }; } if (options.stdout) { cliConfig.output = { ...cliConfig.output, stdout: true, }; } // Only apply securityCheck setting if explicitly set to false if (options.securityCheck === false) { cliConfig.security = { enableSecurityCheck: options.securityCheck }; } // Only apply fileSummary setting if explicitly set to false if (options.fileSummary === false) { cliConfig.output = { ...cliConfig.output, fileSummary: false, }; } // Only apply directoryStructure setting if explicitly set to false if (options.directoryStructure === false) { cliConfig.output = { ...cliConfig.output, directoryStructure: false, }; } // Only apply files setting if explicitly set to false if (options.files === false) { cliConfig.output = { ...cliConfig.output, files: false, }; } if (options.removeComments !== undefined) { cliConfig.output = { ...cliConfig.output, removeComments: options.removeComments, }; } if (options.removeEmptyLines !== undefined) { cliConfig.output = { ...cliConfig.output, removeEmptyLines: options.removeEmptyLines, }; } if (options.headerText !== undefined) { cliConfig.output = { ...cliConfig.output, headerText: options.headerText }; } if (options.compress !== undefined) { cliConfig.output = { ...cliConfig.output, compress: options.compress }; } if (options.tokenCountEncoding) { cliConfig.tokenCount = { encoding: options.tokenCountEncoding }; } if (options.instructionFilePath) { cliConfig.output = { ...cliConfig.output, instructionFilePath: options.instructionFilePath, }; } if (options.includeEmptyDirectories) { cliConfig.output = { ...cliConfig.output, includeEmptyDirectories: options.includeEmptyDirectories, }; } // Only apply gitSortByChanges setting if explicitly set to false if (options.gitSortByChanges === false) { cliConfig.output = { ...cliConfig.output, git: { ...cliConfig.output?.git, sortByChanges: false, }, }; } if (options.includeDiffs) { cliConfig.output = { ...cliConfig.output, git: { ...cliConfig.output?.git, includeDiffs: true, }, }; } try { return repomixConfigCliSchema.parse(cliConfig); } catch (error) { rethrowValidationErrorIfZodError(error, 'Invalid cli arguments'); throw error; } }; ``` ## /src/cli/actions/initAction.ts ```ts path="/src/cli/actions/initAction.ts" import fs from 'node:fs/promises'; import path from 'node:path'; import * as prompts from '@clack/prompts'; import pc from 'picocolors'; import { type RepomixConfigFile, type RepomixOutputStyle, defaultConfig, defaultFilePathMap, } from '../../config/configSchema.js'; import { getGlobalDirectory } from '../../config/globalDirectory.js'; import { getVersion } from '../../core/file/packageJsonParse.js'; import { logger } from '../../shared/logger.js'; const onCancelOperation = () => { prompts.cancel('Initialization cancelled.'); process.exit(0); }; export const runInitAction = async (rootDir: string, isGlobal: boolean): Promise<void> => { prompts.intro(pc.bold(`Welcome to Repomix ${isGlobal ? 'Global ' : ''}Configuration!`)); try { // Step 1: Ask if user wants to create a config file const isCreatedConfig = await createConfigFile(rootDir, isGlobal); // Step 2: Ask if user wants to create a .repomixignore file const isCreatedIgnoreFile = await createIgnoreFile(rootDir, isGlobal); if (!isCreatedConfig && !isCreatedIgnoreFile) { prompts.outro( pc.yellow('No files were created. You can run this command again when you need to create configuration files.'), ); } else { prompts.outro(pc.green('Initialization complete! You can now use Repomix with your specified settings.')); } } catch (error) { logger.error('An error occurred during initialization:', error); } }; export const createConfigFile = async (rootDir: string, isGlobal: boolean): Promise<boolean> => { const configPath = path.resolve(isGlobal ? getGlobalDirectory() : rootDir, 'repomix.config.json'); const isCreateConfig = await prompts.confirm({ message: `Do you want to create a ${isGlobal ? 'global ' : ''}${pc.green('repomix.config.json')} file?`, }); if (!isCreateConfig) { prompts.log.info(`Skipping ${pc.green('repomix.config.json')} file creation.`); return false; } if (prompts.isCancel(isCreateConfig)) { onCancelOperation(); return false; } let isConfigFileExists = false; try { await fs.access(configPath); isConfigFileExists = true; } catch { // File doesn't exist, so we can proceed } if (isConfigFileExists) { const isOverwrite = await prompts.confirm({ message: `A ${isGlobal ? 'global ' : ''}${pc.green('repomix.config.json')} file already exists. Do you want to overwrite it?`, }); if (!isOverwrite) { prompts.log.info(`Skipping ${pc.green('repomix.config.json')} file creation.`); return false; } if (prompts.isCancel(isOverwrite)) { onCancelOperation(); return false; } } const options = await prompts.group( { outputStyle: () => { return prompts.select({ message: 'Output style:', options: [ { value: 'xml', label: 'XML', hint: 'Structured XML format' }, { value: 'markdown', label: 'Markdown', hint: 'Markdown format' }, { value: 'plain', label: 'Plain', hint: 'Simple text format' }, ], initialValue: defaultConfig.output.style, }); }, outputFilePath: ({ results }) => { const defaultFilePath = defaultFilePathMap[results.outputStyle as RepomixOutputStyle]; return prompts.text({ message: 'Output file path:', initialValue: defaultFilePath, validate: (value) => (value.length === 0 ? 'Output file path is required' : undefined), }); }, }, { onCancel: onCancelOperation, }, ); const config: RepomixConfigFile = { $schema: 'https://repomix.com/schemas/latest/schema.json', ...defaultConfig, output: { ...defaultConfig.output, filePath: options.outputFilePath as string, style: options.outputStyle as RepomixOutputStyle, }, }; await fs.mkdir(path.dirname(configPath), { recursive: true }); await fs.writeFile(configPath, JSON.stringify(config, null, 2)); const relativeConfigPath = path.relative(rootDir, configPath); prompts.log.success( pc.green(`${isGlobal ? 'Global config' : 'Config'} file created!\n`) + pc.dim(`Path: ${relativeConfigPath}`), ); return true; }; export const createIgnoreFile = async (rootDir: string, isGlobal: boolean): Promise<boolean> => { if (isGlobal) { prompts.log.info(`Skipping ${pc.green('.repomixignore')} file creation for global configuration.`); return false; } const ignorePath = path.resolve(rootDir, '.repomixignore'); const createIgnore = await prompts.confirm({ message: `Do you want to create a ${pc.green('.repomixignore')} file?`, }); if (!createIgnore) { prompts.log.info(`Skipping ${pc.green('.repomixignore')} file creation.`); return false; } if (prompts.isCancel(createIgnore)) { onCancelOperation(); return false; } let isIgnoreFileExists = false; try { await fs.access(ignorePath); isIgnoreFileExists = true; } catch { // File doesn't exist, so we can proceed } if (isIgnoreFileExists) { const overwrite = await prompts.confirm({ message: `A ${pc.green('.repomixignore')} file already exists. Do you want to overwrite it?`, }); if (!overwrite) { prompts.log.info(`${pc.green('.repomixignore')} file creation skipped. Existing file will not be modified.`); return false; } } const defaultIgnoreContent = `# Add patterns to ignore here, one per line # Example: # *.log # tmp/ `; await fs.writeFile(ignorePath, defaultIgnoreContent); prompts.log.success( pc.green('Created .repomixignore file!\n') + pc.dim(`Path: ${path.relative(rootDir, ignorePath)}`), ); return true; }; ``` ## /src/cli/actions/mcpAction.ts ```ts path="/src/cli/actions/mcpAction.ts" import { runMcpServer } from '../../mcp/mcpServer.js'; import { logger } from '../../shared/logger.js'; export const runMcpAction = async (): Promise<void> => { logger.trace('Starting Repomix MCP server...'); await runMcpServer(); }; ``` ## /src/cli/actions/migrationAction.ts ```ts path="/src/cli/actions/migrationAction.ts" import * as fs from 'node:fs/promises'; import path from 'node:path'; import * as prompts from '@clack/prompts'; import pc from 'picocolors'; import { getGlobalDirectory } from '../../config/globalDirectory.js'; import { logger } from '../../shared/logger.js'; interface MigrationPaths { oldConfigPath: string; newConfigPath: string; oldIgnorePath: string; newIgnorePath: string; oldInstructionPath: string; newInstructionPath: string; oldOutputPaths: string[]; newOutputPaths: string[]; oldGlobalConfigPath: string; newGlobalConfigPath: string; } interface MigrationResult { configMigrated: boolean; ignoreMigrated: boolean; instructionMigrated: boolean; outputFilesMigrated: string[]; globalConfigMigrated: boolean; error?: Error; } /** * Check if a file exists at the given path */ const fileExists = async (filePath: string): Promise<boolean> => { try { await fs.access(filePath); return true; } catch { return false; } }; /** * Replace all occurrences of 'repopack' with 'repomix' in a string */ const replaceRepopackString = (content: string): string => { return content.replace(/repopack/g, 'repomix').replace(/Repopack/g, 'Repomix'); }; /** * Update file content by replacing 'repopack' with 'repomix' */ const updateFileContent = async (filePath: string): Promise<boolean> => { const content = await fs.readFile(filePath, 'utf8'); const updatedContent = replaceRepopackString(content); // Check if content needs to be updated if (content !== updatedContent) { await fs.writeFile(filePath, updatedContent, 'utf8'); const relativePath = path.relative(process.cwd(), filePath); logger.log(`Updated repopack references in ${pc.cyan(relativePath)}`); return true; } return false; }; /** * Parse JSON content, update instructionFilePath if exists */ const updateInstructionPath = (content: string): string => { try { const config = JSON.parse(content); if (config.output?.instructionFilePath) { config.output.instructionFilePath = config.output.instructionFilePath.replace('repopack', 'repomix'); } // Also update output.filePath if it exists if (config.output?.filePath) { config.output.filePath = config.output.filePath.replace('repopack', 'repomix'); } return JSON.stringify(config, null, 2); } catch { return content; } }; /** * Get output file paths pairs */ const getOutputFilePaths = (rootDir: string): { oldPaths: string[]; newPaths: string[] } => { const extensions = ['.txt', '.xml', '.md']; const oldPaths = extensions.map((ext) => path.join(rootDir, `repopack-output${ext}`)); const newPaths = extensions.map((ext) => path.join(rootDir, `repomix-output${ext}`)); return { oldPaths, newPaths }; }; /** * Migrate a single file from old path to new path */ const migrateFile = async ( oldPath: string, newPath: string, description: string, isConfig = false, ): Promise<boolean> => { if (!(await fileExists(oldPath))) { return false; } const exists = await fileExists(newPath); if (exists) { const shouldOverwrite = await prompts.confirm({ message: `${description} already exists at ${newPath}. Do you want to overwrite it?`, }); if (prompts.isCancel(shouldOverwrite) || !shouldOverwrite) { logger.info(`Skipping migration of ${description}`); return false; } } try { // Read and update content let content = await fs.readFile(oldPath, 'utf8'); content = replaceRepopackString(content); // For config files, also update instructionFilePath and output.filePath if (isConfig) { content = updateInstructionPath(content); } // Ensure the target directory exists await fs.mkdir(path.dirname(newPath), { recursive: true }); // Write to new file await fs.writeFile(newPath, content, 'utf8'); // Remove old file await fs.unlink(oldPath); const relativeOldPath = path.relative(process.cwd(), oldPath); const relativeNewPath = path.relative(process.cwd(), newPath); logger.log(`Renamed ${description} from ${relativeOldPath} to ${relativeNewPath}`); return true; } catch (error) { logger.error(`Failed to migrate ${description}:`, error); return false; } }; /** * Update content of gitignore and repomixignore files */ const updateIgnoreFiles = async (rootDir: string): Promise<void> => { const gitignorePath = path.join(rootDir, '.gitignore'); const repomixignorePath = path.join(rootDir, '.repomixignore'); if (await fileExists(gitignorePath)) { const updated = await updateFileContent(gitignorePath); if (!updated) { logger.debug('No changes needed in .gitignore'); } } if (await fileExists(repomixignorePath)) { const updated = await updateFileContent(repomixignorePath); if (!updated) { logger.debug('No changes needed in .repomixignore'); } } }; /** * Get all migration related file paths */ const getMigrationPaths = (rootDir: string): MigrationPaths => { const { oldPaths: oldOutputPaths, newPaths: newOutputPaths } = getOutputFilePaths(rootDir); const oldGlobalDirectory = path.join(process.env.HOME || '', '.config', 'repopack'); const newGlobalDirectory = getGlobalDirectory(); return { oldConfigPath: path.join(rootDir, 'repopack.config.json'), newConfigPath: path.join(rootDir, 'repomix.config.json'), oldIgnorePath: path.join(rootDir, '.repopackignore'), newIgnorePath: path.join(rootDir, '.repomixignore'), oldInstructionPath: path.join(rootDir, 'repopack-instruction.md'), newInstructionPath: path.join(rootDir, 'repomix-instruction.md'), oldOutputPaths, newOutputPaths, oldGlobalConfigPath: path.join(oldGlobalDirectory, 'repopack.config.json'), newGlobalConfigPath: path.join(newGlobalDirectory, 'repomix.config.json'), }; }; /** * Migrate output files */ const migrateOutputFiles = async (oldPaths: string[], newPaths: string[]): Promise<string[]> => { const migratedFiles: string[] = []; for (let i = 0; i < oldPaths.length; i++) { const oldPath = oldPaths[i]; const newPath = newPaths[i]; const ext = path.extname(oldPath); if (await migrateFile(oldPath, newPath, `Output file (${ext})`)) { migratedFiles.push(newPath); } } return migratedFiles; }; export const runMigrationAction = async (rootDir: string): Promise<MigrationResult> => { const result: MigrationResult = { configMigrated: false, ignoreMigrated: false, instructionMigrated: false, outputFilesMigrated: [], globalConfigMigrated: false, }; try { const paths = getMigrationPaths(rootDir); // Check if migration is needed const hasOldConfig = await fileExists(paths.oldConfigPath); const hasOldIgnore = await fileExists(paths.oldIgnorePath); const hasOldInstruction = await fileExists(paths.oldInstructionPath); const hasOldGlobalConfig = await fileExists(paths.oldGlobalConfigPath); const hasOldOutput = await Promise.all(paths.oldOutputPaths.map(fileExists)).then((results) => results.some((exists) => exists), ); if (!hasOldConfig && !hasOldIgnore && !hasOldInstruction && !hasOldOutput && !hasOldGlobalConfig) { logger.debug('No Repopack files found to migrate.'); return result; } // Show migration notice based on what needs to be migrated let migrationMessage = `Found ${pc.green('Repopack')} `; const items = []; if (hasOldConfig || hasOldIgnore || hasOldInstruction || hasOldOutput) items.push('local configuration'); if (hasOldGlobalConfig) items.push('global configuration'); migrationMessage += `${items.join(' and ')}. Would you like to migrate to ${pc.green('Repomix')}?`; // Confirm migration with user const shouldMigrate = await prompts.confirm({ message: migrationMessage, }); if (prompts.isCancel(shouldMigrate) || !shouldMigrate) { logger.info('Migration cancelled.'); return result; } // Show migration notice logger.info(pc.cyan('\nMigrating from Repopack to Repomix...')); logger.log(''); // Migrate config file if (hasOldConfig) { result.configMigrated = await migrateFile(paths.oldConfigPath, paths.newConfigPath, 'Configuration file', true); } // Migrate global config file if (hasOldGlobalConfig) { result.globalConfigMigrated = await migrateFile( paths.oldGlobalConfigPath, paths.newGlobalConfigPath, 'Global configuration file', true, ); } // Migrate ignore file if (hasOldIgnore) { result.ignoreMigrated = await migrateFile(paths.oldIgnorePath, paths.newIgnorePath, 'Ignore file'); } // Migrate instruction file if (hasOldInstruction) { result.instructionMigrated = await migrateFile( paths.oldInstructionPath, paths.newInstructionPath, 'Instruction file', ); } // Migrate output files if (hasOldOutput) { result.outputFilesMigrated = await migrateOutputFiles(paths.oldOutputPaths, paths.newOutputPaths); } // Update content in gitignore and repomixignore await updateIgnoreFiles(rootDir); // Show success message if ( result.configMigrated || result.ignoreMigrated || result.instructionMigrated || result.outputFilesMigrated.length > 0 || result.globalConfigMigrated ) { logger.log(''); logger.success('✔ Migration completed successfully!'); logger.log(''); logger.info( 'You can now use Repomix commands as usual. The old Repopack files have been migrated to the new format.', ); logger.log(''); } return result; } catch (error) { if (error instanceof Error) { result.error = error; } else { result.error = new Error(String(error)); } logger.error('An error occurred during migration:', error); return result; } }; ``` ## /src/cli/actions/remoteAction.ts ```ts path="/src/cli/actions/remoteAction.ts" import * as fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import pc from 'picocolors'; import { execGitShallowClone } from '../../core/git/gitCommand.js'; import { getRemoteRefs } from '../../core/git/gitRemoteHandle.js'; import { parseRemoteValue } from '../../core/git/gitRemoteParse.js'; import { isGitInstalled } from '../../core/git/gitRepositoryHandle.js'; import { RepomixError } from '../../shared/errorHandle.js'; import { logger } from '../../shared/logger.js'; import { Spinner } from '../cliSpinner.js'; import type { CliOptions } from '../types.js'; import { type DefaultActionRunnerResult, runDefaultAction } from './defaultAction.js'; export const runRemoteAction = async ( repoUrl: string, cliOptions: CliOptions, deps = { isGitInstalled, execGitShallowClone, getRemoteRefs, runDefaultAction, }, ): Promise<DefaultActionRunnerResult> => { if (!(await deps.isGitInstalled())) { throw new RepomixError('Git is not installed or not in the system PATH.'); } // Get remote refs let refs: string[] = []; try { refs = await deps.getRemoteRefs(parseRemoteValue(repoUrl).repoUrl); logger.trace(`Retrieved ${refs.length} refs from remote repository`); } catch (error) { logger.trace('Failed to get remote refs, proceeding without them:', (error as Error).message); } // Parse the remote URL with the refs information const parsedFields = parseRemoteValue(repoUrl, refs); const spinner = new Spinner('Cloning repository...', cliOptions); const tempDirPath = await createTempDirectory(); let result: DefaultActionRunnerResult; try { spinner.start(); // Clone the repository await cloneRepository(parsedFields.repoUrl, tempDirPath, cliOptions.remoteBranch || parsedFields.remoteBranch, { execGitShallowClone: deps.execGitShallowClone, }); spinner.succeed('Repository cloned successfully!'); logger.log(''); // Run the default action on the cloned repository result = await deps.runDefaultAction([tempDirPath], tempDirPath, cliOptions); await copyOutputToCurrentDirectory(tempDirPath, process.cwd(), result.config.output.filePath); } catch (error) { spinner.fail('Error during repository cloning. cleanup...'); throw error; } finally { // Cleanup the temporary directory await cleanupTempDirectory(tempDirPath); } return result; }; export const createTempDirectory = async (): Promise<string> => { const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'repomix-')); logger.trace(`Created temporary directory. (path: ${pc.dim(tempDir)})`); return tempDir; }; export const cloneRepository = async ( url: string, directory: string, remoteBranch?: string, deps = { execGitShallowClone, }, ): Promise<void> => { logger.log(`Clone repository: ${url} to temporary directory. ${pc.dim(`path: ${directory}`)}`); logger.log(''); try { await deps.execGitShallowClone(url, directory, remoteBranch); } catch (error) { throw new RepomixError(`Failed to clone repository: ${(error as Error).message}`); } }; export const cleanupTempDirectory = async (directory: string): Promise<void> => { logger.trace(`Cleaning up temporary directory: ${directory}`); await fs.rm(directory, { recursive: true, force: true }); }; export const copyOutputToCurrentDirectory = async ( sourceDir: string, targetDir: string, outputFileName: string, ): Promise<void> => { const sourcePath = path.resolve(sourceDir, outputFileName); const targetPath = path.resolve(targetDir, outputFileName); try { logger.trace(`Copying output file from: ${sourcePath} to: ${targetPath}`); // Create target directory if it doesn't exist await fs.mkdir(path.dirname(targetPath), { recursive: true }); await fs.copyFile(sourcePath, targetPath); } catch (error) { throw new RepomixError(`Failed to copy output file: ${(error as Error).message}`); } }; ``` ## /src/cli/actions/versionAction.ts ```ts path="/src/cli/actions/versionAction.ts" import { getVersion } from '../../core/file/packageJsonParse.js'; import { logger } from '../../shared/logger.js'; export const runVersionAction = async (): Promise<void> => { const version = await getVersion(); logger.log(version); }; ``` ## /src/cli/cliPrint.ts ```ts path="/src/cli/cliPrint.ts" import path from 'node:path'; import pc from 'picocolors'; import type { RepomixConfigMerged } from '../config/configSchema.js'; import type { PackResult } from '../core/packager.js'; import type { SuspiciousFileResult } from '../core/security/securityCheck.js'; import { logger } from '../shared/logger.js'; export const printSummary = (packResult: PackResult, config: RepomixConfigMerged) => { let securityCheckMessage = ''; if (config.security.enableSecurityCheck) { if (packResult.suspiciousFilesResults.length > 0) { securityCheckMessage = pc.yellow( `${packResult.suspiciousFilesResults.length.toLocaleString()} suspicious file(s) detected and excluded`, ); } else { securityCheckMessage = pc.white('✔ No suspicious files detected'); } } else { securityCheckMessage = pc.dim('Security check disabled'); } logger.log(pc.white('📊 Pack Summary:')); logger.log(pc.dim('────────────────')); logger.log(`${pc.white(' Total Files:')} ${pc.white(packResult.totalFiles.toLocaleString())} files`); logger.log(`${pc.white(' Total Chars:')} ${pc.white(packResult.totalCharacters.toLocaleString())} chars`); logger.log(`${pc.white(' Total Tokens:')} ${pc.white(packResult.totalTokens.toLocaleString())} tokens`); logger.log(`${pc.white(' Output:')} ${pc.white(config.output.filePath)}`); logger.log(`${pc.white(' Security:')} ${pc.white(securityCheckMessage)}`); if (config.output.git?.includeDiffs) { let gitDiffsMessage = ''; if (packResult.gitDiffTokenCount) { gitDiffsMessage = pc.white( `✔ Git diffs included ${pc.dim(`(${packResult.gitDiffTokenCount.toLocaleString()} tokens)`)}`, ); } else { gitDiffsMessage = pc.dim('✖ No git diffs included'); } logger.log(`${pc.white(' Git Diffs:')} ${gitDiffsMessage}`); } }; export const printSecurityCheck = ( rootDir: string, suspiciousFilesResults: SuspiciousFileResult[], suspiciousGitDiffResults: SuspiciousFileResult[], config: RepomixConfigMerged, ) => { if (!config.security.enableSecurityCheck) { return; } logger.log(pc.white('🔎 Security Check:')); logger.log(pc.dim('──────────────────')); // Print results for files if (suspiciousFilesResults.length === 0) { logger.log(`${pc.green('✔')} ${pc.white('No suspicious files detected.')}`); } else { logger.log(pc.yellow(`${suspiciousFilesResults.length} suspicious file(s) detected and excluded from the output:`)); suspiciousFilesResults.forEach((suspiciousFilesResult, index) => { const relativeFilePath = path.relative(rootDir, suspiciousFilesResult.filePath); logger.log(`${pc.white(`${index + 1}.`)} ${pc.white(relativeFilePath)}`); logger.log(pc.dim(` - ${suspiciousFilesResult.messages.join('\n - ')}`)); }); logger.log(pc.yellow('\nThese files have been excluded from the output for security reasons.')); logger.log(pc.yellow('Please review these files for potential sensitive information.')); } // Print results for git diffs if (suspiciousGitDiffResults.length > 0) { logger.log(''); logger.log(pc.yellow(`${suspiciousGitDiffResults.length} security issue(s) found in Git diffs:`)); suspiciousGitDiffResults.forEach((suspiciousResult, index) => { logger.log(`${pc.white(`${index + 1}.`)} ${pc.white(suspiciousResult.filePath)}`); logger.log(pc.dim(` - ${suspiciousResult.messages.join('\n - ')}`)); }); logger.log(pc.yellow('\nNote: Git diffs with security issues are still included in the output.')); logger.log(pc.yellow('Please review the diffs before sharing the output.')); } }; export const printTopFiles = ( fileCharCounts: Record<string, number>, fileTokenCounts: Record<string, number>, topFilesLength: number, ) => { const topFilesLengthStrLen = topFilesLength.toString().length; logger.log(pc.white(`📈 Top ${topFilesLength} Files by Character Count and Token Count:`)); logger.log(pc.dim(`─────────────────────────────────────────────────${'─'.repeat(topFilesLengthStrLen)}`)); const topFiles = Object.entries(fileCharCounts) .sort((a, b) => b[1] - a[1]) .slice(0, topFilesLength); // Calculate total token count const totalTokens = Object.values(fileTokenCounts).reduce((sum, count) => sum + count, 0); topFiles.forEach(([filePath, charCount], index) => { const tokenCount = fileTokenCounts[filePath]; const percentageOfTotal = totalTokens > 0 ? Number(((tokenCount / totalTokens) * 100).toFixed(1)) : 0; const indexString = `${index + 1}.`.padEnd(3, ' '); logger.log( `${pc.white(`${indexString}`)} ${pc.white(filePath)} ${pc.dim(`(${charCount.toLocaleString()} chars, ${tokenCount.toLocaleString()} tokens, ${percentageOfTotal}%)`)}`, ); }); }; export const printCompletion = () => { logger.log(pc.green('🎉 All Done!')); logger.log(pc.white('Your repository has been successfully packed.')); logger.log(''); logger.log(`💡 Repomix is now available in your browser! Try it at ${pc.underline('https://repomix.com')}`); }; ``` ## /src/cli/cliRun.ts ```ts path="/src/cli/cliRun.ts" import process from 'node:process'; import { Command, Option, program } from 'commander'; import pc from 'picocolors'; import { getVersion } from '../core/file/packageJsonParse.js'; import { handleError } from '../shared/errorHandle.js'; import { logger, repomixLogLevels } from '../shared/logger.js'; import { runDefaultAction } from './actions/defaultAction.js'; import { runInitAction } from './actions/initAction.js'; import { runMcpAction } from './actions/mcpAction.js'; import { runRemoteAction } from './actions/remoteAction.js'; import { runVersionAction } from './actions/versionAction.js'; import type { CliOptions } from './types.js'; // Semantic mapping for CLI suggestions // This maps conceptually related terms (not typos) to valid options const semanticSuggestionMap: Record<string, string[]> = { exclude: ['--ignore'], reject: ['--ignore'], omit: ['--ignore'], skip: ['--ignore'], blacklist: ['--ignore'], save: ['--output'], export: ['--output'], out: ['--output'], file: ['--output'], format: ['--style'], type: ['--style'], syntax: ['--style'], debug: ['--verbose'], detailed: ['--verbose'], silent: ['--quiet'], mute: ['--quiet'], add: ['--include'], with: ['--include'], whitelist: ['--include'], clone: ['--remote'], git: ['--remote'], minimize: ['--compress'], reduce: ['--compress'], 'strip-comments': ['--remove-comments'], 'no-comments': ['--remove-comments'], print: ['--stdout'], console: ['--stdout'], terminal: ['--stdout'], }; export const run = async () => { try { program .description('Repomix - Pack your repository into a single AI-friendly file') .argument('[directories...]', 'list of directories to process', ['.']) // Basic Options .optionsGroup('Basic Options') .option('-v, --version', 'show version information') // Output Options .optionsGroup('Output Options') .option('-o, --output <file>', 'specify the output file name') .addOption(new Option('--stdout', 'output to stdout instead of writing to a file').conflicts('output')) .option('--style <type>', 'specify the output style (xml, markdown, plain)') .option('--parsable-style', 'by escaping and formatting, ensure the output is parsable as a document of its type') .option('--compress', 'perform code compression to reduce token count') .option('--output-show-line-numbers', 'add line numbers to each line in the output') .option('--copy', 'copy generated output to system clipboard') .option('--no-file-summary', 'disable file summary section output') .option('--no-directory-structure', 'disable directory structure section output') .option('--no-files', 'disable files content output (metadata-only mode)') .option('--remove-comments', 'remove comments') .option('--remove-empty-lines', 'remove empty lines') .option('--header-text <text>', 'specify the header text') .option('--instruction-file-path <path>', 'path to a file containing detailed custom instructions') .option('--include-empty-directories', 'include empty directories in the output') .option('--no-git-sort-by-changes', 'disable sorting files by git change count') .option( '--include-diffs', 'include git diffs in the output (includes both work tree and staged changes separately)', ) // Filter Options .optionsGroup('Filter Options') .option('--include <patterns>', 'list of include patterns (comma-separated)') .option('-i, --ignore <patterns>', 'additional ignore patterns (comma-separated)') .option('--no-gitignore', 'disable .gitignore file usage') .option('--no-default-patterns', 'disable default patterns') // Remote Repository Options .optionsGroup('Remote Repository Options') .option('--remote <url>', 'process a remote Git repository') .option( '--remote-branch <name>', 'specify the remote branch name, tag, or commit hash (defaults to repository default branch)', ) // Configuration Options .optionsGroup('Configuration Options') .option('-c, --config <path>', 'path to a custom config file') .option('--init', 'initialize a new repomix.config.json file') .option('--global', 'use global configuration (only applicable with --init)') // Security Options .optionsGroup('Security Options') .option('--no-security-check', 'disable security check') // Token Count Options .optionsGroup('Token Count Options') .option('--token-count-encoding <encoding>', 'specify token count encoding (e.g., o200k_base, cl100k_base)') // MCP .optionsGroup('MCP') .option('--mcp', 'run as a MCP server') // Other Options .optionsGroup('Other Options') .option('--top-files-len <number>', 'specify the number of top files to display', Number.parseInt) .addOption(new Option('--verbose', 'enable verbose logging for detailed output').conflicts('quiet')) .addOption(new Option('--quiet', 'disable all output to stdout').conflicts('verbose')) .action(commanderActionEndpoint); // Custom error handling function const configOutput = program.configureOutput(); const originalOutputError = configOutput.outputError || ((str, write) => write(str)); program.configureOutput({ outputError: (str, write) => { // Check if this is an unknown option error if (str.includes('unknown option')) { const match = str.match(/unknown option '?(-{1,2}[^ ']+)'?/i); if (match?.[1]) { const unknownOption = match[1]; const cleanOption = unknownOption.replace(/^-+/, ''); // Check if the option has a semantic match const semanticMatches = semanticSuggestionMap[cleanOption]; if (semanticMatches) { // We have a direct semantic match logger.error(`✖ Unknown option: ${unknownOption}`); logger.info(`Did you mean: ${semanticMatches.join(' or ')}?`); return; } } } // Fall back to the original Commander error handler originalOutputError(str, write); }, }); await program.parseAsync(process.argv); } catch (error) { handleError(error); } }; const commanderActionEndpoint = async (directories: string[], options: CliOptions = {}) => { await runCli(directories, process.cwd(), options); }; export const runCli = async (directories: string[], cwd: string, options: CliOptions) => { // Detect stdout mode // NOTE: For compatibility, currently not detecting pipe mode const isForceStdoutMode = options.output === '-'; if (isForceStdoutMode) { options.stdout = true; } // Set log level based on verbose and quiet flags if (options.quiet) { logger.setLogLevel(repomixLogLevels.SILENT); } else if (options.verbose) { logger.setLogLevel(repomixLogLevels.DEBUG); } else { logger.setLogLevel(repomixLogLevels.INFO); } // In stdout mode, set log level to SILENT if (options.stdout) { logger.setLogLevel(repomixLogLevels.SILENT); } logger.trace('directories:', directories); logger.trace('cwd:', cwd); logger.trace('options:', options); if (options.mcp) { return await runMcpAction(); } if (options.version) { await runVersionAction(); return; } const version = await getVersion(); logger.log(pc.dim(`\n📦 Repomix v${version}\n`)); if (options.init) { await runInitAction(cwd, options.global || false); return; } if (options.remote) { return await runRemoteAction(options.remote, options); } return await runDefaultAction(directories, cwd, options); }; ``` ## /src/cli/cliSpinner.ts ```ts path="/src/cli/cliSpinner.ts" import cliSpinners from 'cli-spinners'; import logUpdate from 'log-update'; import pc from 'picocolors'; import type { CliOptions } from './types.js'; export class Spinner { private spinner = cliSpinners.dots; private message: string; private currentFrame = 0; private interval: ReturnType<typeof setInterval> | null = null; private readonly isQuiet: boolean; constructor(message: string, cliOptions: CliOptions) { this.message = message; // If the user has specified the verbose flag, don't show the spinner this.isQuiet = cliOptions.quiet || cliOptions.verbose || cliOptions.stdout || false; } start(): void { if (this.isQuiet) { return; } const frames = this.spinner.frames; const framesLength = frames.length; this.interval = setInterval(() => { this.currentFrame++; const frame = frames[this.currentFrame % framesLength]; logUpdate(`${pc.cyan(frame)} ${this.message}`); }, this.spinner.interval); } update(message: string): void { if (this.isQuiet) { return; } this.message = message; } stop(finalMessage: string): void { if (this.isQuiet) { return; } if (this.interval) { clearInterval(this.interval); this.interval = null; } logUpdate(finalMessage); logUpdate.done(); } succeed(message: string): void { if (this.isQuiet) { return; } this.stop(`${pc.green('✔')} ${message}`); } fail(message: string): void { if (this.isQuiet) { return; } this.stop(`${pc.red('✖')} ${message}`); } } ``` ## /src/cli/types.ts ```ts path="/src/cli/types.ts" import type { OptionValues } from 'commander'; import type { RepomixOutputStyle } from '../config/configSchema.js'; export interface CliOptions extends OptionValues { // Basic Options version?: boolean; // Output Options output?: string; stdout?: boolean; style?: RepomixOutputStyle; parsableStyle?: boolean; compress?: boolean; outputShowLineNumbers?: boolean; copy?: boolean; fileSummary?: boolean; directoryStructure?: boolean; files?: boolean; removeComments?: boolean; removeEmptyLines?: boolean; headerText?: string; instructionFilePath?: string; includeEmptyDirectories?: boolean; gitSortByChanges?: boolean; includeDiffs?: boolean; // Filter Options include?: string; ignore?: string; gitignore?: boolean; defaultPatterns?: boolean; // Remote Repository Options remote?: string; remoteBranch?: string; // Configuration Options config?: string; init?: boolean; global?: boolean; // Security Options securityCheck?: boolean; // Token Count Options tokenCountEncoding?: string; // MCP mcp?: boolean; // Other Options topFilesLen?: number; verbose?: boolean; quiet?: boolean; } ``` ## /src/config/configLoad.ts ```ts path="/src/config/configLoad.ts" import * as fs from 'node:fs/promises'; import path from 'node:path'; import JSON5 from 'json5'; import pc from 'picocolors'; import { RepomixError, rethrowValidationErrorIfZodError } from '../shared/errorHandle.js'; import { logger } from '../shared/logger.js'; import { type RepomixConfigCli, type RepomixConfigFile, type RepomixConfigMerged, defaultConfig, defaultFilePathMap, repomixConfigFileSchema, repomixConfigMergedSchema, } from './configSchema.js'; import { getGlobalDirectory } from './globalDirectory.js'; const defaultConfigPath = 'repomix.config.json'; const getGlobalConfigPath = () => { return path.join(getGlobalDirectory(), 'repomix.config.json'); }; export const loadFileConfig = async (rootDir: string, argConfigPath: string | null): Promise<RepomixConfigFile> => { let useDefaultConfig = false; let configPath = argConfigPath; if (!configPath) { useDefaultConfig = true; configPath = defaultConfigPath; } const fullPath = path.resolve(rootDir, configPath); logger.trace('Loading local config from:', fullPath); // Check local file existence const isLocalFileExists = await fs .stat(fullPath) .then((stats) => stats.isFile()) .catch(() => false); if (isLocalFileExists) { return await loadAndValidateConfig(fullPath); } if (useDefaultConfig) { // Try to load global config const globalConfigPath = getGlobalConfigPath(); logger.trace('Loading global config from:', globalConfigPath); const isGlobalFileExists = await fs .stat(globalConfigPath) .then((stats) => stats.isFile()) .catch(() => false); if (isGlobalFileExists) { return await loadAndValidateConfig(globalConfigPath); } logger.log( pc.dim( `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/yamadashy/repomix for more information.`, ), ); return {}; } throw new RepomixError(`Config file not found at ${configPath}`); }; const loadAndValidateConfig = async (filePath: string): Promise<RepomixConfigFile> => { try { const fileContent = await fs.readFile(filePath, 'utf-8'); const config = JSON5.parse(fileContent); return repomixConfigFileSchema.parse(config); } catch (error) { rethrowValidationErrorIfZodError(error, 'Invalid config schema'); if (error instanceof SyntaxError) { throw new RepomixError(`Invalid JSON5 in config file ${filePath}: ${error.message}`); } if (error instanceof Error) { throw new RepomixError(`Error loading config from ${filePath}: ${error.message}`); } throw new RepomixError(`Error loading config from ${filePath}`); } }; export const mergeConfigs = ( cwd: string, fileConfig: RepomixConfigFile, cliConfig: RepomixConfigCli, ): RepomixConfigMerged => { logger.trace('Default config:', defaultConfig); const baseConfig = defaultConfig; // If the output file path is not provided in the config file or CLI, use the default file path for the style if (cliConfig.output?.filePath == null && fileConfig.output?.filePath == null) { const style = cliConfig.output?.style || fileConfig.output?.style || baseConfig.output.style; baseConfig.output.filePath = defaultFilePathMap[style]; logger.trace('Default output file path is set to:', baseConfig.output.filePath); } const mergedConfig = { cwd, input: { ...baseConfig.input, ...fileConfig.input, ...cliConfig.input, }, output: { ...baseConfig.output, ...fileConfig.output, ...cliConfig.output, }, include: [...(baseConfig.include || []), ...(fileConfig.include || []), ...(cliConfig.include || [])], ignore: { ...baseConfig.ignore, ...fileConfig.ignore, ...cliConfig.ignore, customPatterns: [ ...(baseConfig.ignore.customPatterns || []), ...(fileConfig.ignore?.customPatterns || []), ...(cliConfig.ignore?.customPatterns || []), ], }, security: { ...baseConfig.security, ...fileConfig.security, ...cliConfig.security, }, }; try { return repomixConfigMergedSchema.parse(mergedConfig); } catch (error) { rethrowValidationErrorIfZodError(error, 'Invalid merged config'); throw error; } }; ``` ## /src/config/configSchema.ts ```ts path="/src/config/configSchema.ts" import type { TiktokenEncoding } from 'tiktoken'; import { z } from 'zod'; // Output style enum export const repomixOutputStyleSchema = z.enum(['xml', 'markdown', 'plain']); export type RepomixOutputStyle = z.infer<typeof repomixOutputStyleSchema>; // Default values map export const defaultFilePathMap: Record<RepomixOutputStyle, string> = { xml: 'repomix-output.xml', markdown: 'repomix-output.md', plain: 'repomix-output.txt', } as const; // Base config schema export const repomixConfigBaseSchema = z.object({ $schema: z.string().optional(), input: z .object({ maxFileSize: z.number().optional(), }) .optional(), output: z .object({ filePath: z.string().optional(), style: repomixOutputStyleSchema.optional(), parsableStyle: z.boolean().optional(), headerText: z.string().optional(), instructionFilePath: z.string().optional(), fileSummary: z.boolean().optional(), directoryStructure: z.boolean().optional(), files: z.boolean().optional(), removeComments: z.boolean().optional(), removeEmptyLines: z.boolean().optional(), compress: z.boolean().optional(), topFilesLength: z.number().optional(), showLineNumbers: z.boolean().optional(), copyToClipboard: z.boolean().optional(), includeEmptyDirectories: z.boolean().optional(), git: z .object({ sortByChanges: z.boolean().optional(), sortByChangesMaxCommits: z.number().optional(), includeDiffs: z.boolean().optional(), }) .optional(), }) .optional(), include: z.array(z.string()).optional(), ignore: z .object({ useGitignore: z.boolean().optional(), useDefaultPatterns: z.boolean().optional(), customPatterns: z.array(z.string()).optional(), }) .optional(), security: z .object({ enableSecurityCheck: z.boolean().optional(), }) .optional(), tokenCount: z .object({ encoding: z.string().optional(), }) .optional(), }); // Default config schema with default values export const repomixConfigDefaultSchema = z.object({ input: z .object({ maxFileSize: z .number() .int() .min(1) .default(50 * 1024 * 1024), // Default: 50MB }) .default({}), output: z .object({ filePath: z.string().default(defaultFilePathMap.xml), style: repomixOutputStyleSchema.default('xml'), parsableStyle: z.boolean().default(false), headerText: z.string().optional(), instructionFilePath: z.string().optional(), fileSummary: z.boolean().default(true), directoryStructure: z.boolean().default(true), files: z.boolean().default(true), removeComments: z.boolean().default(false), removeEmptyLines: z.boolean().default(false), compress: z.boolean().default(false), topFilesLength: z.number().int().min(0).default(5), showLineNumbers: z.boolean().default(false), copyToClipboard: z.boolean().default(false), includeEmptyDirectories: z.boolean().optional(), git: z .object({ sortByChanges: z.boolean().default(true), sortByChangesMaxCommits: z.number().int().min(1).default(100), includeDiffs: z.boolean().default(false), }) .default({}), }) .default({}), include: z.array(z.string()).default([]), ignore: z .object({ useGitignore: z.boolean().default(true), useDefaultPatterns: z.boolean().default(true), customPatterns: z.array(z.string()).default([]), }) .default({}), security: z .object({ enableSecurityCheck: z.boolean().default(true), }) .default({}), tokenCount: z .object({ encoding: z .string() .default('o200k_base') .transform((val) => val as TiktokenEncoding), }) .default({}), }); // File-specific schema. Add options for file path and style export const repomixConfigFileSchema = repomixConfigBaseSchema; // CLI-specific schema. Add options for standard output mode export const repomixConfigCliSchema = repomixConfigBaseSchema.and( z.object({ output: z .object({ stdout: z.boolean().optional(), }) .optional(), }), ); // Merged schema for all configurations export const repomixConfigMergedSchema = repomixConfigDefaultSchema .and(repomixConfigFileSchema) .and(repomixConfigCliSchema) .and( z.object({ cwd: z.string(), }), ); export type RepomixConfigDefault = z.infer<typeof repomixConfigDefaultSchema>; export type RepomixConfigFile = z.infer<typeof repomixConfigFileSchema>; export type RepomixConfigCli = z.infer<typeof repomixConfigCliSchema>; export type RepomixConfigMerged = z.infer<typeof repomixConfigMergedSchema>; export const defaultConfig = repomixConfigDefaultSchema.parse({}); ``` ## /src/config/defaultIgnore.ts ```ts path="/src/config/defaultIgnore.ts" export const defaultIgnoreList = [ // Version control '.git/**', '.hg/**', '.hgignore', '.svn/**', // Dependency directories '**/node_modules/**', '**/bower_components/**', '**/jspm_packages/**', 'vendor/**', '**/.bundle/**', '**/.gradle/**', 'target/**', // Logs 'logs/**', '**/*.log', '**/npm-debug.log*', '**/yarn-debug.log*', '**/yarn-error.log*', // Runtime data 'pids/**', '*.pid', '*.seed', '*.pid.lock', // Directory for instrumented libs generated by jscoverage/JSCover 'lib-cov/**', // Coverage directory used by tools like istanbul 'coverage/**', // nyc test coverage '.nyc_output/**', // Grunt intermediate storage '.grunt/**', // node-waf configuration '.lock-wscript', // Compiled binary addons 'build/Release/**', // TypeScript v1 declaration files 'typings/**', // Optional npm cache directory '**/.npm/**', // Cache directories '.eslintcache', '.rollup.cache/**', '.webpack.cache/**', '.parcel-cache/**', '.sass-cache/**', '*.cache', // Optional REPL history '.node_repl_history', // Output of 'npm pack' '*.tgz', // Yarn files '**/.yarn/**', // Yarn Integrity file '**/.yarn-integrity', // dotenv environment variables file '.env', // next.js build output '.next/**', // nuxt.js build output '.nuxt/**', // vuepress build output '.vuepress/dist/**', // Serverless directories '.serverless/**', // FuseBox cache '.fusebox/**', // DynamoDB Local files '.dynamodb/**', // TypeScript output 'dist/**', // OS generated files '**/.DS_Store', '**/Thumbs.db', // Editor directories and files '.idea/**', '.vscode/**', '**/*.swp', '**/*.swo', '**/*.swn', '**/*.bak', // Build outputs 'build/**', 'out/**', // Temporary files 'tmp/**', 'temp/**', // repomix output '**/repomix-output.*', '**/repopack-output.*', // Legacy // Essential Node.js-related entries '**/package-lock.json', '**/yarn-error.log', '**/yarn.lock', '**/pnpm-lock.yaml', '**/bun.lockb', '**/bun.lock', // Essential Python-related entries '**/__pycache__/**', '**/*.py[cod]', '**/venv/**', '**/.venv/**', '**/.pytest_cache/**', '**/.mypy_cache/**', '**/.ipynb_checkpoints/**', '**/Pipfile.lock', '**/poetry.lock', '**/uv.lock', // Essential Rust-related entries '**/Cargo.lock', '**/Cargo.toml.orig', '**/target/**', '**/*.rs.bk', // Essential PHP-related entries '**/composer.lock', // Essential Ruby-related entries '**/Gemfile.lock', // Essential Go-related entries '**/go.sum', // Essential Elixir-related entries '**/mix.lock', // Essential Haskell-related entries '**/stack.yaml.lock', '**/cabal.project.freeze', ]; ``` ## /src/config/globalDirectory.ts ```ts path="/src/config/globalDirectory.ts" import os from 'node:os'; import path from 'node:path'; export const getGlobalDirectory = () => { if (process.platform === 'win32') { const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'); return path.join(localAppData, 'Repomix'); } if (process.env.XDG_CONFIG_HOME) { return path.join(process.env.XDG_CONFIG_HOME, 'repomix'); } return path.join(os.homedir(), '.config', 'repomix'); }; ``` ## /src/core/file/fileCollect.ts ```ts path="/src/core/file/fileCollect.ts" import pc from 'picocolors'; import type { RepomixConfigMerged } from '../../config/configSchema.js'; import { logger } from '../../shared/logger.js'; import { initPiscina } from '../../shared/processConcurrency.js'; import type { RepomixProgressCallback } from '../../shared/types.js'; import type { RawFile } from './fileTypes.js'; import type { FileCollectTask } from './workers/fileCollectWorker.js'; const initTaskRunner = (numOfTasks: number) => { const pool = initPiscina(numOfTasks, new URL('./workers/fileCollectWorker.js', import.meta.url).href); return (task: FileCollectTask) => pool.run(task); }; export const collectFiles = async ( filePaths: string[], rootDir: string, config: RepomixConfigMerged, progressCallback: RepomixProgressCallback = () => {}, deps = { initTaskRunner, }, ): Promise<RawFile[]> => { const runTask = deps.initTaskRunner(filePaths.length); const tasks = filePaths.map( (filePath) => ({ filePath, rootDir, maxFileSize: config.input.maxFileSize, }) satisfies FileCollectTask, ); try { const startTime = process.hrtime.bigint(); logger.trace(`Starting file collection for ${filePaths.length} files using worker pool`); let completedTasks = 0; const totalTasks = tasks.length; const results = await Promise.all( tasks.map((task) => runTask(task).then((result) => { completedTasks++; progressCallback(`Collect file... (${completedTasks}/${totalTasks}) ${pc.dim(task.filePath)}`); logger.trace(`Collect files... (${completedTasks}/${totalTasks}) ${task.filePath}`); return result; }), ), ); const endTime = process.hrtime.bigint(); const duration = Number(endTime - startTime) / 1e6; logger.trace(`File collection completed in ${duration.toFixed(2)}ms`); return results.filter((file): file is RawFile => file !== null); } catch (error) { logger.error('Error during file collection:', error); throw error; } }; ``` ## /src/core/file/fileManipulate.ts ```ts path="/src/core/file/fileManipulate.ts" import path from 'node:path'; import strip from 'strip-comments'; export interface FileManipulator { removeComments(content: string): string; removeEmptyLines(content: string): string; } const rtrimLines = (content: string): string => content .split('\n') .map((line) => line.trimEnd()) .join('\n'); class BaseManipulator implements FileManipulator { removeComments(content: string): string { return content; } removeEmptyLines(content: string): string { return content .split('\n') .filter((line) => line.trim() !== '') .join('\n'); } } class StripCommentsManipulator extends BaseManipulator { private language: string; constructor(language: string) { super(); this.language = language; } removeComments(content: string): string { const result = strip(content, { language: this.language, preserveNewlines: true, }); return rtrimLines(result); } } class CppManipulator extends BaseManipulator { removeComments(content: string): string { let result = strip(content, { language: 'c', preserveNewlines: true, }); result = result .split('\n') .map((line) => { const tripleSlashIndex = line.indexOf('///'); if (tripleSlashIndex !== -1) { return line.substring(0, tripleSlashIndex).trimEnd(); } return line; }) .join('\n'); return rtrimLines(result); } } class PythonManipulator extends BaseManipulator { removeDocStrings(content: string): string { if (!content) return ''; const lines = content.split('\n'); let result = ''; let buffer = ''; let quoteType: '' | "'" | '"' = ''; let tripleQuotes = 0; const doubleQuoteRegex = /^\s*(?<!\\)(?:""")\s*(?:\n)?[\s\S]*?(?<!("""))(?<!\\)(?:""")/gm; const singleQuoteRegex = /^\s*(?<!\\)(?:''')\s*(?:\n)?[\s\S]*?(?<!('''))(?<!\\)(?:''')/gm; const sz = lines.length; for (let i = 0; i < sz; i++) { const line = lines[i] + (i !== sz - 1 ? '\n' : ''); buffer += line; if (quoteType === '') { const indexSingle = line.search(/(?<![\"])(?<!\\)'''(?![\"])/g); const indexDouble = line.search(/(?<![\'])(?<!\\)"""(?![\'])/g); if (indexSingle !== -1 && (indexDouble === -1 || indexSingle < indexDouble)) { quoteType = "'"; } else if (indexDouble !== -1 && (indexSingle === -1 || indexDouble < indexSingle)) { quoteType = '"'; } } if (quoteType === "'") { tripleQuotes += (line.match(/(?<![\"])(?<!\\)'''(?!["])/g) || []).length; } if (quoteType === '"') { tripleQuotes += (line.match(/(?<![\'])(?<!\\)"""(?![\'])/g) || []).length; } if (tripleQuotes % 2 === 0) { const docstringRegex = quoteType === '"' ? doubleQuoteRegex : singleQuoteRegex; buffer = buffer.replace(docstringRegex, ''); result += buffer; buffer = ''; tripleQuotes = 0; quoteType = ''; } } result += buffer; return result; } removeHashComments(content: string): string { const searchInPairs = (pairs: [number, number][], hashIndex: number): boolean => { return pairs.some(([start, end]) => hashIndex > start && hashIndex < end); }; let result = ''; const pairs: [number, number][] = []; let prevQuote = 0; while (prevQuote < content.length) { const openingQuote = content.slice(prevQuote + 1).search(/(?<!\\)(?:"|'|'''|""")/g) + prevQuote + 1; if (openingQuote === prevQuote) break; let closingQuote = -1; if (content.startsWith('"""', openingQuote) || content.startsWith("'''", openingQuote)) { const quoteType = content.slice(openingQuote, openingQuote + 3); closingQuote = content.indexOf(quoteType, openingQuote + 3); } else { const quoteType = content[openingQuote]; closingQuote = content.indexOf(quoteType, openingQuote + 1); } if (closingQuote === -1) break; pairs.push([openingQuote, closingQuote]); prevQuote = closingQuote; } let prevHash = 0; while (prevHash < content.length) { const hashIndex = content.slice(prevHash).search(/(?<!\\)#/g) + prevHash; if (hashIndex === prevHash - 1) { result += content.slice(prevHash); break; } const isInsideString = searchInPairs(pairs, hashIndex); const nextNewLine = content.indexOf('\n', hashIndex); if (!isInsideString) { if (nextNewLine === -1) { result += content.slice(prevHash); break; } result += `${content.slice(prevHash, hashIndex)}\n`; } else { if (nextNewLine === -1) { result += content.slice(prevHash); break; } result += `${content.slice(prevHash, nextNewLine)}\n`; } prevHash = nextNewLine + 1; } return result; } removeComments(content: string): string { let result = this.removeDocStrings(content); result = this.removeHashComments(result); return rtrimLines(result); } } class CompositeManipulator extends BaseManipulator { private manipulators: FileManipulator[]; constructor(...manipulators: FileManipulator[]) { super(); this.manipulators = manipulators; } removeComments(content: string): string { return this.manipulators.reduce((acc, manipulator) => manipulator.removeComments(acc), content); } } const manipulators: Record<string, FileManipulator> = { '.c': new StripCommentsManipulator('c'), '.h': new StripCommentsManipulator('c'), '.hpp': new CppManipulator(), '.cpp': new CppManipulator(), '.cc': new CppManipulator(), '.cxx': new CppManipulator(), '.cs': new StripCommentsManipulator('csharp'), '.css': new StripCommentsManipulator('css'), '.dart': new StripCommentsManipulator('c'), '.go': new StripCommentsManipulator('c'), '.html': new StripCommentsManipulator('html'), '.java': new StripCommentsManipulator('java'), '.js': new StripCommentsManipulator('javascript'), '.jsx': new StripCommentsManipulator('javascript'), '.kt': new StripCommentsManipulator('c'), '.less': new StripCommentsManipulator('less'), '.php': new StripCommentsManipulator('php'), '.rb': new StripCommentsManipulator('ruby'), '.rs': new StripCommentsManipulator('c'), '.sass': new StripCommentsManipulator('sass'), '.scss': new StripCommentsManipulator('sass'), '.sh': new StripCommentsManipulator('perl'), '.sol': new StripCommentsManipulator('c'), '.sql': new StripCommentsManipulator('sql'), '.swift': new StripCommentsManipulator('swift'), '.ts': new StripCommentsManipulator('javascript'), '.tsx': new StripCommentsManipulator('javascript'), '.xml': new StripCommentsManipulator('xml'), '.yaml': new StripCommentsManipulator('perl'), '.yml': new StripCommentsManipulator('perl'), '.py': new PythonManipulator(), '.vue': new CompositeManipulator( new StripCommentsManipulator('html'), new StripCommentsManipulator('css'), new StripCommentsManipulator('javascript'), ), '.svelte': new CompositeManipulator( new StripCommentsManipulator('html'), new StripCommentsManipulator('css'), new StripCommentsManipulator('javascript'), ), }; export const getFileManipulator = (filePath: string): FileManipulator | null => { const ext = path.extname(filePath); return manipulators[ext] || null; }; ``` ## /src/core/file/filePathSort.ts ```ts path="/src/core/file/filePathSort.ts" import path from 'node:path'; // Sort paths for general use (not affected by git change count) export const sortPaths = (filePaths: string[]): string[] => { return filePaths.sort((a, b) => { const partsA = a.split(path.sep); const partsB = b.split(path.sep); for (let i = 0; i < Math.min(partsA.length, partsB.length); i++) { if (partsA[i] !== partsB[i]) { const isLastA = i === partsA.length - 1; const isLastB = i === partsB.length - 1; if (!isLastA && isLastB) return -1; // Directory if (isLastA && !isLastB) return 1; // File return partsA[i].localeCompare(partsB[i]); // Alphabetical order } } // Sort by path length when all parts are equal return partsA.length - partsB.length; }); }; ``` ## /src/core/file/fileProcess.ts ```ts path="/src/core/file/fileProcess.ts" import pc from 'picocolors'; import type { RepomixConfigMerged } from '../../config/configSchema.js'; import { logger } from '../../shared/logger.js'; import { initPiscina } from '../../shared/processConcurrency.js'; import type { RepomixProgressCallback } from '../../shared/types.js'; import { type FileManipulator, getFileManipulator } from './fileManipulate.js'; import type { ProcessedFile, RawFile } from './fileTypes.js'; import type { FileProcessTask } from './workers/fileProcessWorker.js'; type GetFileManipulator = (filePath: string) => FileManipulator | null; const initTaskRunner = (numOfTasks: number) => { const pool = initPiscina(numOfTasks, new URL('./workers/fileProcessWorker.js', import.meta.url).href); return (task: FileProcessTask) => pool.run(task); }; export const processFiles = async ( rawFiles: RawFile[], config: RepomixConfigMerged, progressCallback: RepomixProgressCallback, deps: { initTaskRunner: typeof initTaskRunner; getFileManipulator: GetFileManipulator; } = { initTaskRunner, getFileManipulator, }, ): Promise<ProcessedFile[]> => { const runTask = deps.initTaskRunner(rawFiles.length); const tasks = rawFiles.map( (rawFile, index) => ({ rawFile, config, }) satisfies FileProcessTask, ); try { const startTime = process.hrtime.bigint(); logger.trace(`Starting file processing for ${rawFiles.length} files using worker pool`); let completedTasks = 0; const totalTasks = tasks.length; const results = await Promise.all( tasks.map((task) => runTask(task).then((result) => { completedTasks++; progressCallback(`Processing file... (${completedTasks}/${totalTasks}) ${pc.dim(task.rawFile.path)}`); logger.trace(`Processing file... (${completedTasks}/${totalTasks}) ${task.rawFile.path}`); return result; }), ), ); const endTime = process.hrtime.bigint(); const duration = Number(endTime - startTime) / 1e6; logger.trace(`File processing completed in ${duration.toFixed(2)}ms`); return results; } catch (error) { logger.error('Error during file processing:', error); throw error; } }; ``` ## /src/core/file/fileProcessContent.ts ```ts path="/src/core/file/fileProcessContent.ts" import type { RepomixConfigMerged } from '../../config/configSchema.js'; import { logger } from '../../shared/logger.js'; import { parseFile } from '../treeSitter/parseFile.js'; import { getFileManipulator } from './fileManipulate.js'; import type { RawFile } from './fileTypes.js'; /** * Process the content of a file according to the configuration * Applies various transformations based on the config: * - Remove comments * - Remove empty lines * - Compress content using Tree-sitter * - Add line numbers * * @param rawFile Raw file data containing path and content * @param config Repomix configuration * @returns Processed content string */ export const processContent = async (rawFile: RawFile, config: RepomixConfigMerged): Promise<string> => { const processStartAt = process.hrtime.bigint(); let processedContent = rawFile.content; const manipulator = getFileManipulator(rawFile.path); logger.trace(`Processing file: ${rawFile.path}`); if (manipulator && config.output.removeComments) { processedContent = manipulator.removeComments(processedContent); } if (config.output.removeEmptyLines && manipulator) { processedContent = manipulator.removeEmptyLines(processedContent); } processedContent = processedContent.trim(); if (config.output.compress) { try { const parsedContent = await parseFile(processedContent, rawFile.path, config); if (parsedContent === undefined) { logger.trace(`Failed to parse ${rawFile.path} in compressed mode. Using original content.`); } processedContent = parsedContent ?? processedContent; } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error); logger.error(`Error parsing ${rawFile.path} in compressed mode: ${message}`); //re-throw error throw error; } } else if (config.output.showLineNumbers) { const lines = processedContent.split('\n'); const padding = lines.length.toString().length; const numberedLines = lines.map((line, i) => `${(i + 1).toString().padStart(padding)}: ${line}`); processedContent = numberedLines.join('\n'); } const processEndAt = process.hrtime.bigint(); logger.trace(`Processed file: ${rawFile.path}. Took: ${(Number(processEndAt - processStartAt) / 1e6).toFixed(2)}ms`); return processedContent; }; ``` ## /src/core/file/fileRead.ts ```ts path="/src/core/file/fileRead.ts" import * as fs from 'node:fs/promises'; import iconv from 'iconv-lite'; import { isBinary } from 'istextorbinary'; import jschardet from 'jschardet'; import { logger } from '../../shared/logger.js'; /** * Read a file and return its text content * @param filePath Path to the file * @param maxFileSize Maximum file size in bytes * @returns File content as string, or null if the file is binary or exceeds size limit */ export const readRawFile = async (filePath: string, maxFileSize: number): Promise<string | null> => { try { const stats = await fs.stat(filePath); if (stats.size > maxFileSize) { const sizeKB = (stats.size / 1024).toFixed(1); const maxSizeKB = (maxFileSize / 1024).toFixed(1); logger.trace(`File exceeds size limit: ${sizeKB}KB > ${maxSizeKB}KB (${filePath})`); return null; } if (isBinary(filePath)) { logger.debug(`Skipping binary file: ${filePath}`); return null; } logger.trace(`Reading file: ${filePath}`); const buffer = await fs.readFile(filePath); if (isBinary(null, buffer)) { logger.debug(`Skipping binary file (content check): ${filePath}`); return null; } const encoding = jschardet.detect(buffer).encoding || 'utf-8'; const content = iconv.decode(buffer, encoding); return content; } catch (error) { logger.warn(`Failed to read file: ${filePath}`, error); return null; } }; ``` ## /src/core/file/fileSearch.ts ```ts path="/src/core/file/fileSearch.ts" import fs from 'node:fs/promises'; import path from 'node:path'; import { globby } from 'globby'; import { minimatch } from 'minimatch'; import type { RepomixConfigMerged } from '../../config/configSchema.js'; import { defaultIgnoreList } from '../../config/defaultIgnore.js'; import { RepomixError } from '../../shared/errorHandle.js'; import { logger } from '../../shared/logger.js'; import { sortPaths } from './filePathSort.js'; import { PermissionError, checkDirectoryPermissions } from './permissionCheck.js'; export interface FileSearchResult { filePaths: string[]; emptyDirPaths: string[]; } const findEmptyDirectories = async ( rootDir: string, directories: string[], ignorePatterns: string[], ): Promise<string[]> => { const emptyDirs: string[] = []; for (const dir of directories) { const fullPath = path.join(rootDir, dir); try { const entries = await fs.readdir(fullPath); const hasVisibleContents = entries.some((entry) => !entry.startsWith('.')); if (!hasVisibleContents) { // This checks if the directory itself matches any ignore patterns const shouldIgnore = ignorePatterns.some((pattern) => minimatch(dir, pattern) || minimatch(`${dir}/`, pattern)); if (!shouldIgnore) { emptyDirs.push(dir); } } } catch (error) { logger.debug(`Error checking directory ${dir}:`, error); } } return emptyDirs; }; // Check if a path is a git worktree reference file const isGitWorktreeRef = async (gitPath: string): Promise<boolean> => { try { const stats = await fs.stat(gitPath); if (!stats.isFile()) { return false; } const content = await fs.readFile(gitPath, 'utf8'); return content.startsWith('gitdir:'); } catch { return false; } }; /** * Escapes special characters in glob patterns to handle paths with parentheses. * Example: "src/(categories)" -> "src/\\(categories\\)" */ export const escapeGlobPattern = (pattern: string): string => { // First escape backslashes const escapedBackslashes = pattern.replace(/\\/g, '\\\\'); // Then escape special characters () and [], but NOT {} return escapedBackslashes.replace(/[()[\]]/g, '\\{{contextString}}amp;'); }; /** * Normalizes glob patterns by removing trailing slashes and ensuring consistent directory pattern handling. * Makes "**\/folder", "**\/folder/", and "**\/folder/**\/*" behave identically. * * @param pattern The glob pattern to normalize * @returns The normalized pattern */ export const normalizeGlobPattern = (pattern: string): string => { // Remove trailing slash but preserve patterns that end with "**/" if (pattern.endsWith('/') && !pattern.endsWith('**/')) { return pattern.slice(0, -1); } // Convert **/folder to **/folder/** for consistent ignore pattern behavior if (pattern.startsWith('**/') && !pattern.includes('/**')) { return `${pattern}/**`; } return pattern; }; // Get all file paths considering the config export const searchFiles = async (rootDir: string, config: RepomixConfigMerged): Promise<FileSearchResult> => { // First check directory permissions const permissionCheck = await checkDirectoryPermissions(rootDir); if (permissionCheck.details?.read !== true) { if (permissionCheck.error instanceof PermissionError) { throw permissionCheck.error; } throw new RepomixError( `Target directory is not readable or does not exist. Please check folder access permissions for your terminal app.\npath: ${rootDir}`, ); } const includePatterns = config.include.length > 0 ? config.include.map((pattern) => escapeGlobPattern(pattern)) : ['**/*']; try { const [ignorePatterns, ignoreFilePatterns] = await Promise.all([ getIgnorePatterns(rootDir, config), getIgnoreFilePatterns(config), ]); // Normalize ignore patterns to handle trailing slashes consistently const normalizedIgnorePatterns = ignorePatterns.map(normalizeGlobPattern); logger.trace('Include patterns:', includePatterns); logger.trace('Ignore patterns:', normalizedIgnorePatterns); logger.trace('Ignore file patterns:', ignoreFilePatterns); // Check if .git is a worktree reference const gitPath = path.join(rootDir, '.git'); const isWorktree = await isGitWorktreeRef(gitPath); // Modify ignore patterns for git worktree const adjustedIgnorePatterns = [...normalizedIgnorePatterns]; if (isWorktree) { // Remove '.git/**' pattern and add '.git' to ignore the reference file const gitIndex = adjustedIgnorePatterns.indexOf('.git/**'); if (gitIndex !== -1) { adjustedIgnorePatterns.splice(gitIndex, 1); adjustedIgnorePatterns.push('.git'); } } const filePaths = await globby(includePatterns, { cwd: rootDir, ignore: [...adjustedIgnorePatterns], ignoreFiles: [...ignoreFilePatterns], onlyFiles: true, absolute: false, dot: true, followSymbolicLinks: false, }).catch((error) => { // Handle EPERM errors specifically if (error.code === 'EPERM' || error.code === 'EACCES') { throw new PermissionError( `Permission denied while scanning directory. Please check folder access permissions for your terminal app. path: ${rootDir}`, rootDir, ); } throw error; }); let emptyDirPaths: string[] = []; if (config.output.includeEmptyDirectories) { const directories = await globby(includePatterns, { cwd: rootDir, ignore: [...adjustedIgnorePatterns], ignoreFiles: [...ignoreFilePatterns], onlyDirectories: true, absolute: false, dot: true, followSymbolicLinks: false, }); emptyDirPaths = await findEmptyDirectories(rootDir, directories, adjustedIgnorePatterns); } logger.trace(`Filtered ${filePaths.length} files`); return { filePaths: sortPaths(filePaths), emptyDirPaths: sortPaths(emptyDirPaths), }; } catch (error: unknown) { // Re-throw PermissionError as is if (error instanceof PermissionError) { throw error; } if (error instanceof Error) { logger.error('Error filtering files:', error.message); throw new Error(`Failed to filter files in directory ${rootDir}. Reason: ${error.message}`); } logger.error('An unexpected error occurred:', error); throw new Error('An unexpected error occurred while filtering files.'); } }; export const parseIgnoreContent = (content: string): string[] => { if (!content) return []; return content.split('\n').reduce<string[]>((acc, line) => { const trimmedLine = line.trim(); if (trimmedLine && !trimmedLine.startsWith('#')) { acc.push(trimmedLine); } return acc; }, []); }; export const getIgnoreFilePatterns = async (config: RepomixConfigMerged): Promise<string[]> => { const ignoreFilePatterns: string[] = []; if (config.ignore.useGitignore) { ignoreFilePatterns.push('**/.gitignore'); } ignoreFilePatterns.push('**/.repomixignore'); return ignoreFilePatterns; }; export const getIgnorePatterns = async (rootDir: string, config: RepomixConfigMerged): Promise<string[]> => { const ignorePatterns = new Set<string>(); // Add default ignore patterns if (config.ignore.useDefaultPatterns) { logger.trace('Adding default ignore patterns'); for (const pattern of defaultIgnoreList) { ignorePatterns.add(pattern); } } // Add repomix output file if (config.output.filePath) { const absoluteOutputPath = path.resolve(config.cwd, config.output.filePath); const relativeToTargetPath = path.relative(rootDir, absoluteOutputPath); logger.trace('Adding output file to ignore patterns:', relativeToTargetPath); ignorePatterns.add(relativeToTargetPath); } // Add custom ignore patterns if (config.ignore.customPatterns) { logger.trace('Adding custom ignore patterns:', config.ignore.customPatterns); for (const pattern of config.ignore.customPatterns) { ignorePatterns.add(pattern); } } // Add patterns from .git/info/exclude if useGitignore is enabled if (config.ignore.useGitignore) { const excludeFilePath = path.join(rootDir, '.git', 'info', 'exclude'); try { const excludeFileContent = await fs.readFile(excludeFilePath, 'utf8'); const excludePatterns = parseIgnoreContent(excludeFileContent); for (const pattern of excludePatterns) { ignorePatterns.add(pattern); } } catch (error) { // File might not exist or might not be accessible, which is fine logger.trace('Could not read .git/info/exclude file:', error instanceof Error ? error.message : String(error)); } } return Array.from(ignorePatterns); }; ``` ## /src/core/file/fileTreeGenerate.ts ```ts path="/src/core/file/fileTreeGenerate.ts" import nodepath from 'node:path'; export interface TreeNode { name: string; children: TreeNode[]; isDirectory: boolean; } const createTreeNode = (name: string, isDirectory: boolean): TreeNode => ({ name, children: [], isDirectory }); export const generateFileTree = (files: string[], emptyDirPaths: string[] = []): TreeNode => { const root: TreeNode = createTreeNode('root', true); for (const file of files) { addPathToTree(root, file, false); } // Add empty directories for (const dir of emptyDirPaths) { addPathToTree(root, dir, true); } return root; }; const addPathToTree = (root: TreeNode, path: string, isDirectory: boolean): void => { const parts = path.split(nodepath.sep); let currentNode = root; for (let i = 0; i < parts.length; i++) { const part = parts[i]; const isLastPart = i === parts.length - 1; let child = currentNode.children.find((c) => c.name === part); if (!child) { child = createTreeNode(part, !isLastPart || isDirectory); currentNode.children.push(child); } currentNode = child; } }; const sortTreeNodes = (node: TreeNode) => { node.children.sort((a, b) => { if (a.isDirectory === b.isDirectory) { return a.name.localeCompare(b.name); } return a.isDirectory ? -1 : 1; }); for (const child of node.children) { sortTreeNodes(child); } }; export const treeToString = (node: TreeNode, prefix = ''): string => { sortTreeNodes(node); let result = ''; for (const child of node.children) { result += `${prefix}${child.name}${child.isDirectory ? '/' : ''}\n`; if (child.isDirectory) { result += treeToString(child, `${prefix} `); } } return result; }; export const generateTreeString = (files: string[], emptyDirPaths: string[] = []): string => { const tree = generateFileTree(files, emptyDirPaths); return treeToString(tree).trim(); }; ``` ## /src/core/file/fileTypes.ts ```ts path="/src/core/file/fileTypes.ts" export interface RawFile { path: string; content: string; } export interface ProcessedFile { path: string; content: string; } ``` ## /src/core/file/packageJsonParse.ts ```ts path="/src/core/file/packageJsonParse.ts" import * as fs from 'node:fs/promises'; import path from 'node:path'; import * as url from 'node:url'; import { logger } from '../../shared/logger.js'; export const getVersion = async (): Promise<string> => { try { const packageJson = await parsePackageJson(); if (!packageJson.version) { logger.warn('No version found in package.json'); return 'unknown'; } return packageJson.version; } catch (error) { logger.error('Error reading package.json:', error); return 'unknown'; } }; const parsePackageJson = async (): Promise<{ name: string; version: string; }> => { const dirName = url.fileURLToPath(new URL('.', import.meta.url)); const packageJsonPath = path.join(dirName, '..', '..', '..', 'package.json'); const packageJsonFile = await fs.readFile(packageJsonPath, 'utf-8'); const packageJson = JSON.parse(packageJsonFile); return packageJson; }; ``` ## /src/core/file/permissionCheck.ts ```ts path="/src/core/file/permissionCheck.ts" import { constants } from 'node:fs'; import * as fs from 'node:fs/promises'; import { platform } from 'node:os'; import { logger } from '../../shared/logger.js'; export interface PermissionCheckResult { hasAllPermission: boolean; error?: Error; details?: { read?: boolean; write?: boolean; execute?: boolean; }; } export class PermissionError extends Error { constructor( message: string, public readonly path: string, public readonly code?: string, ) { super(message); this.name = 'PermissionError'; } } export const checkDirectoryPermissions = async (dirPath: string): Promise<PermissionCheckResult> => { try { // First try to read directory contents await fs.readdir(dirPath); // Check specific permissions const details = { read: false, write: false, execute: false, }; try { await fs.access(dirPath, constants.R_OK); details.read = true; } catch {} try { await fs.access(dirPath, constants.W_OK); details.write = true; } catch {} try { await fs.access(dirPath, constants.X_OK); details.execute = true; } catch {} const hasAllPermissions = details.read && details.write && details.execute; if (!hasAllPermissions) { return { hasAllPermission: false, details, }; } return { hasAllPermission: true, details, }; } catch (error) { if (error instanceof Error && 'code' in error) { switch (error.code) { case 'EPERM': case 'EACCES': case 'EISDIR': return { hasAllPermission: false, error: new PermissionError(getMacOSPermissionMessage(dirPath, error.code), dirPath, error.code), }; default: logger.debug('Directory permission check error:', error); return { hasAllPermission: false, error: error as Error, }; } } return { hasAllPermission: false, error: error instanceof Error ? error : new Error(String(error)), }; } }; const getMacOSPermissionMessage = (dirPath: string, errorCode?: string): string => { if (platform() === 'darwin') { return `Permission denied: Cannot access '${dirPath}', error code: ${errorCode}. This error often occurs when macOS security restrictions prevent access to the directory. To fix this: 1. Open System Settings 2. Navigate to Privacy & Security > Files and Folders 3. Find your terminal app (Terminal.app, iTerm2, VS Code, etc.) 4. Grant necessary folder access permissions If your terminal app is not listed: - Try running repomix command again - When prompted by macOS, click "Allow" - Restart your terminal app if needed `; } return `Permission denied: Cannot access '${dirPath}'`; }; ``` ## /src/core/file/workers/fileCollectWorker.ts ```ts path="/src/core/file/workers/fileCollectWorker.ts" import path from 'node:path'; import { logger, setLogLevelByEnv } from '../../../shared/logger.js'; import { readRawFile } from '../fileRead.js'; export interface FileCollectTask { filePath: string; rootDir: string; maxFileSize: number; } // Set logger log level from environment variable if provided setLogLevelByEnv(); export default async ({ filePath, rootDir, maxFileSize }: FileCollectTask) => { const fullPath = path.resolve(rootDir, filePath); const content = await readRawFile(fullPath, maxFileSize); if (content) { return { path: filePath, content, }; } return null; }; ``` ## /src/core/file/workers/fileProcessWorker.ts ```ts path="/src/core/file/workers/fileProcessWorker.ts" import type { RepomixConfigMerged } from '../../../config/configSchema.js'; import { logger, setLogLevelByEnv } from '../../../shared/logger.js'; import { processContent } from '../fileProcessContent.js'; import type { ProcessedFile, RawFile } from '../fileTypes.js'; export interface FileProcessTask { rawFile: RawFile; config: RepomixConfigMerged; } // Set logger log level from environment variable if provided setLogLevelByEnv(); export default async ({ rawFile, config }: FileProcessTask): Promise<ProcessedFile> => { const processedContent = await processContent(rawFile, config); return { path: rawFile.path, content: processedContent, }; }; ``` ## /src/core/git/gitCommand.ts ```ts path="/src/core/git/gitCommand.ts" import { execFile } from 'node:child_process'; import fs from 'node:fs/promises'; import path from 'node:path'; import { promisify } from 'node:util'; import { RepomixError } from '../../shared/errorHandle.js'; import { logger } from '../../shared/logger.js'; const execFileAsync = promisify(execFile); export const execGitLogFilenames = async ( directory: string, maxCommits = 100, deps = { execFileAsync, }, ): Promise<string[]> => { try { const result = await deps.execFileAsync('git', [ '-C', directory, 'log', '--pretty=format:', '--name-only', '-n', maxCommits.toString(), ]); return result.stdout.split('\n').filter(Boolean); } catch (error) { logger.trace('Failed to get git log filenames:', (error as Error).message); return []; } }; export const execGitDiff = async ( directory: string, options: string[] = [], deps = { execFileAsync, }, ): Promise<string> => { try { const result = await deps.execFileAsync('git', [ '-C', directory, 'diff', '--no-color', // Avoid ANSI color codes ...options, ]); return result.stdout || ''; } catch (error) { logger.trace('Failed to execute git diff:', (error as Error).message); throw error; } }; export const execGitVersion = async ( deps = { execFileAsync, }, ): Promise<string> => { try { const result = await deps.execFileAsync('git', ['--version']); return result.stdout || ''; } catch (error) { logger.trace('Failed to execute git version:', (error as Error).message); throw error; } }; export const execGitRevParse = async ( directory: string, deps = { execFileAsync, }, ): Promise<string> => { try { const result = await deps.execFileAsync('git', ['-C', directory, 'rev-parse', '--is-inside-work-tree']); return result.stdout || ''; } catch (error) { logger.trace('Failed to execute git rev-parse:', (error as Error).message); throw error; } }; export const execLsRemote = async ( url: string, deps = { execFileAsync, }, ): Promise<string> => { validateGitUrl(url); try { const result = await deps.execFileAsync('git', ['ls-remote', '--heads', '--tags', url]); return result.stdout || ''; } catch (error) { logger.trace('Failed to execute git ls-remote:', (error as Error).message); throw error; } }; export const execGitShallowClone = async ( url: string, directory: string, remoteBranch?: string, deps = { execFileAsync, }, ) => { validateGitUrl(url); if (remoteBranch) { await deps.execFileAsync('git', ['-C', directory, 'init']); await deps.execFileAsync('git', ['-C', directory, 'remote', 'add', 'origin', url]); try { await deps.execFileAsync('git', ['-C', directory, 'fetch', '--depth', '1', 'origin', remoteBranch]); await deps.execFileAsync('git', ['-C', directory, 'checkout', 'FETCH_HEAD']); } catch (err: unknown) { // git fetch --depth 1 origin <short SHA> always throws "couldn't find remote ref" error const isRefNotfoundError = err instanceof Error && err.message.includes(`couldn't find remote ref ${remoteBranch}`); if (!isRefNotfoundError) { // Rethrow error as nothing else we can do throw err; } // Short SHA detection - matches a hexadecimal string of 4 to 39 characters // If the string matches this regex, it MIGHT be a short SHA // If the string doesn't match, it is DEFINITELY NOT a short SHA const isNotShortSHA = !remoteBranch.match(/^[0-9a-f]{4,39}$/i); if (isNotShortSHA) { // Rethrow error as nothing else we can do throw err; } // Maybe the error is due to a short SHA, let's try again // Can't use --depth 1 here as we need to fetch the specific commit await deps.execFileAsync('git', ['-C', directory, 'fetch', 'origin']); await deps.execFileAsync('git', ['-C', directory, 'checkout', remoteBranch]); } } else { await deps.execFileAsync('git', ['clone', '--depth', '1', url, directory]); } // Clean up .git directory await fs.rm(path.join(directory, '.git'), { recursive: true, force: true }); }; /** * Validates a Git URL for security and format * @throws {RepomixError} If the URL is invalid or contains potentially dangerous parameters */ export const validateGitUrl = (url: string): void => { if (url.includes('--upload-pack') || url.includes('--config') || url.includes('--exec')) { throw new RepomixError(`Invalid repository URL. URL contains potentially dangerous parameters: ${url}`); } // Check if the URL starts with git@ or https:// if (!(url.startsWith('git@') || url.startsWith('https://'))) { throw new RepomixError(`Invalid URL protocol for '${url}'. URL must start with 'git@' or 'https://'`); } try { if (url.startsWith('https://')) { new URL(url); } } catch (error) { throw new RepomixError(`Invalid repository URL. Please provide a valid URL: ${url}`); } }; ``` ## /src/core/git/gitDiffHandle.ts ```ts path="/src/core/git/gitDiffHandle.ts" import type { RepomixConfigMerged } from '../../config/configSchema.js'; import { RepomixError } from '../../shared/errorHandle.js'; import { logger } from '../../shared/logger.js'; import { execGitDiff } from './gitCommand.js'; import { isGitRepository } from './gitRepositoryHandle.js'; export interface GitDiffResult { workTreeDiffContent: string; stagedDiffContent: string; } export const getWorkTreeDiff = async ( directory: string, deps = { execGitDiff, isGitRepository, }, ): Promise<string> => { return getDiff(directory, [], deps); }; export const getStagedDiff = async ( directory: string, deps = { execGitDiff, isGitRepository, }, ): Promise<string> => { return getDiff(directory, ['--cached'], deps); }; /** * Helper function to get git diff with common repository check and error handling */ const getDiff = async ( directory: string, options: string[], deps = { execGitDiff, isGitRepository, }, ): Promise<string> => { try { // Check if the directory is a git repository const isGitRepo = await deps.isGitRepository(directory); if (!isGitRepo) { logger.trace('Not a git repository, skipping diff generation'); return ''; } // Get the diff with provided options const result = await deps.execGitDiff(directory, options); return result; } catch (error) { logger.trace('Failed to get git diff:', (error as Error).message); return ''; } }; export const getGitDiffs = async ( rootDirs: string[], config: RepomixConfigMerged, deps = { getWorkTreeDiff, getStagedDiff, }, ): Promise<GitDiffResult | undefined> => { // Get git diffs if enabled let gitDiffResult: GitDiffResult | undefined; if (config.output.git?.includeDiffs) { try { // Use the first directory as the git repository root // Usually this would be the root of the project const gitRoot = rootDirs[0] || config.cwd; const [workTreeDiffContent, stagedDiffContent] = await Promise.all([ deps.getWorkTreeDiff(gitRoot), deps.getStagedDiff(gitRoot), ]); gitDiffResult = { workTreeDiffContent, stagedDiffContent, }; } catch (error) { if (error instanceof Error) { throw new RepomixError(`Failed to get git diffs: ${error.message}`); } } } return gitDiffResult; }; ``` ## /src/core/metrics/workers/types.ts ```ts path="/src/core/metrics/workers/types.ts" export interface FileMetrics { path: string; charCount: number; tokenCount: number; } ``` The content has been capped at 50000 tokens. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.