diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 3d7c6af..30acf53 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -23,5 +23,10 @@ assignees: '' ## Environment - Version: +- Node.js and npm versions: - MCP client: - OS: + +## Logs or Diagnostics + + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index b105b89..aacd689 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: false contact_links: - name: Security report - url: https://mikesoft.it + url: https://github.com/TheStreamCode/discord-management-mcp/security/advisories/new about: Please report sensitive vulnerabilities privately. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 572e18c..9d840ca 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -18,10 +18,17 @@ Fixes # ## Testing -- [ ] `npm run check` passes -- [ ] Tested mutation guard with a real Discord server -- [ ] Tested backup/restore flow end-to-end +- [ ] `npm run preflight` passes +- [ ] `npm run pack:check` passes when package or release files changed +- [ ] Added or updated regression tests for changed behavior +- [ ] No live Discord mutation was required, or the authorized test guild and result are documented below +- [ ] Backup/restore changes were exercised end-to-end with mocks or an authorized test guild + +## Release impact + +- [ ] `CHANGELOG.md` updated when user-visible behavior changed +- [ ] Package version remains unchanged, or release metadata is consistent ## Notes -- \ No newline at end of file +- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd1273d..6bc05d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,17 +9,22 @@ on: permissions: contents: read +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build-and-test: name: build-and-test runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 with: node-version: 24 cache: npm @@ -30,15 +35,42 @@ jobs: - name: Typecheck run: npm run typecheck + - name: Lint + run: npm run lint + - name: Test run: npm test - name: Build run: npm run build + - name: Verify npm package + run: npm run pack:check + # Gate on high/critical only. A plain `npm audit --omit=dev` exits non-zero # for *any* advisory in the tree, which deadlocked the repo: every Dependabot # PR stayed red because of the vulnerabilities it wasn't fixing, so no PR # could ever go green and none of them could be merged. - name: Runtime dependency audit run: npm audit --omit=dev --audit-level=high + + windows-test: + name: windows-test + runs-on: windows-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Setup Node.js + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: 24 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Check + run: npm run check diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..4cb38bb --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,49 @@ +name: Publish to npm + +on: + release: + types: + - published + +permissions: + contents: read + id-token: write + +concurrency: + group: npm-${{ github.event.release.tag_name }} + cancel-in-progress: false + +jobs: + publish: + name: publish + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Setup Node.js + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: 24 + registry-url: https://registry.npmjs.org/ + package-manager-cache: false + + - name: Install dependencies + run: npm ci + + - name: Verify source, tests, and build + run: npm run preflight + + - name: Verify release metadata + run: npm run release:check + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + + - name: Publish with provenance + run: npm publish --access public --provenance + env: + # Needed only to bootstrap the first publication. Remove the secret + # after configuring npm Trusted Publishing for this workflow. + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 65d2390..9466d85 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ coverage/ !.env.example backups/ *.log - +*.tgz +.eslintcache diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..445548b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,78 @@ +# AGENTS.md + +## Scope + +These instructions apply to the entire repository. + +Discord Management MCP is a Node.js 24, TypeScript ESM, stdio-only MCP server. It manages real Discord guilds, so correctness and explicit operator intent take priority over convenience. + +## Repository Map + +- `src/index.ts`: process entrypoint and tool registration. +- `src/config.ts`: environment and `.env.local` parsing. +- `src/discordClient.ts`: Discord client lifecycle. +- `src/safety.ts`: deterministic confirmation and backup guards. +- `src/backup/`: snapshot schema, validation, storage, diff, and restore primitives. +- `src/tools/`: MCP tool definitions grouped by domain. +- `src/__tests__/`: unit and regression tests. Tests must not contact Discord. +- `docs/`: design, configuration, safety, tool, and release documentation. +- `examples/`: explicitly confirmed operator examples; these may mutate a live guild. +- `.github/`: contribution forms, ownership, CI, and release automation. + +## Setup and Verification + +Use the repository-local toolchain; do not install or change global Node.js tooling. + +```bash +npm ci +npm run lint +npm run typecheck +npm test +npm run build +npm run preflight +npm run pack:check +npm audit --omit=dev +``` + +Run focused tests while iterating, then run `npm run preflight` before handing off code changes. Run `npm run pack:check` whenever package metadata, build output, documentation included in the package, or release automation changes. + +## Safety Invariants + +- Never print, return, commit, or place in fixtures a real bot token, webhook token, invite secret, authorization header, `.env.local`, or backup payload. +- Never run an example or perform a live Discord mutation unless the user explicitly asks for that live action and identifies the intended guild. +- Every mutating MCP tool must require `confirm: true` and a non-empty audit reason. +- Delete and other high-impact tools must retain their backup guard or explicit `allowWithoutBackup: true` acknowledgement. +- Guild-targeted destructive actions must verify that the backup belongs to the target guild. +- Core backup sections such as roles and channels must fail closed when they cannot be captured. Optional sections may emit structured warnings. +- Keep message-content and guild-member privileged intents opt-in. +- Keep webhook tokens out of tool output and recursively remove secret fields from snapshots. +- Cross-guild restore must remain explicit, conservative, and safe when IDs or permission targets cannot be mapped. +- Restore apply must create a pre-restore backup before the first Discord mutation and must report skipped or lossy operations truthfully. +- MCP annotations must describe actual behavior: read-only tools are read-only, additive tools are non-destructive, and updates/deletes are marked destructive where appropriate. + +## Code Conventions + +- Use strict TypeScript and ESM imports with `.js` extensions for local modules. +- Prefer focused modules and typed helpers over broad casts. When an SDK boundary requires a cast, keep it narrow and validate the input first. +- Define bounded Zod input schemas for every tool. Use Discord API limits where known. +- Return both readable MCP text and structured content. Error responses must be actionable and must not expose secrets. +- Treat backup JSON as untrusted local input: validate it before planning or applying changes. +- Preserve stable identity across renames and reorders. Do not derive matching solely from mutable names or positions. +- Add regression tests for safety guards, snapshot validation, identity matching, restore mapping, and error paths. +- Keep comments focused on non-obvious safety or protocol decisions. + +## Generated and Sensitive Files + +- Do not edit or commit `dist/`; it is generated by `npm run build` and included at npm pack time. +- Do not commit `.env*` except `.env.example`, `backups/`, `coverage/`, logs, `node_modules/`, or generated package tarballs. +- Do not inspect or echo `.env.local` during routine review. Verify only that Git ignores it. +- Preserve unrelated working-tree changes and never rewrite user work to clean the repository. + +## GitHub and Releases + +- Use focused commits and pull requests. Prefer Conventional Commit subjects such as `fix:`, `feat:`, `docs:`, `test:`, and `chore:`. +- Do not commit, push, create tags/releases, change repository settings, or publish to npm unless the user explicitly authorizes that external action. +- Keep the required `build-and-test` status context stable unless branch protection is updated in the same authorized operation. +- Pin third-party GitHub Actions to full commit SHAs and retain a version comment. +- For a release, update `package.json` and `package-lock.json`, `CHANGELOG.md`, and `CITATION.cff`; then run `npm run release:check -- vX.Y.Z`, `npm run preflight`, and `npm run pack:check`. +- npm publication should use the `publish.yml` workflow with OIDC trusted publishing and provenance. A one-time `NPM_TOKEN` is allowed only to bootstrap the first publication and should be removed afterward. diff --git a/CHANGELOG.md b/CHANGELOG.md index b4a36c3..dd4aeee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,25 @@ All notable changes to this project are documented in this file. -## 0.1.0 +## Unreleased + +## 0.1.0 - 2026-08-01 + +### Added - Initial release: safe-by-default Discord management MCP server with JSON guild backups, restore planning, and guarded mutations. - Snapshots strip secret fields (tokens, secrets, authorization) so backup files never contain bot or webhook credentials. - Destructive operations are gated and require explicit intent, with restore planning to preview changes before applying them. +- Repository-specific `AGENTS.md`, TypeScript linting, Windows CI coverage, npm package verification, and an npm publication workflow with provenance. +- Regression tests for restore identity, role ordering, cross-guild permission mapping, required snapshot sections, and malformed backup data. + +### Changed + +- Restore matching now prefers immutable Discord IDs and uses unambiguous semantic matching across guilds, avoiding duplicate resources after renames or reorders. +- Role and channel restore now applies supported positions, parents, and safely mapped permission overwrites. +- Runtime and development dependencies were refreshed, and GitHub Actions are pinned to immutable commits. + +### Security + +- Backup reads now validate the complete snapshot structure before restore planning or application. +- Core role/channel capture fails closed, backup paths use restrictive modes where supported, and fixable dependency advisories were removed. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f63744..a5dbcc6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,10 +7,12 @@ Maintainer: [Michael Gasperini](https://mikesoft.it) / [TheStreamCode](https://g ## Local Development ```bash -npm install +npm ci +npm run lint npm run typecheck npm test npm run build +npm run pack:check ``` Use Node.js `>=24`. Do not commit `node_modules/`, `dist/`, `.env.local`, backups, or logs. @@ -28,10 +30,10 @@ Use Node.js `>=24`. Do not commit `node_modules/`, `dist/`, `.env.local`, backup - New tools have clear names and descriptions. - Inputs use Zod validation with useful bounds. -- Mutating tools use confirmation guards. -- Destructive tools use backup guards. +- Mutating tools use confirmation guards and accurate MCP annotations. +- Delete and other high-impact destructive tools use backup guards. - Error responses include actionable next steps. - Tests cover safety-critical behavior. -- `npm run typecheck`, `npm test`, and `npm run build` pass. +- `npm run preflight` and `npm run pack:check` pass. All changes should be submitted through pull requests. Direct pushes to the protected default branch are reserved for the maintainer bypass path. diff --git a/README.md b/README.md index 386962a..f498e03 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Discord Management MCP -![Discord Management MCP hero](./discord-management-mcp.png) +[![CI](https://github.com/TheStreamCode/discord-management-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/TheStreamCode/discord-management-mcp/actions/workflows/ci.yml) + +![Discord Management MCP hero](https://raw.githubusercontent.com/TheStreamCode/discord-management-mcp/main/discord-management-mcp.png) Safe-by-default Discord management server for the Model Context Protocol (MCP). It lets MCP clients inspect and manage Discord guilds through a local stdio server, with JSON backups, restore planning, and explicit guardrails for risky operations. @@ -11,10 +13,10 @@ Created by [Michael Gasperini](https://mikesoft.it). Repository: [TheStreamCode/ - **Local stdio MCP server**: no public HTTP listener, no hosted service, no proxy. - **Discord-first tooling**: guild, channel, role, member, AutoMod, scheduled event, invite, webhook, emoji, sticker, and application-command tools. - **Safe mutations**: write operations require `confirm: true` and a non-empty `reason`. -- **Destructive-operation guards**: destructive tools require a valid backup ID or an explicit `allowWithoutBackup: true`. +- **High-impact operation guards**: delete and other high-impact tools require a valid backup ID or an explicit `allowWithoutBackup: true`. - **Guild-matched backups**: destructive guards reject backups from a different Discord guild. - **Readable JSON backups**: snapshots are stored locally and can be diffed or used for conservative restore planning. -- **Conservative restore apply**: role/channel create and update operations only by default; deletes are opt-in. +- **Conservative restore apply**: role/channel fields, ordering, parents, and safely mappable permission overwrites; deletes are opt-in. - **Optional message reading**: `Message Content` is disabled by default and must be explicitly enabled. ## Requirements @@ -39,14 +41,20 @@ ENABLE_GUILD_MEMBERS=false Use the token from **Discord Developer Portal > Application > Bot > Token**. Do not use the Application ID or Public Key. -Install, build, and run: +Install from source, build, and run: ```bash -npm install +npm ci npm run build npm start ``` +Published npm releases can also be run without cloning the repository: + +```bash +npx --yes discord-management-mcp +``` + ## Discord Intents Default gateway intents avoid privileged intents so a new bot can start with minimal setup. @@ -82,6 +90,10 @@ If installed as a package, the binary entry is: discord-management-mcp ``` +For an npm-based MCP client configuration, use `npx` as the command and +`["--yes", "discord-management-mcp"]` as the arguments. Environment variables, +including `DISCORD_TOKEN`, should be supplied through the client's secure environment configuration. + ## Safety Model Read-only tools do not require confirmation. @@ -95,7 +107,7 @@ Mutating tools require: } ``` -Destructive tools also require either: +Delete and other high-impact destructive tools also require either: ```json { @@ -129,7 +141,7 @@ Recommended workflow: Restore is best-effort. Discord cannot restore original IDs after recreation, message history, audit-log history, invite codes, webhook tokens, managed integration-owned roles, or every community/discovery setting. -`discord_backup_restore_apply` is intentionally conservative: it creates a pre-restore backup automatically, applies role/channel create and update operations, and skips deletes unless `includeDeletes: true` is set. +`discord_backup_restore_apply` is intentionally conservative: it creates a pre-restore backup automatically, applies supported role/channel fields and permission overwrites, and skips deletes unless `includeDeletes: true` is set. When a cross-guild permission target cannot be mapped safely, existing overwrites are preserved and the skipped mapping is reported. ## Tool Coverage @@ -153,6 +165,8 @@ npm run typecheck npm test npm run build npm run check +npm run preflight +npm run pack:check npm audit --omit=dev ``` diff --git a/SECURITY.md b/SECURITY.md index 1996acb..4c40e7d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,7 +8,7 @@ Security fixes target the latest `main` branch until tagged releases are introdu Do not open a public issue for secrets, bypasses, or exploitable behavior. -Report privately to [Michael Gasperini](https://mikesoft.it) or the repository maintainer. Include: +Report privately through [GitHub private vulnerability reporting](https://github.com/TheStreamCode/discord-management-mcp/security/advisories/new). If GitHub reporting is unavailable, contact [Michael Gasperini](https://mikesoft.it). Include: - Impacted version or commit - Reproduction steps diff --git a/docs/design.md b/docs/design.md index 85fa6c6..d04c005 100644 --- a/docs/design.md +++ b/docs/design.md @@ -15,7 +15,7 @@ Build a local, Node 24-compatible MCP server for Discord server management. The ## Safety Model -Read-only tools can run without confirmation. Mutating tools require `confirm: true` and a non-empty human-readable `reason`. Destructive tools also require either `backupId` or `allowWithoutBackup: true`. When the target guild is known, destructive guards load the backup and reject it if the backup guild ID does not match the target guild ID. +Read-only tools can run without confirmation. Mutating tools require `confirm: true` and a non-empty human-readable `reason`. Delete and other high-impact destructive tools also require either `backupId` or `allowWithoutBackup: true`. When the target guild is known, destructive guards load the backup and reject it if the backup guild ID does not match the target guild ID. The MCP never prints the bot token. Errors mention missing/invalid token state without exposing secret values. @@ -23,9 +23,9 @@ Message content reading is disabled by default. When enabled, the operator must ## Backup Model -Backups are JSON snapshots stored in `backups/`. A snapshot captures guild metadata, roles, channels/categories, permission overwrites, AutoMod rules, scheduled events, webhooks metadata, invites metadata, emojis, stickers, and application command metadata when available. +Backups are JSON snapshots stored in `backups/`. A snapshot captures guild metadata, roles, channels/categories, permission overwrites, AutoMod rules, scheduled events, webhooks metadata, invites metadata, emojis, stickers, and application command metadata when available. Roles and channels are required core sections; capture fails closed if either section is unavailable. Optional sections are best-effort and record structured warnings. -Restore is best-effort. Discord cannot preserve IDs for deleted/recreated objects, cannot restore message history, and does not expose every server setting. Restore plans report unsupported or lossy operations before apply. Restore apply is intentionally conservative: it creates a pre-restore backup, applies role/channel create and update operations, and only deletes when `includeDeletes: true` is provided. +Restore is best-effort. Discord cannot preserve IDs for deleted/recreated objects, cannot restore message history, and does not expose every server setting. Restore plans report unsupported or lossy operations before apply. Restore apply is intentionally conservative: it creates a pre-restore backup, applies supported role/channel fields including positions, parents, and safely mappable permission overwrites, and only deletes when `includeDeletes: true` is provided. ## Tool Coverage diff --git a/docs/github-publishing.md b/docs/github-publishing.md index dcd4236..6db1c69 100644 --- a/docs/github-publishing.md +++ b/docs/github-publishing.md @@ -1,31 +1,63 @@ -# GitHub Publishing Checklist +# GitHub and npm Publishing -Before publishing this repository: +## Release Preconditions -1. Confirm `.env.local` is not committed. -2. Confirm `backups/` is not committed. -3. Confirm `node_modules/` and `dist/` are not committed. -4. Run: +Before creating a release: + +1. Confirm the worktree contains only the intended release changes. +2. Confirm `.env.local`, `backups/`, `node_modules/`, `dist/`, coverage, logs, and package tarballs are not tracked. +3. Update the version in `package.json` and `package-lock.json`. +4. Add the matching section to `CHANGELOG.md` and update `CITATION.cff`. +5. Run: ```bash - npm run typecheck - npm test - npm run build + npm ci + npm run release:check -- vX.Y.Z + npm run preflight + npm run pack:check npm audit --omit=dev ``` -5. Review `README.md`, `SECURITY.md`, and `CONTRIBUTING.md`. -6. Add repository metadata on GitHub: - - Description: `Safe-by-default Discord management MCP server with JSON backups and rollback planning.` - - Topics: `mcp`, `discord`, `discord-bot`, `model-context-protocol`, `backup`, `rollback` - - Website: `https://mikesoft.it` -7. Create the first tag after verification: +6. Inspect the `npm run pack:check` manifest. It must contain the compiled `dist/` entrypoint and must not contain secrets, backups, source maps with private paths, or the repository hero image. +7. Merge the release change through the protected `main` branch. +8. Create a GitHub release whose tag exactly matches `vX.Y.Z`. Publishing the release triggers `.github/workflows/publish.yml`. - ```bash - git tag v0.1.0 - ``` +## First npm Publication + +The package name is `discord-management-mcp` and publication is configured for the public npm registry. + +npm Trusted Publishing can only be configured after the package exists. Bootstrap the first GitHub release with a short-lived granular npm automation token stored as the `NPM_TOKEN` repository secret. Give it only the package publication permissions required for this release. + +After the first package version is visible on npm: + +1. Configure npm Trusted Publishing for: + - provider: GitHub Actions + - organization: `TheStreamCode` + - repository: `discord-management-mcp` + - workflow: `publish.yml` + - allowed action: `npm publish` +2. Remove the `NPM_TOKEN` GitHub secret. +3. Restrict traditional token-based publication in the npm package settings. + +The workflow grants `id-token: write`, runs on a GitHub-hosted runner, verifies source and release metadata, and publishes with npm provenance. + +## GitHub Repository Settings + +Keep the following settings enabled: + +- Issues, secret scanning, and push protection. +- Dependency vulnerability alerts and Dependabot security updates. +- Squash merge and automatic branch deletion. +- Branch protection on `main` with the `build-and-test` check, linear history, conversation resolution, and pull-request review. +- Auto-merge only for dependency updates that pass all required checks. + +Recommended repository metadata: + +- Description: `Safe-by-default Discord management MCP server with JSON backups, restore planning, and guarded mutations.` +- Website: `https://mikesoft.it` +- Topics: `mcp`, `discord`, `discord-bot`, `discord-management`, `model-context-protocol`, `backup`, `rollback` -Do not publish bot tokens, webhook URLs, backup files, or screenshots that expose private guild details. +Do not publish bot tokens, webhook URLs, invite secrets, backup files, or screenshots that expose private guild details. ## Maintainer diff --git a/docs/safety-and-backups.md b/docs/safety-and-backups.md index b0873fc..2eb02ec 100644 --- a/docs/safety-and-backups.md +++ b/docs/safety-and-backups.md @@ -15,9 +15,9 @@ Every mutating tool requires: The `reason` is passed to Discord where the API supports audit-log reasons. -## Destructive Guard +## High-Impact Destructive Guard -Destructive tools require either a backup: +Delete and other high-impact destructive tools require either a backup: ```json { @@ -47,4 +47,8 @@ Restore is best-effort. Discord does not allow full restoration of: - Managed integration-owned roles - Every community, discovery, or boost-related setting -`discord_backup_restore_apply` creates a pre-restore backup first and only applies conservative role/channel create and update operations by default. +`discord_backup_restore_apply` creates a pre-restore backup first and only applies conservative role/channel create and update operations by default; deletes remain opt-in. + +Role and channel identity is matched by Discord ID first and by an unambiguous semantic match when cloning across guilds. Restore applies supported positions, parents, and permission overwrites; targets that cannot be mapped safely are reported and left unchanged rather than partially replacing permissions. + +Role and channel capture is mandatory. If either core section cannot be read, backup creation fails closed instead of writing a partial snapshot that could provide a false sense of rollback safety. Optional sections remain best-effort and produce structured warnings. diff --git a/docs/tools.md b/docs/tools.md index 71e2cf4..ef70529 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -71,5 +71,5 @@ All tools use the `discord_` prefix. - Read-only tools do not require confirmation. - Mutating tools require `confirm: true` and `reason`. -- Destructive tools require `backupId` or `allowWithoutBackup: true`. +- Delete and other high-impact destructive tools require `backupId` or `allowWithoutBackup: true`. - Backup IDs are validated and destructive guild-targeted actions reject backups from another guild. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..950af23 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,43 @@ +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { + ignores: ["backups/**", "coverage/**", "dist/**", "node_modules/**"], + }, + { + files: ["src/**/*.ts"], + extends: [eslint.configs.recommended, ...tseslint.configs.recommended], + languageOptions: { + parserOptions: { + project: "./tsconfig.eslint.json", + tsconfigRootDir: import.meta.dirname, + }, + }, + linterOptions: { + reportUnusedDisableDirectives: "error", + }, + rules: { + "@typescript-eslint/consistent-type-imports": "error", + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + }, + ], + }, + }, + { + files: ["eslint.config.js", "examples/**/*.mjs", "scripts/**/*.mjs"], + ...eslint.configs.recommended, + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + globals: { + console: "readonly", + process: "readonly", + }, + }, + }, +); diff --git a/package-lock.json b/package-lock.json index b655c9d..b293d42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,21 +9,28 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", - "discord.js": "^14.26.4", + "@modelcontextprotocol/sdk": "^1.30.0", + "discord.js": "^14.27.0", "zod": "^4.4.3" }, "bin": { "discord-management-mcp": "dist/index.js" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@types/node": "^24.13.3", - "tsx": "^4.23.0", + "eslint": "^10.8.0", + "tsx": "^4.23.1", "typescript": "^6.0.3", - "vitest": "^4.1.9" + "typescript-eslint": "^8.65.0", + "vitest": "^4.1.10" }, "engines": { - "node": ">=24.0.0" + "node": ">=24.0.0", + "npm": ">=11.5.1" + }, + "funding": { + "url": "https://github.com/sponsors/TheStreamCode" } }, "node_modules/@discordjs/builders": { @@ -72,9 +79,9 @@ } }, "node_modules/@discordjs/rest": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.1.tgz", - "integrity": "sha512-wwQdgjeaoYFiaG+atbqx6aJDpqW7JHAo0HrQkBTbYzM3/PJ3GweQIpgElNcGZ26DCUOXMyawYd0YF7vtr+fZXg==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.3.tgz", + "integrity": "sha512-wvOylxNYJkwKjctS/Mn5GP1w9r3/rzyH+ThD1JlAca6zEdlHs8QWBBUQJpU5Q+W6DoIj/Ljh1IPlZs7hTU+UAg==", "license": "Apache-2.0", "dependencies": { "@discordjs/collection": "^2.1.1", @@ -82,10 +89,10 @@ "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.5", "@vladfrangu/async_event_emitter": "^2.4.6", - "discord-api-types": "^0.38.40", + "discord-api-types": "^0.38.50", "magic-bytes.js": "^1.13.0", "tslib": "^2.6.3", - "undici": "6.24.1" + "undici": "^6.27.0" }, "engines": { "node": ">=18" @@ -106,16 +113,6 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@discordjs/rest/node_modules/@sapphire/snowflake": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.5.tgz", - "integrity": "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ==", - "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, "node_modules/@discordjs/util": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz", @@ -167,21 +164,21 @@ } }, "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz", + "integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.1", + "@emnapi/wasi-threads": "2.0.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz", + "integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==", "dev": true, "license": "MIT", "optional": true, @@ -190,9 +187,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz", + "integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==", "dev": true, "license": "MIT", "optional": true, @@ -642,18 +639,212 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "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/eslint-utils/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-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.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, "node_modules/@hono/node-server": { - "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.12.tgz", + "integrity": "sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==", "license": "MIT", "engines": { - "node": ">=18.14.1" + "node": ">=20" }, "peerDependencies": { "hono": "^4" } }, + "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/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -662,12 +853,12 @@ "license": "MIT" }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", - "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", + "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", "license": "MIT", "dependencies": { - "@hono/node-server": "^1.19.9", + "@hono/node-server": "^1.19.9 || ^2.0.5", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", @@ -702,28 +893,31 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", - "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.2" + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" } }, "node_modules/@oxc-project/types": { - "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz", + "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==", "dev": true, "license": "MIT", "funding": { @@ -731,9 +925,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.1.tgz", + "integrity": "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==", "cpu": [ "arm64" ], @@ -748,9 +942,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.1.tgz", + "integrity": "sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==", "cpu": [ "arm64" ], @@ -765,9 +959,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.1.tgz", + "integrity": "sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==", "cpu": [ "x64" ], @@ -782,9 +976,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.1.tgz", + "integrity": "sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==", "cpu": [ "x64" ], @@ -799,9 +993,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.1.tgz", + "integrity": "sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==", "cpu": [ "arm" ], @@ -816,9 +1010,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.1.tgz", + "integrity": "sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==", "cpu": [ "arm64" ], @@ -836,9 +1030,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.1.tgz", + "integrity": "sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==", "cpu": [ "arm64" ], @@ -856,9 +1050,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.1.tgz", + "integrity": "sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==", "cpu": [ "ppc64" ], @@ -876,9 +1070,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.1.tgz", + "integrity": "sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==", "cpu": [ "s390x" ], @@ -896,9 +1090,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.1.tgz", + "integrity": "sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==", "cpu": [ "x64" ], @@ -916,9 +1110,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.1.tgz", + "integrity": "sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==", "cpu": [ "x64" ], @@ -936,9 +1130,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.1.tgz", + "integrity": "sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==", "cpu": [ "arm64" ], @@ -953,28 +1147,25 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", - "cpu": [ - "wasm32" - ], + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.1.tgz", + "integrity": "sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" + "@emnapi/core": "2.0.0-alpha.3", + "@emnapi/runtime": "2.0.0-alpha.3", + "@napi-rs/wasm-runtime": "^1.2.0" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.1.tgz", + "integrity": "sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==", "cpu": [ "arm64" ], @@ -989,9 +1180,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.1.tgz", + "integrity": "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==", "cpu": [ "x64" ], @@ -1036,9 +1227,9 @@ } }, "node_modules/@sapphire/snowflake": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", - "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.5.tgz", + "integrity": "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ==", "license": "MIT", "engines": { "node": ">=v14.0.0", @@ -1053,9 +1244,9 @@ "license": "MIT" }, "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, "license": "MIT", "optional": true, @@ -1081,6 +1272,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -1088,6 +1286,13 @@ "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": "24.13.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", @@ -1106,17 +1311,247 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.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.65.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.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.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.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.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.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.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "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.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.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.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "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.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.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.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.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.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.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/@vitest/expect": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", - "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -1125,13 +1560,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", - "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.9", + "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -1152,9 +1587,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", - "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1165,13 +1600,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", - "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.9", + "@vitest/utils": "4.1.10", "pathe": "^2.0.3" }, "funding": { @@ -1179,14 +1614,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", - "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -1195,9 +1630,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", - "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", "dev": true, "license": "MIT", "funding": { @@ -1205,13 +1640,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", - "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.9", + "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -1242,6 +1677,29 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "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/ajv": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", @@ -1285,21 +1743,31 @@ "node": ">=12" } }, + "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/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", + "content-type": "^2.0.0", "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { "node": ">=18" @@ -1309,6 +1777,32 @@ "url": "https://opencollective.com/express" } }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1452,6 +1946,13 @@ } } }, + "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/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1472,33 +1973,33 @@ } }, "node_modules/discord-api-types": { - "version": "0.38.48", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.48.tgz", - "integrity": "sha512-WFUE/2o0lBlLeCQonQ+Pu2RqHAqbytBJ2RlXR91gzk05InSS6k9ShzzLYoymrA4c2oRgRKGE7/VqQJNNdGWSxQ==", + "version": "0.38.52", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.52.tgz", + "integrity": "sha512-uwe9EKfbjsmgWc2fdFjvDbj+dQqx3lp7wqDCmIha0jInuU+xeQjkCK9tMMn+p7RXfdVQORCInq4cD3U2ymDmyg==", "license": "MIT", "workspaces": [ "scripts/actions/documentation" ] }, "node_modules/discord.js": { - "version": "14.26.4", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.26.4.tgz", - "integrity": "sha512-4oBp8tc6Kf8IDBwAHhbsMaAqx1b5fob9SNasZT7V6yyyUydoO5i5fGuX7TmvRtR+q/WgKRnRViRoAWnG7fNyvA==", + "version": "14.27.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.27.0.tgz", + "integrity": "sha512-qHbFlFG2N7y3LjPySYsL6A1+BnX6bkTVgo842EX0CqVPk/KTMwZkojPHEXKsQUpWZNyz5BISNHK1cPpQw0+m4A==", "license": "Apache-2.0", "dependencies": { "@discordjs/builders": "^1.14.1", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.2", - "@discordjs/rest": "^2.6.1", + "@discordjs/rest": "^2.6.2", "@discordjs/util": "^1.2.0", "@discordjs/ws": "^1.2.3", - "@sapphire/snowflake": "3.5.3", - "discord-api-types": "^0.38.40", + "@sapphire/snowflake": "3.5.5", + "discord-api-types": "^0.38.49", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.13.0", "tslib": "^2.6.3", - "undici": "6.24.1" + "undici": "^6.27.0" }, "engines": { "node": ">=18" @@ -1584,42 +2085,224 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "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" + }, + "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": "10.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", + "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@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", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.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", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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/eslint/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/eslint/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/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "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/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" + "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", @@ -1631,6 +2314,16 @@ "@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/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -1738,6 +2431,20 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "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-uri": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", @@ -1772,6 +2479,19 @@ } } }, + "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/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -1793,6 +2513,44 @@ "url": "https://opencollective.com/express" } }, + "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/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.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1872,6 +2630,19 @@ "node": ">= 0.4" } }, + "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/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1909,9 +2680,9 @@ } }, "node_modules/hono": { - "version": "4.12.26", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.26.tgz", - "integrity": "sha512-uyZtpnYxM9CmQ7QsQknM4zN8EftNqhON1qYeIKM0Se67CCEe2c44xyGURwB0axX2fBDu1dqHrHAc1hmNT8ITkw==", + "version": "4.12.33", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.33.tgz", + "integrity": "sha512-+SwvkaiJtxsiPjhy9LivY/1m7UsNqCJetM1BrZl9A5DkQhlbHQDU730mMiDPWjnoCYOM8Chf3WrCJw27kNTPFQ==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -1953,6 +2724,26 @@ "url": "https://opencollective.com/express" } }, + "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/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", @@ -1977,6 +2768,29 @@ "node": ">= 0.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-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-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -1998,6 +2812,13 @@ "url": "https://github.com/sponsors/panva" } }, + "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": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -2010,10 +2831,41 @@ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", "license": "BSD-2-Clause" }, + "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/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/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/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -2027,23 +2879,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -2062,9 +2914,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -2083,9 +2935,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -2104,9 +2956,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -2125,9 +2977,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -2146,9 +2998,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], @@ -2170,9 +3022,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], @@ -2194,9 +3046,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], @@ -2218,9 +3070,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], @@ -2242,9 +3094,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -2263,9 +3115,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -2283,6 +3135,22 @@ "url": "https://opencollective.com/parcel" } }, + "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": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", @@ -2366,6 +3234,22 @@ "url": "https://opencollective.com/express" } }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2373,9 +3257,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.14", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.14.tgz", - "integrity": "sha512-U9kYi5bpVMEI31yC8iw4bJJp0avcHXA0W8/wNfLfnvJYzihQo2ZRPYPvpAAd570HAcCBjCTN7vnr+v4StKl1IQ==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "dev": true, "funding": [ { @@ -2391,6 +3275,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "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/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", @@ -2453,6 +3344,56 @@ "wrappy": "1" } }, + "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": "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": { + "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/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2462,6 +3403,16 @@ "node": ">= 0.8" } }, + "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", @@ -2496,9 +3447,9 @@ "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==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -2518,9 +3469,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", "dev": true, "funding": [ { @@ -2538,7 +3489,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -2546,6 +3497,16 @@ "node": "^10 || ^12 || >=14" } }, + "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/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2559,6 +3520,16 @@ "node": ">= 0.10" } }, + "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/qs": { "version": "6.15.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", @@ -2608,13 +3579,13 @@ } }, "node_modules/rolldown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.1.tgz", + "integrity": "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.133.0", + "@oxc-project/types": "=0.142.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -2624,21 +3595,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.3", - "@rolldown/binding-darwin-arm64": "1.0.3", - "@rolldown/binding-darwin-x64": "1.0.3", - "@rolldown/binding-freebsd-x64": "1.0.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", - "@rolldown/binding-linux-arm64-gnu": "1.0.3", - "@rolldown/binding-linux-arm64-musl": "1.0.3", - "@rolldown/binding-linux-ppc64-gnu": "1.0.3", - "@rolldown/binding-linux-s390x-gnu": "1.0.3", - "@rolldown/binding-linux-x64-gnu": "1.0.3", - "@rolldown/binding-linux-x64-musl": "1.0.3", - "@rolldown/binding-openharmony-arm64": "1.0.3", - "@rolldown/binding-wasm32-wasi": "1.0.3", - "@rolldown/binding-win32-arm64-msvc": "1.0.3", - "@rolldown/binding-win32-x64-msvc": "1.0.3" + "@rolldown/binding-android-arm64": "1.2.1", + "@rolldown/binding-darwin-arm64": "1.2.1", + "@rolldown/binding-darwin-x64": "1.2.1", + "@rolldown/binding-freebsd-x64": "1.2.1", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.1", + "@rolldown/binding-linux-arm64-gnu": "1.2.1", + "@rolldown/binding-linux-arm64-musl": "1.2.1", + "@rolldown/binding-linux-ppc64-gnu": "1.2.1", + "@rolldown/binding-linux-s390x-gnu": "1.2.1", + "@rolldown/binding-linux-x64-gnu": "1.2.1", + "@rolldown/binding-linux-x64-musl": "1.2.1", + "@rolldown/binding-openharmony-arm64": "1.2.1", + "@rolldown/binding-wasm32-wasi": "1.2.1", + "@rolldown/binding-win32-arm64-msvc": "1.2.1", + "@rolldown/binding-win32-x64-msvc": "1.2.1" } }, "node_modules/router": { @@ -2663,6 +3634,19 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", @@ -2882,9 +3866,9 @@ } }, "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", "dev": true, "license": "MIT", "engines": { @@ -2900,6 +3884,19 @@ "node": ">=0.6" } }, + "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-mixer": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", @@ -2913,9 +3910,9 @@ "license": "0BSD" }, "node_modules/tsx": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.0.tgz", - "integrity": "sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz", + "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2931,6 +3928,19 @@ "fsevents": "~2.3.3" } }, + "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/type-is": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", @@ -2976,6 +3986,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.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/undici": { "version": "6.27.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", @@ -3000,6 +4034,16 @@ "node": ">= 0.8" } }, + "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/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -3010,16 +4054,16 @@ } }, "node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz", + "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.23", + "rolldown": "~1.2.0", "tinyglobby": "^0.2.17" }, "bin": { @@ -3036,7 +4080,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", + "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -3088,19 +4132,19 @@ } }, "node_modules/vitest": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", - "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.9", - "@vitest/mocker": "4.1.9", - "@vitest/pretty-format": "4.1.9", - "@vitest/runner": "4.1.9", - "@vitest/snapshot": "4.1.9", - "@vitest/spy": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -3128,12 +4172,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.9", - "@vitest/browser-preview": "4.1.9", - "@vitest/browser-webdriverio": "4.1.9", - "@vitest/coverage-istanbul": "4.1.9", - "@vitest/coverage-v8": "4.1.9", - "@vitest/ui": "4.1.9", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -3209,6 +4253,16 @@ "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/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3236,6 +4290,19 @@ } } }, + "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/zod": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", diff --git a/package.json b/package.json index 35d4dd9..fc93d80 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "bugs": { "url": "https://github.com/TheStreamCode/discord-management-mcp/issues" }, + "funding": "https://github.com/sponsors/TheStreamCode", "keywords": [ "mcp", "model-context-protocol", @@ -33,15 +34,21 @@ "docs", "examples", "README.md", + "CHANGELOG.md", "LICENSE", "NOTICE", "SECURITY.md", "CONTRIBUTING.md", - "discord-management-mcp.png", ".env.example" ], "engines": { - "node": ">=24.0.0" + "node": ">=24.0.0", + "npm": ">=11.5.1" + }, + "packageManager": "npm@11.16.0", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" }, "scripts": { "clean": "node -e \"import('node:fs').then(fs=>fs.rmSync('dist',{recursive:true,force:true}))\"", @@ -49,22 +56,31 @@ "build": "tsc -p tsconfig.json", "start": "node dist/index.js", "dev": "tsx src/index.ts", + "lint": "eslint .", "test": "node scripts/run-vitest.mjs", "typecheck": "tsc -p tsconfig.json --noEmit", - "check": "npm run typecheck && npm test" + "check": "npm run lint && npm run typecheck && npm test", + "preflight": "npm run check && npm run build", + "pack:check": "npm pack --dry-run --json", + "release:check": "node scripts/verify-release.mjs", + "prepack": "npm run build", + "prepublishOnly": "npm run check" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", - "discord.js": "^14.26.4", + "@modelcontextprotocol/sdk": "^1.30.0", + "discord.js": "^14.27.0", "zod": "^4.4.3" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@types/node": "^24.13.3", - "tsx": "^4.23.0", + "eslint": "^10.8.0", + "tsx": "^4.23.1", "typescript": "^6.0.3", - "vitest": "^4.1.9" + "typescript-eslint": "^8.65.0", + "vitest": "^4.1.10" }, - "overrides": { - "undici": "^6.27.0" + "allowScripts": { + "esbuild@0.28.1": true } } diff --git a/scripts/verify-release.mjs b/scripts/verify-release.mjs new file mode 100644 index 0000000..b94017b --- /dev/null +++ b/scripts/verify-release.mjs @@ -0,0 +1,27 @@ +import { readFile } from "node:fs/promises"; +import { URL } from "node:url"; + +const projectRoot = new URL("../", import.meta.url); +const packageJson = JSON.parse(await readFile(new URL("package.json", projectRoot), "utf8")); +const changelog = await readFile(new URL("CHANGELOG.md", projectRoot), "utf8"); +const citation = await readFile(new URL("CITATION.cff", projectRoot), "utf8"); +const releaseTag = process.env.RELEASE_TAG ?? process.env.GITHUB_REF_NAME ?? process.argv[2]; +const expectedTag = `v${packageJson.version}`; + +if (!releaseTag) { + throw new Error(`Release tag is required. Run: npm run release:check -- ${expectedTag}`); +} + +if (releaseTag !== expectedTag) { + throw new Error(`Release tag ${releaseTag} does not match package version ${packageJson.version}.`); +} + +if (!changelog.includes(`## ${packageJson.version}`)) { + throw new Error(`CHANGELOG.md does not contain a section for ${packageJson.version}.`); +} + +if (!citation.includes(`version: "${packageJson.version}"`)) { + throw new Error(`CITATION.cff does not declare version ${packageJson.version}.`); +} + +console.log(`Release metadata is consistent for ${expectedTag}.`); diff --git a/src/__tests__/backup-diff.test.ts b/src/__tests__/backup-diff.test.ts index a256a2d..72ee97b 100644 --- a/src/__tests__/backup-diff.test.ts +++ b/src/__tests__/backup-diff.test.ts @@ -100,4 +100,44 @@ describe("backup diff", () => { }, ]); }); + + test("matches roles by immutable Discord ID when mutable key fields change", () => { + const before = role({ + key: "role:old-name:1:12345678", + id: "12345678", + name: "Old Name", + position: 1, + }); + const after = role({ + key: "role:new-name:5:12345678", + id: "12345678", + name: "New Name", + position: 5, + }); + + expect(diffRoles([before], [after])).toEqual([ + { + type: "update", + resource: "role", + key: before.key, + before, + after, + changes: ["name", "position"], + }, + ]); + }); + + test("matches unique cross-guild resources semantically instead of duplicating them", () => { + const currentRole = role({ key: "role:member:1:target", id: "target", name: "Member" }); + const desiredRole = role({ key: "role:member:1:source", id: "source", name: "Member" }); + const currentChannel = channel({ key: "channel:general:0:target", id: "target", name: "general" }); + const desiredChannel = channel({ key: "channel:general:0:source", id: "source", name: "general" }); + + expect(diffRoles([currentRole], [desiredRole])).toEqual([ + { type: "skip", resource: "role", key: currentRole.key, reason: "no changes" }, + ]); + expect(diffChannels([currentChannel], [desiredChannel])).toEqual([ + { type: "skip", resource: "channel", key: currentChannel.key, reason: "no changes" }, + ]); + }); }); diff --git a/src/__tests__/backup-store.test.ts b/src/__tests__/backup-store.test.ts index fcb81b8..145ecbd 100644 --- a/src/__tests__/backup-store.test.ts +++ b/src/__tests__/backup-store.test.ts @@ -61,6 +61,27 @@ describe("backup store", () => { } }); + test("does not overwrite an existing backup when IDs collide", async () => { + const backupDir = await mkdtemp(join(tmpdir(), "discord-backups-")); + const capturedAt = "2026-05-29T12:00:00.000Z"; + const original = snapshot({ capturedAt }); + + try { + const backupId = await writeSnapshot(backupDir, original, new Date(capturedAt)); + + await expect( + writeSnapshot( + backupDir, + snapshot({ capturedAt, guild: { ...original.guild, name: "Replacement" } }), + new Date(capturedAt), + ), + ).rejects.toMatchObject({ code: "EEXIST" }); + await expect(readSnapshot(backupDir, backupId)).resolves.toEqual(original); + } finally { + await rm(backupDir, { recursive: true, force: true }); + } + }); + test("rejects invalid backup ids before filesystem access", async () => { expect(() => validateBackupId("../backup.json")).toThrow("Invalid backupId"); expect(() => validateBackupId("backup.json")).toThrow("Invalid backupId"); @@ -84,4 +105,24 @@ describe("backup store", () => { await rm(backupDir, { recursive: true, force: true }); } }); + + test("rejects malformed snapshot resources before restore code can use them", async () => { + const backupDir = await mkdtemp(join(tmpdir(), "discord-backups-")); + const invalidId = "2026-05-29T12-34-56-000Z-guild.json"; + + try { + await ensureBackupDir(backupDir); + await writeFile( + join(backupDir, invalidId), + JSON.stringify({ ...snapshot(), roles: [{ id: "role-without-required-fields" }] }), + "utf8", + ); + + await expect(readSnapshot(backupDir, invalidId)).rejects.toThrow( + "Invalid backup snapshot at roles.0", + ); + } finally { + await rm(backupDir, { recursive: true, force: true }); + } + }); }); diff --git a/src/__tests__/discord-client.test.ts b/src/__tests__/discord-client.test.ts new file mode 100644 index 0000000..00606c8 --- /dev/null +++ b/src/__tests__/discord-client.test.ts @@ -0,0 +1,47 @@ +import type * as DiscordJs from "discord.js"; +import { describe, expect, test, vi } from "vitest"; + +const discordMock = vi.hoisted(() => ({ attempts: 0, instances: 0 })); + +vi.mock("discord.js", async (importOriginal) => { + const actual = await importOriginal(); + const { EventEmitter } = await import("node:events"); + + class FakeClient extends EventEmitter { + constructor() { + super(); + discordMock.instances += 1; + } + + isReady() { + return false; + } + + async login() { + discordMock.attempts += 1; + if (discordMock.attempts === 1) { + throw new Error("invalid token"); + } + + queueMicrotask(() => this.emit(actual.Events.ClientReady, this)); + return "test-token"; + } + + destroy() {} + } + + return { ...actual, Client: FakeClient }; +}); + +import { DiscordClientManager } from "../discordClient.js"; + +describe("DiscordClientManager", () => { + test("clears a failed login so a later call can retry with a fresh client", async () => { + const manager = new DiscordClientManager("test-token"); + + await expect(manager.getClient()).rejects.toThrow("invalid token"); + await expect(manager.getClient()).resolves.toBeDefined(); + expect(discordMock.attempts).toBe(2); + expect(discordMock.instances).toBe(2); + }); +}); diff --git a/src/__tests__/restore.test.ts b/src/__tests__/restore.test.ts new file mode 100644 index 0000000..a7cb847 --- /dev/null +++ b/src/__tests__/restore.test.ts @@ -0,0 +1,155 @@ +import { ChannelType, type PermissionsBitField } from "discord.js"; +import { describe, expect, test, vi } from "vitest"; +import type { RestoreOperation, Snapshot } from "../backup/schema.js"; +import { applyRestoreOperations } from "../tools/backupTools.js"; + +function snapshot(overrides: Partial = {}): Snapshot { + return { + schemaVersion: 1, + capturedAt: "2026-05-29T12:34:56.000Z", + guild: { + id: "source-guild", + name: "Source Guild", + icon: null, + ownerId: null, + preferredLocale: "en-US", + verificationLevel: 1, + defaultMessageNotifications: 0, + explicitContentFilter: 0, + features: [], + }, + roles: [], + channels: [], + autoModRules: [], + scheduledEvents: [], + ...overrides, + }; +} + +describe("restore apply", () => { + test("restores role position as part of an update", async () => { + const edit = vi.fn(); + const liveRole = { + id: "target-role", + name: "Member", + managed: false, + editable: true, + edit, + }; + edit.mockResolvedValue(liveRole); + const guild = { + id: "target-guild", + roles: { + fetch: vi.fn(async (id?: string) => id ? liveRole : new Map([[liveRole.id, liveRole]])), + }, + channels: { + fetch: vi.fn(async () => new Map()), + }, + }; + const desiredRole = { + key: "role:member:7:source", + id: "source-role", + name: "Member", + color: 0, + hoist: false, + mentionable: false, + permissions: "1024", + position: 7, + managed: false, + }; + const operation: RestoreOperation = { + type: "update", + resource: "role", + key: "role:member:1:target", + before: { ...desiredRole, key: "role:member:1:target", id: "target-role", position: 1 }, + after: desiredRole, + changes: ["position"], + }; + + await applyRestoreOperations( + guild as never, + snapshot({ roles: [desiredRole] }), + [operation], + { includeDeletes: false, reason: "restore test" }, + ); + + expect(edit).toHaveBeenCalledOnce(); + expect(edit.mock.calls[0]?.[0]).toMatchObject({ position: 7, reason: "restore test" }); + }); + + test("maps role permission overwrites when restoring across guilds", async () => { + const channelEdit = vi.fn(); + const liveRole = { id: "target-role", name: "Member" }; + const liveChannel = { + id: "target-channel", + name: "general", + type: ChannelType.GuildText, + edit: channelEdit, + }; + channelEdit.mockResolvedValue(liveChannel); + const guild = { + id: "target-guild", + roles: { + fetch: vi.fn(async () => new Map([[liveRole.id, liveRole]])), + }, + channels: { + fetch: vi.fn(async (id?: string) => id ? liveChannel : new Map([[liveChannel.id, liveChannel]])), + create: vi.fn(), + }, + }; + const desiredRole = { + key: "role:member:1:source", + id: "source-role", + name: "Member", + color: 0, + hoist: false, + mentionable: false, + permissions: "1024", + position: 1, + managed: false, + }; + const desiredChannel = { + key: "channel:general:0:source", + id: "source-channel", + name: "general", + type: ChannelType.GuildText, + parentKey: null, + position: 0, + topic: null, + nsfw: false, + rateLimitPerUser: 0, + permissionOverwrites: [{ + id: "source-role", + type: "role" as const, + targetKey: desiredRole.key, + allow: "1024", + deny: "0", + }], + }; + const operation: RestoreOperation = { + type: "update", + resource: "channel", + key: "channel:general:0:target", + before: { ...desiredChannel, key: "channel:general:0:target", id: "target-channel" }, + after: desiredChannel, + changes: ["permissionOverwrites"], + }; + + await applyRestoreOperations( + guild as never, + snapshot({ roles: [desiredRole], channels: [desiredChannel] }), + [operation], + { includeDeletes: false, reason: "restore test" }, + ); + + const options = channelEdit.mock.calls[0]?.[0] as { + parent: string | null; + permissionOverwrites: Array<{ id: string; allow: PermissionsBitField }>; + type?: ChannelType; + }; + expect(options.parent).toBeNull(); + expect(options.type).toBeUndefined(); + expect(options.permissionOverwrites[0]?.id).toBe("target-role"); + expect(options.permissionOverwrites[0]?.allow.bitfield).toBe(1024n); + }); +}); diff --git a/src/__tests__/snapshot.test.ts b/src/__tests__/snapshot.test.ts index 5bfcbb2..23a9855 100644 --- a/src/__tests__/snapshot.test.ts +++ b/src/__tests__/snapshot.test.ts @@ -98,7 +98,24 @@ describe("backup snapshot", () => { invites: [{ code: "abc", uses: 1 }], }); expect(snapshot.channels[1]?.permissionOverwrites).toEqual([ - { id: "role-1", type: "role", allow: "1024", deny: "0" }, + { + id: "role-1", + type: "role", + targetKey: "role:member:2:role-1", + allow: "1024", + deny: "0", + }, ]); }); + + test("rejects incomplete backups when a required role or channel fetch fails", async () => { + const guild = { + roles: { fetch: async () => { throw new Error("missing Manage Roles"); } }, + channels: { fetch: async () => collection([]) }, + }; + + await expect(createSnapshot(guild as never)).rejects.toThrow( + "Failed to capture required snapshot section roles", + ); + }); }); diff --git a/src/backup/diff.ts b/src/backup/diff.ts index 6b929a6..346ae70 100644 --- a/src/backup/diff.ts +++ b/src/backup/diff.ts @@ -9,28 +9,46 @@ type DiffResource = "role" | "channel"; type DiffableSnapshot = RoleSnapshot | ChannelSnapshot; export function diffRoles(before: RoleSnapshot[], after: RoleSnapshot[]): RestoreOperation[] { - return diffByKey("role", before, after, normalizeRole, (snapshot) => - snapshot.managed ? "managed role" : null, + return diffSnapshots( + "role", + before, + after, + normalizeRole, + (snapshot) => snapshot.name, + (snapshot) => (snapshot.managed ? "managed role" : null), ); } export function diffChannels(before: ChannelSnapshot[], after: ChannelSnapshot[]): RestoreOperation[] { - return diffByKey("channel", before, after, normalizeChannel); + return diffSnapshots( + "channel", + before, + after, + normalizeChannel, + (snapshot) => `${snapshot.type}:${snapshot.name}`, + ); } -function diffByKey( +function diffSnapshots( resource: DiffResource, before: TSnapshot[], after: TSnapshot[], normalize: (snapshot: TSnapshot) => Record, + semanticIdentity: (snapshot: TSnapshot) => string, skipReason: (snapshot: TSnapshot) => string | null = () => null, ): RestoreOperation[] { - const beforeByKey = new Map(before.map((snapshot) => [snapshot.key, snapshot])); - const afterByKey = new Map(after.map((snapshot) => [snapshot.key, snapshot])); + const unmatchedAfter = new Set(after.map((_, index) => index)); const operations: RestoreOperation[] = []; for (const beforeSnapshot of before) { - const reason = skipReason(beforeSnapshot); + const afterIndex = findMatchIndex(beforeSnapshot, after, unmatchedAfter, semanticIdentity); + const afterSnapshot = afterIndex === undefined ? undefined : after[afterIndex]; + + if (afterIndex !== undefined) { + unmatchedAfter.delete(afterIndex); + } + + const reason = skipReason(beforeSnapshot) ?? (afterSnapshot ? skipReason(afterSnapshot) : null); if (reason !== null) { operations.push({ @@ -42,8 +60,6 @@ function diffByKey( continue; } - const afterSnapshot = afterByKey.get(beforeSnapshot.key); - if (afterSnapshot === undefined) { operations.push({ type: "delete", @@ -76,11 +92,8 @@ function diffByKey( }); } - for (const afterSnapshot of after) { - if (beforeByKey.has(afterSnapshot.key)) { - continue; - } - + for (const afterIndex of unmatchedAfter) { + const afterSnapshot = after[afterIndex]!; const reason = skipReason(afterSnapshot); operations.push( @@ -103,14 +116,41 @@ function diffByKey( return operations; } +function findMatchIndex( + beforeSnapshot: TSnapshot, + after: TSnapshot[], + unmatchedAfter: Set, + semanticIdentity: (snapshot: TSnapshot) => string, +): number | undefined { + const candidates = [...unmatchedAfter]; + const idMatch = candidates.find((index) => after[index]?.id === beforeSnapshot.id); + + if (idMatch !== undefined) { + return idMatch; + } + + const keyMatch = candidates.find((index) => after[index]?.key === beforeSnapshot.key); + + if (keyMatch !== undefined) { + return keyMatch; + } + + const semanticKey = semanticIdentity(beforeSnapshot); + const semanticMatches = candidates.filter( + (index) => after[index] && semanticIdentity(after[index]) === semanticKey, + ); + + return semanticMatches.length === 1 ? semanticMatches[0] : undefined; +} + function normalizeRole(snapshot: RoleSnapshot): Record { - const { id: _id, ...normalized } = snapshot; + const { id: _id, key: _key, ...normalized } = snapshot; return normalized; } function normalizeChannel(snapshot: ChannelSnapshot): Record { - const { id: _id, permissionOverwrites, ...normalized } = snapshot; + const { id: _id, key: _key, permissionOverwrites, ...normalized } = snapshot; return { ...normalized, diff --git a/src/backup/schema.ts b/src/backup/schema.ts index 712d74f..9c0e29a 100644 --- a/src/backup/schema.ts +++ b/src/backup/schema.ts @@ -53,6 +53,7 @@ export interface RoleSnapshot { export interface PermissionOverwriteSnapshot { id: string; type: "role" | "member"; + targetKey?: string; allow: string; deny: string; } diff --git a/src/backup/snapshot.ts b/src/backup/snapshot.ts index 6390484..b0d9094 100644 --- a/src/backup/snapshot.ts +++ b/src/backup/snapshot.ts @@ -6,8 +6,8 @@ type CollectionLike = Iterable | { values(): IterableIterator } | { map export async function createSnapshot(guild: Guild, capturedAt = new Date()): Promise { const warnings: SnapshotWarning[] = []; - const roles = await fetchCollection("roles", guild.roles?.fetch?.bind(guild.roles), warnings); - const channels = await fetchCollection("channels", guild.channels?.fetch?.bind(guild.channels), warnings); + const roles = await fetchRequiredCollection("roles", guild.roles?.fetch?.bind(guild.roles)); + const channels = await fetchRequiredCollection("channels", guild.channels?.fetch?.bind(guild.channels)); const channelKeysById = buildChannelKeysById(channels); const roleKeysById = buildRoleKeysById(roles); @@ -39,7 +39,7 @@ export async function createSnapshot(guild: Guild, capturedAt = new Date()): Pro icon: valueOrNull(role.icon), unicodeEmoji: valueOrNull(role.unicodeEmoji), })), - channels: channels.map((channel) => serializeChannel(channel, channelKeysById)), + channels: channels.map((channel) => serializeChannel(channel, channelKeysById, roleKeysById)), autoModRules: (await fetchCollection( "autoModRules", guild.autoModerationRules?.fetch?.bind(guild.autoModerationRules), @@ -85,7 +85,11 @@ export async function createSnapshot(guild: Guild, capturedAt = new Date()): Pro }; } -function serializeChannel(channel: AnyRecord, channelKeysById: Map): ChannelSnapshot { +function serializeChannel( + channel: AnyRecord, + channelKeysById: Map, + roleKeysById: Map, +): ChannelSnapshot { const key = channelKey(channel); const parentId = typeof channel.parentId === "string" ? channel.parentId : null; @@ -100,12 +104,18 @@ function serializeChannel(channel: AnyRecord, channelKeysById: Map ({ - id: String(overwrite.id), - type: overwriteType(overwrite.type), - allow: bitfieldString(overwrite.allow), - deny: bitfieldString(overwrite.deny), - })) + .map((overwrite) => { + const id = String(overwrite.id); + const type = overwriteType(overwrite.type); + + return { + id, + type, + targetKey: type === "role" ? roleKeysById.get(id) : undefined, + allow: bitfieldString(overwrite.allow), + deny: bitfieldString(overwrite.deny), + }; + }) .sort((left, right) => `${left.type}:${left.id}`.localeCompare(`${right.type}:${right.id}`)), }; } @@ -164,6 +174,24 @@ async function fetchCollection( } } +async function fetchRequiredCollection( + section: string, + fetcher: (() => Promise) | undefined, +): Promise { + if (!fetcher) { + throw new Error(`Cannot capture required snapshot section: ${section}.`); + } + + try { + return toArray(await fetcher()); + } catch (error) { + throw new Error( + `Failed to capture required snapshot section ${section}: ${errorMessage(error)}`, + { cause: error }, + ); + } +} + async function fetchJsonArray( section: string, fetcher: () => Promise, diff --git a/src/backup/store.ts b/src/backup/store.ts index 746b19f..ba229b7 100644 --- a/src/backup/store.ts +++ b/src/backup/store.ts @@ -1,12 +1,107 @@ import { mkdir, readFile, readdir, writeFile } from "node:fs/promises"; import path from "node:path"; +import { z } from "zod"; import { schemaVersion, type Snapshot } from "./schema.js"; const BACKUP_EXTENSION = ".json"; const BACKUP_ID_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z-[A-Za-z0-9._-]+\.json$/; +const bitfieldSchema = z.string().regex(/^\d+$/, "expected a non-negative decimal bitfield"); +const jsonValueSchema: z.ZodType = z.lazy(() => + z.union([ + z.string(), + z.number(), + z.boolean(), + z.null(), + z.array(jsonValueSchema), + z.record(z.string(), jsonValueSchema), + ]), +); +const permissionOverwriteSchema = z.object({ + id: z.string().min(1), + type: z.enum(["role", "member"]), + targetKey: z.string().min(1).optional(), + allow: bitfieldSchema, + deny: bitfieldSchema, +}).strict(); +const roleSchema = z.object({ + key: z.string().min(1), + id: z.string().min(1), + name: z.string(), + color: z.number().int().min(0), + hoist: z.boolean(), + mentionable: z.boolean(), + permissions: bitfieldSchema, + position: z.number().int(), + managed: z.boolean(), + icon: z.string().nullable().optional(), + unicodeEmoji: z.string().nullable().optional(), +}).strict(); +const channelSchema = z.object({ + key: z.string().min(1), + id: z.string().min(1), + name: z.string(), + type: z.number().int(), + parentKey: z.string().nullable(), + position: z.number().int(), + topic: z.string().nullable().optional(), + nsfw: z.boolean().optional(), + rateLimitPerUser: z.number().int().nullable().optional(), + permissionOverwrites: z.array(permissionOverwriteSchema), +}).strict(); +const snapshotSchema = z.object({ + schemaVersion: z.literal(schemaVersion), + capturedAt: z.string().datetime(), + guild: z.object({ + id: z.string().min(1), + name: z.string(), + icon: z.string().nullable(), + ownerId: z.string().nullable(), + preferredLocale: z.string().nullable(), + verificationLevel: z.number().int(), + defaultMessageNotifications: z.number().int(), + explicitContentFilter: z.number().int(), + features: z.array(z.string()), + }).strict(), + warnings: z.array(z.object({ + section: z.string().min(1), + message: z.string(), + }).strict()).optional(), + roles: z.array(roleSchema), + channels: z.array(channelSchema), + autoModRules: z.array(z.object({ + key: z.string().min(1), + id: z.string().min(1), + name: z.string(), + enabled: z.boolean(), + eventType: z.number().int(), + triggerType: z.number().int(), + triggerMetadata: jsonValueSchema, + actions: z.array(jsonValueSchema), + exemptRoleKeys: z.array(z.string()), + exemptChannelKeys: z.array(z.string()), + }).strict()), + scheduledEvents: z.array(z.object({ + key: z.string().min(1), + id: z.string().min(1), + name: z.string(), + description: z.string().nullable(), + scheduledStartTime: z.string().datetime(), + scheduledEndTime: z.string().datetime().nullable(), + privacyLevel: z.number().int(), + entityType: z.number().int(), + entityMetadata: jsonValueSchema, + channelKey: z.string().nullable(), + status: z.number().int(), + }).strict()), + webhooks: z.array(jsonValueSchema).optional(), + invites: z.array(jsonValueSchema).optional(), + emojis: z.array(jsonValueSchema).optional(), + stickers: z.array(jsonValueSchema).optional(), + applicationCommands: z.array(jsonValueSchema).optional(), +}).strict(); export async function ensureBackupDir(backupDir: string): Promise { - await mkdir(backupDir, { recursive: true }); + await mkdir(backupDir, { recursive: true, mode: 0o700 }); } export function createBackupId(date: Date, guildId: string): string { @@ -27,7 +122,7 @@ export async function writeSnapshot( const backupPath = backupPathForId(backupDir, backupId); const payload = `${JSON.stringify(snapshot, null, 2)}\n`; - await writeFile(backupPath, payload, "utf8"); + await writeFile(backupPath, payload, { encoding: "utf8", flag: "wx", mode: 0o600 }); return backupId; } @@ -81,15 +176,12 @@ function validateSnapshot(value: unknown): Snapshot { throw new Error(`Unsupported backup schema version: ${String(snapshot.schemaVersion)}`); } - if (!snapshot.guild || typeof snapshot.guild.id !== "string") { - throw new Error("Invalid backup snapshot: missing guild.id."); + const parsed = snapshotSchema.safeParse(value); + if (!parsed.success) { + const issue = parsed.error.issues[0]; + const location = issue?.path.length ? issue.path.join(".") : "root"; + throw new Error(`Invalid backup snapshot at ${location}: ${issue?.message ?? "validation failed"}`); } - for (const key of ["roles", "channels", "autoModRules", "scheduledEvents"] as const) { - if (!Array.isArray(snapshot[key])) { - throw new Error(`Invalid backup snapshot: ${key} must be an array.`); - } - } - - return snapshot as Snapshot; + return parsed.data as Snapshot; } diff --git a/src/discordClient.ts b/src/discordClient.ts index bec5338..fcd9e5c 100644 --- a/src/discordClient.ts +++ b/src/discordClient.ts @@ -2,8 +2,8 @@ import { Client, Events, GatewayIntentBits, - Guild, Partials, + type Guild, } from "discord.js"; export type DiscordClientOptions = { @@ -26,47 +26,57 @@ export class DiscordClientManager { } if (!this.ready) { - const intents = [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.GuildMessageReactions, - GatewayIntentBits.GuildModeration, - GatewayIntentBits.GuildScheduledEvents, - ]; + this.ready = this.connectClient(); + } - if (this.options.enableMessageContent === true) { - intents.push(GatewayIntentBits.MessageContent); - } + return this.ready; + } - if (this.options.enableGuildMembers === true) { - intents.push(GatewayIntentBits.GuildMembers); - } + private async connectClient(): Promise { + const intents = [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.GuildMessageReactions, + GatewayIntentBits.GuildModeration, + GatewayIntentBits.GuildScheduledEvents, + ]; - this.client = new Client({ - intents, - partials: [Partials.Channel, Partials.Message, Partials.Reaction], - }); + if (this.options.enableMessageContent === true) { + intents.push(GatewayIntentBits.MessageContent); + } - this.ready = new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - reject(new Error("Discord client login timed out.")); - }, 30_000); + if (this.options.enableGuildMembers === true) { + intents.push(GatewayIntentBits.GuildMembers); + } - this.client?.once(Events.ClientReady, () => { - clearTimeout(timeout); - resolve(this.client!); - }); + const client = new Client({ + intents, + partials: [Partials.Channel, Partials.Message, Partials.Reaction], + }); + this.client = client; - this.client?.once("error", (error) => { - clearTimeout(timeout); - reject(error); - }); - }); + let timeout: NodeJS.Timeout | undefined; + const ready = new Promise((resolve, reject) => { + timeout = setTimeout(() => reject(new Error("Discord client login timed out.")), 30_000); + client.once(Events.ClientReady, () => resolve(client)); + client.once(Events.Error, reject); + }); - await this.client.login(this.token); + try { + await Promise.race([ready, client.login(this.token).then(() => ready)]); + return client; + } catch (error) { + client.destroy(); + if (this.client === client) { + this.client = undefined; + this.ready = undefined; + } + throw error; + } finally { + if (timeout) { + clearTimeout(timeout); + } } - - return this.ready; } async getGuild(guildId: string): Promise { diff --git a/src/index.ts b/src/index.ts index 29e21dd..6e79bb9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createRequire } from "node:module"; import { loadConfig } from "./config.js"; import { DiscordClientManager } from "./discordClient.js"; import { registerBackupTools } from "./tools/backupTools.js"; @@ -11,6 +12,8 @@ import { registerModerationTools } from "./tools/moderationTools.js"; import { registerRoleTools } from "./tools/roleTools.js"; import { registerServerConfigTools } from "./tools/serverConfigTools.js"; +const packageMetadata = createRequire(import.meta.url)("../package.json") as { version: string }; + const config = loadConfig(); const discord = new DiscordClientManager(config.discordToken, { enableMessageContent: config.enableMessageContent, @@ -19,7 +22,7 @@ const discord = new DiscordClientManager(config.discordToken, { const server = new McpServer({ name: "discord-management-mcp", - version: "0.1.0", + version: packageMetadata.version, }); registerGuildTools(server, discord, config); diff --git a/src/toolAnnotations.ts b/src/toolAnnotations.ts new file mode 100644 index 0000000..e60a064 --- /dev/null +++ b/src/toolAnnotations.ts @@ -0,0 +1,34 @@ +export const readOnlyDiscordAnnotations = { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, +} as const; + +export const localReadOnlyAnnotations = { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: false, +} as const; + +export const additiveDiscordAnnotations = { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, +} as const; + +export const destructiveDiscordAnnotations = { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, +} as const; + +export const idempotentDiscordMutationAnnotations = { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, +} as const; diff --git a/src/tools/backupTools.ts b/src/tools/backupTools.ts index 2a7da03..99d625e 100644 --- a/src/tools/backupTools.ts +++ b/src/tools/backupTools.ts @@ -1,5 +1,5 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { ChannelType, PermissionsBitField, type Guild } from "discord.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { ChannelType, OverwriteType, PermissionsBitField, type Guild } from "discord.js"; import { z } from "zod"; import type { ServerConfig } from "../config.js"; import type { DiscordClientManager } from "../discordClient.js"; @@ -9,6 +9,7 @@ import { listBackups, readSnapshot, writeSnapshot } from "../backup/store.js"; import { schemaVersion, type ChannelSnapshot, + type PermissionOverwriteSnapshot, type RestoreOperation, type RestorePlan, type RestoreWarning, @@ -17,6 +18,12 @@ import { } from "../backup/schema.js"; import { errorResponse, successResponse } from "../responses.js"; import { requireConfirmation } from "../safety.js"; +import { + additiveDiscordAnnotations, + destructiveDiscordAnnotations, + localReadOnlyAnnotations, + readOnlyDiscordAnnotations, +} from "../toolAnnotations.js"; export function registerBackupTools( server: McpServer, @@ -36,13 +43,9 @@ export function registerBackupTools( guildId: z.string(), capturedAt: z.string(), counts: countsSchema(), + warnings: snapshotWarningsSchema(), }, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, + annotations: additiveDiscordAnnotations, }, async ({ guildId }) => { try { @@ -75,12 +78,7 @@ export function registerBackupTools( backupIds: z.array(z.string()), count: z.number(), }, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, + annotations: localReadOnlyAnnotations, }, async () => { try { @@ -110,13 +108,9 @@ export function registerBackupTools( backupId: z.string(), snapshot: z.unknown(), counts: countsSchema(), + warnings: snapshotWarningsSchema(), }, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, + annotations: localReadOnlyAnnotations, }, async ({ backupId }) => { try { @@ -152,12 +146,7 @@ export function registerBackupTools( operations: z.array(z.unknown()), summary: summarySchema(), }, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, + annotations: localReadOnlyAnnotations, }, async ({ beforeBackupId, afterBackupId }) => { try { @@ -196,12 +185,7 @@ export function registerBackupTools( summary: summarySchema(), safetyMessage: z.string(), }, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, + annotations: readOnlyDiscordAnnotations, }, async ({ backupId, targetGuildId }) => { try { @@ -261,12 +245,7 @@ export function registerBackupTools( skipped: z.array(z.unknown()), warnings: z.array(z.unknown()), }, - annotations: { - readOnlyHint: false, - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, + annotations: destructiveDiscordAnnotations, }, async (input) => { try { @@ -343,7 +322,7 @@ function restoreWarnings(sourceGuildId: string, targetGuildId: string): RestoreW return warnings; } -async function applyRestoreOperations( +export async function applyRestoreOperations( guild: Guild, desired: Snapshot, operations: RestoreOperation[], @@ -351,23 +330,30 @@ async function applyRestoreOperations( ): Promise<{ applied: unknown[]; skipped: unknown[] }> { const applied: unknown[] = []; const skipped: unknown[] = []; - const roleIdByKey = new Map(desired.roles.map((role) => [role.key, role.id])); - const channelIdByKey = new Map(desired.channels.map((channel) => [channel.key, channel.id])); + const roleIdByKey = new Map(); + const channelIdByKey = new Map(); + const liveRoles = await guild.roles.fetch().then((roles) => [...roles.values()]); + const liveChannels = await guild.channels.fetch().then((channels) => + [...channels.values()].filter((channel) => channel !== null), + ); - for (const role of await guild.roles.fetch().then((roles) => [...roles.values()])) { - const matching = desired.roles.find((snapshot) => snapshot.id === role.id || snapshot.name === role.name); + for (const snapshot of desired.roles) { + const exact = liveRoles.find((role) => role.id === snapshot.id); + const nameMatches = liveRoles.filter((role) => role.name === snapshot.name); + const matching = exact ?? (nameMatches.length === 1 ? nameMatches[0] : undefined); if (matching) { - roleIdByKey.set(matching.key, role.id); + roleIdByKey.set(snapshot.key, matching.id); } } - for (const channel of await guild.channels.fetch().then((channels) => [...channels.values()])) { - if (!channel) { - continue; - } - const matching = desired.channels.find((snapshot) => snapshot.id === channel.id || snapshot.name === channel.name); + for (const snapshot of desired.channels) { + const exact = liveChannels.find((channel) => channel.id === snapshot.id); + const nameMatches = liveChannels.filter( + (channel) => channel.name === snapshot.name && channel.type === snapshot.type, + ); + const matching = exact ?? (nameMatches.length === 1 ? nameMatches[0] : undefined); if (matching) { - channelIdByKey.set(matching.key, channel.id); + channelIdByKey.set(snapshot.key, matching.id); } } @@ -386,7 +372,14 @@ async function applyRestoreOperations( ]; for (const operation of sortedChannelOperations) { - const result = await applyChannelOperation(guild, operation, channelIdByKey, options); + const result = await applyChannelOperation( + guild, + operation, + channelIdByKey, + roleIdByKey, + desired.guild.id !== guild.id, + options, + ); (result.applied ? applied : skipped).push(result.detail); if (result.key && result.id) { channelIdByKey.set(result.key, result.id); @@ -435,6 +428,7 @@ async function applyRoleOperation( hoist: desiredRole.hoist, mentionable: desiredRole.mentionable, permissions: new PermissionsBitField(BigInt(desiredRole.permissions)), + position: desiredRole.position, reason: options.reason, }; @@ -458,6 +452,8 @@ async function applyChannelOperation( guild: Guild, operation: RestoreOperation, channelIdByKey: Map, + roleIdByKey: Map, + crossGuild: boolean, options: { includeDeletes: boolean; reason: string }, ): Promise<{ applied: boolean; detail: unknown; key?: string; id?: string }> { if (operation.type === "skip") { @@ -488,28 +484,108 @@ async function applyChannelOperation( return { applied: false, detail: { ...operation, reason: "missing desired channel payload" } }; } - const parent = desiredChannel.parentKey ? channelIdByKey.get(desiredChannel.parentKey) : undefined; + const parent = desiredChannel.parentKey === null + ? null + : channelIdByKey.get(desiredChannel.parentKey); + + if (desiredChannel.parentKey !== null && parent === undefined) { + return { + applied: false, + detail: { ...operation, reason: `parent channel could not be resolved: ${desiredChannel.parentKey}` }, + }; + } + + const restoredOverwrites = restorePermissionOverwrites( + desiredChannel.permissionOverwrites, + roleIdByKey, + crossGuild, + ); + const preserveExistingOverwrites = + operation.type === "update" && restoredOverwrites.warnings.length > 0; + + if (preserveExistingOverwrites) { + restoredOverwrites.warnings.push( + "Permission overwrites were left unchanged because a partial replacement would remove unmapped targets.", + ); + } + const channelOptions: Record = { name: desiredChannel.name, - type: desiredChannel.type as ChannelType, parent, + position: desiredChannel.position, topic: desiredChannel.topic ?? undefined, nsfw: desiredChannel.nsfw, rateLimitPerUser: desiredChannel.rateLimitPerUser ?? undefined, + permissionOverwrites: preserveExistingOverwrites ? undefined : restoredOverwrites.values, reason: options.reason, }; + const detail = restoredOverwrites.warnings.length === 0 + ? operation + : { ...operation, warnings: restoredOverwrites.warnings }; if (operation.type === "create" || !channel) { + channelOptions.type = desiredChannel.type as ChannelType; const created = await guild.channels.create(channelOptions as unknown as Parameters[0]); - return { applied: true, detail: operation, key: desiredChannel.key, id: created.id }; + return { applied: true, detail, key: desiredChannel.key, id: created.id }; } if (!("edit" in channel) || typeof channel.edit !== "function") { return { applied: false, detail: { ...operation, reason: "channel cannot be edited" } }; } + if (channel.type !== desiredChannel.type) { + const convertibleTypes = new Set([ChannelType.GuildText, ChannelType.GuildAnnouncement]); + if (!convertibleTypes.has(channel.type) || !convertibleTypes.has(desiredChannel.type)) { + return { + applied: false, + detail: { ...operation, reason: "channel type change is not supported by Discord" }, + }; + } + channelOptions.type = desiredChannel.type; + } + const updated = await channel.edit(channelOptions as Parameters[0]); - return { applied: true, detail: operation, key: desiredChannel.key, id: updated.id }; + return { applied: true, detail, key: desiredChannel.key, id: updated.id }; +} + +function restorePermissionOverwrites( + overwrites: PermissionOverwriteSnapshot[], + roleIdByKey: Map, + crossGuild: boolean, +): { + values: Array<{ id: string; type: OverwriteType; allow: PermissionsBitField; deny: PermissionsBitField }>; + warnings: string[]; +} { + const values: Array<{ + id: string; + type: OverwriteType; + allow: PermissionsBitField; + deny: PermissionsBitField; + }> = []; + const warnings: string[] = []; + + for (const overwrite of overwrites) { + const mappedRoleId = overwrite.targetKey ? roleIdByKey.get(overwrite.targetKey) : undefined; + const targetId = overwrite.type === "role" + ? (mappedRoleId ?? (crossGuild ? undefined : overwrite.id)) + : (crossGuild ? undefined : overwrite.id); + + if (!targetId) { + warnings.push( + `Skipped ${overwrite.type} permission overwrite ${overwrite.id}: target cannot be mapped across guilds.`, + ); + continue; + } + + values.push({ + id: targetId, + type: overwrite.type === "role" ? OverwriteType.Role : OverwriteType.Member, + allow: new PermissionsBitField(BigInt(overwrite.allow)), + deny: new PermissionsBitField(BigInt(overwrite.deny)), + }); + } + + return { values, warnings }; } function asRoleSnapshot(value: unknown): RoleSnapshot { @@ -544,6 +620,13 @@ function countsSchema() { }); } +function snapshotWarningsSchema() { + return z.array(z.object({ + section: z.string(), + message: z.string(), + })); +} + function summarySchema() { return z.object({ create: z.number(), diff --git a/src/tools/channelTools.ts b/src/tools/channelTools.ts index 877e3a9..6a2379b 100644 --- a/src/tools/channelTools.ts +++ b/src/tools/channelTools.ts @@ -10,6 +10,11 @@ import type { ServerConfig } from "../config.js"; import type { DiscordClientManager } from "../discordClient.js"; import { errorResponse, successResponse } from "../responses.js"; import { requireConfirmation, requireDestructiveBackupForGuild } from "../safety.js"; +import { + additiveDiscordAnnotations, + destructiveDiscordAnnotations, + idempotentDiscordMutationAnnotations, +} from "../toolAnnotations.js"; const channelTypeByName = { text: ChannelType.GuildText, @@ -116,7 +121,7 @@ export function registerChannelTools( ...confirmSchema, }, outputSchema: channelMutationResultSchema, - annotations: { destructiveHint: false, idempotentHint: false }, + annotations: additiveDiscordAnnotations, }, async (input) => { try { @@ -167,7 +172,7 @@ export function registerChannelTools( ...confirmSchema, }, outputSchema: channelMutationResultSchema, - annotations: { destructiveHint: true, idempotentHint: false }, + annotations: idempotentDiscordMutationAnnotations, }, async (input) => { try { @@ -214,7 +219,7 @@ export function registerChannelTools( ...confirmSchema, }, outputSchema: channelMutationResultSchema, - annotations: { destructiveHint: true, idempotentHint: false }, + annotations: destructiveDiscordAnnotations, }, async (input) => { try { @@ -255,7 +260,7 @@ export function registerChannelTools( ...confirmSchema, }, outputSchema: channelMutationResultSchema, - annotations: { destructiveHint: true, idempotentHint: false }, + annotations: idempotentDiscordMutationAnnotations, }, async (input) => { try { @@ -316,7 +321,7 @@ export function registerChannelTools( ...confirmSchema, }, outputSchema: channelMutationResultSchema, - annotations: { destructiveHint: true, idempotentHint: false }, + annotations: idempotentDiscordMutationAnnotations, }, async (input) => { try { diff --git a/src/tools/guildTools.ts b/src/tools/guildTools.ts index ebc72a7..3a6da0e 100644 --- a/src/tools/guildTools.ts +++ b/src/tools/guildTools.ts @@ -18,6 +18,7 @@ import { z } from "zod"; import type { ServerConfig } from "../config.js"; import type { DiscordClientManager } from "../discordClient.js"; import { errorResponse, successResponse } from "../responses.js"; +import { readOnlyDiscordAnnotations } from "../toolAnnotations.js"; type JsonObject = Record; @@ -45,13 +46,6 @@ const listMessagesSchema = { after: z.string().min(1).optional(), around: z.string().min(1).optional(), }; -const readOnlyAnnotations = { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, -}; - function errorText(error: unknown): string { return error instanceof Error ? error.message : "Unknown Discord API error."; } @@ -400,7 +394,7 @@ export function registerGuildTools( { description: "List Discord guilds visible to the configured bot.", inputSchema: emptySchema, - annotations: readOnlyAnnotations, + annotations: readOnlyDiscordAnnotations, }, async () => { try { @@ -423,7 +417,7 @@ export function registerGuildTools( { description: "Get Discord guild metadata.", inputSchema: guildIdSchema, - annotations: readOnlyAnnotations, + annotations: readOnlyDiscordAnnotations, }, async ({ guildId }) => { try { @@ -445,7 +439,7 @@ export function registerGuildTools( { description: "List channels in a Discord guild.", inputSchema: guildIdSchema, - annotations: readOnlyAnnotations, + annotations: readOnlyDiscordAnnotations, }, async ({ guildId }) => { try { @@ -472,7 +466,7 @@ export function registerGuildTools( { description: "Get Discord channel metadata.", inputSchema: channelIdSchema, - annotations: readOnlyAnnotations, + annotations: readOnlyDiscordAnnotations, }, async ({ guildId, channelId }) => { try { @@ -501,7 +495,7 @@ export function registerGuildTools( description: "List recent messages from a text-based channel. Requires ENABLE_MESSAGE_CONTENT=true and the Discord Message Content privileged intent.", inputSchema: listMessagesSchema, - annotations: readOnlyAnnotations, + annotations: readOnlyDiscordAnnotations, }, async ({ guildId, channelId, limit, before, after, around }) => { if (!config.enableMessageContent) { @@ -574,7 +568,7 @@ export function registerGuildTools( { description: "List roles in a Discord guild.", inputSchema: guildIdSchema, - annotations: readOnlyAnnotations, + annotations: readOnlyDiscordAnnotations, }, async ({ guildId }) => { try { @@ -599,7 +593,7 @@ export function registerGuildTools( { description: "Get Discord role metadata.", inputSchema: roleIdSchema, - annotations: readOnlyAnnotations, + annotations: readOnlyDiscordAnnotations, }, async ({ guildId, roleId }) => { try { @@ -627,7 +621,7 @@ export function registerGuildTools( { description: "List guild members, capped at 1000.", inputSchema: listMembersSchema, - annotations: readOnlyAnnotations, + annotations: readOnlyDiscordAnnotations, }, async ({ guildId, limit, after }) => { try { @@ -658,7 +652,7 @@ export function registerGuildTools( { description: "List AutoMod rules in a Discord guild.", inputSchema: guildIdSchema, - annotations: readOnlyAnnotations, + annotations: readOnlyDiscordAnnotations, }, async ({ guildId }) => { try { @@ -685,7 +679,7 @@ export function registerGuildTools( { description: "List scheduled events in a Discord guild.", inputSchema: guildIdSchema, - annotations: readOnlyAnnotations, + annotations: readOnlyDiscordAnnotations, }, async ({ guildId }) => { try { @@ -712,7 +706,7 @@ export function registerGuildTools( { description: "List webhooks in a Discord guild without exposing tokens.", inputSchema: guildIdSchema, - annotations: readOnlyAnnotations, + annotations: readOnlyDiscordAnnotations, }, async ({ guildId }) => { try { @@ -737,7 +731,7 @@ export function registerGuildTools( { description: "List invites in a Discord guild.", inputSchema: guildIdSchema, - annotations: readOnlyAnnotations, + annotations: readOnlyDiscordAnnotations, }, async ({ guildId }) => { try { @@ -762,7 +756,7 @@ export function registerGuildTools( { description: "List emojis in a Discord guild.", inputSchema: guildIdSchema, - annotations: readOnlyAnnotations, + annotations: readOnlyDiscordAnnotations, }, async ({ guildId }) => { try { @@ -787,7 +781,7 @@ export function registerGuildTools( { description: "List stickers in a Discord guild.", inputSchema: guildIdSchema, - annotations: readOnlyAnnotations, + annotations: readOnlyDiscordAnnotations, }, async ({ guildId }) => { try { @@ -812,7 +806,7 @@ export function registerGuildTools( { description: "List guild application commands.", inputSchema: guildIdSchema, - annotations: readOnlyAnnotations, + annotations: readOnlyDiscordAnnotations, }, async ({ guildId }) => { try { diff --git a/src/tools/moderationTools.ts b/src/tools/moderationTools.ts index 5df229f..95bfa81 100644 --- a/src/tools/moderationTools.ts +++ b/src/tools/moderationTools.ts @@ -16,6 +16,11 @@ import type { ServerConfig } from "../config.js"; import type { DiscordClientManager } from "../discordClient.js"; import { errorResponse, successResponse } from "../responses.js"; import { requireConfirmation, requireDestructiveBackupForGuild } from "../safety.js"; +import { + additiveDiscordAnnotations, + destructiveDiscordAnnotations, + idempotentDiscordMutationAnnotations, +} from "../toolAnnotations.js"; const optionalReason = z.string().min(1).max(512).optional(); const snowflake = z.string().min(1); @@ -205,6 +210,7 @@ export function registerModerationTools( { title: "Timeout Discord member", description: "Timeout a guild member for the requested duration in seconds.", + annotations: idempotentDiscordMutationAnnotations, inputSchema: { guildId: snowflake, userId: snowflake, @@ -236,6 +242,7 @@ export function registerModerationTools( { title: "Kick Discord member", description: "Kick a guild member. Requires confirmation and either a backupId or allowWithoutBackup.", + annotations: destructiveDiscordAnnotations, inputSchema: { guildId: snowflake, userId: snowflake, @@ -264,6 +271,7 @@ export function registerModerationTools( { title: "Ban Discord member", description: "Ban a guild member. Requires confirmation and either a backupId or allowWithoutBackup.", + annotations: destructiveDiscordAnnotations, inputSchema: { guildId: snowflake, userId: snowflake, @@ -299,6 +307,7 @@ export function registerModerationTools( { title: "Unban Discord member", description: "Remove a guild ban for a user.", + annotations: idempotentDiscordMutationAnnotations, inputSchema: { guildId: snowflake, userId: snowflake, @@ -323,6 +332,7 @@ export function registerModerationTools( { title: "Bulk delete Discord messages", description: "Bulk delete messages by count or explicit message IDs. Requires confirmation and backup acknowledgement.", + annotations: destructiveDiscordAnnotations, inputSchema: { channelId: snowflake, limit: z.number().int().min(1).max(100).optional(), @@ -373,6 +383,7 @@ export function registerModerationTools( { title: "Create Discord AutoMod rule", description: "Create a Discord AutoMod rule with JSON trigger metadata and actions.", + annotations: additiveDiscordAnnotations, inputSchema: { guildId: snowflake, name: z.string().min(1).max(100), @@ -409,6 +420,7 @@ export function registerModerationTools( { title: "Update Discord AutoMod rule", description: "Update a Discord AutoMod rule with JSON trigger metadata and actions.", + annotations: idempotentDiscordMutationAnnotations, inputSchema: { guildId: snowflake, ruleId: snowflake, @@ -450,6 +462,7 @@ export function registerModerationTools( { title: "Delete Discord AutoMod rule", description: "Delete a Discord AutoMod rule.", + annotations: destructiveDiscordAnnotations, inputSchema: { guildId: snowflake, ruleId: snowflake, @@ -482,6 +495,7 @@ export function registerModerationTools( { title: "Create Discord scheduled event", description: "Create a Discord scheduled event. Use channelId for voice/stage events or location for external events.", + annotations: additiveDiscordAnnotations, inputSchema: { guildId: snowflake, name: z.string().min(1).max(100), @@ -520,6 +534,7 @@ export function registerModerationTools( { title: "Update Discord scheduled event", description: "Update a Discord scheduled event.", + annotations: idempotentDiscordMutationAnnotations, inputSchema: { guildId: snowflake, eventId: snowflake, @@ -565,6 +580,7 @@ export function registerModerationTools( { title: "Delete Discord scheduled event", description: "Delete a Discord scheduled event.", + annotations: destructiveDiscordAnnotations, inputSchema: { guildId: snowflake, eventId: snowflake, diff --git a/src/tools/roleTools.ts b/src/tools/roleTools.ts index e369083..cf49b76 100644 --- a/src/tools/roleTools.ts +++ b/src/tools/roleTools.ts @@ -10,6 +10,11 @@ import type { ServerConfig } from "../config.js"; import type { DiscordClientManager } from "../discordClient.js"; import { errorResponse, successResponse } from "../responses.js"; import { requireConfirmation, requireDestructiveBackupForGuild } from "../safety.js"; +import { + additiveDiscordAnnotations, + destructiveDiscordAnnotations, + idempotentDiscordMutationAnnotations, +} from "../toolAnnotations.js"; const confirmSchema = { confirm: z.boolean().optional(), @@ -101,7 +106,7 @@ export function registerRoleTools( ...confirmSchema, }, outputSchema: roleMutationResultSchema, - annotations: { destructiveHint: false, idempotentHint: false }, + annotations: additiveDiscordAnnotations, }, async (input) => { try { @@ -152,7 +157,7 @@ export function registerRoleTools( ...confirmSchema, }, outputSchema: roleMutationResultSchema, - annotations: { destructiveHint: true, idempotentHint: false }, + annotations: idempotentDiscordMutationAnnotations, }, async (input) => { try { @@ -208,7 +213,7 @@ export function registerRoleTools( ...confirmSchema, }, outputSchema: roleMutationResultSchema, - annotations: { destructiveHint: true, idempotentHint: false }, + annotations: destructiveDiscordAnnotations, }, async (input) => { try { @@ -249,7 +254,7 @@ export function registerRoleTools( ...confirmSchema, }, outputSchema: roleMutationResultSchema, - annotations: { destructiveHint: false, idempotentHint: false }, + annotations: additiveDiscordAnnotations, }, async (input) => { try { @@ -292,7 +297,7 @@ export function registerRoleTools( ...confirmSchema, }, outputSchema: roleMutationResultSchema, - annotations: { destructiveHint: false, idempotentHint: false }, + annotations: idempotentDiscordMutationAnnotations, }, async (input) => { try { @@ -334,7 +339,7 @@ export function registerRoleTools( ...confirmSchema, }, outputSchema: roleMutationResultSchema, - annotations: { destructiveHint: true, idempotentHint: false }, + annotations: idempotentDiscordMutationAnnotations, }, async (input) => { try { diff --git a/src/tools/serverConfigTools.ts b/src/tools/serverConfigTools.ts index a6f220a..6b0e750 100644 --- a/src/tools/serverConfigTools.ts +++ b/src/tools/serverConfigTools.ts @@ -10,6 +10,11 @@ import type { ServerConfig } from "../config.js"; import type { DiscordClientManager } from "../discordClient.js"; import { errorResponse, successResponse } from "../responses.js"; import { requireConfirmation, requireDestructiveBackupForGuild } from "../safety.js"; +import { + additiveDiscordAnnotations, + destructiveDiscordAnnotations, + idempotentDiscordMutationAnnotations, +} from "../toolAnnotations.js"; const optionalReason = z.string().min(1).max(512).optional(); const snowflake = z.string().min(1); @@ -84,6 +89,7 @@ export function registerServerConfigTools( { title: "Update Discord guild settings", description: "Update basic guild settings such as name, description, moderation levels, locale, and AFK timeout.", + annotations: idempotentDiscordMutationAnnotations, inputSchema: { guildId: snowflake, name: z.string().min(2).max(100).optional(), @@ -123,6 +129,7 @@ export function registerServerConfigTools( { title: "Create Discord invite", description: "Create an invite for a channel.", + annotations: additiveDiscordAnnotations, inputSchema: { channelId: snowflake, maxAge: z.number().int().min(0).max(604_800).optional(), @@ -169,6 +176,7 @@ export function registerServerConfigTools( { title: "Create Discord webhook", description: "Create a webhook in a text-capable guild channel.", + annotations: additiveDiscordAnnotations, inputSchema: { channelId: snowflake, name: z.string().min(1).max(80), @@ -210,6 +218,7 @@ export function registerServerConfigTools( { title: "Delete Discord webhook", description: "Delete a webhook by ID. Requires confirmation and either a backupId or allowWithoutBackup.", + annotations: destructiveDiscordAnnotations, inputSchema: { webhookId: snowflake, confirm: z.boolean().optional(), diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 0000000..1e4dae1 --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] +}