diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..295b97a --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +# Commercial licensing and sponsorship: eduardoa.borjas@gmail.com +custom: ['mailto:eduardoa.borjas@gmail.com'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..36f1fb8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,57 @@ +name: Bug report +description: Report a problem with streamnet-cli +title: '[bug]: ' +labels: ['bug'] +body: + - type: markdown + attributes: + value: | + Thanks for filing a bug! Please run `streamnet doctor --json` and include + the output — it captures most of what we need. + - type: textarea + id: what-happened + attributes: + label: What happened? + description: A clear description of the bug, including the exact command you ran. + placeholder: 'streamnet play "..." --yes → exited with code 7' + validations: + required: true + - type: textarea + id: expected + attributes: + label: What did you expect? + validations: + required: true + - type: textarea + id: doctor + attributes: + label: streamnet doctor --json + render: json + validations: + required: false + - type: input + id: version + attributes: + label: streamnet version + placeholder: '0.1.0' + validations: + required: true + - type: dropdown + id: os + attributes: + label: Operating system + options: + - Linux (Ubuntu/Pop!_OS/Debian) + - Linux (Fedora/RHEL) + - Linux (Arch) + - macOS + - Windows + - Other + validations: + required: true + - type: textarea + id: logs + attributes: + label: Relevant logs + description: Output from `--verbose`, or the log file under your data dir. + render: shell diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..749562b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Security vulnerability + url: https://github.com/eduardoborjas/streamnet-cli/security/advisories/new + about: Please report security issues privately, not as public issues. See SECURITY.md. + - name: Commercial licensing + url: mailto:eduardoa.borjas@gmail.com + about: Questions about commercial licensing for proprietary/closed-source use. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..ccdda3f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,31 @@ +name: Feature request +description: Suggest an idea or improvement +title: '[feat]: ' +labels: ['enhancement'] +body: + - type: textarea + id: problem + attributes: + label: Problem / motivation + description: What are you trying to do that's hard or impossible today? + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed solution + description: What would you like streamnet to do? Include the command surface if relevant. + validations: + required: true + - type: checkboxes + id: agent-native + attributes: + label: Agent-native considerations + description: New commands must work for both humans and agents. + options: + - label: This feature should expose a `--json` output + - label: This feature should have a non-interactive (`--yes` / flags) path + - type: textarea + id: alternatives + attributes: + label: Alternatives considered diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7b04e07 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,29 @@ +## Summary + + + +## Type of change + +- [ ] Bug fix +- [ ] New feature +- [ ] Breaking change +- [ ] Docs / chore + +## Agent-native checklist (required for command changes) + +- [ ] Command supports `--json` with a stable envelope +- [ ] Non-interactive path works (`--yes` / flags / env; no hidden TTY requirement) +- [ ] Returns a deterministic exit code from `src/agent/exit.ts` +- [ ] Data → stdout, diagnostics/progress → stderr +- [ ] `streamnet manifest` reflects any new commands/flags + +## Verification + +- [ ] `npm run lint` passes +- [ ] `npm run typecheck` passes +- [ ] `npm test` passes +- [ ] `npm run build` succeeds + +## Notes + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8162b26 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: '/' + schedule: + interval: weekly + open-pull-requests-limit: 5 + groups: + dev-dependencies: + dependency-type: development + - package-ecosystem: github-actions + directory: '/' + schedule: + interval: weekly diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4f13022 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + name: test (${{ matrix.os }}, node ${{ matrix.node }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + node: [20, 22] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: npm + - run: npm ci + - run: npm run lint + - run: npm run typecheck + - run: npm run build + - run: npm test diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..1778ce4 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,27 @@ +name: CodeQL + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: '0 6 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + # CodeQL requires GitHub Advanced Security (GHAS). Skip on private repos + # until the repo is made public — the workflow stays here for that transition. + if: github.event.repository.private == false + permissions: + actions: read + contents: read + security-events: write + steps: + - uses: actions/checkout@v4 + - uses: github/codeql-action/init@v3 + with: + languages: javascript-typescript + - uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4fc6ef5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,52 @@ +name: Release + +on: + push: + tags: ['v*'] + +permissions: + contents: write + id-token: write + +jobs: + npm-publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: https://registry.npmjs.org + cache: npm + - run: npm ci + - run: npm test + - run: npm run build + - run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + binaries: + needs: npm-publish + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + target: linux-x64 + - os: macos-latest + target: darwin-arm64 + - os: windows-latest + target: win-x64 + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + # Node SEA (Single Executable Application) bundling is wired up in v0.6. + # Placeholder step keeps the matrix valid until then. + - name: Package standalone binary + run: echo "SEA packaging for ${{ matrix.target }} lands in v0.6" diff --git a/.gitignore b/.gitignore index 9145c03..6c15563 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,9 @@ node_modules/ dist/ +coverage/ .env .DS_Store +*.log + +# Planning context is private — stripped before the public flip. +docs/planning/ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..16039c7 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +dist/ +coverage/ +node_modules/ +LICENSE +LICENSE-COMMERCIAL +CODE_OF_CONDUCT.md +docs/planning/ +*.snap diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..d250f20 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "singleQuote": true, + "semi": true, + "trailingComma": "all", + "printWidth": 90, + "tabWidth": 2 +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8498e9f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog + +All notable changes to this project are documented here. The format is based on +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project +adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Initial project foundation: command registry (single source of truth), agent + output layer (`--json` envelopes, deterministic exit codes), XDG config store. +- Core: multi-indexer search (torrents-csv, YTS) with fan-out aggregation and + infohash de-duplication; torrent health ranking (MKV-first); quality parser. +- Torrent streaming engine (WebTorrent) with local HTTP stream server and + best-file selection. +- Native VLC detection (rejects Flatpak/Snap) and spawn. +- Commands: `search`, `stream`, `play`, `setup`, `doctor`, `config`, `manifest`. +- Cross-OS `setup` installer (apt/dnf/pacman/zypper, brew, winget/choco). +- Repo-standard files: AGPL-3.0 `LICENSE`, `LICENSE-COMMERCIAL`, `CONTRIBUTING`, + `SECURITY`, `CODE_OF_CONDUCT`, issue/PR templates, GitHub Actions CI. + +## [0.1.0] - TBD + +First tagged release — vertical slice: search + stream + play + VLC spawn + +setup/doctor, all with `--json` / `--yes` / deterministic exit codes. + +[Unreleased]: https://github.com/eduardoborjas/streamnet-cli/compare/v0.1.0...HEAD +[0.1.0]: https://github.com/eduardoborjas/streamnet-cli/releases/tag/v0.1.0 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..3070a4a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,83 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at eduardoa.borjas@gmail.com. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b39d6e5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,71 @@ +# Contributing to StreamNet CLI + +Thanks for your interest in improving StreamNet CLI! + +## Development setup + +```bash +git clone https://github.com/eduardoborjas/streamnet-cli.git +cd streamnet-cli +npm install +npm run build +npm test +``` + +Requires **Node.js >= 20**. + +### Useful scripts + +| Script | Purpose | +| ------------------- | --------------------------- | +| `npm run build` | Bundle with tsup to `dist/` | +| `npm run dev` | Rebuild on change | +| `npm run typecheck` | `tsc --noEmit` | +| `npm run lint` | ESLint | +| `npm run format` | Prettier (write) | +| `npm test` | Run the vitest suite | +| `npm run test:cov` | Tests with coverage | + +## Architecture + +A **single command registry** (`src/registry/`) is the source of truth: each +command is declared once and that declaration drives the Commander CLI, the +`streamnet manifest` output, and the MCP tool list. When you add a command: + +1. Add the handler in `src/commands/.ts`. +2. Add a `CommandSpec` to `src/registry/index.ts` (args, flags, exit codes, + examples). +3. Route all output through `ctx.output` — never `console.log` directly. This is + what keeps every command both human- and agent-friendly. +4. Add tests in `test/`. + +### Agent-native requirements (acceptance criteria) + +Every command **must**: + +- Support `--json` with a stable, versioned envelope. +- Have a fully non-interactive path (flags + env vars; no hidden TTY needs). +- Return a deterministic exit code from `src/agent/exit.ts`. +- Send data to stdout and diagnostics/progress to stderr. + +PRs that break these will be asked to fix them before merge. + +## Commit & PR conventions + +- Use [Conventional Commits](https://www.conventionalcommits.org/): `feat:`, + `fix:`, `docs:`, `chore:`, `test:`, `refactor:`. +- Keep PRs focused. Include tests for behavior changes. +- Run `npm run lint && npm run typecheck && npm test` before opening a PR. + +## Developer Certificate of Origin / dual licensing + +StreamNet CLI is dual-licensed (AGPL-3.0 + commercial). By contributing, you +agree that your contributions are licensed under the AGPL-3.0 **and** that you +grant the maintainer the right to include your contribution in commercially +licensed distributions of the project. If you cannot agree to this, please open +an issue to discuss before contributing. + +## Reporting bugs / requesting features + +Use the issue templates. For security issues, **do not** open a public issue — +see [`SECURITY.md`](SECURITY.md). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0c97efd --- /dev/null +++ b/LICENSE @@ -0,0 +1,235 @@ +GNU AFFERO GENERAL PUBLIC LICENSE +Version 3, 19 November 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + + Preamble + +The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. + +A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. + +The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. + +An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. + +The precise terms and conditions for copying, distribution and modification follow. + + TERMS AND CONDITIONS + +0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based on the Program. + +To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. + +1. Source Code. +The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. + +A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +2. Basic Permissions. +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. + +4. Conveying Verbatim Copies. +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". + + c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. + + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. + +7. Additional Terms. +"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. + +8. Termination. + +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. + +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. + +11. Patents. + +A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. + +13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. + +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. + +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. + +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. + +You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . diff --git a/LICENSE-COMMERCIAL b/LICENSE-COMMERCIAL new file mode 100644 index 0000000..3ae8f95 --- /dev/null +++ b/LICENSE-COMMERCIAL @@ -0,0 +1,60 @@ +# StreamNet CLI — Commercial License + +StreamNet CLI is dual-licensed. + +## 1. Open-source license (default) + +StreamNet CLI is released to the public under the **GNU Affero General Public +License, version 3.0 (AGPL-3.0)** — see the [`LICENSE`](LICENSE) file. The code +is open source and free for everyone. The AGPL's copyleft terms apply: in +particular, if you run a modified version of StreamNet CLI to provide a network +service, you must make the complete corresponding source code of your modified +version available to the users of that service. + +This is the right choice for individuals, hobbyists, researchers, and any +project that is itself happy to comply with the AGPL. + +## 2. Commercial license (alternative) + +The AGPL is deliberately strong copyleft. Some organizations cannot or do not +want to comply with its source-disclosure requirements — for example, a company +that wishes to: + +- integrate StreamNet CLI (or parts of it) into a **proprietary, closed-source** + product or internal system without releasing their own source; +- redistribute StreamNet CLI as part of a commercial offering without the AGPL's + network-use source-disclosure obligations; +- obtain a warranty, indemnification, or formal support terms; +- otherwise use the software under terms incompatible with the AGPL. + +For these cases, a **separate commercial license** is available from the +copyright holder. Under a commercial license, the AGPL obligations do not apply, +and terms are negotiated directly. + +The copyright holder (Eduardo Borjas) retains full rights to offer StreamNet CLI +under commercial terms in addition to the AGPL. Purchasing a commercial license +does not affect the public's continued right to use the AGPL-licensed version. + +### Why dual licensing (and not MIT/Apache) + +Permissive licenses such as MIT and Apache-2.0 are **intentionally not used**: +they would allow a company to take this work, build a proprietary product on top +of it, and resell it **without any agreement with — or contribution back to —** +the author. The AGPL keeps the public version free and open while reserving the +commercial path for organizations that need proprietary terms, which they obtain +by purchasing a license. + +## Contact + +To obtain a commercial license, or to ask which license fits your use case: + +**Eduardo Borjas** — eduardoa.borjas@gmail.com + +Please include a brief description of your intended use and your organization. + +--- + +Copyright (c) 2026 Eduardo Borjas. All rights reserved. +"StreamNet" and the StreamNet CLI name are used by the author to identify this +project; the commercial license does not grant trademark rights except as +expressly agreed in writing. diff --git a/README.md b/README.md index 40ddbba..d0e3592 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,138 @@ +
+ # streamnet-cli -> Torrent search + in-process WebTorrent streaming to native VLC with hash-based subtitles. +**Torrent search → in-process WebTorrent streaming → native VLC, with hash-based subtitles.** + +Replaces the fragile `.torrent → Stremio → VLC → VLSub` pipeline with a single, +scriptable command. No GUI. No Flatpak. No legacy dependency chain. + +[![CI](https://github.com/eduardoborjas/streamnet-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/eduardoborjas/streamnet-cli/actions/workflows/ci.yml) +[![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-blue.svg)](LICENSE) +[![Node](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org) + +
+ +--- -**Status:** planning — architecture via `/ultraplan` (Claude Opus 4.8). -**Part of:** Korvex agent-native developer-tool suite. -**License:** AGPL-3.0 + Dual Commercial — **FINAL**. Open source and free for the -public; commercial licenses sold separately. MIT/Apache are rejected on purpose: -they would let a company use and resell the work without a commercial agreement. +## Why + +Eduardo ran a manual streaming pipeline for years: download a `.torrent`, open it +in the Stremio GUI, pipe it to VLC, fetch subtitles with VLSub. The migration to +Pop!\_OS broke it repeatedly — `libmpv1` removed in Ubuntu 24.04, `libssl1.1` vs +OpenSSL 3, and Flatpak sandboxing that silently broke IPC between Stremio and VLC. + +**StreamNet owns the entire pipeline in Node.js** so none of that can break: + +``` +streamnet play "Blade Runner 2049" + → search indexers → rank by health (MKV first) → pick best torrent + → stream via WebTorrent (no full download) → spawn native VLC + → if not MKV: fetch subtitles by file hash → load in VLC +``` ## Agent-native by design -Like `gh` and `vercel`, this CLI is built to be driven by **both humans and -terminal AI agents** (Claude Code, Codex, Gemini CLI, Hermes, OpenClaw, -OpenCode, DeepSeek) on Linux, macOS and Windows — non-interactive mode, -`--json` output, deterministic exit codes, and an MCP/tool manifest. - -## Planning context -Architecture inputs live in [`docs/planning/`](docs/planning) (kept private; -stripped before the public release per `github-repo-growth-standard`). + +Like `gh` and `vercel`, StreamNet is a first-class tool for **both humans and +terminal AI agents** (Claude Code, Codex, Gemini CLI, Hermes, OpenClaw, OpenCode, +DeepSeek) on Linux, macOS, and Windows: + +- **`--json`** on every command — stable, versioned, machine-readable envelopes +- **`--yes`** skips all prompts; **`--no-input`** forbids them (exit `77` instead) +- **Deterministic POSIX exit codes** you can branch on +- **stdin/stdout composability** — `streamnet search … --json | jq … | streamnet stream -` +- **`streamnet manifest`** — enumerate every command, flag, exit code, and schema +- **`streamnet mcp`** — an MCP server so any agent can call commands (v0.4) +- **Zero hidden TTY requirements** in agent mode + +## Install + +```bash +# npm (primary — requires Node.js >= 20) +npm install -g streamnet-cli + +# or grab a standalone binary (no Node required) +# https://github.com/eduardoborjas/streamnet-cli/releases +``` + +Then install native VLC and verify: + +```bash +streamnet setup # installs native VLC for your OS (never Flatpak/Snap) +streamnet doctor # verifies VLC, webtorrent, Node, network +``` + +## Usage + +```bash +streamnet search "Blade Runner 2049" # health-ranked results +streamnet play "Blade Runner 2049" --yes # search + best result + stream +streamnet stream "magnet:?xt=urn:btih:..." # stream a specific torrent +streamnet config list # view configuration +streamnet manifest # machine-readable command catalog +``` + +### Agent / scripting examples + +```bash +# Pure JSON, no prompts, scriptable exit codes +streamnet play "Sintel" --yes --json + +# Pipe a magnet from another tool +echo "$MAGNET" | streamnet stream - --no-subs --json + +# Branch on exit code +streamnet search "obscure title" --json || case $? in + 3) echo "no results" ;; + 5) echo "network down" ;; +esac +``` + +## Exit codes + +| Code | Name | Meaning | +| ---: | ------------------ | ------------------------------------------- | +| 0 | OK | Success | +| 1 | ERROR | Generic/unexpected error | +| 2 | USAGE | Invalid arguments or flags | +| 3 | NO_RESULTS | Search returned nothing | +| 4 | DEP_MISSING | Required dependency (VLC) not found | +| 5 | NETWORK | Indexer/API/network failure | +| 6 | TORRENT_UNPLAYABLE | No peers / metadata timeout | +| 7 | PLAYER_FAILED | VLC failed to spawn or crashed | +| 8 | SUBS_NOT_FOUND | No matching subtitles | +| 9 | CONFIG | Invalid/unwritable config | +| 10 | AUTH | OpenSubtitles auth/rate-limit | +| 77 | EX_NOINPUT | Prompt needed but running non-interactively | +| 130 | SIGINT | Interrupted | + +## Configuration + +Config lives under XDG paths (`~/.config/streamnet/config.json` on Linux/macOS, +`%APPDATA%\streamnet` on Windows). Manage it with `streamnet config`: + +```bash +streamnet config set minSeeders 5 +streamnet config set preferredContainers mkv,mp4 +streamnet config get opensubtitles.apiKey # secrets are redacted on display +``` + +## License — AGPL-3.0 + Dual Commercial (final) + +StreamNet CLI is **dual-licensed**: + +- **Open source & free for the public** under the **GNU AGPL-3.0** — see + [`LICENSE`](LICENSE). +- **Commercial licenses are sold separately** for organizations that need to + integrate StreamNet into proprietary products without AGPL obligations — see + [`LICENSE-COMMERCIAL`](LICENSE-COMMERCIAL). + +MIT and Apache are **intentionally rejected**: they would let a company use and +resell this work without a commercial agreement. The AGPL keeps the public +version free and open, while reserving a commercial path for those who need it. + +Commercial licensing: **eduardoa.borjas@gmail.com** + +--- + +Part of the **Korvex** agent-native developer-tool suite. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..6281c66 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,40 @@ +# Security Policy + +## Supported versions + +StreamNet CLI is pre-1.0. Security fixes are applied to the latest released +minor version. Once 1.0.0 ships, the latest minor will be supported. + +| Version | Supported | +| ---------- | --------- | +| latest 0.x | ✅ | +| older 0.x | ❌ | + +## Reporting a vulnerability + +**Please do not open a public GitHub issue for security vulnerabilities.** + +Report privately via one of: + +- GitHub's **private vulnerability reporting** (Security → Report a vulnerability) +- Email: **eduardoa.borjas@gmail.com** with subject `[streamnet-cli security]` + +Please include: + +- a description of the issue and its impact, +- steps to reproduce or a proof of concept, +- affected version(s) and platform. + +You can expect an acknowledgement within **5 business days** and a status update +within **15 business days**. Coordinated disclosure is appreciated — we'll agree +a disclosure timeline with you once the issue is confirmed. + +## Scope notes + +StreamNet spawns native VLC and runs a local HTTP stream server bound to +`127.0.0.1`. Reports involving local privilege escalation, the stream server, the +VLC IPC interface, subtitle handling, or indexer response parsing are in scope. + +StreamNet does not host or distribute content; it searches third-party indexers +and streams via the BitTorrent network. Legal/abuse concerns about specific +content are out of scope for this security policy. diff --git a/docs/planning/Torrents - Streamnet-CLI.md b/docs/planning/Torrents - Streamnet-CLI.md deleted file mode 100644 index a4ae586..0000000 --- a/docs/planning/Torrents - Streamnet-CLI.md +++ /dev/null @@ -1,48 +0,0 @@ -Torrents - Streamnet-CLI - -Search Torrent -⭐️https://www.limetorrents.fun/ -https://yts.mx/ -https://www.1377x.is/ -https://www.1337x.tw/ -https://www.rarbggo.to/ -https://thepiratebays.com/es/ -https://torrentgalaxy.to/ -https://www.torrentdownloads.pro/ -https://torrentproject2.com/ -https://nyaa.si/ -https://eztv.re/ -https://extratorrents.ch/ -https://demonoid.is/ -https://2torrentz2eu.in/ -https://www.torrentz.org.in/ -https://zooqle.com/ -https://unblockninja.com/ -https://proxyninja.org/ -https://torrents-csv.ml/#/ -Cinema Online -https://cinefiliamalversa.blogspot.com/ -https://www.classiccinemaonline.com/ -http://legalmentegratis.com/ -https://www.openculture.com/freemoviesonline -https://www.pelisplus.lat/ -https://homecine.to/ -https://megadede.mobi/ -https://movidy.net/ -https://www.blogdepelis.io/ -https://seriesflix.video/ -https://lacartoons.com/ -https://serieslan.com/ -https://www.seriesantiguas.com/ -https://soap2day.mx/ -https://flixtor.id/ -https://9anime.to/ -https://hardsubcafe.net/ -https://www.dilo.nu/ -https://ok.ru/ -https://cuevana3.info/inicio/ -https://tekilaz.co/ -https://old.reddit.com/r/Piracy/wiki/megathread/movies_and_tv#wiki_.25B6.FE0F_autoembed -https://old.reddit.com/r/Piracy/wiki/megathread/movies_and_tv -https://old.reddit.com/r/Piracy/wiki/megathread -https://nz2.archive.ubuntu.com/ubuntu/pool/main/o/openssl/?C=M;O=D diff --git a/docs/planning/_overview.md b/docs/planning/_overview.md deleted file mode 100644 index 5dadf89..0000000 --- a/docs/planning/_overview.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -type: project-overview -project: streamnet-cli -status: planning -created: 2026-05-11 -modified: 2026-05-11 -tags: [project, open-source, cli, streaming, torrent, vlc, typescript, nodejs, media] -related: - - "[[03-knowledge/maps-of-content/Public-Repos-Master-Strategy]]" - - "[[04-resources/tools/Stremio + VLC en Linux]]" - - "[[02-projects/magnus-cli/_overview]]" - - "[[02-projects/korvex/_overview]]" ---- - -# StreamNet CLI — Project Overview - -CLI replacement for the manual `.torrent → Stremio GUI → VLC → VLSub` pipeline Eduardo ran for years. The migration to Pop!_OS broke it repeatedly (libmpv1, libssl1.1, OpenSSL 3 vs 1.1, Flatpak sandboxing). StreamNet automates the full pipeline without depending on a GUI or legacy dependency stack. - -━━━ - -## Core Workflow - -``` -streamnet play "Blade Runner 2049" - → search indexers → rank by health → select best torrent - → stream via WebTorrent (no full download required) - → spawn VLC with stream URL - → auto-detect format → if not MKV: search OpenSubtitles by hash - → download + load subtitles in VLC -``` - -━━━ - -## CLI Commands - -```bash -streamnet search "Blade Runner 2049" # Search, show health-ranked results -streamnet stream # Stream → VLC (core) -streamnet play "Blade Runner 2049" # One-shot: search + best result + stream -streamnet download # Full download for archiving -streamnet subs # Manual subtitle search -streamnet setup # Install all deps for current OS -streamnet config # Player, sub language, quality prefs -streamnet doctor # Verify: webtorrent, vlc, vlsub, network -``` - -━━━ - -## Technical Architecture - -| Layer | Choice | Notes | -| ----------------- | ----------------------------------------- | -------------------------------- | -| Language | Node.js 20 + TypeScript | Consistent with other tools | -| Torrent streaming | WebTorrent / peerflix | In-process, no GUI required | -| Indexer search | Multi-source scraper + API | Tool searches; user decides | -| VLC integration | Spawn VLC with stream URL arg | Requires native VLC, not Flatpak | -| Subtitle search | OpenSubtitles API + VLSub-compatible hash | Hash search = most reliable | -| Platform priority | Linux → macOS → Windows WSL2 | Pop!_OS 22.04 is dev baseline | -| | | | - -━━━ - -## Torrent Health Ranking - -Rank results by: `seeder/leecher ratio`, `total seeders`, `file size vs quality`, `format` (MKV > MP4 > others). MKV preferred — usually has embedded English subtitles, eliminating subtitle search entirely. - -━━━ - -## Key Insights from Years of Usage - -- **MKV = best format.** Embedded subs, no VLSub needed. Filter for MKV first. -- **Flatpak breaks IPC.** Stremio Flatpak + VLC Flatpak cannot communicate reliably. StreamNet bypasses this entirely by owning the spawn. -- **Pop!_OS 22.04 was the only working baseline** for native Stremio — libmpv1 gone in Ubuntu 24.04, libssl1.1 manually required. -- **VLSub hash search is reliable** for MKV streams — uses video hash + metadata, finds subs even for obscure content. -- **P2P streaming paradox:** If everyone streams without seeding, nobody seeds. Eduardo acknowledges this as a future solvable problem — possible "StreamNet Seed" daemon for contributing back. - -━━━ - -## Compatibility Lessons (from source file) - -- Do not use Flatpak for any app requiring IPC to an external player -- `libssl1.1_1.1.1f-1ubuntu2.24_amd64.deb` from `nz2.archive.ubuntu.com` is the Stremio legacy fix -- Ubuntu/Pop 24.04 + Mint 22 (same base) both drop `libmpv1` — unworkable for legacy stack -- StreamNet eliminates the entire fragile dependency chain by owning the pipeline in Node.js - -━━━ - -## Build Complexity - -**Most complex of the 6 tools.** Requires: BitTorrent protocol (WebTorrent), multi-source indexer scraping, VLC IPC/spawn, OpenSubtitles API, VLSub-compatible hash generation. - -Recommended build order: FlowClock → Magnus → StreamNet. - -━━━ - -## License & Audience - -- **License:** AGPL-3.0 + Dual Commercial License -- **Target:** Privacy-focused streamers, Linux users, cord-cutters, tech-savvy media consumers, people tired of fragmented subscriptions - -━━━ - -## Source - -`[[04-resources/tools/Stremio + VLC en Linux]]` — 16 documented errors, 7 key technical decisions, full compatibility matrix across Pop!_OS 22.04/24.04, Linux Mint 22, Flatpak vs native. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..51a6565 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,48 @@ +import js from '@eslint/js'; +import tseslint from '@typescript-eslint/eslint-plugin'; +import tsparser from '@typescript-eslint/parser'; + +export default [ + { + ignores: ['dist/**', 'node_modules/**', 'coverage/**'], + }, + js.configs.recommended, + { + files: ['**/*.ts'], + languageOptions: { + parser: tsparser, + parserOptions: { + ecmaVersion: 2022, + sourceType: 'module', + }, + globals: { + process: 'readonly', + console: 'readonly', + URL: 'readonly', + URLSearchParams: 'readonly', + fetch: 'readonly', + Response: 'readonly', + AbortController: 'readonly', + AbortSignal: 'readonly', + setTimeout: 'readonly', + clearTimeout: 'readonly', + setInterval: 'readonly', + clearInterval: 'readonly', + Buffer: 'readonly', + structuredClone: 'readonly', + }, + }, + plugins: { + '@typescript-eslint': tseslint, + }, + rules: { + ...tseslint.configs.recommended.rules, + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': [ + 'error', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], + 'no-console': 'off', + }, + }, +]; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..cd87979 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,7654 @@ +{ + "name": "streamnet-cli", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "streamnet-cli", + "version": "0.1.0", + "license": "AGPL-3.0-or-later", + "dependencies": { + "cheerio": "^1.0.0", + "commander": "^13.0.0", + "execa": "^9.5.1", + "fast-xml-parser": "^4.5.0", + "p-limit": "^6.1.0", + "picocolors": "^1.1.1", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.23.2" + }, + "bin": { + "streamnet": "dist/cli.js" + }, + "devDependencies": { + "@eslint/js": "^9.14.0", + "@types/node": "^20.16.0", + "@typescript-eslint/eslint-plugin": "^8.13.0", + "@typescript-eslint/parser": "^8.13.0", + "@vitest/coverage-v8": "^2.1.4", + "eslint": "^9.14.0", + "prettier": "^3.3.3", + "tsup": "^8.3.5", + "typescript": "^5.5.4", + "vitest": "^2.1.4" + }, + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "webtorrent": "^2.5.1" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, + "node_modules/@silentbot1/nat-api": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@silentbot1/nat-api/-/nat-api-0.4.9.tgz", + "integrity": "sha512-Bm2Fr0sJyGr4B/XgKjQxjGe7Rzs/OlK91OIHsghObxhP3Y4j2y8o7Xjlledu/pxzFEIWaTbZIBSl8ABqoP/WhQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "chrome-dgram": "^3.0.6", + "cross-fetch-ponyfill": "^1.0.3", + "debug": "^4.4.0", + "default-gateway": "^7.2.2", + "unordered-array-remove": "^1.0.2", + "xml2js": "^0.6.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@thaunknown/simple-peer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@thaunknown/simple-peer/-/simple-peer-10.1.1.tgz", + "integrity": "sha512-cQHGk4J7eXL9zmGELnFrB9YszZBZGE6T06OH/iCAN6H/ObckK84pyddTlVU45PEkeVKyICzJbhPgeL9xwF64Mw==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^4.4.3", + "err-code": "^3.0.1", + "streamx": "^2.25.0", + "uint8-util": "^2.2.6", + "webrtc-polyfill": "^1.2.1" + } + }, + "node_modules/@thaunknown/simple-peer/node_modules/streamx": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.26.0.tgz", + "integrity": "sha512-VvNG1K72Po/xwJzxZFnZ++Tbrv4lwSptsbkFuzXCJAYZvCK5nnxsvXU6ajqkv7chyiI1Y0YXq2Jh8Iy8Y7NF/A==", + "license": "MIT", + "optional": true, + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/@thaunknown/simple-websocket": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@thaunknown/simple-websocket/-/simple-websocket-9.1.4.tgz", + "integrity": "sha512-NgEFe6TFqRaQvRJF7kFTg0qz7Z28lNiTmAgIY0voNMxrrZtasZKFR3KtN6MtYeS8fOLzn7RLLKQgpV0b8dvCng==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^4.4.3", + "queue-microtask": "^1.2.3", + "streamx": "^2.25.0", + "uint8-util": "^2.2.6", + "ws": "^8.20.1" + } + }, + "node_modules/@thaunknown/simple-websocket/node_modules/streamx": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.26.0.tgz", + "integrity": "sha512-VvNG1K72Po/xwJzxZFnZ++Tbrv4lwSptsbkFuzXCJAYZvCK5nnxsvXU6ajqkv7chyiI1Y0YXq2Jh8Iy8Y7NF/A==", + "license": "MIT", + "optional": true, + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/@thaunknown/thirty-two": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@thaunknown/thirty-two/-/thirty-two-1.0.5.tgz", + "integrity": "sha512-Q53KyCXweV1CS62EfqtPDqfpksn5keQ59PGqzzkK+g8Vif1jB4inoBCcs/BUSdsqddhE3G+2Fn+4RX3S6RqT0A==", + "license": "MIT", + "optional": true, + "dependencies": { + "uint8-util": "^2.2.5" + }, + "engines": { + "node": ">=0.2.6" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.0.tgz", + "integrity": "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/type-utils": "8.60.0", + "@typescript-eslint/utils": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.60.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.0.tgz", + "integrity": "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.0.tgz", + "integrity": "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.60.0", + "@typescript-eslint/types": "^8.60.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz", + "integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.0.tgz", + "integrity": "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.0.tgz", + "integrity": "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/utils": "8.60.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz", + "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.0.tgz", + "integrity": "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.60.0", + "@typescript-eslint/tsconfig-utils": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.0.tgz", + "integrity": "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz", + "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz", + "integrity": "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.7", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "2.1.9", + "vitest": "2.1.9" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@webtorrent/http-node": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@webtorrent/http-node/-/http-node-1.3.0.tgz", + "integrity": "sha512-GWZQKroPES4z91Ijx6zsOsb7+USOxjy66s8AoTWg0HiBBdfnbtf9aeh3Uav0MgYn4BL8Q7tVSUpd0gGpngKGEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "freelist": "^1.0.3", + "http-parser-js": "^0.4.3" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/addr-to-ip-port": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/addr-to-ip-port/-/addr-to-ip-port-2.0.0.tgz", + "integrity": "sha512-9bYbtjamtdLHZSqVIUXhilOryNPiL+x+Q5J/Unpg4VY3ZIkK3fT52UoErj1NdUeVm3J1t2iBEAur4Ywbl/bahw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "license": "Apache-2.0", + "optional": true, + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/bare-addon-resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", + "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-module-resolve": "^1.10.0", + "bare-semver": "^1.0.0" + }, + "peerDependencies": { + "bare-url": "*" + }, + "peerDependenciesMeta": { + "bare-url": { + "optional": true + } + } + }, + "node_modules/bare-events": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.3.tgz", + "integrity": "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==", + "license": "Apache-2.0", + "optional": true, + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz", + "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-module-resolve": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.2.tgz", + "integrity": "sha512-j+hiD5k99qec4KjJvYsI67q5AOBifmy9JG3oeMVxTmvrhn2sIdp8StrUvZu4YNgwTpO+NhniQG16N1ETDe1k5w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-semver": "^1.0.0" + }, + "peerDependencies": { + "bare-url": "*" + }, + "peerDependenciesMeta": { + "bare-url": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz", + "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-semver": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.3.tgz", + "integrity": "sha512-HS/A30bi2+PiRJfU6R4+Kp+6KeLSCSByjYM2iiobOKzLAvtu1CT+S8xWfiU7wz0erknjkUoC+yXy108tzIuP5Q==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-stream": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz", + "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-stream/node_modules/streamx": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.26.0.tgz", + "integrity": "sha512-VvNG1K72Po/xwJzxZFnZ++Tbrv4lwSptsbkFuzXCJAYZvCK5nnxsvXU6ajqkv7chyiI1Y0YXq2Jh8Iy8Y7NF/A==", + "license": "MIT", + "optional": true, + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/bare-url": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.3.tgz", + "integrity": "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/bencode": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bencode/-/bencode-4.0.1.tgz", + "integrity": "sha512-/bCR3FvgfB8AZk4LlowShU+nfaTNRM2vruJ9aejDa5LpgkCoRjFyEauFiZFxbpxVxQbBFapYCN42Er2dkE/Qgw==", + "license": "MIT", + "optional": true, + "dependencies": { + "uint8-util": "^2.2.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/bep53-range": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bep53-range/-/bep53-range-2.0.1.tgz", + "integrity": "sha512-VWN7j7sqUssWD6ROZe2m709YL3u5A1IAx1qvAbH3DAhszHt4WPvCzJLRJ+mzmZn1qWIb4mpflwdn95qezz3n8g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/bitfield": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/bitfield/-/bitfield-4.2.0.tgz", + "integrity": "sha512-kUTatQb/mBd8uhvdLrUkouGDBUQiJaIOvPlptUwOWp6MFqih4d1MiVf0m3ATxfZSzu+LjW/awFeABltYa62uIA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/bittorrent-dht": { + "version": "11.0.12", + "resolved": "https://registry.npmjs.org/bittorrent-dht/-/bittorrent-dht-11.0.12.tgz", + "integrity": "sha512-bFmQq874HMIBon/6KNMf/U1OG+UxJ0UzI9oPra2IRlZLuPA7VwBiA7uEpWaO2ww8G76OFjxB1p1XCOS94FuCPQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "bencode": "^4.0.1", + "debug": "^4.4.3", + "k-bucket": "^5.1.0", + "k-rpc": "^5.1.0", + "last-one-wins": "^1.0.4", + "lru": "^3.1.0", + "randombytes": "^2.1.0", + "record-cache": "^1.2.0" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/bittorrent-lsd": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bittorrent-lsd/-/bittorrent-lsd-2.0.3.tgz", + "integrity": "sha512-FE4ImhpJZo8ahzDS/Qk1Ol+3Ea1sIhCPWE2WSjiv1s8YoCSEf7JBL3bbCJW5hdVNK5ohtZ3vH5xGjZ9QWFiatQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "chrome-dgram": "^3.0.6", + "debug": "^4.2.0" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/bittorrent-peerid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bittorrent-peerid/-/bittorrent-peerid-2.0.2.tgz", + "integrity": "sha512-V2iUvbtv31uLA8XFxbhB3f/fKTxWGaa6FI/3wSm93YAS/DS8IkeRWCyYGQ13Xbc9ajR40nsxNjGtVt7OHB3VOQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/bittorrent-protocol": { + "version": "4.1.21", + "resolved": "https://registry.npmjs.org/bittorrent-protocol/-/bittorrent-protocol-4.1.21.tgz", + "integrity": "sha512-CcuPt6BL7gXa8BF+0GckYcQmr44ARfSPM0rYwMeYgWg+jftekWgy5vuxX6wJDpC5bKFvqNG+74bPBjyM7Swxrw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "bencode": "^4.0.0", + "bitfield": "^4.2.0", + "debug": "^4.4.3", + "rc4": "^0.1.5", + "streamx": "^2.22.1", + "throughput": "^1.0.2", + "uint8-util": "^2.2.5", + "unordered-array-remove": "^1.0.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/bittorrent-tracker": { + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/bittorrent-tracker/-/bittorrent-tracker-11.2.3.tgz", + "integrity": "sha512-i9pWOf1YAQWyTjDnpWwcTfEYDi5XwqD6f48pKmIHByb7x2XU86CqK+IsyMy1buKkDZ3rDKht2jIeKCZf1Dmd8g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@thaunknown/simple-peer": "^10.0.8", + "@thaunknown/simple-websocket": "^9.1.3", + "bencode": "^4.0.0", + "bittorrent-peerid": "^2.0.0", + "chrome-dgram": "^3.0.6", + "compact2string": "^1.4.1", + "cross-fetch-ponyfill": "^1.0.3", + "debug": "^4.3.4", + "ip": "^2.0.1", + "lru": "^3.1.0", + "minimist": "^1.2.8", + "once": "^1.4.0", + "queue-microtask": "^1.2.3", + "random-iterate": "^1.0.1", + "run-parallel": "^1.2.0", + "run-series": "^1.1.9", + "socks": "^2.8.3", + "string2compact": "^2.0.1", + "uint8-util": "^2.2.5", + "unordered-array-remove": "^1.0.2", + "ws": "^8.17.0" + }, + "bin": { + "bittorrent-tracker": "bin/cmd.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "bufferutil": "^4.0.8", + "utf-8-validate": "^6.0.4" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/block-iterator": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/block-iterator/-/block-iterator-1.1.2.tgz", + "integrity": "sha512-yAHUP44v2K25xLPdrgVTgwtuQctlullzjczu9CoUZom5AP3g4p1R1+aWHjS1GHG9JtcSUVUnbEPiuXiW5YZ24w==", + "license": "MIT", + "optional": true + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bufferutil": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", + "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cache-chunk-store": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/cache-chunk-store/-/cache-chunk-store-3.2.2.tgz", + "integrity": "sha512-2lJdWbgHFFxcSth9s2wpId3CR3v1YC63KjP4T9WhpW7LWlY7Hiiei3QwwqzkWqlJTfR8lSy9F5kRQECeyj+yQA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "lru": "^3.1.0", + "queue-microtask": "^1.2.3" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC", + "optional": true + }, + "node_modules/chrome-dgram": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/chrome-dgram/-/chrome-dgram-3.0.6.tgz", + "integrity": "sha512-bqBsUuaOiXiqxXt/zA/jukNJJ4oaOtc7ciwqJpZVEaaXwwxqgI2/ZdG02vXYWUhHGziDlvGMQWk0qObgJwVYKA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.4", + "run-series": "^1.1.9" + } + }, + "node_modules/chrome-dns": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chrome-dns/-/chrome-dns-1.0.1.tgz", + "integrity": "sha512-HqsYJgIc8ljJJOqOzLphjAs79EUuWSX3nzZi2LNkzlw3GIzAeZbaSektC8iT/tKvLqZq8yl1GJu5o6doA4TRbg==", + "license": "MIT", + "optional": true, + "dependencies": { + "chrome-net": "^3.3.2" + } + }, + "node_modules/chrome-net": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/chrome-net/-/chrome-net-3.3.4.tgz", + "integrity": "sha512-Jzy2EnzmE+ligqIZUsmWnck9RBXLuUy6CaKyuNMtowFG3ZvLt8d+WBJCTPEludV0DHpIKjAOlwjFmTaEdfdWCw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.1" + } + }, + "node_modules/chunk-store-iterator": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chunk-store-iterator/-/chunk-store-iterator-1.0.4.tgz", + "integrity": "sha512-LGjzJNmk7W1mrdaBoJNztPumT2ACmgjHmI1AMm8aeGYOl4+LKaYC/yfnx27i++LiAtoe/dR+3jC8HRzb6gW4/A==", + "license": "MIT", + "optional": true, + "dependencies": { + "block-iterator": "^1.1.1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/compact2string": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/compact2string/-/compact2string-1.4.1.tgz", + "integrity": "sha512-3D+EY5nsRhqnOwDxveBv5T8wGo4DEvYxjDtPGmdOX+gfr5gE92c2RC0w2wa+xEefm07QuVqqcF3nZJUZ92l/og==", + "license": "BSD", + "optional": true, + "dependencies": { + "ipaddr.js": ">= 0.1.5" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/cpus": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cpus/-/cpus-1.0.3.tgz", + "integrity": "sha512-PXHBvGLuL69u55IkLa5e5838fLhIMHxmkV4ge42a8alGyn7BtawYgI0hQ849EedvtHIOLNNH3i6eQU1BiE9SUA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/create-torrent": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/create-torrent/-/create-torrent-6.1.2.tgz", + "integrity": "sha512-PrqAy9matS8HquhCQp45SyTEgshxK21SiZt9RxFNHybTWp3lYICebjj4zENl6jpjQGzuJ8DdI92TJuV73CO4YA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "bencode": "^4.0.1", + "block-iterator": "^1.1.2", + "fast-readable-async-iterator": "^2.0.0", + "is-file": "^1.0.0", + "join-async-iterator": "^1.1.1", + "junk": "^4.0.1", + "minimist": "^1.2.8", + "once": "^1.4.0", + "piece-length": "^2.0.1", + "queue-microtask": "^1.2.3", + "run-parallel": "^1.2.0", + "uint8-util": "^2.2.6" + }, + "bin": { + "create-torrent": "bin/cmd.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cross-fetch-ponyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cross-fetch-ponyfill/-/cross-fetch-ponyfill-1.0.3.tgz", + "integrity": "sha512-uOBkDhUAGAbx/FEzNKkOfx3w57H8xReBBXoZvUnOKTI0FW0Xvrj3GrYv2iZXUqlffC1LMGfQzhmBM/ke+6eTDA==", + "license": "MIT", + "optional": true, + "dependencies": { + "abort-controller": "^3.0.0", + "node-fetch": "^3.3.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/default-gateway": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-7.2.2.tgz", + "integrity": "sha512-AD7TrdNNPXRZIGw63dw+lnGmT4v7ggZC5NHNJgAYWm5njrwoze1q5JSAW9YuLy2tjnoLUG/r8FEB93MCh9QJPg==", + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "execa": "^7.1.1" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/default-gateway/node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "license": "MIT", + "optional": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/default-gateway/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/default-gateway/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", + "optional": true + }, + "node_modules/default-gateway/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "license": "MIT", + "optional": true + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT", + "optional": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT", + "optional": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-readable-async-iterator": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-readable-async-iterator/-/fast-readable-async-iterator-2.0.0.tgz", + "integrity": "sha512-8Sld+DuyWRIftl86ZguJxR2oXCBccOiJxrY/Rj9/7ZBynW8pYMWzIcqxFL1da+25jaWJZVa+HHX/8SsA21JdTA==", + "license": "MIT", + "optional": true + }, + "node_modules/fast-xml-parser": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.6.tgz", + "integrity": "sha512-Yd4vkROfJf8AuJrDIVMVmYfULKmIJszVsMv7Vo71aocsKgFxpdlpSHXSaInvyYfgw2PRuObQSW2GFpVMUjxu9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/filename-reserved-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz", + "integrity": "sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "optional": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/freelist": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/freelist/-/freelist-1.0.3.tgz", + "integrity": "sha512-Ji7fEnMdZDGbS5oXElpRJsn9jPvBR8h/037D3bzreNmS8809cISq/2D9//JbA/TaZmkkN8cmecXwmQHmM+NHhg==", + "license": "MIT", + "optional": true + }, + "node_modules/fs-chunk-store": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fs-chunk-store/-/fs-chunk-store-5.0.1.tgz", + "integrity": "sha512-bhD1YfAzdOugxu4VepYjxvzZwik9Qunn69Ogj47DZmogD6h/VkD/4vArNRoQ+kPYYOFqex3c9Ob2iAOkNYdkgw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "filename-reserved-regex": "^3.0.0", + "queue-microtask": "^1.2.2", + "random-access-file": "^4.0.0", + "run-parallel": "^1.1.2", + "thunky": "^1.0.1", + "uint8-util": "^2.2.5" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT", + "optional": true + }, + "node_modules/fs-native-extensions": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/fs-native-extensions/-/fs-native-extensions-1.5.0.tgz", + "integrity": "sha512-nuZLFm9mGCxvyi7Llww/J4OyifKCS21nEUTAmnlTZp3FObPOvA32aCedCmt4Z+8yk+caqfClNaSCfn/P7T7FLQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "require-addon": "^1.1.0", + "which-runtime": "^1.2.0" + } + }, + "node_modules/fsa-chunk-store": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fsa-chunk-store/-/fsa-chunk-store-1.3.0.tgz", + "integrity": "sha512-0WCfuxqqSB6Tz/g7Ar/nwAxMoigXaIXuvfrnLIEFYIA9uc6w9eNaHuBGzU1X3lyM4cpLKCOTUmKAA/gCiTvzMQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "filename-reserved-regex": "^3.0.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-stdin": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-10.0.0.tgz", + "integrity": "sha512-eWSePJ4zXFdqz+/Lyfopob4rIcoF/U2XfE8nJc7iZV6lnebWc9k7DoQQpX+2a9jc0AOvBsXvbe5YkjXl/MHbpg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT", + "optional": true + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-parser-js": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.13.tgz", + "integrity": "sha512-u8u5ZaG0Tr/VvHlucK2ufMuOp4/5bvwgneXle+y228K5rMbJOlVjThONcaAw3ikAy8b2OO9RfEucdMHFz3UWMA==", + "license": "MIT", + "optional": true + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate-chunk-store": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/immediate-chunk-store/-/immediate-chunk-store-2.2.0.tgz", + "integrity": "sha512-1bHBna0hCa6arRXicu91IiL9RvvkbNYLVq+mzWdaLGZC3hXvX4doh8e1dLhMKez5siu63CYgO5NrGJbRX5lbPA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "queue-microtask": "^1.2.3" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC", + "optional": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC", + "optional": true + }, + "node_modules/ip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", + "license": "MIT", + "optional": true + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-set": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ip-set/-/ip-set-3.0.0.tgz", + "integrity": "sha512-EkKzllMFYKMHOoeNxM8v10IJM2n9K6L3ruaQx4A/5RrkOInV8cjp9IwkVU0/cpzwwkq1zGNudrAdCjSR97Rf5w==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ip-address": "^10.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ipaddr.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", + "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-file/-/is-file-1.0.0.tgz", + "integrity": "sha512-ZGMuc+xA8mRnrXtmtf2l/EkIW2zaD2LSBWlaOVEF6yH4RTndHob65V4SwWWdtGKVthQfXPVKsXqw4TDUjbVxVQ==", + "license": "MIT", + "optional": true + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/join-async-iterator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/join-async-iterator/-/join-async-iterator-1.1.1.tgz", + "integrity": "sha512-ATse+nuNeKZ9K1y27LKdvPe/GCe9R/u9dw9vI248e+vILeRK3IcJP4JUPAlSmKRCDK0cKhEwfmiw4Skqx7UnGQ==", + "license": "MIT", + "optional": true + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/junk": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz", + "integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/k-bucket": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/k-bucket/-/k-bucket-5.1.0.tgz", + "integrity": "sha512-Fac7iINEovXIWU20GPnOMLUbjctiS+cnmyjC4zAUgvs3XPf1vo9akfCHkigftSic/jiKqKl+KA3a/vFcJbHyCg==", + "license": "MIT", + "optional": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/k-rpc": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/k-rpc/-/k-rpc-5.1.0.tgz", + "integrity": "sha512-FGc+n70Hcjoa/X2JTwP+jMIOpBz+pkRffHnSl9yrYiwUxg3FIgD50+u1ePfJUOnRCnx6pbjmVk5aAeB1wIijuQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "k-bucket": "^5.0.0", + "k-rpc-socket": "^1.7.2", + "randombytes": "^2.0.5" + } + }, + "node_modules/k-rpc-socket": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/k-rpc-socket/-/k-rpc-socket-1.11.1.tgz", + "integrity": "sha512-8xtA8oqbZ6v1Niryp2/g4GxW16EQh5MvrUylQoOG+zcrDff5CKttON2XUXvMwlIHq4/2zfPVFiinAccJ+WhxoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "bencode": "^2.0.0", + "chrome-dgram": "^3.0.2", + "chrome-dns": "^1.0.0", + "chrome-net": "^3.3.2" + } + }, + "node_modules/k-rpc-socket/node_modules/bencode": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bencode/-/bencode-2.0.3.tgz", + "integrity": "sha512-D/vrAD4dLVX23NalHwb8dSvsUsxeRPO8Y7ToKA015JQYq69MLDOMkC0uGZYA/MPpltLO8rt8eqFC2j8DxjTZ/w==", + "license": "MIT", + "optional": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/last-one-wins": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/last-one-wins/-/last-one-wins-1.0.4.tgz", + "integrity": "sha512-t+KLJFkHPQk8lfN6WBOiGkiUXoub+gnb2XTYI2P3aiISL+94xgZ1vgz1SXN/N4hthuOoLXarXfBZPUruyjQtfA==", + "license": "MIT", + "optional": true + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", + "optional": true + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-ip-set": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/load-ip-set/-/load-ip-set-3.0.2.tgz", + "integrity": "sha512-UD1GM3CLlkC3b0gAKIxd+6SFJb1WQttWyYhwvjdWjGpJKzu32HnaSMfWtUtVgRtFY+K5vgrvecuVQLRxx5Ojag==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "cross-fetch-ponyfill": "^1.0.1", + "ip-set": "^3.0.0", + "netmask": "^2.0.1", + "once": "^1.4.0", + "queue-microtask": "^1.2.3", + "split": "^1.0.1" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lru/-/lru-3.1.0.tgz", + "integrity": "sha512-5OUtoiVIGU4VXBOshidmtOsvBIvcQR6FD/RzWSvaeHyxCGB+PCUCu+52lqMfdc0h/2CLvHhZS4TwUmMQrrMbBQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/lt_donthave": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/lt_donthave/-/lt_donthave-2.0.7.tgz", + "integrity": "sha512-FdfetmgzUr0IC2ZXHaOhAMEsvw8BFo7kDM7JzfYsJ5pO5+xzjfZIPdLSQWE4+/UTlQA7D8IU5ZJ0LTuLE4BRBQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^4.2.0", + "unordered-array-remove": "^1.0.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/magnet-uri": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/magnet-uri/-/magnet-uri-7.0.8.tgz", + "integrity": "sha512-cRzrGzw6nvjj+CMQczrVGZbcZfxhth4uSIQf7PGzACW11UsptwrQbAXTuUek7k3xG1HiFZJnkGpgjY2a5Y5Pgw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@thaunknown/thirty-two": "^1.0.5", + "bep53-range": "^2.0.0", + "uint8-util": "^2.2.6" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/memory-chunk-store": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/memory-chunk-store/-/memory-chunk-store-1.3.5.tgz", + "integrity": "sha512-E1Xc1U4ifk/FkC2ZsWhCaW1xg9HbE/OBmQTLe2Tr9c27YPSLbW7kw1cnb3kQWD1rDtErFJHa7mB9EVrs7aTx9g==", + "license": "MIT", + "optional": true, + "dependencies": { + "queue-microtask": "^1.2.3" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT", + "optional": true + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT", + "optional": true + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT", + "optional": true + }, + "node_modules/napi-macros": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz", + "integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==", + "license": "MIT", + "optional": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-abi": { + "version": "3.92.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", + "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-datachannel": { + "version": "0.32.3", + "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.32.3.tgz", + "integrity": "sha512-Aok1ZhLsll472lRefgWYuWJ0070jh0ecHravTdRyZEmoESumebMEQV8Y+poBwSW2ZbEwAokAOGsK5Cu8pDDT2g==", + "hasInstallScript": true, + "license": "MPL 2.0", + "optional": true, + "dependencies": { + "prebuild-install": "^7.1.3" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "optional": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-torrent": { + "version": "11.0.21", + "resolved": "https://registry.npmjs.org/parse-torrent/-/parse-torrent-11.0.21.tgz", + "integrity": "sha512-yQeIv9PaT/AQGhyyYpj0h2W5iP7Eka5CyaG6ECw/4SeoFK9lcIxCG3EqBHisrM3LA+LrtGzo68DLcE4rSd5YLA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "bencode": "^4.0.1", + "cross-fetch-ponyfill": "^1.0.3", + "get-stdin": "^10.0.0", + "magnet-uri": "^7.0.8", + "queue-microtask": "^1.2.3", + "uint8-util": "^2.2.6" + }, + "bin": { + "parse-torrent": "bin/cmd.js" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/piece-length": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/piece-length/-/piece-length-2.0.1.tgz", + "integrity": "sha512-dBILiDmm43y0JPISWEmVGKBETQjwJe6mSU9GND+P9KW0SJGUwoU/odyH1nbalOP9i8WSYuqf1lQnaj92Bhw+Ug==", + "license": "MIT", + "optional": true + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "license": "MIT", + "optional": true + }, + "node_modules/random-access-file": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/random-access-file/-/random-access-file-4.1.2.tgz", + "integrity": "sha512-GQM6R78DceZDcQod8KxlDFwXIiUvlvuy1EOzxTDsjuDjW5NlnlZi0MOk6iI4itAj/2vcvdqcEExYbVpC/dJcEw==", + "license": "MIT", + "optional": true, + "dependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0", + "random-access-storage": "^3.0.0" + }, + "optionalDependencies": { + "fs-native-extensions": "^1.3.1" + } + }, + "node_modules/random-access-storage": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/random-access-storage/-/random-access-storage-3.0.2.tgz", + "integrity": "sha512-Es9maUyWdJXWKckKy9s1+vT+DEgAt+PBb9lxPaake/0EDUsHehloKGv9v1zimS2V3gpFAcQXubvc1Rgci2sDPQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "bare-events": "^2.2.0", + "queue-tick": "^1.0.0" + } + }, + "node_modules/random-iterate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/random-iterate/-/random-iterate-1.0.1.tgz", + "integrity": "sha512-Jdsdnezu913Ot8qgKgSgs63XkAjEsnMcS1z+cC6D6TNXsUXsMxy0RpclF2pzGZTEiTXL9BiArdGTEexcv4nqcA==", + "license": "MIT", + "optional": true + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rc4": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/rc4/-/rc4-0.1.5.tgz", + "integrity": "sha512-xdDTNV90z5x5u25Oc871Xnvu7yAr4tV7Eluh0VSvrhUkry39q1k+zkz7xroqHbRq+8PiazySHJPArqifUvz9VA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/record-cache": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/record-cache/-/record-cache-1.2.0.tgz", + "integrity": "sha512-kyy3HWCez2WrotaL3O4fTn0rsIdfRKOdQQcEJ9KpvmKmbffKVvwsloX063EgRUlpJIXHiDQFhJcTbZequ2uTZw==", + "license": "MIT", + "optional": true, + "dependencies": { + "b4a": "^1.3.1" + } + }, + "node_modules/require-addon": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", + "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-addon-resolve": "^1.3.0" + }, + "engines": { + "bare": ">=1.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/run-parallel-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", + "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/run-series": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", + "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", + "license": "MIT", + "optional": true, + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speed-limiter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/speed-limiter/-/speed-limiter-1.0.2.tgz", + "integrity": "sha512-Ax+TbUOho84bWUc3AKqWtkIvAIVws7d6QI4oJkgH4yQ5Yil+lR3vjd/7qd51dHKGzS5bFxg0++QwyNRN7s6rZA==", + "license": "MIT", + "optional": true, + "dependencies": { + "limiter": "^1.1.5", + "streamx": "^2.10.3" + } + }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "license": "MIT", + "optional": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "license": "MIT", + "optional": true, + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string2compact": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/string2compact/-/string2compact-2.0.2.tgz", + "integrity": "sha512-ZUUIyrS+sSj84gR6fFoz/boiqCptyw37/hMxIehReuf6ekpJOlwSXhTfEasqc5t3QucBXi2ghwLlHOSC8hjEaw==", + "license": "MIT", + "optional": true, + "dependencies": { + "addr-to-ip-port": "^2.0.0", + "ipaddr.js": "2.0.1" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/string2compact/node_modules/ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT", + "optional": true + }, + "node_modules/throughput": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/throughput/-/throughput-1.0.2.tgz", + "integrity": "sha512-jvK1ZXuhsggjb3qYQjMiU/AVYYiTeqT5thWvYR2yuy2LGM84P5MSSyAinwHahGsdBYKR9m9HncVR/3f3nFKkxg==", + "license": "MIT", + "optional": true + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "license": "MIT", + "optional": true + }, + "node_modules/timeout-refresh": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-1.0.3.tgz", + "integrity": "sha512-Mz0CX4vBGM5lj8ttbIFt7o4ZMxk/9rgudJRh76EvB7xXZMur7T/cjRiH2w4Fmkq0zxf2QpM8IFvOSRn8FEu3gA==", + "license": "MIT", + "optional": true + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/torrent-discovery": { + "version": "11.0.21", + "resolved": "https://registry.npmjs.org/torrent-discovery/-/torrent-discovery-11.0.21.tgz", + "integrity": "sha512-x+RGu60qJp2u4S7B7XglyYCrfDJWKB0RIexODlA5dpBpMh94q4MbmRjusdbvxN+XD4NHhpwEfub/CRA9pUXjEQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "bittorrent-dht": "^11.0.12", + "bittorrent-lsd": "^2.0.3", + "bittorrent-tracker": "^11.2.3", + "debug": "^4.4.3", + "run-parallel": "^1.2.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/torrent-piece": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/torrent-piece/-/torrent-piece-3.0.2.tgz", + "integrity": "sha512-K1A5tZ3BolFrUtnBpk9iDg8av1na0OgQ7E0IlA9tj0bcsPhLhzvln+oMtMmtkqAwmUsbNCilRm2ymUdZg0rVbQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "uint8-util": "^2.1.9" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/tsup/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/uint8-util": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/uint8-util/-/uint8-util-2.2.6.tgz", + "integrity": "sha512-r+ZjS8CzPhtPF771ROOadUoqC40OVdiMKBI8lTfJQWb4W7+73sMBwMYmai/uvNcmZ7tBJJyZSad03yMWIt3RQg==", + "license": "MIT", + "optional": true, + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, + "node_modules/undici": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.26.0.tgz", + "integrity": "sha512-3O9Tf67pGhgOv9jM35AbhkXAKi13f3oy3aE4CSgr+TckGeY+/iu97ZXN+J7DpHPzLbVApFd1IFhcnBjREYXYcg==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unordered-array-remove": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unordered-array-remove/-/unordered-array-remove-1.0.2.tgz", + "integrity": "sha512-45YsfD6svkgaCBNyvD+dFHm4qFX9g3wRSIVgWVPtm2OCnphvPxzJoe20ATsiNpNJrmzHifnxm+BN5F7gFT/4gw==", + "license": "MIT", + "optional": true + }, + "node_modules/unordered-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unordered-set/-/unordered-set-2.0.1.tgz", + "integrity": "sha512-eUmNTPzdx+q/WvOHW0bgGYLWvWHNT3PTKEQLg0MAQhc0AHASHVHoP/9YytYd4RBVariqno/mEUhVZN98CmD7bg==", + "license": "MIT", + "optional": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/ut_metadata": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/ut_metadata/-/ut_metadata-4.0.3.tgz", + "integrity": "sha512-2tovup0VDYpT8t8+EhhhKBmbgIyiYyJQZ+Hf+/61+SvjuRS2MEeA5CiSARP4q+9/83Wu09OsGrUre/Zv6OI5NA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "bencode": "^4.0.0", + "bitfield": "^4.0.0", + "debug": "^4.2.0", + "uint8-util": "^2.1.3" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/ut_pex": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/ut_pex/-/ut_pex-4.0.4.tgz", + "integrity": "sha512-isVTbp2TKGoMOu+4Zh/i6ijpYr0VG83xjRPgCXaUjKzgXXndjCMWg32/9kZjubD+kxEXcmXMkoS8IttS9FZE8g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "bencode": "^4.0.0", + "compact2string": "^1.4.1", + "string2compact": "^2.0.1" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/utf-8-validate": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.6.tgz", + "integrity": "sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT", + "optional": true + }, + "node_modules/utp-native": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/utp-native/-/utp-native-2.5.3.tgz", + "integrity": "sha512-sWTrWYXPhhWJh+cS2baPzhaZc89zwlWCfwSthUjGhLkZztyPhcQllo+XVVCbNGi7dhyRlxkWxN4NKU6FbA9Y8w==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "napi-macros": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.0.2", + "timeout-refresh": "^1.0.0", + "unordered-set": "^2.0.1" + }, + "bin": { + "ucat": "ucat.js" + }, + "engines": { + "node": ">=8.12" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/webrtc-polyfill": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/webrtc-polyfill/-/webrtc-polyfill-1.2.1.tgz", + "integrity": "sha512-B52Rwxu7wzhLhANMRBys8W1wXAi9LwVzLfWWyueSQZmjpgojzX5p5g3m/fcZMFDtB+JZLnOt0Ud9u9FOZakg1Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "node-datachannel": "^0.32.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/webtorrent": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/webtorrent/-/webtorrent-2.8.5.tgz", + "integrity": "sha512-oIjpuBrypApJ+RCZ8RRaHEncVSkt2cd25/I4Trb2sk9nlaEF92Dg1u8BCwqA4eJR7wIZQM95GyO7Wo4QTbrUUA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@silentbot1/nat-api": "^0.4.9", + "@thaunknown/simple-peer": "^10.0.11", + "@webtorrent/http-node": "^1.3.0", + "addr-to-ip-port": "^2.0.0", + "bitfield": "^4.2.0", + "bittorrent-dht": "^11.0.10", + "bittorrent-protocol": "^4.1.20", + "cache-chunk-store": "^3.2.2", + "chunk-store-iterator": "^1.0.4", + "cpus": "^1.0.3", + "create-torrent": "^6.1.0", + "cross-fetch-ponyfill": "^1.0.3", + "debug": "^4.4.1", + "escape-html": "^1.0.3", + "fs-chunk-store": "^5.0.0", + "fsa-chunk-store": "^1.3.0", + "immediate-chunk-store": "^2.2.0", + "join-async-iterator": "^1.1.1", + "load-ip-set": "^3.0.1", + "lt_donthave": "^2.0.5", + "memory-chunk-store": "^1.3.5", + "mime": "^3.0.0", + "once": "^1.4.0", + "parse-torrent": "^11.0.18", + "pump": "^3.0.2", + "queue-microtask": "^1.2.3", + "random-iterate": "^1.0.1", + "range-parser": "^1.2.1", + "run-parallel": "^1.2.0", + "run-parallel-limit": "^1.1.0", + "speed-limiter": "^1.0.2", + "streamx": "2.22.1", + "throughput": "^1.0.2", + "torrent-discovery": "^11.0.17", + "torrent-piece": "^3.0.2", + "uint8-util": "^2.2.5", + "unordered-array-remove": "^1.0.2", + "ut_metadata": "^4.0.3", + "ut_pex": "^4.0.4" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "utp-native": "^2.5.3" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-runtime": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.4.0.tgz", + "integrity": "sha512-0ugbP4CJW4e2D20jvEcC4973dCgIaHI4Rw1PT+26U9zEve7FyYdWAIwUnoeOYvoCfn+wXHoHTKb1KhkYlb60Pw==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "optional": true + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "license": "MIT", + "optional": true, + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b3a8717 --- /dev/null +++ b/package.json @@ -0,0 +1,83 @@ +{ + "name": "streamnet-cli", + "version": "0.1.0", + "description": "Torrent search + in-process WebTorrent streaming to native VLC with hash-based subtitles. Agent-native by design.", + "type": "module", + "license": "AGPL-3.0-or-later", + "author": "Eduardo Borjas ", + "homepage": "https://github.com/eduardoborjas/streamnet-cli", + "repository": { + "type": "git", + "url": "https://github.com/eduardoborjas/streamnet-cli.git" + }, + "bugs": { + "url": "https://github.com/eduardoborjas/streamnet-cli/issues" + }, + "keywords": [ + "torrent", + "webtorrent", + "vlc", + "streaming", + "subtitles", + "opensubtitles", + "cli", + "agent-native", + "mcp", + "korvex" + ], + "engines": { + "node": ">=20" + }, + "bin": { + "streamnet": "dist/cli.js" + }, + "files": [ + "dist", + "LICENSE", + "LICENSE-COMMERCIAL", + "README.md" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "typecheck": "tsc --noEmit", + "lint": "eslint .", + "format": "prettier --write .", + "format:check": "prettier --check .", + "test": "vitest run", + "test:watch": "vitest", + "test:cov": "vitest run --coverage", + "prepublishOnly": "npm run build" + }, + "dependencies": { + "commander": "^13.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.23.2", + "cheerio": "^1.0.0", + "execa": "^9.5.1", + "p-limit": "^6.1.0", + "fast-xml-parser": "^4.5.0", + "picocolors": "^1.1.1" + }, + "optionalDependencies": { + "webtorrent": "^2.5.1" + }, + "devDependencies": { + "@eslint/js": "^9.14.0", + "@types/node": "^20.16.0", + "@typescript-eslint/eslint-plugin": "^8.13.0", + "@typescript-eslint/parser": "^8.13.0", + "@vitest/coverage-v8": "^2.1.4", + "eslint": "^9.14.0", + "prettier": "^3.3.3", + "tsup": "^8.3.5", + "typescript": "^5.5.4", + "vitest": "^2.1.4" + } +} diff --git a/src/agent/envelope.ts b/src/agent/envelope.ts new file mode 100644 index 0000000..349eef5 --- /dev/null +++ b/src/agent/envelope.ts @@ -0,0 +1,66 @@ +import { ExitCode, EXIT_CODE_NAMES } from './exit.js'; + +/** Bumped only on breaking changes to the machine-readable envelope shape. */ +export const ENVELOPE_SCHEMA_VERSION = 1 as const; + +export interface EnvelopeError { + code: ExitCode; + name: string; + message: string; + hint?: string; +} + +/** + * The stable JSON envelope emitted on stdout in `--json` mode. Every command + * returns exactly one of these. Agents may rely on this shape across a major + * version; `schemaVersion` guards breaking changes. + */ +export interface Envelope { + ok: boolean; + command: string; + version: string; + schemaVersion: typeof ENVELOPE_SCHEMA_VERSION; + data?: T; + error?: EnvelopeError; + meta: { + timestamp: string; + durationMs?: number; + }; +} + +export function successEnvelope( + command: string, + version: string, + data: T, + durationMs?: number, +): Envelope { + return { + ok: true, + command, + version, + schemaVersion: ENVELOPE_SCHEMA_VERSION, + data, + meta: { timestamp: new Date().toISOString(), durationMs }, + }; +} + +export function errorEnvelope( + command: string, + version: string, + error: { code: ExitCode; message: string; hint?: string }, + durationMs?: number, +): Envelope { + return { + ok: false, + command, + version, + schemaVersion: ENVELOPE_SCHEMA_VERSION, + error: { + code: error.code, + name: EXIT_CODE_NAMES[error.code] ?? 'ERROR', + message: error.message, + hint: error.hint, + }, + meta: { timestamp: new Date().toISOString(), durationMs }, + }; +} diff --git a/src/agent/exit.ts b/src/agent/exit.ts new file mode 100644 index 0000000..f5b522a --- /dev/null +++ b/src/agent/exit.ts @@ -0,0 +1,78 @@ +/** + * Deterministic POSIX exit codes. + * + * These are part of the public, stable contract: agents and shell scripts branch + * on them. They are surfaced in `streamnet manifest` and documented in the README. + * Do not renumber existing codes — only append. + */ +export enum ExitCode { + OK = 0, + ERROR = 1, + USAGE = 2, + NO_RESULTS = 3, + DEP_MISSING = 4, + NETWORK = 5, + TORRENT_UNPLAYABLE = 6, + PLAYER_FAILED = 7, + SUBS_NOT_FOUND = 8, + CONFIG = 9, + AUTH = 10, + /** A prompt was required but the CLI is running non-interactively. */ + EX_NOINPUT = 77, + SIGINT = 130, +} + +export const EXIT_CODE_NAMES: Record = { + [ExitCode.OK]: 'OK', + [ExitCode.ERROR]: 'ERROR', + [ExitCode.USAGE]: 'USAGE', + [ExitCode.NO_RESULTS]: 'NO_RESULTS', + [ExitCode.DEP_MISSING]: 'DEP_MISSING', + [ExitCode.NETWORK]: 'NETWORK', + [ExitCode.TORRENT_UNPLAYABLE]: 'TORRENT_UNPLAYABLE', + [ExitCode.PLAYER_FAILED]: 'PLAYER_FAILED', + [ExitCode.SUBS_NOT_FOUND]: 'SUBS_NOT_FOUND', + [ExitCode.CONFIG]: 'CONFIG', + [ExitCode.AUTH]: 'AUTH', + [ExitCode.EX_NOINPUT]: 'EX_NOINPUT', + [ExitCode.SIGINT]: 'SIGINT', +}; + +export const EXIT_CODE_DESCRIPTIONS: Record = { + [ExitCode.OK]: 'Success.', + [ExitCode.ERROR]: 'Generic or unexpected error.', + [ExitCode.USAGE]: 'Invalid arguments or flags.', + [ExitCode.NO_RESULTS]: 'Search returned no results.', + [ExitCode.DEP_MISSING]: 'A required dependency (e.g. VLC) was not found.', + [ExitCode.NETWORK]: 'An indexer, API, or network call failed.', + [ExitCode.TORRENT_UNPLAYABLE]: 'No peers or torrent metadata timed out.', + [ExitCode.PLAYER_FAILED]: 'The media player failed to spawn or exited with an error.', + [ExitCode.SUBS_NOT_FOUND]: 'No matching subtitles were found.', + [ExitCode.CONFIG]: 'Configuration is invalid or could not be written.', + [ExitCode.AUTH]: 'Authentication failed or rate limit exceeded (OpenSubtitles).', + [ExitCode.EX_NOINPUT]: + 'A prompt was required but the CLI is running non-interactively (pass the flag or --yes).', + [ExitCode.SIGINT]: 'Interrupted by signal.', +}; + +/** + * A typed, user-facing error that carries an exit code. Thrown by handlers and + * caught at the top level, where it is rendered (human or JSON) and used as the + * process exit code. + */ +export class StreamNetError extends Error { + readonly code: ExitCode; + readonly hint?: string; + + constructor(code: ExitCode, message: string, hint?: string) { + super(message); + this.name = 'StreamNetError'; + this.code = code; + this.hint = hint; + } +} + +/** Convenience constructor mirroring the Go-style `fail(code, msg)` idiom. */ +export function fail(code: ExitCode, message: string, hint?: string): never { + throw new StreamNetError(code, message, hint); +} diff --git a/src/agent/output.ts b/src/agent/output.ts new file mode 100644 index 0000000..8a0fa1a --- /dev/null +++ b/src/agent/output.ts @@ -0,0 +1,129 @@ +import pc from 'picocolors'; +import { successEnvelope, errorEnvelope, type Envelope } from './envelope.js'; +import { ExitCode } from './exit.js'; + +export type OutputMode = 'human' | 'json'; + +export interface OutputOptions { + json: boolean; + yes: boolean; + noInput: boolean; + quiet: boolean; + verbose: boolean; + color: boolean; +} + +/** + * Central output adapter. Commands never write to stdout/stderr directly; they + * call into an OutputContext so the same handler renders cleanly as a human TUI + * line *or* a single JSON envelope, with progress and logs kept on stderr. + * + * Contract: in JSON mode, stdout receives exactly one line — the envelope. + * Everything diagnostic (logs, progress, prompts) goes to stderr. + */ +export class OutputContext { + readonly mode: OutputMode; + readonly interactive: boolean; + readonly options: OutputOptions; + + constructor(options: OutputOptions) { + this.options = options; + this.mode = options.json ? 'json' : 'human'; + // Interactive only when a real TTY is present, JSON is off, --no-input is + // off, and we are not in CI. Agents get a fully non-interactive surface. + this.interactive = + !options.json && + !options.noInput && + Boolean(process.stdout.isTTY) && + Boolean(process.stdin.isTTY) && + !isCI(); + } + + private paint(fn: (s: string) => string, s: string): string { + return this.options.color ? fn(s) : s; + } + + /** Diagnostic log — always stderr, suppressed by --quiet. */ + log(message: string): void { + if (this.options.quiet) return; + process.stderr.write(message + '\n'); + } + + info(message: string): void { + this.log(this.paint(pc.cyan, 'ℹ ') + message); + } + + success(message: string): void { + this.log(this.paint(pc.green, '✔ ') + message); + } + + warn(message: string): void { + this.log(this.paint(pc.yellow, '⚠ ') + message); + } + + error(message: string): void { + process.stderr.write(this.paint(pc.red, '✖ ') + message + '\n'); + } + + /** Verbose-only diagnostic. */ + debug(message: string): void { + if (this.options.verbose && !this.options.quiet) { + process.stderr.write(this.paint(pc.dim, '· ' + message) + '\n'); + } + } + + /** A structured progress event — NDJSON on stderr for agents, human line otherwise. */ + progress(event: Record): void { + if (this.options.quiet) return; + if (this.mode === 'json') { + process.stderr.write(JSON.stringify({ type: 'progress', ...event }) + '\n'); + } else { + const parts = Object.entries(event) + .map(([k, v]) => `${k}=${String(v)}`) + .join(' '); + process.stderr.write(this.paint(pc.dim, parts) + '\r'); + } + } + + /** + * Emit the terminal result for a command. In JSON mode this writes the single + * stdout envelope; in human mode it invokes the provided renderer. + */ + emit(command: string, version: string, data: T, render: (data: T) => void): void { + if (this.mode === 'json') { + process.stdout.write( + JSON.stringify(successEnvelope(command, version, data)) + '\n', + ); + } else { + render(data); + } + } + + /** Emit a failure envelope (JSON) or a human error line. */ + emitError( + command: string, + version: string, + error: { code: ExitCode; message: string; hint?: string }, + ): void { + if (this.mode === 'json') { + process.stdout.write(JSON.stringify(errorEnvelope(command, version, error)) + '\n'); + } else { + this.error(error.message); + if (error.hint) this.log(this.paint(pc.dim, ' ' + error.hint)); + } + } + + /** Raw envelope passthrough (used by `manifest` which builds its own data). */ + emitEnvelope(envelope: Envelope): void { + process.stdout.write(JSON.stringify(envelope) + '\n'); + } +} + +export function isCI(): boolean { + return Boolean( + process.env.CI || + process.env.CONTINUOUS_INTEGRATION || + process.env.GITHUB_ACTIONS || + process.env.GITLAB_CI, + ); +} diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000..7304540 --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,120 @@ +import { Command } from 'commander'; +import { createRequire } from 'node:module'; +import { buildCommand } from './registry/build.js'; +import { COMMAND_SPECS } from './registry/index.js'; +import { OutputContext, type OutputOptions } from './agent/output.js'; +import { loadConfig } from './config/store.js'; +import { ExitCode } from './agent/exit.js'; +import { manifestHandler } from './commands/manifest.js'; +import type { CommandContext } from './registry/types.js'; + +// Resolve package.json version without relying on import assertions (Node 20 ESM) +const req = createRequire(import.meta.url); +const pkg = req('../package.json') as { version: string }; +const VERSION = pkg.version; + +const program = new Command('streamnet') + .version(VERSION, '-V, --version') + .description('Torrent search + in-process WebTorrent streaming to native VLC.') + .option('--json', 'Machine-readable JSON output', false) + .option('-y, --yes', 'Skip prompts; auto-select top result', false) + .option('--no-input', 'Disable interactive prompts; exit 77 if one is needed', false) + .option('-q, --quiet', 'Suppress diagnostic output', false) + .option('-v, --verbose', 'Verbose output', false) + .option('--no-color', 'Disable ANSI colour') + .option('--config ', 'Path to config file', process.env.STREAMNET_CONFIG) + .addHelpText( + 'after', + ` +Environment variables: + STREAMNET_JSON=1 Same as --json + STREAMNET_YES=1 Same as --yes + STREAMNET_NO_INPUT=1 Same as --no-input + STREAMNET_CONFIG= Path to config file + STREAMNET_LOG_LEVEL=debug|info|warn|error + STREAMNET_OPENSUBTITLES_API_KEY= + NO_COLOR=1 Disable ANSI colour + +Exit codes: + 0 OK 2 USAGE 3 NO_RESULTS 4 DEP_MISSING + 5 NETWORK 6 UNPLAYABLE 7 PLAYER_FAIL 8 SUBS_NOT_FOUND + 9 CONFIG 10 AUTH 77 EX_NOINPUT 130 SIGINT +`, + ); + +/** Build a CommandContext from the current program option values. */ +function makeContext(opts: Record): CommandContext { + // Env vars override flags + const json = Boolean(opts.json) || process.env.STREAMNET_JSON === '1'; + const yes = Boolean(opts.yes) || process.env.STREAMNET_YES === '1'; + const noInput = Boolean(opts.noInput) || process.env.STREAMNET_NO_INPUT === '1'; + const quiet = Boolean(opts.quiet); + const verbose = Boolean(opts.verbose); + const color = opts.color !== false && !process.env.NO_COLOR; + + const outputOpts: OutputOptions = { json, yes, noInput, quiet, verbose, color }; + const output = new OutputContext(outputOpts); + + const config = loadConfig(opts.config as string | undefined); + + return { output, config, version: VERSION }; +} + +// Wire each CommandSpec onto the program +for (const spec of COMMAND_SPECS) { + if (spec.id === 'manifest') { + // manifest needs access to all specs — wired separately below + continue; + } + const cmd = buildCommand(spec, () => { + const opts = program.opts>(); + return makeContext(opts); + }); + program.addCommand(cmd); +} + +// manifest command — special case +program + .command('manifest') + .description('Emit the machine-readable command manifest (agent discovery).') + .action(async () => { + const opts = program.opts>(); + const ctx = makeContext(opts); + await manifestHandler(ctx, {}, COMMAND_SPECS); + process.exit(ExitCode.OK); + }); + +// mcp command — MCP stdio server +program + .command('mcp') + .description('Start an MCP stdio server exposing all commands as tools.') + .option('--stdio', 'Use stdio transport (default)', true) + .action(async () => { + const opts = program.opts>(); + const context = makeContext(opts); + context.output.info('MCP server (stdio) — available in v0.4'); + context.output.info('Install an MCP client and run: streamnet mcp --stdio'); + process.exit(ExitCode.OK); + }); + +// completion command +program + .command('completion ') + .description('Print shell completion script (bash|zsh|fish|pwsh).') + .action((shell: string) => { + const opts = program.opts>(); + const ctx = makeContext(opts); + ctx.output.info(`Shell completion for ${shell} — available in v0.5`); + process.exit(ExitCode.OK); + }); + +// Handle SIGINT cleanly +process.on('SIGINT', () => { + process.exit(ExitCode.SIGINT); +}); + +// Parse — exits on --help / --version automatically +program.parseAsync(process.argv).catch((err: unknown) => { + process.stderr.write(`Unhandled error: ${String(err)}\n`); + process.exit(ExitCode.ERROR); +}); diff --git a/src/commands/config.ts b/src/commands/config.ts new file mode 100644 index 0000000..63cf0ea --- /dev/null +++ b/src/commands/config.ts @@ -0,0 +1,82 @@ +import { existsSync } from 'node:fs'; +import type { CommandContext } from '../registry/types.js'; +import type { OutputContext } from '../agent/output.js'; +import { + saveConfig, + getConfigValue, + setConfigValue, + redactConfig, +} from '../config/store.js'; +import { configFile } from '../config/paths.js'; +import { ExitCode, fail } from '../agent/exit.js'; + +export interface ConfigInput { + subcommand: string; + key?: string; + value?: string; +} + +export interface ConfigResult { + subcommand: string; + key?: string; + value?: unknown; + config?: unknown; + path?: string; + exists?: boolean; +} + +export async function configHandler( + ctx: CommandContext, + input: ConfigInput, +): Promise { + const sub = input.subcommand?.toLowerCase(); + + switch (sub) { + case 'get': { + if (!input.key) fail(ExitCode.USAGE, '`config get` requires a key argument.'); + return { + subcommand: 'get', + key: input.key, + value: getConfigValue(ctx.config, input.key), + }; + } + + case 'set': { + if (!input.key) + fail(ExitCode.USAGE, '`config set` requires key and value arguments.'); + if (input.value === undefined) + fail(ExitCode.USAGE, '`config set` requires a value.'); + const updated = setConfigValue(ctx.config, input.key, input.value); + saveConfig(updated, process.env.STREAMNET_CONFIG); + return { subcommand: 'set', key: input.key, value: input.value }; + } + + case 'list': + return { subcommand: 'list', config: redactConfig(ctx.config) }; + + case 'path': { + const p = configFile(); + return { subcommand: 'path', path: p, exists: existsSync(p) }; + } + + default: + fail(ExitCode.USAGE, `Unknown config subcommand: "${sub}". Use get|set|list|path`); + } +} + +export function renderConfig(data: ConfigResult, output: OutputContext): void { + switch (data.subcommand) { + case 'get': + process.stdout.write(`${data.key}: ${JSON.stringify(data.value, null, 2)}\n`); + break; + case 'set': + output.success(`Set ${data.key} = ${String(data.value)}`); + break; + case 'list': + process.stdout.write(JSON.stringify(data.config, null, 2) + '\n'); + break; + case 'path': + process.stdout.write(data.path + (data.exists ? '\n' : ' (not yet created)\n')); + break; + } +} diff --git a/src/commands/doctor.ts b/src/commands/doctor.ts new file mode 100644 index 0000000..59f33fe --- /dev/null +++ b/src/commands/doctor.ts @@ -0,0 +1,34 @@ +import type { CommandContext } from '../registry/types.js'; +import type { OutputContext } from '../agent/output.js'; +import { runAllChecks, type CheckResult } from '../core/setup/checks.js'; +import pc from 'picocolors'; + +export interface DoctorResult { + checks: CheckResult[]; + allOk: boolean; +} + +export async function doctorHandler( + _ctx: CommandContext, + _input: Record, +): Promise { + const checks = await runAllChecks(); + const allOk = checks.every((c) => c.ok); + return { checks, allOk }; +} + +export function renderDoctor(data: DoctorResult, _output: OutputContext): void { + for (const check of data.checks) { + const icon = check.ok ? pc.green('✔') : pc.red('✖'); + process.stdout.write(` ${icon} ${check.name.padEnd(14)} ${check.message}\n`); + if (!check.ok && check.hint) { + process.stdout.write(` ${pc.dim(check.hint)}\n`); + } + } + process.stdout.write('\n'); + if (data.allOk) { + process.stdout.write(pc.green('All checks passed.\n')); + } else { + process.stdout.write(pc.red('Some checks failed. Run `streamnet setup` to fix.\n')); + } +} diff --git a/src/commands/manifest.ts b/src/commands/manifest.ts new file mode 100644 index 0000000..be1ff8a --- /dev/null +++ b/src/commands/manifest.ts @@ -0,0 +1,129 @@ +import type { CommandContext } from '../registry/types.js'; +import { ExitCode, EXIT_CODE_NAMES, EXIT_CODE_DESCRIPTIONS } from '../agent/exit.js'; +import type { CommandSpec } from '../registry/types.js'; + +export interface ManifestResult { + name: string; + version: string; + schemaVersion: number; + description: string; + commands: ManifestCommand[]; + exitCodes: ManifestExitCode[]; + globalFlags: ManifestFlag[]; + installHints: Record; +} + +interface ManifestCommand { + id: string; + summary: string; + description?: string; + args?: { name: string; required: boolean; description: string }[]; + flags?: ManifestFlag[]; + exitCodes?: { code: number; meaning: string }[]; + examples?: string[]; +} + +interface ManifestFlag { + long: string; + short?: string; + description: string; + default?: unknown; + env?: string; +} + +interface ManifestExitCode { + code: number; + name: string; + description: string; +} + +const GLOBAL_FLAGS: ManifestFlag[] = [ + { + long: 'json', + description: 'Emit machine-readable JSON envelope on stdout', + env: 'STREAMNET_JSON', + }, + { + long: 'yes', + short: 'y', + description: 'Skip all prompts; auto-select top result', + env: 'STREAMNET_YES', + }, + { + long: 'no-input', + description: 'Disable all interactive prompts; exit 77 if one is needed', + env: 'STREAMNET_NO_INPUT', + }, + { long: 'quiet', short: 'q', description: 'Suppress diagnostic output' }, + { + long: 'verbose', + short: 'v', + description: 'Verbose diagnostic output', + env: 'STREAMNET_LOG_LEVEL', + }, + { long: 'no-color', description: 'Disable ANSI colour', env: 'NO_COLOR' }, + { long: 'config', description: 'Path to config file', env: 'STREAMNET_CONFIG' }, + { long: 'version', description: 'Print version and exit' }, + { long: 'help', short: 'h', description: 'Show help' }, +]; + +export function buildManifest(specs: CommandSpec[], version: string): ManifestResult { + return { + name: 'streamnet', + version, + schemaVersion: 1, + description: + 'Torrent search + in-process WebTorrent streaming to native VLC with hash-based subtitles. Agent-native CLI.', + globalFlags: GLOBAL_FLAGS, + commands: specs.map((s) => ({ + id: s.id, + summary: s.summary, + description: s.description, + args: s.args?.map((a) => ({ + name: a.name, + required: a.required, + description: a.description, + })), + flags: s.flags?.map((f) => ({ + long: f.long, + short: f.short, + description: f.description, + default: f.default, + env: f.env, + })), + exitCodes: s.exitCodes?.map((e) => ({ code: e.code, meaning: e.meaning })), + examples: s.examples, + })), + exitCodes: Object.values(ExitCode) + .filter((v): v is ExitCode => typeof v === 'number') + .map((code) => ({ + code, + name: EXIT_CODE_NAMES[code] ?? String(code), + description: EXIT_CODE_DESCRIPTIONS[code] ?? '', + })), + installHints: { + npm: 'npm install -g streamnet-cli', + binary: 'https://github.com/eduardoborjas/streamnet-cli/releases', + homebrew: 'brew install eduardoborjas/tap/streamnet (v1.0)', + scoop: 'scoop install streamnet (v1.0)', + }, + }; +} + +export async function manifestHandler( + ctx: CommandContext, + _input: Record, + specs: CommandSpec[], +): Promise { + const manifest = buildManifest(specs, ctx.version); + + // Always emit as JSON regardless of --json flag — manifest IS the machine interface. + // Awaited write: process.stdout.write is async when stdout is a pipe; without the + // callback, process.exit() in the caller can race the OS buffer flush on macOS/Node 20. + await new Promise((resolve, reject) => + process.stdout.write(JSON.stringify(manifest, null, 2) + '\n', (err) => + err ? reject(err) : resolve(), + ), + ); + return manifest; +} diff --git a/src/commands/play.ts b/src/commands/play.ts new file mode 100644 index 0000000..95a5c51 --- /dev/null +++ b/src/commands/play.ts @@ -0,0 +1,74 @@ +import type { CommandContext } from '../registry/types.js'; +import type { OutputContext } from '../agent/output.js'; +import { searchHandler } from './search.js'; +import { streamHandler, type StreamResult } from './stream.js'; +import { ExitCode, fail } from '../agent/exit.js'; +import type { TorrentResult } from '../core/indexers/types.js'; + +export interface PlayInput { + query: string; + quality?: string; + container?: string; + minSeeders?: number; + yes?: boolean; + fileIndex?: number; + noSubs?: boolean; + subLang?: string; +} + +export interface PlayResult extends StreamResult { + selectedTorrent: TorrentResult; +} + +export async function playHandler( + ctx: CommandContext, + input: PlayInput, +): Promise { + // 1. Search + const searchResult = await searchHandler(ctx, { + query: input.query, + minSeeders: input.minSeeders, + container: input.container, + quality: input.quality, + }); + + const results = searchResult.results; + if (results.length === 0) { + fail(ExitCode.NO_RESULTS, `No results found for "${input.query}".`); + } + + // 2. Select: agent/--yes → top result; interactive → prompt (TUI in v0.7) + let selected: TorrentResult; + const useTop = input.yes || ctx.output.options.yes || !ctx.output.interactive; + + if (useTop) { + selected = results[0]!; + ctx.output.info(`Auto-selecting top result: ${selected.title}`); + } else { + // Non-interactive fallback until TUI lands in v0.7 + selected = results[0]!; + ctx.output.info(`Selecting top result: ${selected.title}`); + ctx.output.info('(Interactive selection will be available in v0.7)'); + } + + const source = selected.magnet ?? selected.torrentUrl; + if (!source) { + fail(ExitCode.TORRENT_UNPLAYABLE, `No magnet or URL for: ${selected.title}`); + } + + // 3. Stream → VLC + const streamResult = await streamHandler(ctx, { + source, + fileIndex: input.fileIndex, + noSubs: input.noSubs, + subLang: input.subLang, + }); + + return { ...streamResult, selectedTorrent: selected }; +} + +export function renderPlay(data: PlayResult, output: OutputContext): void { + output.success( + `Played: ${data.selectedTorrent.title} → ${data.fileName} (VLC exit: ${data.vlcExitCode})`, + ); +} diff --git a/src/commands/search.ts b/src/commands/search.ts new file mode 100644 index 0000000..01eed2d --- /dev/null +++ b/src/commands/search.ts @@ -0,0 +1,96 @@ +import type { CommandContext } from '../registry/types.js'; +import type { OutputContext } from '../agent/output.js'; +import type { TorrentResult } from '../core/indexers/types.js'; +import { getEnabledIndexers, getIndexerById } from '../core/indexers/registry.js'; +import { aggregateSearch } from '../core/indexers/aggregate.js'; +import { rankResults } from '../core/health/rank.js'; +import { formatBytes, truncate } from '../util/format.js'; +import { ExitCode, fail } from '../agent/exit.js'; +import pc from 'picocolors'; + +export interface SearchInput { + query: string; + limit?: number; + indexer?: string; + minSeeders?: number; + quality?: string; + container?: string; + sort?: string; +} + +export interface SearchResult { + query: string; + results: TorrentResult[]; + total: number; +} + +export async function searchHandler( + ctx: CommandContext, + input: SearchInput, +): Promise { + const query = input.query; + if (!query?.trim()) { + fail(ExitCode.USAGE, 'Search query cannot be empty.'); + } + + const indexers = input.indexer + ? [getIndexerById(input.indexer)].filter(Boolean).map((i) => i!) + : getEnabledIndexers(ctx.config); + + if (indexers.length === 0) { + fail(ExitCode.CONFIG, 'No indexers enabled. Check `streamnet config get indexers`.'); + } + + ctx.output.info(`Searching ${indexers.map((i) => i.id).join(', ')} for "${query}"…`); + + const raw = await aggregateSearch(query, indexers, { limit: input.limit ?? 25 }); + + const minSeeders = input.minSeeders ?? ctx.config.minSeeders; + let ranked = rankResults(raw, { + minSeeders, + preferredContainers: ctx.config.preferredContainers, + preferredQuality: ctx.config.preferredQuality, + }); + + // Optional post-filters + if (input.container) { + const want = input.container.toLowerCase(); + ranked = ranked.filter((r) => (r.quality.container ?? '').toLowerCase() === want); + } + if (input.quality) { + ranked = ranked.filter((r) => r.quality.resolution === input.quality); + } + + if (ranked.length === 0) { + fail(ExitCode.NO_RESULTS, `No results for "${query}" (min seeders: ${minSeeders}).`); + } + + return { query, results: ranked, total: ranked.length }; +} + +export function renderSearch(data: SearchResult, _output: OutputContext): void { + const W = { idx: 4, score: 6, seeders: 7, size: 9, title: 58 }; + const header = + '#'.padEnd(W.idx) + + 'Score'.padStart(W.score) + + ' ' + + 'Seeds'.padStart(W.seeders) + + ' ' + + 'Size'.padStart(W.size) + + ' ' + + 'Title'; + process.stdout.write(pc.dim(header) + '\n'); + process.stdout.write(pc.dim('─'.repeat(header.length)) + '\n'); + + data.results.slice(0, 30).forEach((r, i) => { + const idx = String(i + 1).padEnd(W.idx); + const score = String(r.healthScore ?? 0).padStart(W.score); + const seeds = String(r.seeders).padStart(W.seeders); + const size = formatBytes(r.sizeBytes).padStart(W.size); + const container = r.quality.container + ? pc.cyan(`[${r.quality.container.toUpperCase()}]`) + : ''; + const title = truncate(r.title, W.title); + process.stdout.write(`${idx}${score} ${seeds} ${size} ${container} ${title}\n`); + }); +} diff --git a/src/commands/setup.ts b/src/commands/setup.ts new file mode 100644 index 0000000..431a6cd --- /dev/null +++ b/src/commands/setup.ts @@ -0,0 +1,40 @@ +import type { CommandContext } from '../registry/types.js'; +import { installVlc } from '../core/setup/install.js'; +import { findVlc } from '../core/player/detect.js'; + +export interface SetupInput { + yes?: boolean; + check?: boolean; +} + +export interface SetupResult { + vlcPath: string | null; + installed: boolean; +} + +export async function setupHandler( + ctx: CommandContext, + input: SetupInput, +): Promise { + const existing = findVlc(); + + if (existing) { + ctx.output.success(`Native VLC already installed: ${existing}`); + return { vlcPath: existing, installed: false }; + } + + if (input.check) { + ctx.output.warn('VLC not found (check-only mode, not installing).'); + return { vlcPath: null, installed: false }; + } + + if (!ctx.output.options.yes && !input.yes && ctx.output.interactive) { + ctx.output.info('VLC not found. Installing native VLC...'); + } + + await installVlc(Boolean(ctx.output.options.yes || input.yes)); + const vlcPath = findVlc(); + ctx.output.success(`VLC installed: ${vlcPath ?? '(not found after install?)'}`); + + return { vlcPath, installed: true }; +} diff --git a/src/commands/stream.ts b/src/commands/stream.ts new file mode 100644 index 0000000..5d6a6e6 --- /dev/null +++ b/src/commands/stream.ts @@ -0,0 +1,88 @@ +import { readFileSync } from 'node:fs'; +import type { CommandContext } from '../registry/types.js'; +import type { OutputContext } from '../agent/output.js'; +import { startStream } from '../core/torrent/engine.js'; +import { isMkv } from '../core/torrent/select.js'; +import { spawnVlc, waitForVlc } from '../core/player/vlc.js'; +import { ExitCode, fail } from '../agent/exit.js'; +import { formatBytes } from '../util/format.js'; + +export interface StreamInput { + source: string; + fileIndex?: number; + port?: number; + noSubs?: boolean; + subLang?: string; + player?: string; +} + +export interface StreamResult { + streamUrl: string; + fileName: string; + fileIndex: number; + sizeBytes: number; + subtitleFile?: string; + vlcExitCode: number; +} + +export async function streamHandler( + ctx: CommandContext, + input: StreamInput, +): Promise { + // Support `-` for stdin piped source + let source = input.source; + if (source === '-') { + source = readFileSync('/dev/stdin', 'utf8').trim(); + if (!source) fail(ExitCode.USAGE, 'No source provided via stdin.'); + } + + ctx.output.info(`Starting stream: ${source.slice(0, 72)}…`); + + const info = await startStream({ + source, + fileIndex: input.fileIndex, + port: input.port ?? ctx.config.streamPort, + preferredContainers: ctx.config.preferredContainers, + onProgress: (p) => { + ctx.output.progress({ + progress: (p.progress * 100).toFixed(1) + '%', + peers: p.peers, + speed: formatBytes(p.downloadSpeed) + '/s', + }); + }, + }); + + ctx.output.success(`Streaming: ${info.fileName} (${formatBytes(info.sizeBytes)})`); + ctx.output.info(`Local URL: ${info.streamUrl}`); + + let subtitleFile: string | undefined; + const skipSubs = input.noSubs || isMkv(info.fileName); + + if (!skipSubs) { + // Subtitle search happens in v0.3; for now inform the user + ctx.output.info('Non-MKV file detected. Subtitle search will be added in v0.3.'); + } + + const vlcProc = spawnVlc({ + streamUrl: info.streamUrl, + subFile: subtitleFile, + title: info.fileName, + }); + ctx.output.success(`VLC launched: ${vlcProc.vlcPath}`); + + const vlcExitCode = await waitForVlc(vlcProc); + info.destroy(); + + return { + streamUrl: info.streamUrl, + fileName: info.fileName, + fileIndex: info.fileIndex, + sizeBytes: info.sizeBytes, + subtitleFile, + vlcExitCode, + }; +} + +export function renderStream(data: StreamResult, output: OutputContext): void { + output.success(`Playback finished: ${data.fileName} (VLC exit: ${data.vlcExitCode})`); +} diff --git a/src/config/paths.ts b/src/config/paths.ts new file mode 100644 index 0000000..a666f32 --- /dev/null +++ b/src/config/paths.ts @@ -0,0 +1,64 @@ +import { homedir } from 'node:os'; +import { join } from 'node:path'; + +const APP = 'streamnet'; + +function isWindows(): boolean { + return process.platform === 'win32'; +} + +function winBase(envVar: string, fallback: string): string { + return process.env[envVar] ?? join(homedir(), fallback); +} + +/** + * Resolve an XDG-style base directory. + * + * Order: explicit XDG_* env var → platform default. On Windows we use + * %APPDATA% / %LOCALAPPDATA%; elsewhere we follow the XDG Base Directory spec + * (the Korvex-suite convention, consistent across Linux and macOS). + */ +export interface Paths { + config: string; + cache: string; + data: string; + state: string; +} + +export function resolvePaths(): Paths { + if (isWindows()) { + const appData = winBase('APPDATA', 'AppData/Roaming'); + const localAppData = winBase('LOCALAPPDATA', 'AppData/Local'); + return { + config: join(appData, APP), + cache: join(localAppData, APP, 'cache'), + data: join(localAppData, APP, 'data'), + state: join(localAppData, APP, 'state'), + }; + } + + const home = homedir(); + const xdg = (envVar: string, fallback: string): string => + join(process.env[envVar] ?? join(home, fallback), APP); + + return { + config: xdg('XDG_CONFIG_HOME', '.config'), + cache: xdg('XDG_CACHE_HOME', '.cache'), + data: xdg('XDG_DATA_HOME', '.local/share'), + state: xdg('XDG_STATE_HOME', '.local/state'), + }; +} + +export function configFile(override?: string): string { + if (override) return override; + if (process.env.STREAMNET_CONFIG) return process.env.STREAMNET_CONFIG; + return join(resolvePaths().config, 'config.json'); +} + +export function downloadsDir(): string { + return join(resolvePaths().data, 'downloads'); +} + +export function logFile(): string { + return join(resolvePaths().data, 'logs', 'streamnet.log'); +} diff --git a/src/config/schema.ts b/src/config/schema.ts new file mode 100644 index 0000000..00df8b9 --- /dev/null +++ b/src/config/schema.ts @@ -0,0 +1,39 @@ +import { z } from 'zod'; +import { downloadsDir } from './paths.js'; + +export const CONFIG_SCHEMA_VERSION = 1 as const; + +export const ConfigSchema = z.object({ + schemaVersion: z.literal(CONFIG_SCHEMA_VERSION).default(CONFIG_SCHEMA_VERSION), + player: z.enum(['vlc']).default('vlc'), + subtitleLanguages: z.array(z.string()).default(['en', 'es']), + minSeeders: z.number().int().min(0).default(3), + preferredQuality: z.array(z.string()).default(['1080p', '720p', '2160p']), + /** Container preference order — drives MKV-first ranking and the subtitle skip. */ + preferredContainers: z.array(z.string()).default(['mkv', 'mp4']), + indexers: z + .object({ + enabled: z.array(z.string()).default(['torrents-csv', 'yts']), + order: z.array(z.string()).default(['torrents-csv', 'yts']), + }) + .default({ enabled: ['torrents-csv', 'yts'], order: ['torrents-csv', 'yts'] }), + opensubtitles: z + .object({ + apiKey: z.string().optional(), + username: z.string().optional(), + }) + .default({}), + /** 0 = ephemeral OS-assigned port for the local stream server. */ + streamPort: z.number().int().min(0).max(65535).default(0), + downloadDir: z.string().default(() => downloadsDir()), + telemetry: z.literal(false).default(false), +}); + +export type Config = z.infer; + +/** Keys whose values must be redacted in `config list` / `--json`. */ +export const SECRET_PATHS = new Set(['opensubtitles.apiKey']); + +export function defaultConfig(): Config { + return ConfigSchema.parse({}); +} diff --git a/src/config/store.ts b/src/config/store.ts new file mode 100644 index 0000000..8a382b0 --- /dev/null +++ b/src/config/store.ts @@ -0,0 +1,116 @@ +import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'; +import { dirname } from 'node:path'; +import { ExitCode, fail } from '../agent/exit.js'; +import { configFile } from './paths.js'; +import { ConfigSchema, type Config, defaultConfig, SECRET_PATHS } from './schema.js'; + +/** + * Load config from disk, validating against the schema. A missing file yields + * defaults (the CLI works zero-config). Invalid JSON or schema violations are a + * hard CONFIG error so agents get a deterministic signal. + */ +export function loadConfig(override?: string): Config { + const file = configFile(override); + if (!existsSync(file)) { + return defaultConfig(); + } + let raw: unknown; + try { + raw = JSON.parse(readFileSync(file, 'utf8')); + } catch (err) { + fail(ExitCode.CONFIG, `Config file is not valid JSON: ${file}`, String(err)); + } + const parsed = ConfigSchema.safeParse(raw); + if (!parsed.success) { + fail( + ExitCode.CONFIG, + `Config file failed validation: ${file}`, + parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; '), + ); + } + return parsed.data; +} + +export function saveConfig(config: Config, override?: string): void { + const file = configFile(override); + try { + mkdirSync(dirname(file), { recursive: true }); + writeFileSync(file, JSON.stringify(config, null, 2) + '\n', 'utf8'); + } catch (err) { + fail(ExitCode.CONFIG, `Could not write config file: ${file}`, String(err)); + } +} + +/** Read a dotted key path from config (e.g. `opensubtitles.apiKey`). */ +export function getConfigValue(config: Config, key: string): unknown { + return key.split('.').reduce((acc, part) => { + if (acc && typeof acc === 'object' && part in (acc as Record)) { + return (acc as Record)[part]; + } + return undefined; + }, config); +} + +/** + * Set a dotted key path, coercing scalars/arrays from string input, then + * re-validate the whole object so we never persist an invalid config. + */ +export function setConfigValue(config: Config, key: string, value: string): Config { + const parts = key.split('.'); + const draft = structuredClone(config) as Record; + let cursor = draft; + for (let i = 0; i < parts.length - 1; i++) { + const part = parts[i]!; + if (typeof cursor[part] !== 'object' || cursor[part] === null) { + cursor[part] = {}; + } + cursor = cursor[part] as Record; + } + cursor[parts[parts.length - 1]!] = coerce(value); + + const parsed = ConfigSchema.safeParse(draft); + if (!parsed.success) { + fail( + ExitCode.CONFIG, + `Invalid value for "${key}"`, + parsed.error.issues.map((iss) => iss.message).join('; '), + ); + } + return parsed.data; +} + +function coerce(value: string): unknown { + if (value === 'true') return true; + if (value === 'false') return false; + if (/^-?\d+$/.test(value)) return Number(value); + if (value.includes(',')) return value.split(',').map((s) => s.trim()); + return value; +} + +/** Produce a config copy with secret values redacted for display. */ +export function redactConfig(config: Config): Config { + const copy = structuredClone(config) as Record; + for (const path of SECRET_PATHS) { + const parts = path.split('.'); + let cursor = copy; + let ok = true; + for (let i = 0; i < parts.length - 1; i++) { + const next = cursor[parts[i]!]; + if (typeof next !== 'object' || next === null) { + ok = false; + break; + } + cursor = next as Record; + } + const leaf = parts[parts.length - 1]!; + if (ok && cursor[leaf] !== undefined) { + cursor[leaf] = '***'; + } + } + return copy as unknown as Config; +} + +/** Resolve the OpenSubtitles API key: env wins over config. */ +export function resolveOpenSubtitlesKey(config: Config): string | undefined { + return process.env.STREAMNET_OPENSUBTITLES_API_KEY ?? config.opensubtitles.apiKey; +} diff --git a/src/core/health/quality.ts b/src/core/health/quality.ts new file mode 100644 index 0000000..ebab463 --- /dev/null +++ b/src/core/health/quality.ts @@ -0,0 +1,72 @@ +import type { TorrentQuality } from '../indexers/types.js'; + +const RESOLUTIONS: [RegExp, string][] = [ + [/\b4k|2160p|uhd\b/i, '2160p'], + [/\b1080p|1080i|fhd\b/i, '1080p'], + [/\b720p|720i|hd\b/i, '720p'], + [/\b480p\b/i, '480p'], + [/\b360p\b/i, '360p'], +]; + +const CODECS: [RegExp, string][] = [ + [/\bx265|h\.?265|hevc\b/i, 'x265'], + [/\bx264|h\.?264|avc\b/i, 'x264'], + [/\bav1\b/i, 'AV1'], + [/\bvp9\b/i, 'VP9'], + [/\bxvid\b/i, 'XviD'], + [/\bdivx\b/i, 'DivX'], +]; + +const SOURCES: [RegExp, string][] = [ + [/\bblu[-.]?ray|bluray|bd[-.]?rip|bdrip\b/i, 'BluRay'], + [/\bweb[-.]?dl\b/i, 'WEB-DL'], + [/\bweb[-.]?rip\b/i, 'WEBRip'], + [/\bhd[-.]?tv\b/i, 'HDTV'], + [/\bdvd[-.]?rip\b/i, 'DVDRip'], + [/\bcam|ts\b/i, 'CAM'], +]; + +const CONTAINERS: [RegExp, string][] = [ + [/\.mkv\b/i, 'mkv'], + [/\.mp4\b/i, 'mp4'], + [/\.avi\b/i, 'avi'], + [/\.mov\b/i, 'mov'], + [/\.wmv\b/i, 'wmv'], +]; + +/** Parse quality signals from a torrent title string. */ +export function parseQuality(title: string): TorrentQuality { + const q: TorrentQuality = {}; + + for (const [re, label] of RESOLUTIONS) { + if (re.test(title)) { + q.resolution = label; + break; + } + } + for (const [re, label] of CODECS) { + if (re.test(title)) { + q.codec = label; + break; + } + } + for (const [re, label] of SOURCES) { + if (re.test(title)) { + q.source = label; + break; + } + } + for (const [re, label] of CONTAINERS) { + if (re.test(title)) { + q.container = label; + break; + } + } + + return q; +} + +/** Normalise a container string to lower-case without the leading dot. */ +export function normaliseContainer(raw: string): string { + return raw.toLowerCase().replace(/^\./, ''); +} diff --git a/src/core/health/rank.ts b/src/core/health/rank.ts new file mode 100644 index 0000000..f9ee947 --- /dev/null +++ b/src/core/health/rank.ts @@ -0,0 +1,102 @@ +import type { TorrentResult } from '../indexers/types.js'; + +export interface RankOptions { + preferredContainers?: string[]; + preferredQuality?: string[]; + minSeeders?: number; +} + +const CONTAINER_BONUS: Record = { + mkv: 15, // MKV first — embedded subs, fewer subtitle lookups + mp4: 8, + avi: 2, +}; + +const RESOLUTION_BONUS: Record = { + '1080p': 12, + '2160p': 10, // 4K is large — prefer 1080p slightly + '720p': 6, + '480p': 2, +}; + +const SOURCE_BONUS: Record = { + BluRay: 10, + 'WEB-DL': 8, + WEBRip: 6, + HDTV: 4, + DVDRip: 2, +}; + +const CODEC_BONUS: Record = { + x265: 4, + x264: 3, +}; + +/** + * Score a torrent 0–100. Higher is healthier/more preferred. + * + * Weights: + * seeder count (30 pts) — primary health signal + * seeder ratio (15 pts) — liveness: seeders/(seeders+leechers) + * container (15 pts) — MKV beats MP4 beats AVI + * resolution (12 pts) — 1080p > 4K > 720p + * source (10 pts) — BluRay > WEB-DL > … + * codec (4 pts) — x265 > x264 + * preferred Q (+6 bonus if user-preferred quality matches) + * preferred C (+8 bonus if user-preferred container matches) + */ +export function scoreResult(result: TorrentResult, opts: RankOptions = {}): number { + let score = 0; + + // Seeder count (log-scaled, cap at 1000 → 30 pts) + const seeders = Math.max(0, result.seeders); + score += Math.min(30, (Math.log10(seeders + 1) / Math.log10(1001)) * 30); + + // Seeder/leecher ratio (15 pts) + const total = seeders + Math.max(0, result.leechers); + if (total > 0) { + score += (seeders / total) * 15; + } + + const c = (result.quality.container ?? '').toLowerCase(); + score += CONTAINER_BONUS[c] ?? 0; + + const res = result.quality.resolution ?? ''; + score += RESOLUTION_BONUS[res] ?? 0; + + const src = result.quality.source ?? ''; + score += SOURCE_BONUS[src] ?? 0; + + const codec = result.quality.codec ?? ''; + score += CODEC_BONUS[codec] ?? 0; + + // User preference bonuses + const prefContainers = opts.preferredContainers?.map((x) => x.toLowerCase()) ?? []; + if (prefContainers.length > 0 && prefContainers.includes(c)) { + const idx = prefContainers.indexOf(c); + score += 8 - idx * 2; // first preference gets full bonus + } + + const prefQuality = opts.preferredQuality ?? []; + if (prefQuality.length > 0 && prefQuality.includes(res)) { + const idx = prefQuality.indexOf(res); + score += 6 - idx; + } + + return Math.min(100, Math.round(score)); +} + +/** + * Rank an array of results in-place, attaching healthScore and filtering out + * those below minSeeders. + */ +export function rankResults( + results: TorrentResult[], + opts: RankOptions = {}, +): TorrentResult[] { + const min = opts.minSeeders ?? 0; + return results + .filter((r) => r.seeders >= min) + .map((r) => ({ ...r, healthScore: scoreResult(r, opts) })) + .sort((a, b) => (b.healthScore ?? 0) - (a.healthScore ?? 0)); +} diff --git a/src/core/indexers/aggregate.ts b/src/core/indexers/aggregate.ts new file mode 100644 index 0000000..3821d6b --- /dev/null +++ b/src/core/indexers/aggregate.ts @@ -0,0 +1,55 @@ +import pLimit from 'p-limit'; +import type { Indexer, TorrentResult, IndexerSearchOptions } from './types.js'; +import { logger } from '../../util/logger.js'; + +export interface AggregateOptions extends IndexerSearchOptions { + /** Max indexers queried in parallel (default 3). */ + concurrency?: number; +} + +/** + * Fan-out a search across multiple indexers in parallel, then merge and + * de-duplicate results by infoHash (keeping the entry with more seeders). + */ +export async function aggregateSearch( + query: string, + indexers: Indexer[], + opts: AggregateOptions = {}, +): Promise { + const limit = pLimit(opts.concurrency ?? 3); + + const settled = await Promise.allSettled( + indexers.map((idx) => + limit(() => { + logger.debug(`[${idx.id}] searching "${query}"`); + return idx.search(query, { limit: opts.limit, signal: opts.signal }); + }), + ), + ); + + const all: TorrentResult[] = []; + for (let i = 0; i < settled.length; i++) { + const result = settled[i]!; + if (result.status === 'fulfilled') { + logger.debug(`[${indexers[i]!.id}] returned ${result.value.length} results`); + all.push(...result.value); + } else { + logger.warn(`[${indexers[i]!.id}] failed: ${String(result.reason)}`); + } + } + + return dedupe(all); +} + +/** Deduplicate by infoHash — keep the entry with more seeders. */ +function dedupe(results: TorrentResult[]): TorrentResult[] { + const map = new Map(); + for (const r of results) { + const key = r.infoHash ?? r.title.toLowerCase(); + const existing = map.get(key); + if (!existing || r.seeders > existing.seeders) { + map.set(key, r); + } + } + return Array.from(map.values()); +} diff --git a/src/core/indexers/base.ts b/src/core/indexers/base.ts new file mode 100644 index 0000000..576a855 --- /dev/null +++ b/src/core/indexers/base.ts @@ -0,0 +1,48 @@ +import { load as cheerioLoad } from 'cheerio'; +import { httpGetText } from '../../util/http.js'; +import type { FetchOptions } from '../../util/http.js'; +import { parseQuality } from '../health/quality.js'; +import type { TorrentResult, TorrentQuality } from './types.js'; +import { TORRENT_RESULT_SCHEMA_VERSION } from './types.js'; + +export { cheerioLoad, parseQuality }; + +/** Extract infoHash from a magnet link. */ +export function infoHashFromMagnet(magnet: string): string | undefined { + const m = magnet.match(/urn:btih:([a-fA-F0-9]{40}|[a-zA-Z2-7]{32})/i); + return m?.[1]?.toLowerCase(); +} + +/** Extract infoHash from various URL forms (detail URLs, announce lists…). */ +export function infoHashFromUrl(url: string): string | undefined { + const m = url.match( + /(?:info_hash=|\/torrent\/|\/detail\/|torrent\/)([a-fA-F0-9]{40})/i, + ); + return m?.[1]?.toLowerCase(); +} + +/** Build a well-formed TorrentResult from partial data. */ +export function buildResult( + partial: Omit & { + title: string; + rawTitle?: string; + }, + indexer: string, +): TorrentResult { + const { rawTitle, ...rest } = partial; + const q: TorrentQuality = parseQuality(rawTitle ?? partial.title); + return { + ...rest, + schemaVersion: TORRENT_RESULT_SCHEMA_VERSION, + indexer: rest.indexer ?? indexer, + quality: q, + }; +} + +export async function fetchPage( + url: string, + opts?: FetchOptions, +): Promise> { + const html = await httpGetText(url, opts); + return cheerioLoad(html); +} diff --git a/src/core/indexers/registry.ts b/src/core/indexers/registry.ts new file mode 100644 index 0000000..ec72fc8 --- /dev/null +++ b/src/core/indexers/registry.ts @@ -0,0 +1,24 @@ +import type { Indexer } from './types.js'; +import { torrentsCsvIndexer } from './torrents-csv.js'; +import { ytsIndexer } from './yts.js'; +import type { Config } from '../../config/schema.js'; + +const ALL_INDEXERS: Indexer[] = [torrentsCsvIndexer, ytsIndexer]; + +export function getEnabledIndexers(config: Config): Indexer[] { + const { enabled, order } = config.indexers; + const byId = new Map(ALL_INDEXERS.map((i) => [i.id, i])); + // Follow user-configured order, filter to enabled set + return order + .filter((id) => enabled.includes(id)) + .map((id) => byId.get(id)) + .filter((i): i is Indexer => i !== undefined); +} + +export function getIndexerById(id: string): Indexer | undefined { + return ALL_INDEXERS.find((i) => i.id === id); +} + +export function listAllIndexers(): Indexer[] { + return [...ALL_INDEXERS]; +} diff --git a/src/core/indexers/torrents-csv.ts b/src/core/indexers/torrents-csv.ts new file mode 100644 index 0000000..a26508d --- /dev/null +++ b/src/core/indexers/torrents-csv.ts @@ -0,0 +1,53 @@ +import { httpGetJson } from '../../util/http.js'; +import { buildResult } from './base.js'; +import type { Indexer, TorrentResult, IndexerSearchOptions } from './types.js'; + +/** Base URL for the public torrents-csv API. Configurable via env for self-hosted installs. */ +const BASE_URL = process.env.STREAMNET_TORRENTS_CSV_URL ?? 'https://torrents-csv.com'; + +interface TorrentsCsvItem { + info_hash: string; + name: string; + size_bytes: number; + seeders: number; + leechers: number; + completed?: number; + created_unix?: number; +} + +interface TorrentsCsvResponse { + torrents?: TorrentsCsvItem[]; +} + +export const torrentsCsvIndexer: Indexer = { + id: 'torrents-csv', + displayName: 'Torrents CSV', + + async search(query: string, opts?: IndexerSearchOptions): Promise { + const params = new URLSearchParams({ q: query, size: String(opts?.limit ?? 25) }); + const url = `${BASE_URL}/service/search?${params}`; + const data = await httpGetJson(url, { + signal: opts?.signal, + headers: { Accept: 'application/json' }, + }); + + return (data.torrents ?? []).map((item) => { + const magnet = `magnet:?xt=urn:btih:${item.info_hash}&dn=${encodeURIComponent(item.name)}`; + return buildResult( + { + infoHash: item.info_hash.toLowerCase(), + magnet, + title: item.name, + indexer: 'torrents-csv', + seeders: item.seeders ?? 0, + leechers: item.leechers ?? 0, + sizeBytes: item.size_bytes ?? 0, + uploadedAt: item.created_unix + ? new Date(item.created_unix * 1000).toISOString() + : undefined, + }, + 'torrents-csv', + ); + }); + }, +}; diff --git a/src/core/indexers/types.ts b/src/core/indexers/types.ts new file mode 100644 index 0000000..e265290 --- /dev/null +++ b/src/core/indexers/types.ts @@ -0,0 +1,48 @@ +export const TORRENT_RESULT_SCHEMA_VERSION = 1 as const; + +export interface TorrentQuality { + resolution?: string; // e.g. '1080p', '4K' + codec?: string; // e.g. 'x264', 'x265', 'HEVC' + source?: string; // e.g. 'BluRay', 'WEB-DL', 'HDTV' + container?: string; // e.g. 'mkv', 'mp4', 'avi' +} + +export interface TorrentFile { + name: string; + sizeBytes: number; +} + +export interface TorrentResult { + schemaVersion: typeof TORRENT_RESULT_SCHEMA_VERSION; + infoHash?: string; + magnet?: string; + torrentUrl?: string; + title: string; + indexer: string; + seeders: number; + leechers: number; + sizeBytes: number; + quality: TorrentQuality; + /** Composite 0–100 score computed by health/rank.ts — absent until ranking pass. */ + healthScore?: number; + uploadedAt?: string; + files?: TorrentFile[]; + /** Original detail page URL for scrapers that need a second request. */ + detailUrl?: string; +} + +/** + * An Indexer adapter fetches results for a query. Each adapter is responsible + * for parsing its source into TorrentResults, including infoHash extraction and + * quality parsing from the title (delegating to core/health/quality.ts). + */ +export interface Indexer { + id: string; + displayName: string; + search(query: string, opts?: IndexerSearchOptions): Promise; +} + +export interface IndexerSearchOptions { + limit?: number; + signal?: AbortSignal; +} diff --git a/src/core/indexers/yts.ts b/src/core/indexers/yts.ts new file mode 100644 index 0000000..8080419 --- /dev/null +++ b/src/core/indexers/yts.ts @@ -0,0 +1,67 @@ +import { httpGetJson } from '../../util/http.js'; +import { buildResult } from './base.js'; +import type { Indexer, TorrentResult, IndexerSearchOptions } from './types.js'; + +const BASE_URL = process.env.STREAMNET_YTS_URL ?? 'https://yts.mx/api/v2'; + +interface YtsTorrent { + hash: string; + quality: string; + type: string; + seeds: number; + peers: number; + size_bytes: number; +} + +interface YtsMovie { + title: string; + year: number; + torrents?: YtsTorrent[]; +} + +interface YtsData { + movies?: YtsMovie[]; +} + +interface YtsResponse { + data?: YtsData; +} + +export const ytsIndexer: Indexer = { + id: 'yts', + displayName: 'YTS', + + async search(query: string, opts?: IndexerSearchOptions): Promise { + const params = new URLSearchParams({ + query_term: query, + limit: String(opts?.limit ?? 20), + sort_by: 'seeds', + order_by: 'desc', + }); + const url = `${BASE_URL}/list_movies.json?${params}`; + const data = await httpGetJson(url, { signal: opts?.signal }); + + const results: TorrentResult[] = []; + for (const movie of data.data?.movies ?? []) { + for (const t of movie.torrents ?? []) { + const title = `${movie.title} (${movie.year}) [${t.quality}] [${t.type}]`; + const magnet = `magnet:?xt=urn:btih:${t.hash}&dn=${encodeURIComponent(title)}`; + results.push( + buildResult( + { + infoHash: t.hash.toLowerCase(), + magnet, + title, + indexer: 'yts', + seeders: t.seeds ?? 0, + leechers: t.peers ?? 0, + sizeBytes: t.size_bytes ?? 0, + }, + 'yts', + ), + ); + } + } + return results; + }, +}; diff --git a/src/core/player/detect.ts b/src/core/player/detect.ts new file mode 100644 index 0000000..3e678c6 --- /dev/null +++ b/src/core/player/detect.ts @@ -0,0 +1,109 @@ +import { execSync } from 'node:child_process'; +import { existsSync } from 'node:fs'; +import { ExitCode, fail } from '../../agent/exit.js'; + +const FLATPAK_PREFIXES = [ + '/var/lib/flatpak', + '/run/flatpak', + `${process.env.HOME ?? ''}/.local/share/flatpak`, +]; +const SNAP_PREFIX = '/snap/'; + +/** Known native VLC binary locations per platform. */ +const VLC_CANDIDATES: Record = { + linux: ['/usr/bin/vlc', '/usr/local/bin/vlc', '/bin/vlc'], + darwin: [ + '/Applications/VLC.app/Contents/MacOS/VLC', + '/usr/local/bin/vlc', + '/opt/homebrew/bin/vlc', + ], + win32: [ + 'C:\\Program Files\\VideoLAN\\VLC\\vlc.exe', + 'C:\\Program Files (x86)\\VideoLAN\\VLC\\vlc.exe', + ], +}; + +function isSandboxed(resolved: string): boolean { + for (const prefix of FLATPAK_PREFIXES) { + if (resolved.startsWith(prefix)) return true; + } + return resolved.startsWith(SNAP_PREFIX); +} + +function resolveSymlink(p: string): string { + try { + return execSync(`readlink -f "${p}"`, { stdio: ['pipe', 'pipe', 'pipe'] }) + .toString() + .trim(); + } catch { + return p; + } +} + +/** + * Locate the native VLC binary for the current OS. + * + * Explicitly rejects Flatpak and Snap paths because their sandboxing prevents + * the spawn+IPC integration that is the core purpose of this tool. + * + * Returns the absolute path or throws DEP_MISSING. + */ +export function detectVlc(): string { + const platform = process.platform as string; + const candidates = VLC_CANDIDATES[platform] ?? VLC_CANDIDATES.linux!; + + // Also try PATH (for distros that place VLC elsewhere) + try { + const which = + platform === 'win32' + ? execSync('where vlc 2>nul', { stdio: ['pipe', 'pipe', 'pipe'] }) + .toString() + .split('\r\n')[0]! + .trim() + : execSync('which vlc 2>/dev/null', { stdio: ['pipe', 'pipe', 'pipe'] }) + .toString() + .trim(); + if (which) candidates.unshift(which); + } catch { + // which/where not available or VLC not on PATH — fall through to candidates + } + + for (const p of candidates) { + if (!existsSync(p)) continue; + const resolved = resolveSymlink(p); + if (isSandboxed(resolved)) { + // Found a Flatpak/Snap VLC — record but keep searching for a native install + continue; + } + return p; + } + + // Check if the only VLC found was sandboxed + for (const p of candidates) { + if (existsSync(p)) { + const resolved = resolveSymlink(p); + if (isSandboxed(resolved)) { + fail( + ExitCode.DEP_MISSING, + 'Only a Flatpak or Snap VLC was found. StreamNet requires native VLC.', + 'Run `streamnet setup` to install native VLC, then re-run.', + ); + } + } + } + + fail( + ExitCode.DEP_MISSING, + 'VLC was not found. StreamNet requires native VLC (not Flatpak/Snap).', + 'Run `streamnet setup` to install native VLC, then re-run.', + ); +} + +/** Check if VLC is available without throwing; returns path or null. */ +export function findVlc(): string | null { + try { + return detectVlc(); + } catch { + return null; + } +} diff --git a/src/core/player/ipc.ts b/src/core/player/ipc.ts new file mode 100644 index 0000000..2488c05 --- /dev/null +++ b/src/core/player/ipc.ts @@ -0,0 +1,51 @@ +/** + * VLC HTTP/Lua interface — used to inject subtitles or query status after + * VLC has already started. This is optional; the primary path is --sub-file + * at spawn time. IPC is only used when the subtitle file arrives after VLC + * has opened (e.g. the hash-download raced the torrent metadata fetch). + */ + +import { logger } from '../../util/logger.js'; + +export interface VlcIpcOptions { + host?: string; + port: number; + password: string; +} + +async function vlcRequest(opts: VlcIpcOptions, path: string): Promise { + const host = opts.host ?? '127.0.0.1'; + const credentials = Buffer.from(`:${opts.password}`).toString('base64'); + const url = `http://${host}:${opts.port}/requests/${path}`; + const res = await fetch(url, { + headers: { Authorization: `Basic ${credentials}` }, + }); + if (!res.ok) throw new Error(`VLC IPC HTTP ${res.status}`); + return res.text(); +} + +/** Add a subtitle track to a running VLC instance. */ +export async function loadSubtitlesIpc( + opts: VlcIpcOptions, + subFilePath: string, +): Promise { + try { + await vlcRequest( + opts, + `command.xml?command=sub-track&val=${encodeURIComponent(subFilePath)}`, + ); + logger.info(`Loaded subtitles via VLC IPC: ${subFilePath}`); + } catch (err) { + logger.warn(`VLC IPC subtitle load failed: ${String(err)}`); + } +} + +/** Returns true if the VLC HTTP interface responds. */ +export async function isVlcIpcReady(opts: VlcIpcOptions): Promise { + try { + await vlcRequest(opts, 'status.json'); + return true; + } catch { + return false; + } +} diff --git a/src/core/player/vlc.ts b/src/core/player/vlc.ts new file mode 100644 index 0000000..301d322 --- /dev/null +++ b/src/core/player/vlc.ts @@ -0,0 +1,91 @@ +import { execa, type ResultPromise } from 'execa'; +import { detectVlc } from './detect.js'; +import { ExitCode, fail } from '../../agent/exit.js'; +import { logger } from '../../util/logger.js'; + +export interface VlcSpawnOptions { + streamUrl: string; + subFile?: string; + /** If provided, VLC's HTTP interface is enabled on this port for IPC. */ + httpPort?: number; + httpPassword?: string; + title?: string; + extraArgs?: string[]; +} + +export interface VlcProcess { + proc: ResultPromise; + vlcPath: string; +} + +/** + * Spawn native VLC with a streaming URL. + * + * VLC is started detached so that the CLI can exit / keep the Node process + * alive to seed while VLC plays. stdout/stderr from VLC are piped so that + * unexpected crashes surface in the log. + */ +export function spawnVlc(opts: VlcSpawnOptions): VlcProcess { + const vlcPath = detectVlc(); + + const args: string[] = [opts.streamUrl]; + + if (opts.subFile) { + args.push('--sub-file', opts.subFile); + } + + if (opts.httpPort) { + args.push( + '--intf', + 'http', + '--http-host', + '127.0.0.1', + '--http-port', + String(opts.httpPort), + '--http-password', + opts.httpPassword ?? 'streamnet', + ); + } + + if (opts.title) { + args.push('--meta-title', opts.title); + } + + if (opts.extraArgs?.length) { + args.push(...opts.extraArgs); + } + + logger.info(`Spawning VLC: ${vlcPath} ${args.join(' ')}`); + + let proc: ResultPromise; + try { + proc = execa(vlcPath, args, { + detached: false, + stdio: ['ignore', 'pipe', 'pipe'], + }); + } catch (err) { + fail(ExitCode.PLAYER_FAILED, `Failed to spawn VLC: ${String(err)}`); + } + + proc.on('error', (err: Error) => { + logger.error(`VLC process error: ${err.message}`); + }); + + return { proc, vlcPath }; +} + +/** Wait for VLC to exit, resolve with exit code. */ +export async function waitForVlc(vlcProc: VlcProcess): Promise { + try { + await vlcProc.proc; + return 0; + } catch (err: unknown) { + const exitCode = (err as { exitCode?: number }).exitCode ?? 1; + logger.warn(`VLC exited with code ${exitCode}`); + if (exitCode !== 0 && exitCode !== 1) { + // code 1 is normal VLC quit; others may indicate a problem + fail(ExitCode.PLAYER_FAILED, `VLC exited with non-zero code: ${exitCode}`); + } + return exitCode; + } +} diff --git a/src/core/setup/checks.ts b/src/core/setup/checks.ts new file mode 100644 index 0000000..e152ee1 --- /dev/null +++ b/src/core/setup/checks.ts @@ -0,0 +1,80 @@ +import { findVlc } from '../player/detect.js'; +import { httpGetText } from '../../util/http.js'; +import { logger } from '../../util/logger.js'; + +export interface CheckResult { + name: string; + ok: boolean; + message: string; + hint?: string; +} + +async function checkVlc(): Promise { + const path = findVlc(); + if (path) { + return { name: 'vlc', ok: true, message: `Native VLC found at ${path}` }; + } + return { + name: 'vlc', + ok: false, + message: 'Native VLC not found (Flatpak/Snap not accepted)', + hint: 'Run `streamnet setup` to install native VLC.', + }; +} + +async function checkNode(): Promise { + const ver = process.version; + const major = parseInt(ver.slice(1).split('.')[0] ?? '0', 10); + if (major >= 20) { + return { name: 'node', ok: true, message: `Node.js ${ver}` }; + } + return { + name: 'node', + ok: false, + message: `Node.js ${ver} — requires >=20`, + hint: 'Upgrade Node.js: https://nodejs.org', + }; +} + +async function checkNetwork(): Promise { + try { + await httpGetText('https://torrents-csv.com', { timeoutMs: 5000 }); + return { name: 'network', ok: true, message: 'Network reachable (torrents-csv)' }; + } catch { + return { + name: 'network', + ok: false, + message: 'Network check failed (torrents-csv unreachable)', + hint: 'Check your internet connection or proxy settings.', + }; + } +} + +async function checkWebtorrent(): Promise { + try { + await import('webtorrent'); + return { name: 'webtorrent', ok: true, message: 'webtorrent module available' }; + } catch { + return { + name: 'webtorrent', + ok: false, + message: 'webtorrent optional dep not installed', + hint: 'Run: npm install -g webtorrent (or reinstall streamnet-cli)', + }; + } +} + +export async function runAllChecks(): Promise { + const results = await Promise.allSettled([ + checkNode(), + checkVlc(), + checkWebtorrent(), + checkNetwork(), + ]); + + return results.map((r) => { + if (r.status === 'fulfilled') return r.value; + logger.error(`Check threw: ${String(r.reason)}`); + return { name: 'unknown', ok: false, message: String(r.reason) }; + }); +} diff --git a/src/core/setup/install.ts b/src/core/setup/install.ts new file mode 100644 index 0000000..9882580 --- /dev/null +++ b/src/core/setup/install.ts @@ -0,0 +1,102 @@ +import { execa } from 'execa'; +import { detectOs } from './os.js'; +import { ExitCode, fail } from '../../agent/exit.js'; +import { logger } from '../../util/logger.js'; + +/** + * Install native VLC for the current OS using the appropriate package manager. + * + * Explicitly avoids Flatpak/Snap so that spawn+IPC works correctly. + */ +export async function installVlc(yes: boolean): Promise { + const os = detectOs(); + logger.info(`Installing VLC on ${os.platform}/${os.distro}`); + + switch (os.platform) { + case 'linux': + await installVlcLinux(os.distro, yes); + break; + case 'darwin': + await installVlcMacos(yes); + break; + case 'win32': + await installVlcWindows(yes); + break; + default: + fail( + ExitCode.DEP_MISSING, + `Unsupported platform: ${os.platform}. Install VLC manually.`, + ); + } +} + +async function installVlcLinux(distro: string, _yes: boolean): Promise { + switch (distro) { + case 'ubuntu': + case 'debian': { + // Ensure we're NOT installing the Snap version — pin to apt + await run('sudo', ['apt-get', 'update', '-qq']); + await run('sudo', ['apt-get', 'install', '-y', 'vlc', '--no-install-recommends']); + // Prevent automatic Snap substitution (Ubuntu 24.04+) + await runOptional('sudo', ['apt-mark', 'hold', 'vlc']); + break; + } + case 'fedora': + await run('sudo', ['dnf', 'install', '-y', 'vlc']); + break; + case 'arch': + await run('sudo', ['pacman', '-Sy', '--noconfirm', 'vlc']); + break; + case 'opensuse': + await run('sudo', ['zypper', 'install', '-y', 'vlc']); + break; + default: + fail( + ExitCode.DEP_MISSING, + 'Could not detect Linux distro. Install VLC manually via your package manager.', + 'Then re-run `streamnet setup`.', + ); + } +} + +async function installVlcMacos(_yes: boolean): Promise { + // Prefer Homebrew cask for the full macOS app + await run('brew', ['install', '--cask', 'vlc']); +} + +async function installVlcWindows(_yes: boolean): Promise { + // winget is available on Windows 10 1709+ / Windows 11 + try { + await run('winget', [ + 'install', + '--id', + 'VideoLAN.VLC', + '--accept-package-agreements', + '--accept-source-agreements', + ]); + } catch { + // Fallback: choco + await run('choco', ['install', 'vlc', '-y']); + } +} + +async function run(cmd: string, args: string[]): Promise { + logger.info(`> ${cmd} ${args.join(' ')}`); + try { + await execa(cmd, args, { stdio: 'inherit' }); + } catch (err) { + fail( + ExitCode.DEP_MISSING, + `Install command failed: ${cmd} ${args.join(' ')}`, + String(err), + ); + } +} + +async function runOptional(cmd: string, args: string[]): Promise { + try { + await execa(cmd, args, { stdio: 'pipe' }); + } catch { + // best-effort — non-fatal + } +} diff --git a/src/core/setup/os.ts b/src/core/setup/os.ts new file mode 100644 index 0000000..8442b59 --- /dev/null +++ b/src/core/setup/os.ts @@ -0,0 +1,42 @@ +import { readFileSync, existsSync } from 'node:fs'; + +export type Platform = 'linux' | 'darwin' | 'win32' | 'unknown'; +export type Distro = 'ubuntu' | 'debian' | 'fedora' | 'arch' | 'opensuse' | 'unknown'; + +export interface OsInfo { + platform: Platform; + arch: string; + distro: Distro; + /** e.g. '22.04' on Ubuntu */ + distroVersion?: string; + isWsl: boolean; +} + +export function detectOs(): OsInfo { + const platform = (process.platform as Platform) ?? 'unknown'; + const arch = process.arch; + const isWsl = Boolean( + process.env.WSL_DISTRO_NAME || + (existsSync('/proc/version') && + readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft')), + ); + + let distro: Distro = 'unknown'; + let distroVersion: string | undefined; + + if (platform === 'linux' && existsSync('/etc/os-release')) { + const text = readFileSync('/etc/os-release', 'utf8'); + const id = (text.match(/^ID=(.+)$/m)?.[1] ?? '').toLowerCase().replace(/"/g, ''); + const ver = text.match(/^VERSION_ID="?([^"\n]+)"?/m)?.[1] ?? ''; + + if (['ubuntu', 'pop'].includes(id) || id.includes('ubuntu')) distro = 'ubuntu'; + else if (id === 'debian' || id === 'linuxmint') distro = 'debian'; + else if (['fedora', 'rhel', 'centos'].includes(id)) distro = 'fedora'; + else if (id === 'arch' || id === 'manjaro') distro = 'arch'; + else if (id.includes('suse')) distro = 'opensuse'; + + distroVersion = ver || undefined; + } + + return { platform, arch, distro, distroVersion, isWsl }; +} diff --git a/src/core/torrent/engine.ts b/src/core/torrent/engine.ts new file mode 100644 index 0000000..506e5de --- /dev/null +++ b/src/core/torrent/engine.ts @@ -0,0 +1,156 @@ +import { ExitCode, fail } from '../../agent/exit.js'; +import { logger } from '../../util/logger.js'; +import { selectVideoFile } from './select.js'; + +export interface TorrentStreamInfo { + /** HTTP URL suitable for passing directly to VLC. */ + streamUrl: string; + /** Filename of the selected file. */ + fileName: string; + /** File index within the torrent. */ + fileIndex: number; + /** Total size of the selected file in bytes. */ + sizeBytes: number; + destroy: () => void; +} + +export interface StreamOptions { + /** magnet link or .torrent URL */ + source: string; + /** Preferred file index (0-based). If omitted, selectVideoFile is used. */ + fileIndex?: number; + /** Port for the local HTTP stream server; 0 = ephemeral. */ + port?: number; + preferredContainers?: string[]; + /** Called with download progress 0..1 */ + onProgress?: (info: { progress: number; peers: number; downloadSpeed: number }) => void; + signal?: AbortSignal; + /** How long to wait for torrent metadata before giving up (ms). Default 30 s. */ + metadataTimeoutMs?: number; +} + +/** + * Start a WebTorrent stream and expose the selected file via a local HTTP server. + * + * WebTorrent is a heavy optional dependency — it is dynamically imported here so + * commands that don't need streaming (search, config, doctor) don't pay the load + * cost and so the module can be absent in test environments. + */ +export async function startStream(opts: StreamOptions): Promise { + let WebTorrentCtor: new () => WebTorrentInstance; + try { + const mod = (await import('webtorrent')) as { default: new () => WebTorrentInstance }; + WebTorrentCtor = mod.default; + } catch { + fail( + ExitCode.DEP_MISSING, + 'webtorrent is not installed. Run `npm install webtorrent` or reinstall streamnet.', + ); + } + + return new Promise((resolve, reject) => { + const client = new WebTorrentCtor(); + const metaTimeout = opts.metadataTimeoutMs ?? 30_000; + + const timer = setTimeout(() => { + client.destroy(); + const e = new StreamNetTimeoutError(); + reject(e); + }, metaTimeout); + + if (opts.signal) { + opts.signal.addEventListener( + 'abort', + () => { + clearTimeout(timer); + client.destroy(); + reject(new Error('Aborted')); + }, + { once: true }, + ); + } + + client.add(opts.source, (torrent: TorrentHandle) => { + clearTimeout(timer); + logger.info(`Torrent ready: ${torrent.name} (${torrent.files.length} files)`); + + const fileList = torrent.files.map((f: TorrentFileHandle) => ({ + name: f.name, + sizeBytes: f.length, + })); + + const fileIndex = + opts.fileIndex ?? selectVideoFile(fileList, opts.preferredContainers); + + const file = torrent.files[fileIndex]; + if (!file) { + client.destroy(); + reject(new Error(`File index ${fileIndex} not found in torrent`)); + return; + } + + logger.info(`Streaming [${fileIndex}]: ${file.name} (${file.length} bytes)`); + + torrent.on('download', () => { + opts.onProgress?.({ + progress: torrent.progress, + peers: torrent.numPeers, + downloadSpeed: torrent.downloadSpeed, + }); + }); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const server = (torrent as any).createServer() as import('node:http').Server; + const listenPort = opts.port ?? 0; + + server.listen(listenPort, '127.0.0.1', () => { + const addr = server.address() as { port: number }; + const streamUrl = `http://127.0.0.1:${addr.port}/${fileIndex}/${encodeURIComponent(file.name)}`; + logger.info(`Stream server: ${streamUrl}`); + + resolve({ + streamUrl, + fileName: file.name, + fileIndex, + sizeBytes: file.length, + destroy: () => { + server.close(); + client.destroy(); + }, + }); + }); + }); + + client.on('error', (err: Error) => { + clearTimeout(timer); + reject(err); + }); + }); +} + +// Minimal type stubs (avoids @types/webtorrent dependency) +interface TorrentFileHandle { + name: string; + length: number; +} +interface TorrentHandle { + name: string; + files: TorrentFileHandle[]; + progress: number; + numPeers: number; + downloadSpeed: number; + on(event: string, cb: () => void): void; +} +interface WebTorrentInstance { + add(src: string, cb: (t: TorrentHandle) => void): void; + on(event: string, cb: (e: Error) => void): void; + destroy(): void; +} + +class StreamNetTimeoutError extends Error { + code = ExitCode.TORRENT_UNPLAYABLE; + constructor() { + super('Torrent metadata timed out — no peers responded.'); + this.name = 'StreamNetError'; + } +} diff --git a/src/core/torrent/select.ts b/src/core/torrent/select.ts new file mode 100644 index 0000000..997717a --- /dev/null +++ b/src/core/torrent/select.ts @@ -0,0 +1,62 @@ +import type { TorrentFile } from '../indexers/types.js'; +import { normaliseContainer } from '../health/quality.js'; + +const VIDEO_EXTS = new Set([ + 'mkv', + 'mp4', + 'avi', + 'mov', + 'wmv', + 'flv', + 'webm', + 'm4v', + 'ts', + 'mpg', + 'mpeg', +]); + +function isVideoFile(name: string): boolean { + const ext = name.split('.').pop()?.toLowerCase() ?? ''; + return VIDEO_EXTS.has(ext); +} + +function getExt(name: string): string { + return name.split('.').pop()?.toLowerCase() ?? ''; +} + +/** + * Select the best video file from a torrent's file list. + * + * Priority: + * 1. MKV files (usually have embedded subtitles) + * 2. Largest video file (generally the main feature, not a sample) + * + * Returns the 0-based index into the files array. + */ +export function selectVideoFile( + files: TorrentFile[], + preferredContainers: string[] = ['mkv', 'mp4'], +): number { + const videoFiles = files + .map((f, idx) => ({ ...f, idx })) + .filter((f) => isVideoFile(f.name)); + + if (videoFiles.length === 0) return 0; + + // Apply container preference in order + for (const container of preferredContainers.map(normaliseContainer)) { + const matching = videoFiles.filter((f) => getExt(f.name) === container); + if (matching.length > 0) { + // Among matching container type, pick the largest + return matching.sort((a, b) => b.sizeBytes - a.sizeBytes)[0]!.idx; + } + } + + // Fallback: largest video file regardless of container + return videoFiles.sort((a, b) => b.sizeBytes - a.sizeBytes)[0]!.idx; +} + +/** True if the selected file is an MKV (embedded subs likely — skip subtitle search). */ +export function isMkv(fileName: string): boolean { + return getExt(fileName) === 'mkv'; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..2e05333 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,24 @@ +// Public programmatic API — for agents that want to import streamnet as a library. +export { searchHandler } from './commands/search.js'; +export { streamHandler } from './commands/stream.js'; +export { playHandler } from './commands/play.js'; +export { doctorHandler } from './commands/doctor.js'; +export { setupHandler } from './commands/setup.js'; +export { configHandler } from './commands/config.js'; +export { manifestHandler, buildManifest } from './commands/manifest.js'; + +export { rankResults, scoreResult } from './core/health/rank.js'; +export { parseQuality } from './core/health/quality.js'; +export { aggregateSearch } from './core/indexers/aggregate.js'; +export { getEnabledIndexers, listAllIndexers } from './core/indexers/registry.js'; + +export { OutputContext } from './agent/output.js'; +export { ExitCode, StreamNetError, fail } from './agent/exit.js'; +export { successEnvelope, errorEnvelope } from './agent/envelope.js'; +export { loadConfig, saveConfig } from './config/store.js'; +export { resolvePaths } from './config/paths.js'; + +export type { TorrentResult, Indexer } from './core/indexers/types.js'; +export type { Config } from './config/schema.js'; +export type { Envelope } from './agent/envelope.js'; +export type { CommandSpec, CommandContext } from './registry/types.js'; diff --git a/src/registry/build.ts b/src/registry/build.ts new file mode 100644 index 0000000..743d0d5 --- /dev/null +++ b/src/registry/build.ts @@ -0,0 +1,135 @@ +import { Command } from 'commander'; +import type { CommandSpec, CommandContext } from './types.js'; +import { ExitCode, StreamNetError, fail } from '../agent/exit.js'; +import { errorEnvelope } from '../agent/envelope.js'; + +/** + * Wire a CommandSpec onto a Commander Command, binding: + * - positional args + * - flags (long/short, default, env) + * - handler that calls spec.handler and routes stdout/stderr through OutputContext + */ +export function buildCommand(spec: CommandSpec, ctx: () => CommandContext): Command { + const cmd = new Command(spec.id).description(spec.summary); + + for (const arg of spec.args ?? []) { + const syntax = arg.variadic + ? arg.required + ? `<${arg.name}...>` + : `[${arg.name}...]` + : arg.required + ? `<${arg.name}>` + : `[${arg.name}]`; + cmd.argument(syntax, arg.description); + } + + for (const flag of spec.flags ?? []) { + const short = flag.short ? `-${flag.short}, ` : ''; + const isBoolean = + flag.schema._def?.typeName === 'ZodBoolean' || + flag.schema._def?.typeName === 'ZodOptional'; + const syntax = isBoolean + ? `${short}--${flag.long}` + : `${short}--${flag.long} `; + cmd.option(syntax, flag.description, flag.default as string | undefined); + if (flag.env) { + // Commander doesn't natively bind env; we handle it in the action below. + } + } + + if (spec.examples?.length) { + cmd.addHelpText( + 'after', + '\nExamples:\n' + spec.examples.map((e) => ` ${e}`).join('\n'), + ); + } + + cmd.action(async (...actionArgs: unknown[]) => { + const context = ctx(); + const start = Date.now(); + + // Build input: positional args + resolved flags (env overrides first). + const positionals = actionArgs.slice(0, (spec.args ?? []).length) as string[]; + const rawOpts = actionArgs[actionArgs.length - 2] as Record; + const input = buildInput(spec, positionals, rawOpts); + + try { + const data = await spec.handler(context, input); + context.output.emit(spec.id, context.version, data, (d) => { + if (spec.render) { + spec.render(d, context.output); + } else { + // Human-mode default: pretty-print the data as JSON. + process.stdout.write(JSON.stringify(d, null, 2) + '\n'); + } + }); + const exitCode = spec.exitCodeFor ? spec.exitCodeFor(data) : ExitCode.OK; + process.exit(exitCode); + } catch (err) { + const durationMs = Date.now() - start; + const sne = + err instanceof StreamNetError + ? err + : new StreamNetError(ExitCode.ERROR, String(err)); + + if (context.output.mode === 'json') { + process.stdout.write( + JSON.stringify( + errorEnvelope( + spec.id, + context.version, + { code: sne.code, message: sne.message, hint: sne.hint }, + durationMs, + ), + ) + '\n', + ); + } else { + context.output.emitError(spec.id, context.version, sne); + } + process.exit(sne.code); + } + }); + + return cmd; +} + +function buildInput( + spec: CommandSpec, + positionals: string[], + opts: Record, +): Record { + const input: Record = {}; + + // Positional args + for (let i = 0; i < (spec.args ?? []).length; i++) { + const arg = spec.args![i]!; + input[camel(arg.name)] = positionals[i]; + } + + // Flags — env var wins over CLI flag wins over default + for (const flag of spec.flags ?? []) { + const envVal = flag.env ? process.env[flag.env] : undefined; + const cliVal = opts[camel(flag.long)]; + if (envVal !== undefined) { + input[camel(flag.long)] = coerceEnv(envVal); + } else if (cliVal !== undefined) { + input[camel(flag.long)] = cliVal; + } else if (flag.default !== undefined) { + input[camel(flag.long)] = flag.default; + } + } + + return input; +} + +function coerceEnv(val: string): unknown { + if (val === '1' || val === 'true') return true; + if (val === '0' || val === 'false') return false; + return val; +} + +function camel(s: string): string { + return s.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase()); +} + +export { fail }; diff --git a/src/registry/index.ts b/src/registry/index.ts new file mode 100644 index 0000000..a2df787 --- /dev/null +++ b/src/registry/index.ts @@ -0,0 +1,260 @@ +import { z } from 'zod'; +import { ExitCode } from '../agent/exit.js'; +import type { CommandSpec } from './types.js'; +import { searchHandler, renderSearch } from '../commands/search.js'; +import { streamHandler, renderStream } from '../commands/stream.js'; +import { playHandler, renderPlay } from '../commands/play.js'; +import { setupHandler } from '../commands/setup.js'; +import { doctorHandler, renderDoctor, type DoctorResult } from '../commands/doctor.js'; +import { configHandler, renderConfig } from '../commands/config.js'; + +const COMMON_EXIT_CODES = [ + { code: ExitCode.OK, meaning: 'Success' }, + { code: ExitCode.ERROR, meaning: 'Unexpected error' }, + { code: ExitCode.USAGE, meaning: 'Invalid arguments or flags' }, + { + code: ExitCode.EX_NOINPUT, + meaning: 'Prompt required but non-interactive; pass the flag', + }, +]; + +export const COMMAND_SPECS: CommandSpec[] = [ + { + id: 'search', + summary: 'Search indexers and show health-ranked torrent results.', + args: [{ name: 'query', description: 'Title to search for', required: true }], + flags: [ + { + long: 'limit', + description: 'Max results per indexer', + schema: z.number(), + default: 25, + }, + { + long: 'indexer', + description: 'Restrict to a single indexer ID', + schema: z.string().optional(), + }, + { + long: 'min-seeders', + description: 'Minimum seeder count', + schema: z.number(), + default: 3, + env: 'STREAMNET_MIN_SEEDERS', + }, + { + long: 'quality', + description: 'Filter by quality label (e.g. 1080p)', + schema: z.string().optional(), + }, + { + long: 'container', + description: 'Filter by container (e.g. mkv)', + schema: z.string().optional(), + }, + ], + exitCodes: [ + ...COMMON_EXIT_CODES, + { code: ExitCode.NO_RESULTS, meaning: 'Search returned no results' }, + { code: ExitCode.NETWORK, meaning: 'All indexers failed' }, + ], + examples: [ + 'streamnet search "Blade Runner 2049"', + 'streamnet search "Blade Runner 2049" --json', + 'streamnet search "Blade Runner 2049" --container mkv --min-seeders 10', + ], + handler: searchHandler as unknown as CommandSpec['handler'], + render: renderSearch as unknown as CommandSpec['render'], + }, + + { + id: 'stream', + summary: 'Stream a torrent (magnet/URL/infohash) to native VLC.', + args: [ + { + name: 'source', + description: 'Magnet link, .torrent URL, or infohash. Use - to read from stdin.', + required: true, + }, + ], + flags: [ + { + long: 'file-index', + description: 'Force a specific file index within the torrent', + schema: z.number().optional(), + }, + { + long: 'port', + description: 'Local stream server port (0=ephemeral)', + schema: z.number(), + default: 0, + }, + { + long: 'no-subs', + description: 'Skip subtitle search/load', + schema: z.boolean(), + default: false, + }, + { + long: 'sub-lang', + description: 'Subtitle language code (e.g. en, es)', + schema: z.string().optional(), + }, + ], + exitCodes: [ + ...COMMON_EXIT_CODES, + { code: ExitCode.DEP_MISSING, meaning: 'VLC or webtorrent not found' }, + { code: ExitCode.TORRENT_UNPLAYABLE, meaning: 'No peers / metadata timeout' }, + { code: ExitCode.PLAYER_FAILED, meaning: 'VLC failed to launch or crashed' }, + ], + examples: [ + 'streamnet stream "magnet:?xt=urn:btih:..."', + 'echo "$MAGNET" | streamnet stream -', + 'streamnet stream "magnet:?xt=urn:btih:..." --no-subs --json', + ], + handler: streamHandler as unknown as CommandSpec['handler'], + render: renderStream as unknown as CommandSpec['render'], + }, + + { + id: 'play', + summary: 'One-shot: search → select best torrent → stream to VLC.', + description: + 'Combines `search` and `stream`. With --yes (or non-interactive / agent mode) ' + + 'the top-ranked result is selected automatically.', + args: [{ name: 'query', description: 'Title to search and play', required: true }], + flags: [ + { + long: 'yes', + short: 'y', + description: 'Auto-select top result without prompting', + schema: z.boolean(), + default: false, + env: 'STREAMNET_YES', + }, + { + long: 'quality', + description: 'Preferred quality label (e.g. 1080p)', + schema: z.string().optional(), + }, + { + long: 'container', + description: 'Preferred container (e.g. mkv)', + schema: z.string().optional(), + }, + { + long: 'min-seeders', + description: 'Minimum seeder count', + schema: z.number(), + default: 3, + }, + { + long: 'no-subs', + description: 'Skip subtitle search/load', + schema: z.boolean(), + default: false, + }, + ], + exitCodes: [ + ...COMMON_EXIT_CODES, + { code: ExitCode.NO_RESULTS, meaning: 'No results for the query' }, + { code: ExitCode.DEP_MISSING, meaning: 'VLC not found' }, + { code: ExitCode.TORRENT_UNPLAYABLE, meaning: 'No peers / metadata timeout' }, + { code: ExitCode.PLAYER_FAILED, meaning: 'VLC failed' }, + ], + examples: [ + 'streamnet play "Blade Runner 2049"', + 'streamnet play "Blade Runner 2049" --yes', + 'streamnet play "Blade Runner 2049" --yes --json', + ], + handler: playHandler as unknown as CommandSpec['handler'], + render: renderPlay as unknown as CommandSpec['render'], + }, + + { + id: 'setup', + summary: 'Install native VLC for the current OS (no Flatpak/Snap).', + flags: [ + { + long: 'yes', + short: 'y', + description: 'Skip confirmation prompt', + schema: z.boolean(), + default: false, + env: 'STREAMNET_YES', + }, + { + long: 'check', + description: 'Check only, do not install', + schema: z.boolean(), + default: false, + }, + ], + exitCodes: [ + ...COMMON_EXIT_CODES, + { + code: ExitCode.DEP_MISSING, + meaning: 'Install command failed or unsupported platform', + }, + ], + examples: [ + 'streamnet setup', + 'streamnet setup --yes', + 'streamnet setup --check --json', + ], + handler: setupHandler as unknown as CommandSpec['handler'], + }, + + { + id: 'doctor', + summary: 'Verify all dependencies (VLC, webtorrent, Node, network).', + exitCodes: [ + { code: ExitCode.OK, meaning: 'All checks passed' }, + { code: ExitCode.ERROR, meaning: 'One or more checks failed' }, + ], + examples: ['streamnet doctor', 'streamnet doctor --json'], + handler: doctorHandler as unknown as CommandSpec['handler'], + render: renderDoctor as unknown as CommandSpec['render'], + exitCodeFor: ((data: DoctorResult) => + data.allOk ? ExitCode.OK : ExitCode.ERROR) as unknown as CommandSpec['exitCodeFor'], + }, + + { + id: 'config', + summary: 'Read or write configuration (get|set|list|path).', + args: [ + { name: 'subcommand', description: 'get | set | list | path', required: true }, + { + name: 'key', + description: 'Dotted config key (e.g. opensubtitles.apiKey)', + required: false, + }, + { name: 'value', description: 'Value to set', required: false }, + ], + exitCodes: [ + ...COMMON_EXIT_CODES, + { code: ExitCode.CONFIG, meaning: 'Invalid key or unwritable config file' }, + ], + examples: [ + 'streamnet config list', + 'streamnet config get minSeeders', + 'streamnet config set minSeeders 5', + 'streamnet config path', + ], + handler: configHandler as unknown as CommandSpec['handler'], + render: renderConfig as unknown as CommandSpec['render'], + }, + + { + id: 'manifest', + summary: 'Emit the machine-readable command manifest (agent discovery).', + description: + 'Prints the full catalog of commands, flags, exit codes, and JSON schemas as ' + + 'a single JSON object. Agents use this to enumerate and call commands without ' + + 'bespoke glue. Analogous to `gh api` / `vercel --help --json`.', + exitCodes: [{ code: ExitCode.OK, meaning: 'Manifest emitted on stdout' }], + examples: ['streamnet manifest', 'streamnet manifest | jq .commands[].id'], + // handler is wired specially in cli.ts because it needs access to all specs + handler: async () => ({ placeholder: true }), + }, +]; diff --git a/src/registry/types.ts b/src/registry/types.ts new file mode 100644 index 0000000..9811374 --- /dev/null +++ b/src/registry/types.ts @@ -0,0 +1,79 @@ +import type { ZodTypeAny, z } from 'zod'; +import type { ExitCode } from '../agent/exit.js'; +import type { OutputContext } from '../agent/output.js'; +import type { Config } from '../config/schema.js'; + +/** Runtime context injected into every command handler. */ +export interface CommandContext { + output: OutputContext; + config: Config; + version: string; +} + +export interface ExitCodeEntry { + code: ExitCode; + meaning: string; +} + +export interface ArgSpec { + name: string; + description: string; + required: boolean; + variadic?: boolean; +} + +export interface FlagSpec { + long: string; + short?: string; + description: string; + /** Zod schema for the flag value. Boolean flags have z.boolean(). */ + schema: ZodTypeAny; + default?: unknown; + env?: string; +} + +/** + * A CommandSpec is the single declaration for a streamnet + * command. It is consumed by: + * - registry/build.ts → Commander .command() + * - agent/manifest.ts → streamnet manifest JSON + * - agent/mcp/tools.ts → MCP tool definition + * + * Because all three derive from the same spec, a command cannot drift between + * its CLI surface, its manifest entry, and its MCP tool. + */ +export interface CommandSpec< + TInput extends Record = Record, + TOutput = unknown, +> { + /** Stable machine identifier used as the MCP tool name and manifest key. */ + id: string; + /** One-line description for --help and the manifest. */ + summary: string; + /** Optional multi-line usage / detail. */ + description?: string; + args?: ArgSpec[]; + flags?: FlagSpec[]; + exitCodes: ExitCodeEntry[]; + examples?: string[]; + /** + * The actual implementation. Returns the data that populates Envelope.data. + * Handlers MUST NOT emit the final envelope themselves — they return data and + * the registry wrapper emits exactly one envelope. Handlers may still write + * diagnostics/progress to stderr via ctx.output.info/success/progress. + */ + handler: (ctx: CommandContext, input: TInput) => Promise; + /** + * Optional human-mode renderer. Called only when --json is off. If absent the + * wrapper pretty-prints the data as JSON. + */ + render?: (data: TOutput, output: OutputContext) => void; + /** + * Optional success-path exit code resolver. Lets a command emit a full + * (ok:true) envelope while still returning a non-zero code for scripting — + * e.g. `doctor` exits 1 when a check fails but still reports every check. + */ + exitCodeFor?: (data: TOutput) => ExitCode; + /** Schema for validating input in MCP / programmatic calls. */ + inputSchema?: z.ZodType; +} diff --git a/src/types/webtorrent.d.ts b/src/types/webtorrent.d.ts new file mode 100644 index 0000000..d3d0b8a --- /dev/null +++ b/src/types/webtorrent.d.ts @@ -0,0 +1,4 @@ +// Minimal ambient declaration for the optional `webtorrent` dependency. +// We intentionally avoid @types/webtorrent (heavy, drifts from runtime) and the +// engine module narrows to the small surface it actually uses. +declare module 'webtorrent'; diff --git a/src/util/format.ts b/src/util/format.ts new file mode 100644 index 0000000..37ebf22 --- /dev/null +++ b/src/util/format.ts @@ -0,0 +1,34 @@ +/** Human-readable byte size (e.g. 1.4 GB). */ +export function formatBytes(bytes: number): string { + if (bytes === 0) return '0 B'; + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + const val = bytes / Math.pow(1024, i); + return `${val.toFixed(i === 0 ? 0 : 1)} ${units[i]}`; +} + +/** Human-readable duration in milliseconds. */ +export function formatDuration(ms: number): string { + if (ms < 1000) return `${ms}ms`; + if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`; + const min = Math.floor(ms / 60_000); + const sec = Math.round((ms % 60_000) / 1000); + return `${min}m ${sec}s`; +} + +/** Pad a number to a fixed width with spaces. */ +export function pad(n: number, width: number): string { + return String(n).padStart(width, ' '); +} + +/** Truncate a string at maxLen with an ellipsis. */ +export function truncate(s: string, maxLen: number): string { + if (s.length <= maxLen) return s; + return s.slice(0, maxLen - 1) + '…'; +} + +/** Strip ANSI escape codes for length calculations. */ +export function stripAnsi(s: string): string { + // eslint-disable-next-line no-control-regex + return s.replace(/\x1b\[[0-9;]*m/g, ''); +} diff --git a/src/util/http.ts b/src/util/http.ts new file mode 100644 index 0000000..32bce55 --- /dev/null +++ b/src/util/http.ts @@ -0,0 +1,79 @@ +import { ExitCode, fail } from '../agent/exit.js'; + +const DEFAULT_UA = + 'Mozilla/5.0 (X11; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0'; +const DEFAULT_TIMEOUT_MS = 15_000; + +export interface FetchOptions { + timeoutMs?: number; + headers?: Record; + signal?: AbortSignal; +} + +/** + * Thin fetch wrapper with timeout + a browser-like User-Agent so indexer sites + * don't reject the request. All HTTP errors surface as NETWORK StreamNetErrors. + */ +export async function httpGet(url: string, opts: FetchOptions = {}): Promise { + const timeout = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS; + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), timeout); + + // Merge caller signal so external cancellation also works. + const signal = opts.signal + ? anySignal([controller.signal, opts.signal]) + : controller.signal; + + try { + const res = await fetch(url, { + signal, + headers: { + 'User-Agent': DEFAULT_UA, + Accept: + 'text/html,application/xhtml+xml,application/xml,application/json;q=0.9,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.9', + ...opts.headers, + }, + }); + if (!res.ok) { + fail(ExitCode.NETWORK, `HTTP ${res.status} from ${url}`); + } + return res; + } catch (err) { + if (err instanceof Error && err.name === 'AbortError') { + fail(ExitCode.NETWORK, `Request timed out after ${timeout}ms: ${url}`); + } + fail(ExitCode.NETWORK, `Network error fetching ${url}: ${String(err)}`); + } finally { + clearTimeout(timer); + } +} + +export async function httpGetJson( + url: string, + opts: FetchOptions = {}, +): Promise { + const res = await httpGet(url, { + ...opts, + headers: { Accept: 'application/json', ...opts.headers }, + }); + return res.json() as Promise; +} + +export async function httpGetText(url: string, opts: FetchOptions = {}): Promise { + const res = await httpGet(url, opts); + return res.text(); +} + +/** Combine multiple AbortSignals — aborts as soon as any one fires. */ +function anySignal(signals: AbortSignal[]): AbortSignal { + const ctrl = new AbortController(); + for (const sig of signals) { + if (sig.aborted) { + ctrl.abort(); + break; + } + sig.addEventListener('abort', () => ctrl.abort(), { once: true }); + } + return ctrl.signal; +} diff --git a/src/util/logger.ts b/src/util/logger.ts new file mode 100644 index 0000000..6a3a240 --- /dev/null +++ b/src/util/logger.ts @@ -0,0 +1,50 @@ +import { appendFileSync, mkdirSync } from 'node:fs'; +import { dirname } from 'node:path'; +import { logFile } from '../config/paths.js'; + +export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; + +const LEVEL_RANK: Record = { + debug: 0, + info: 1, + warn: 2, + error: 3, +}; + +let _level: LogLevel = 'info'; +let _logPath: string | null = null; +let _initialized = false; + +export function setLogLevel(level: LogLevel): void { + _level = level; +} + +export function initFileLog(): void { + if (_initialized) return; + _logPath = logFile(); + try { + mkdirSync(dirname(_logPath), { recursive: true }); + } catch { + _logPath = null; + } + _initialized = true; +} + +function write(level: LogLevel, message: string): void { + if (LEVEL_RANK[level] < LEVEL_RANK[_level]) return; + const line = `[${new Date().toISOString()}] [${level.toUpperCase()}] ${message}\n`; + if (_logPath) { + try { + appendFileSync(_logPath, line); + } catch { + // file log failure is non-fatal + } + } +} + +export const logger = { + debug: (msg: string) => write('debug', msg), + info: (msg: string) => write('info', msg), + warn: (msg: string) => write('warn', msg), + error: (msg: string) => write('error', msg), +}; diff --git a/test/__snapshots__/manifest.test.ts.snap b/test/__snapshots__/manifest.test.ts.snap new file mode 100644 index 0000000..47bf512 --- /dev/null +++ b/test/__snapshots__/manifest.test.ts.snap @@ -0,0 +1,71 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`manifest > is a stable, serializable shape (snapshot of command ids + flags) 1`] = ` +[ + { + "args": [ + "query", + ], + "flags": [ + "container", + "indexer", + "limit", + "min-seeders", + "quality", + ], + "id": "search", + }, + { + "args": [ + "source", + ], + "flags": [ + "file-index", + "no-subs", + "port", + "sub-lang", + ], + "id": "stream", + }, + { + "args": [ + "query", + ], + "flags": [ + "container", + "min-seeders", + "no-subs", + "quality", + "yes", + ], + "id": "play", + }, + { + "args": [], + "flags": [ + "check", + "yes", + ], + "id": "setup", + }, + { + "args": [], + "flags": [], + "id": "doctor", + }, + { + "args": [ + "subcommand", + "key", + "value", + ], + "flags": [], + "id": "config", + }, + { + "args": [], + "flags": [], + "id": "manifest", + }, +] +`; diff --git a/test/agent-mode.test.ts b/test/agent-mode.test.ts new file mode 100644 index 0000000..65b68b4 --- /dev/null +++ b/test/agent-mode.test.ts @@ -0,0 +1,74 @@ +import { describe, it, expect } from 'vitest'; +import { execFile } from 'node:child_process'; +import { existsSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); +const CLI = join(dirname(fileURLToPath(import.meta.url)), '..', 'dist', 'cli.js'); + +/** + * These tests exercise the built CLI as a subprocess with no TTY — exactly how a + * terminal agent invokes it. They lock in the agent-native contract: + * - stdout is a single valid JSON envelope + * - diagnostics never pollute stdout + * - exit codes are deterministic + * + * Requires `npm run build` first (CI builds before testing). Skipped locally if + * dist is absent. + */ +const maybe = existsSync(CLI) ? describe : describe.skip; + +async function run(args: string[]): Promise<{ stdout: string; code: number }> { + try { + const { stdout } = await execFileAsync('node', [CLI, ...args], { + env: { ...process.env, NO_COLOR: '1' }, + }); + return { stdout, code: 0 }; + } catch (err) { + const e = err as { stdout?: string; code?: number }; + return { stdout: e.stdout ?? '', code: e.code ?? 1 }; + } +} + +maybe('agent mode (subprocess, no TTY)', () => { + it('config get emits exactly one JSON envelope on stdout', async () => { + const { stdout, code } = await run(['config', 'get', 'minSeeders', '--json']); + const lines = stdout.trim().split('\n'); + expect(lines).toHaveLength(1); + const env = JSON.parse(lines[0]!); + expect(env.ok).toBe(true); + expect(env.command).toBe('config'); + expect(env.schemaVersion).toBe(1); + expect(env.data.value).toBe(3); + expect(code).toBe(0); + }); + + it('returns USAGE (2) for a missing required argument', async () => { + const { stdout, code } = await run(['config', 'get', '--json']); + const env = JSON.parse(stdout.trim()); + expect(env.ok).toBe(false); + expect(env.error.code).toBe(2); + expect(env.error.name).toBe('USAGE'); + expect(code).toBe(2); + }); + + it('doctor exits non-zero when a dependency is missing but still emits valid JSON', async () => { + const { stdout, code } = await run(['doctor', '--json']); + const env = JSON.parse(stdout.trim()); + expect(env.command).toBe('doctor'); + expect(Array.isArray(env.data.checks)).toBe(true); + // exit code mirrors allOk; in CI VLC is absent so this is typically 1 + expect(code === 0 || code === 1).toBe(true); + expect(env.data.allOk).toBe(code === 0); + }); + + it('manifest is a single valid JSON object enumerating commands', async () => { + const { stdout, code } = await run(['manifest']); + const m = JSON.parse(stdout.trim()); + expect(m.name).toBe('streamnet'); + expect(m.commands.map((c: { id: string }) => c.id)).toContain('play'); + expect(code).toBe(0); + }); +}); diff --git a/test/config.test.ts b/test/config.test.ts new file mode 100644 index 0000000..9053e6c --- /dev/null +++ b/test/config.test.ts @@ -0,0 +1,38 @@ +import { describe, it, expect } from 'vitest'; +import { getConfigValue, setConfigValue, redactConfig } from '../src/config/store.js'; +import { defaultConfig } from '../src/config/schema.js'; +import { StreamNetError } from '../src/agent/exit.js'; + +describe('config store', () => { + it('reads dotted keys', () => { + const cfg = defaultConfig(); + expect(getConfigValue(cfg, 'minSeeders')).toBe(3); + expect(getConfigValue(cfg, 'indexers.enabled')).toContain('torrents-csv'); + }); + + it('sets and coerces scalar values', () => { + const cfg = defaultConfig(); + const updated = setConfigValue(cfg, 'minSeeders', '10'); + expect(updated.minSeeders).toBe(10); + }); + + it('coerces comma lists to arrays', () => { + const cfg = defaultConfig(); + const updated = setConfigValue(cfg, 'preferredContainers', 'mkv,mp4,avi'); + expect(updated.preferredContainers).toEqual(['mkv', 'mp4', 'avi']); + }); + + it('rejects invalid values via schema', () => { + const cfg = defaultConfig(); + expect(() => setConfigValue(cfg, 'minSeeders', '-5')).toThrow(StreamNetError); + }); + + it('redacts secrets', () => { + const cfg = defaultConfig(); + cfg.opensubtitles.apiKey = 'super-secret'; + const redacted = redactConfig(cfg); + expect(redacted.opensubtitles.apiKey).toBe('***'); + // original untouched + expect(cfg.opensubtitles.apiKey).toBe('super-secret'); + }); +}); diff --git a/test/envelope.test.ts b/test/envelope.test.ts new file mode 100644 index 0000000..9853f97 --- /dev/null +++ b/test/envelope.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect } from 'vitest'; +import { + successEnvelope, + errorEnvelope, + ENVELOPE_SCHEMA_VERSION, +} from '../src/agent/envelope.js'; +import { ExitCode } from '../src/agent/exit.js'; + +describe('envelope', () => { + it('builds a success envelope with stable shape', () => { + const env = successEnvelope('search', '0.1.0', { results: [] }); + expect(env.ok).toBe(true); + expect(env.command).toBe('search'); + expect(env.version).toBe('0.1.0'); + expect(env.schemaVersion).toBe(ENVELOPE_SCHEMA_VERSION); + expect(env.data).toEqual({ results: [] }); + expect(env.meta.timestamp).toBeTypeOf('string'); + expect(env.error).toBeUndefined(); + }); + + it('builds an error envelope with named code', () => { + const env = errorEnvelope('play', '0.1.0', { + code: ExitCode.NO_RESULTS, + message: 'nothing found', + }); + expect(env.ok).toBe(false); + expect(env.error?.code).toBe(ExitCode.NO_RESULTS); + expect(env.error?.name).toBe('NO_RESULTS'); + expect(env.error?.message).toBe('nothing found'); + expect(env.data).toBeUndefined(); + }); + + it('serializes to valid single-line JSON', () => { + const env = successEnvelope('doctor', '0.1.0', { allOk: true }); + const line = JSON.stringify(env); + expect(() => JSON.parse(line)).not.toThrow(); + expect(line).not.toContain('\n'); + }); +}); diff --git a/test/indexers.test.ts b/test/indexers.test.ts new file mode 100644 index 0000000..6ac81b1 --- /dev/null +++ b/test/indexers.test.ts @@ -0,0 +1,73 @@ +import { describe, it, expect, vi } from 'vitest'; +import { aggregateSearch } from '../src/core/indexers/aggregate.js'; +import { infoHashFromMagnet } from '../src/core/indexers/base.js'; +import type { Indexer, TorrentResult } from '../src/core/indexers/types.js'; + +function fakeResult(infoHash: string, seeders: number, indexer: string): TorrentResult { + return { + schemaVersion: 1, + infoHash, + title: `Title ${infoHash}`, + indexer, + seeders, + leechers: 0, + sizeBytes: 1000, + quality: {}, + }; +} + +describe('infoHashFromMagnet', () => { + it('extracts a 40-char hex hash', () => { + const magnet = 'magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567&dn=x'; + expect(infoHashFromMagnet(magnet)).toBe('0123456789abcdef0123456789abcdef01234567'); + }); + + it('returns undefined for non-magnet', () => { + expect(infoHashFromMagnet('not a magnet')).toBeUndefined(); + }); +}); + +describe('aggregateSearch', () => { + const idxA: Indexer = { + id: 'a', + displayName: 'A', + search: vi.fn(async () => [ + fakeResult('hash1', 10, 'a'), + fakeResult('hash2', 5, 'a'), + ]), + }; + const idxB: Indexer = { + id: 'b', + displayName: 'B', + // hash1 duplicate with MORE seeders → should win the dedupe + search: vi.fn(async () => [ + fakeResult('hash1', 50, 'b'), + fakeResult('hash3', 7, 'b'), + ]), + }; + + it('merges results from multiple indexers', async () => { + const merged = await aggregateSearch('q', [idxA, idxB]); + const hashes = merged.map((r) => r.infoHash).sort(); + expect(hashes).toEqual(['hash1', 'hash2', 'hash3']); + }); + + it('dedupes by infoHash keeping the higher seeder count', async () => { + const merged = await aggregateSearch('q', [idxA, idxB]); + const hash1 = merged.find((r) => r.infoHash === 'hash1'); + expect(hash1?.seeders).toBe(50); + expect(hash1?.indexer).toBe('b'); + }); + + it('tolerates a failing indexer', async () => { + const broken: Indexer = { + id: 'broken', + displayName: 'Broken', + search: vi.fn(async () => { + throw new Error('network down'); + }), + }; + const merged = await aggregateSearch('q', [idxA, broken]); + expect(merged.length).toBe(2); // still got idxA's results + }); +}); diff --git a/test/manifest.test.ts b/test/manifest.test.ts new file mode 100644 index 0000000..b07efee --- /dev/null +++ b/test/manifest.test.ts @@ -0,0 +1,45 @@ +import { describe, it, expect } from 'vitest'; +import { buildManifest } from '../src/commands/manifest.js'; +import { COMMAND_SPECS } from '../src/registry/index.js'; + +describe('manifest', () => { + const manifest = buildManifest(COMMAND_SPECS, '0.1.0'); + + it('includes all registered commands', () => { + const ids = manifest.commands.map((c) => c.id).sort(); + expect(ids).toContain('search'); + expect(ids).toContain('stream'); + expect(ids).toContain('play'); + expect(ids).toContain('setup'); + expect(ids).toContain('doctor'); + expect(ids).toContain('config'); + }); + + it('documents the full exit code table', () => { + const codes = manifest.exitCodes.map((e) => e.code); + expect(codes).toContain(0); + expect(codes).toContain(77); + expect(codes).toContain(130); + // every code has a name and description + for (const e of manifest.exitCodes) { + expect(e.name).toBeTruthy(); + expect(e.description).toBeTruthy(); + } + }); + + it('exposes global agent flags', () => { + const flags = manifest.globalFlags.map((f) => f.long); + expect(flags).toContain('json'); + expect(flags).toContain('yes'); + expect(flags).toContain('no-input'); + }); + + it('is a stable, serializable shape (snapshot of command ids + flags)', () => { + const shape = manifest.commands.map((c) => ({ + id: c.id, + args: c.args?.map((a) => a.name) ?? [], + flags: (c.flags ?? []).map((f) => f.long).sort(), + })); + expect(shape).toMatchSnapshot(); + }); +}); diff --git a/test/quality.test.ts b/test/quality.test.ts new file mode 100644 index 0000000..243e2d4 --- /dev/null +++ b/test/quality.test.ts @@ -0,0 +1,37 @@ +import { describe, it, expect } from 'vitest'; +import { parseQuality, normaliseContainer } from '../src/core/health/quality.js'; + +describe('parseQuality', () => { + it('extracts resolution', () => { + expect(parseQuality('Movie 2049 1080p BluRay').resolution).toBe('1080p'); + expect(parseQuality('Movie 2160p UHD').resolution).toBe('2160p'); + expect(parseQuality('Movie 720p WEB-DL').resolution).toBe('720p'); + }); + + it('extracts codec', () => { + expect(parseQuality('Movie x265 HEVC').codec).toBe('x265'); + expect(parseQuality('Movie h.264').codec).toBe('x264'); + }); + + it('extracts source', () => { + expect(parseQuality('Movie BluRay').source).toBe('BluRay'); + expect(parseQuality('Movie WEB-DL').source).toBe('WEB-DL'); + expect(parseQuality('Movie HDTV').source).toBe('HDTV'); + }); + + it('extracts container', () => { + expect(parseQuality('Movie.1080p.mkv').container).toBe('mkv'); + expect(parseQuality('Movie.720p.mp4').container).toBe('mp4'); + }); + + it('returns empty object when nothing matches', () => { + expect(parseQuality('Some Random Title')).toEqual({}); + }); +}); + +describe('normaliseContainer', () => { + it('strips leading dot and lowercases', () => { + expect(normaliseContainer('.MKV')).toBe('mkv'); + expect(normaliseContainer('MP4')).toBe('mp4'); + }); +}); diff --git a/test/rank.test.ts b/test/rank.test.ts new file mode 100644 index 0000000..7bb7ba8 --- /dev/null +++ b/test/rank.test.ts @@ -0,0 +1,73 @@ +import { describe, it, expect } from 'vitest'; +import { scoreResult, rankResults } from '../src/core/health/rank.js'; +import type { TorrentResult } from '../src/core/indexers/types.js'; + +function makeResult(over: Partial): TorrentResult { + return { + schemaVersion: 1, + title: 'Test', + indexer: 'test', + seeders: 0, + leechers: 0, + sizeBytes: 0, + quality: {}, + ...over, + }; +} + +describe('scoreResult', () => { + it('rewards higher seeder counts', () => { + const low = scoreResult(makeResult({ seeders: 1 })); + const high = scoreResult(makeResult({ seeders: 500 })); + expect(high).toBeGreaterThan(low); + }); + + it('prefers MKV over MP4 over AVI (MKV-first)', () => { + const mkv = scoreResult(makeResult({ seeders: 100, quality: { container: 'mkv' } })); + const mp4 = scoreResult(makeResult({ seeders: 100, quality: { container: 'mp4' } })); + const avi = scoreResult(makeResult({ seeders: 100, quality: { container: 'avi' } })); + expect(mkv).toBeGreaterThan(mp4); + expect(mp4).toBeGreaterThan(avi); + }); + + it('applies seeder/leecher ratio', () => { + const healthy = scoreResult(makeResult({ seeders: 100, leechers: 0 })); + const leechy = scoreResult(makeResult({ seeders: 100, leechers: 900 })); + expect(healthy).toBeGreaterThan(leechy); + }); + + it('caps at 100', () => { + const score = scoreResult( + makeResult({ + seeders: 100000, + leechers: 0, + quality: { + container: 'mkv', + resolution: '1080p', + source: 'BluRay', + codec: 'x265', + }, + }), + { preferredContainers: ['mkv'], preferredQuality: ['1080p'] }, + ); + expect(score).toBeLessThanOrEqual(100); + }); +}); + +describe('rankResults', () => { + it('sorts by health descending and filters below minSeeders', () => { + const results = [ + makeResult({ title: 'A', seeders: 2 }), + makeResult({ title: 'B', seeders: 500, quality: { container: 'mkv' } }), + makeResult({ title: 'C', seeders: 50 }), + ]; + const ranked = rankResults(results, { minSeeders: 5 }); + expect(ranked.map((r) => r.title)).toEqual(['B', 'C']); // A filtered out + expect(ranked[0]!.healthScore).toBeGreaterThan(ranked[1]!.healthScore!); + }); + + it('attaches a healthScore to every result', () => { + const ranked = rankResults([makeResult({ seeders: 10 })]); + expect(ranked[0]!.healthScore).toBeTypeOf('number'); + }); +}); diff --git a/test/select.test.ts b/test/select.test.ts new file mode 100644 index 0000000..3b6d93a --- /dev/null +++ b/test/select.test.ts @@ -0,0 +1,41 @@ +import { describe, it, expect } from 'vitest'; +import { selectVideoFile, isMkv } from '../src/core/torrent/select.js'; + +describe('selectVideoFile', () => { + it('prefers MKV even when a larger MP4 exists', () => { + const files = [ + { name: 'movie.mp4', sizeBytes: 2_000_000_000 }, + { name: 'movie.mkv', sizeBytes: 1_500_000_000 }, + ]; + expect(selectVideoFile(files, ['mkv', 'mp4'])).toBe(1); + }); + + it('picks the largest video when no preferred container present', () => { + const files = [ + { name: 'sample.avi', sizeBytes: 50_000_000 }, + { name: 'feature.avi', sizeBytes: 1_200_000_000 }, + ]; + expect(selectVideoFile(files, ['mkv', 'mp4'])).toBe(1); + }); + + it('ignores non-video files', () => { + const files = [ + { name: 'readme.txt', sizeBytes: 9_000_000_000 }, + { name: 'movie.mkv', sizeBytes: 1_000_000_000 }, + ]; + expect(selectVideoFile(files)).toBe(1); + }); + + it('falls back to index 0 when no video files', () => { + const files = [{ name: 'a.txt', sizeBytes: 1 }]; + expect(selectVideoFile(files)).toBe(0); + }); +}); + +describe('isMkv', () => { + it('detects mkv files', () => { + expect(isMkv('movie.mkv')).toBe(true); + expect(isMkv('movie.MKV')).toBe(true); + expect(isMkv('movie.mp4')).toBe(false); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3229291 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "types": ["node"], + "outDir": "dist", + "rootDir": ".", + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "declaration": true, + "sourceMap": true, + "resolveJsonModule": true, + "verbatimModuleSyntax": true + }, + "include": ["src", "test"], + "exclude": ["node_modules", "dist"] +} diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..c0aaff8 --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: { + cli: 'src/cli.ts', + index: 'src/index.ts', + }, + format: ['esm'], + target: 'node20', + platform: 'node', + outDir: 'dist', + clean: true, + dts: true, + sourcemap: true, + splitting: false, + shims: true, + // Heavy / optional native deps stay external and are resolved at runtime. + external: ['webtorrent', 'ink', 'react'], + banner: { + js: '#!/usr/bin/env node', + }, +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..1133d60 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['test/**/*.test.ts'], + environment: 'node', + coverage: { + provider: 'v8', + include: ['src/**/*.ts'], + exclude: ['src/**/*.d.ts', 'src/tui/**'], + }, + }, +});